Compare commits
30 Commits
eef6eebd8c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f3e854854 | |||
| 75a51fb1db | |||
| a32b66700f | |||
| 59e51f7e83 | |||
| b22cee3456 | |||
| 2e90eaf7d7 | |||
| 3983fa4513 | |||
| d014a3f577 | |||
| 4f7d9c0596 | |||
| 37fc572cc3 | |||
| 99b430f2ed | |||
| d438a1d3da | |||
| 79ea473910 | |||
| cf06896dbd | |||
| 8bbc6b4d07 | |||
| 565ba98852 | |||
| ac9871c58c | |||
| 1e6c377edc | |||
| 0280b05933 | |||
| f2093e0d89 | |||
| af5f0a4638 | |||
| 3dbad6120c | |||
| 1dc89b0f26 | |||
| e1ee447b7f | |||
| 18f5115e0d | |||
| b3bf2cca3d | |||
| 3b2dd8bfcc | |||
| ecf5ecfc14 | |||
| 4cffbc9aa4 | |||
| 24bb724c22 |
@@ -0,0 +1,43 @@
|
|||||||
|
# Идеи и вопросы «на подумать» (PoP)
|
||||||
|
|
||||||
|
Не план работ, а список того, что осознанно отложено: каждая запись —
|
||||||
|
гипотеза с причиной, по которой её стоит проверить, и с тем, что мешает
|
||||||
|
сделать это прямо сейчас.
|
||||||
|
|
||||||
|
## Заменить генератор псевдослучайных чисел
|
||||||
|
|
||||||
|
Сейчас стоит LCG оригинала, шаг на ассемблере (~1 020 тактов), бит-в-бит
|
||||||
|
совместимый с SDLPoP. Есть более дешёвые Z80-генераторы (86–148 тактов),
|
||||||
|
но потолок выигрыша — 2 814 тактов за кадр, 0.65 %, и он растворяется в
|
||||||
|
обёртках вызова. Тексты процедур, разбор качества и порядок действий —
|
||||||
|
`prng_alternatives.md`. Первый шаг там не про генератор: слить приведение
|
||||||
|
к диапазону в ту же asm-процедуру, чтобы на вызов был один `call`, а не три.
|
||||||
|
|
||||||
|
## Отключать мышь на время игры
|
||||||
|
|
||||||
|
**Гипотеза.** Мышь на Sprinter — источник прерываний (обёртки RST 30h,
|
||||||
|
см. memory `mouse_api`). Игре она не нужна вообще: управление —
|
||||||
|
raw-клавиатура (`<kbd_raw.h>`), которую мы и так забираем у DSS целиком.
|
||||||
|
Значит каждое мышиное прерывание за кадр — украденные такты в бюджете,
|
||||||
|
который у нас и без того занят на 86 %.
|
||||||
|
|
||||||
|
**Откуда взялось (2026-07-30).** При замере бюджета по 100 кадрам три
|
||||||
|
кадра выбились до 552–647 К тактов (1.28–1.51 кадра) при типичных 371 К.
|
||||||
|
Причиной оказалось движение мыши на ХОСТЕ: при неподвижной мыши 225
|
||||||
|
кадров подряд прошли без единого превышения. То есть эффект реальный и
|
||||||
|
измеримый, просто в тесте он был наведён извне.
|
||||||
|
|
||||||
|
**Что проверить.**
|
||||||
|
1. Есть ли у драйвера мыши (RST 30h) функция «выключить/включить» —
|
||||||
|
разобрать список из 14 обёрток; если нет явной, посмотреть, что делает
|
||||||
|
«hide cursor» и снимает ли она обработчик.
|
||||||
|
2. Сколько тактов реально стоит одно мышиное прерывание на нашем железе
|
||||||
|
(замер: breakpoint на входе ISR + totalcycles, при движении мыши).
|
||||||
|
3. Не ломает ли отключение выход в DSS: состояние обязано
|
||||||
|
восстанавливаться при `exit`, включая аварийный (atexit).
|
||||||
|
|
||||||
|
**Почему не сейчас.** Выигрыш проявляется только когда игрок реально
|
||||||
|
двигает мышью, то есть в норме его нет; а риск оставить систему без мыши
|
||||||
|
после выхода — заметный. Делать после того, как закроем стражей и
|
||||||
|
займёмся бюджетом всерьёз (там же, где батчинг кроссбанковых вызовов и
|
||||||
|
возможный возврат `pop_bg` в резидент `--w3`).
|
||||||
@@ -11,6 +11,82 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## СТАТУС ВЫПОЛНЕНИЯ (обновлено 2026-07-29, коммит ecf5ecf)
|
||||||
|
|
||||||
|
| Шаг | Статус | Факт |
|
||||||
|
|-----|--------|------|
|
||||||
|
| 1. `kid_data.h` → EMM-страница + `load_frame`/`cur_frame` | **сделан** | `_CODE` −3 247 Б; попутно найден и обойдён баг кодогенерации SDCC (см. ниже) |
|
||||||
|
| 2. `pop_geom.c` (дедуп геометрии + PRNG) | **сделан** | −41 Б `_CODE`, −11 Б W3; ценность — не байты, а bank-safe слой |
|
||||||
|
| 3. `pop_map` без вызовов графики | **сделан** (фазы 1a/1b) | через пометки перерисовки, см. ниже |
|
||||||
|
| 4. Разгрузка/перебалансировка W3 | **сделан** | резидент = `pop_bg` + `pop_gdraw` (отрисовка стража, 2026-07-29); W3 14 376 → 12 819 (свободно 3 565 Б) |
|
||||||
|
| 5. Данные (`room_modif`, `dl1/dl2`, `_kbdraw_down`) | не начат | потенциал ~1.5 КБ |
|
||||||
|
| 6. Контракт банка стражей + пробник | **пробник сделан** | `tests/w3bankgfx` — модель подтверждена в MAME, см. ниже |
|
||||||
|
| 7. Дедуп семейства `draw_tile` | отложен по решению пользователя | «мороки много, выгода не так велика» |
|
||||||
|
|
||||||
|
**Замер сейчас против замера §1:** `_CODE` 24 881 → 24 421, куча W2 2 076 → 2 592 Б,
|
||||||
|
W3-резидент 14 376 → 11 632 (свободно 2 008 → 4 752 Б). Сумма кода упала
|
||||||
|
на ~3.2 КБ (данные Kid уехали в EMM), остальное — перераспределение.
|
||||||
|
|
||||||
|
**Замер 2026-07-29 (после стража).** Появление стража съело кучу до 805 Б;
|
||||||
|
разгрузка — вынос ОТРИСОВКИ стража в резидент (`pop_gdraw.c`, `--w3`), логика
|
||||||
|
и состояние остались в W1/W2, чтобы банк `guards.c` их видел (R2). Итог:
|
||||||
|
`_CODE` 26 149 → 25 703, куча **805 → 1 245 Б**, W3-резидент 11 656 → 12 819
|
||||||
|
(свободно 4 728 → 3 565 Б), банк 1 — 236 / 16 384 Б. Граница «что резидент»
|
||||||
|
теперь формулируется одним правилом: **резидент = только то, что рисует и
|
||||||
|
зовётся исключительно из главного цикла**; всё, что может понадобиться банку,
|
||||||
|
остаётся в W1/W2.
|
||||||
|
|
||||||
|
### Что сделано вместо §5.3 (вынос loose в W3)
|
||||||
|
|
||||||
|
Вместо переноса кода между окнами выбран (по обсуждению с пользователем)
|
||||||
|
**порт архитектуры оригинала**: логика ставит пометку, отрисовка идёт
|
||||||
|
отдельным проходом — `set_redraw_*` (seg007) + `redraw_needed` (seg008:0178).
|
||||||
|
Появился `pop_redraw.c/.h`; `pop_trob` и `pop_map` больше не рисуют тайлы.
|
||||||
|
Наши самодельные счётчики (`spike_rest`, `button_rest`, `ldoor_rest`,
|
||||||
|
`loose_bake`, `loose_rest`, `ceil_rest`, `ceil_bake`, `land_bake`) удалены —
|
||||||
|
их роль (вторая страница дабл-буфера) взял счётчик страниц в пометке.
|
||||||
|
|
||||||
|
**Два исключения остались** (обе — функции ТОЛЬКО главного цикла, звать из
|
||||||
|
банка нельзя):
|
||||||
|
- `pop_process_trobs` — пламя факела и пузырёк зелья (покадровый оверлей);
|
||||||
|
- `pop_loose_tick` — падающий кусок (mob): spawn/tick/pos. В оригинале это
|
||||||
|
отдельная подсистема (`mobs` + `draw_moving`), разделение на логику и
|
||||||
|
отрисовку — задел следующей фазы.
|
||||||
|
|
||||||
|
### Пробник банка (2026-07-29): модель ПОДТВЕРЖДЕНА
|
||||||
|
|
||||||
|
`tests/w3bankgfx` (huge + `--w3 res.c` + `--bank 1=bank1.c`, графика 256):
|
||||||
|
|
||||||
|
- банк рисует примитивом libbgi НАПРЯМУЮ — работает; страница W3 внутри
|
||||||
|
банка до блита, после блита и после возврата из вызванной им W1/W2-функции
|
||||||
|
одна и та же (0xF0), резидент — 0xF3. То есть `_bgi_begin`/`_bgi_end`
|
||||||
|
корректно возвращают ИМЕННО банковую страницу (правило R4);
|
||||||
|
- вызов W1/W2-функции из банка работает, и она тоже может рисовать;
|
||||||
|
- резидент W3 жив и вызывается после возврата из банка (R3).
|
||||||
|
|
||||||
|
**Дополнительно выяснено (важно для стражей):** писучие статики
|
||||||
|
`__banked`-модуля линкуются В СТРАНИЦУ БАНКА (0x1C000+) — снаружи их не
|
||||||
|
прочитать, из W1/W2 по 0xC000 видна резидентная страница. Значит всё
|
||||||
|
состояние банкованного кода (позиции стражей, таймеры боя) обязано жить в
|
||||||
|
W1/W2 как обычные глобалы, а банк — только код.
|
||||||
|
|
||||||
|
Ещё одна мина, найденная там же: инлайновый `in a,(#0xE2)` посреди тела
|
||||||
|
функции затирает A, куда SDCC уже положил параметр (у нас из-за этого цвет
|
||||||
|
заливки стал номером страницы, и «резидент не рисовал»). Читать порт
|
||||||
|
отдельной `__naked`-функцией.
|
||||||
|
|
||||||
|
### Найденная по дороге ловушка компилятора
|
||||||
|
|
||||||
|
`(const T *)КОНСТАНТА + var*K` SDCC 4.5 может собрать неверно: умножение
|
||||||
|
делает в 16 битах, а потом берёт только младший байт (`ld c,l` / `inc b`).
|
||||||
|
Кадры Kid с индексом ≥ 52 читались из чужой строки таблицы, у бега/шага
|
||||||
|
пропадал `FRAME_NEEDS_FLOOR` и персонаж проваливался сквозь пол. Лечение —
|
||||||
|
считать адрес в `uint16_t` и кастовать один раз. Тот же паттерн в
|
||||||
|
`pop_level.c` компилируется ПРАВИЛЬНО, т.е. полагаться на «у соседа
|
||||||
|
работает» нельзя. Подробности: memory `sdcc_z80_const_ptr_index_bug`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. Что уже сделано из v1 (не повторять)
|
## 0. Что уже сделано из v1 (не повторять)
|
||||||
|
|
||||||
- `--opt-code-size` и `--max-allocs 100000` **уже включены по умолчанию** в
|
- `--opt-code-size` и `--max-allocs 100000` **уже включены по умолчанию** в
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# Генераторы псевдослучайных чисел: запасные варианты
|
||||||
|
|
||||||
|
Что сейчас стоит в порте, какие есть альтернативы и сколько на них реально
|
||||||
|
можно выиграть. Заготовка на случай, если упрёмся в бюджет кадра —
|
||||||
|
**сейчас менять ничего не нужно**.
|
||||||
|
|
||||||
|
## Что стоит сейчас
|
||||||
|
|
||||||
|
`pop_geom.c`, ветка `POP_PRANDOM_EXACT=1` (по умолчанию) — LCG оригинала
|
||||||
|
`s = s*214013 + 2531011`, шаг написан на Z80-ассемблере (единственное такое
|
||||||
|
место в порте). Схема Горнера по разреженной записи константы:
|
||||||
|
|
||||||
|
```
|
||||||
|
214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3
|
||||||
|
```
|
||||||
|
|
||||||
|
17 удвоений, три сложения, одно вычитание; величина `3*s`, нужная в конце,
|
||||||
|
попадается по дороге на втором шаге. Тело — **≈1 020 тактов** по статическому
|
||||||
|
подсчёту. Бит-в-бит совместим с SDLPoP, поэтому по картинке можно сверяться
|
||||||
|
с эталоном.
|
||||||
|
|
||||||
|
Вторая ветка, `POP_PRANDOM_EXACT=0` — xorshift16 + шаг Вейля на C.
|
||||||
|
Совместимость теряется.
|
||||||
|
|
||||||
|
Замер в MAME, комната 3, 175 кадров (медиана кадра):
|
||||||
|
|
||||||
|
| вариант | кадр | prandom → torch_draw |
|
||||||
|
|---|---|---|
|
||||||
|
| C, бит-в-бит (16-битные половины) | 403 632 | 10 933 |
|
||||||
|
| C, xorshift16 + Вейль | 397 986 | 7 927 |
|
||||||
|
| **asm, бит-в-бит (сейчас)** | **400 800** | **9 331** |
|
||||||
|
|
||||||
|
## Вариант A — комбинированный LFSR + LCG, ~148 тактов
|
||||||
|
|
||||||
|
Период > 4 млрд (lcm(65536, 65535) ≈ 4.29e9), младшие биты не вырождены.
|
||||||
|
|
||||||
|
```z80
|
||||||
|
prng16:
|
||||||
|
seed1=$+1
|
||||||
|
ld hl, 9999
|
||||||
|
ld b, h
|
||||||
|
ld c, l
|
||||||
|
add hl, hl
|
||||||
|
add hl, hl
|
||||||
|
inc l
|
||||||
|
add hl, bc
|
||||||
|
ld (seed1), hl
|
||||||
|
seed2=$+1
|
||||||
|
ld hl, 987
|
||||||
|
add hl, hl
|
||||||
|
sbc a, a
|
||||||
|
and 101101b
|
||||||
|
xor l
|
||||||
|
ld l, a
|
||||||
|
ld (seed2), hl
|
||||||
|
add hl, bc
|
||||||
|
ret
|
||||||
|
```
|
||||||
|
|
||||||
|
Устройство: `seed1` — LCG `x = 5x + 1` (по модулю 2^16; `inc l` вместо
|
||||||
|
`inc hl` — экономия байта, на период не влияет). `seed2` — 16-битный
|
||||||
|
LFSR Галуа: сдвиг влево, и если выехала единица, XOR младшего байта с маской
|
||||||
|
`0x2D` (примитивный многочлен `x^16 + x^5 + x^3 + x^2 + 1`). На выходе
|
||||||
|
сумма обоих состояний — она и разрушает регулярность младших бит LCG.
|
||||||
|
|
||||||
|
**Что мешает взять как есть:** сиды зашиты в код (SMC), а нам нужны ДВЕ
|
||||||
|
независимые последовательности — раскладка кладки и анимация тайлов.
|
||||||
|
Пришлось бы передавать состояние через указатель, как сейчас у `pop_prandom`
|
||||||
|
(это +20…40 тактов, не принципиально).
|
||||||
|
|
||||||
|
## Вариант B — xorshift(7,9,8), ~86 тактов
|
||||||
|
|
||||||
|
Самый быстрый, период 65535.
|
||||||
|
|
||||||
|
```z80
|
||||||
|
xrnd:
|
||||||
|
ld hl, 1 ; seed must not be 0
|
||||||
|
ld a, h
|
||||||
|
rra
|
||||||
|
ld a, l
|
||||||
|
rra
|
||||||
|
xor h
|
||||||
|
ld h, a
|
||||||
|
ld a, l
|
||||||
|
rra
|
||||||
|
ld a, h
|
||||||
|
rra
|
||||||
|
xor l
|
||||||
|
ld l, a
|
||||||
|
xor h
|
||||||
|
ld h, a
|
||||||
|
ld (xrnd+1), hl
|
||||||
|
ret
|
||||||
|
```
|
||||||
|
|
||||||
|
**Две оговорки.** Ноль — неподвижная точка, а сид раскладки кладки у нас
|
||||||
|
считается как `номер комнаты + смещение ряда + колонка` и вполне может
|
||||||
|
оказаться нулём: нужен либо guard, либо шаг Вейля поверх. И тот же SMC-сид,
|
||||||
|
что в варианте A.
|
||||||
|
|
||||||
|
## Чего НЕ брать: RND из Apple II
|
||||||
|
|
||||||
|
Оригинальный `Prince-of-Persia-Apple-II`:
|
||||||
|
|
||||||
|
```
|
||||||
|
RNDseed := (5 * RNDseed + 23) mod 256
|
||||||
|
```
|
||||||
|
|
||||||
|
```asm
|
||||||
|
RND
|
||||||
|
lda RNDseed
|
||||||
|
asl
|
||||||
|
asl
|
||||||
|
clc
|
||||||
|
adc RNDseed
|
||||||
|
clc
|
||||||
|
adc #23
|
||||||
|
sta RNDseed
|
||||||
|
rts
|
||||||
|
```
|
||||||
|
|
||||||
|
Полный период 256 (`a ≡ 1 mod 4`, `c` нечётное), и для своего движка он
|
||||||
|
работал. Нам не годится: у LCG по модулю 256 младшие биты вырождены — бит 0
|
||||||
|
просто чередуется. Наши вызовы это увидят: раскладка кладки берёт
|
||||||
|
`prandom(1)` (ОДИН бит) и `prandom(4)`, то есть вместо шума получилась бы
|
||||||
|
аккуратная шахматка.
|
||||||
|
|
||||||
|
## Сколько реально можно выиграть
|
||||||
|
|
||||||
|
Меньше, чем кажется по числам 86/148 против 1 020. Тело генератора — уже не
|
||||||
|
весь расход: остаются обёртка `pop_prandom`, приведение к диапазону
|
||||||
|
`pop_rnd_fit` и ABI вызова. Верхняя граница выигрыша видна из замера выше:
|
||||||
|
между нынешним asm-LCG и самым дешёвым из проверенных вариантов разница
|
||||||
|
**2 814 тактов за кадр (0.65 %)** при двух вызовах за кадр, и это ПОТОЛОК —
|
||||||
|
любой из вариантов A/B ниже него не опустится.
|
||||||
|
|
||||||
|
Порядок действий, если понадобится:
|
||||||
|
|
||||||
|
1. Сначала убрать обёртки: слить `pop_rnd_fit` в ту же asm-процедуру, чтобы
|
||||||
|
на вызов приходился один `call`, а не три. Это ничего не ломает и не
|
||||||
|
трогает совместимость с эталоном.
|
||||||
|
2. И только если этого мало — менять генератор, начиная с варианта A
|
||||||
|
(качество последовательности у него не хуже LCG, в отличие от B).
|
||||||
|
|
||||||
|
Важно помнить: число вызовов вырастет с боёвкой. Сейчас их два за кадр
|
||||||
|
(факелы), а `guard_advance` / `guard_block` / `guard_strike` дёргают
|
||||||
|
`prandom(255)` каждый по разу за кадр боя — то есть при драке станет 5–6, и
|
||||||
|
цена вопроса вырастет во столько же раз.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -37,5 +37,5 @@ static const char *const kid_atl[KID_PAGES] = {
|
|||||||
};
|
};
|
||||||
#define KID_PAL_FILE "kid.pal"
|
#define KID_PAL_FILE "kid.pal"
|
||||||
#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */
|
#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */
|
||||||
#define KID_SWORD_ID0 20 /* индекс в атласе = sword_tbl.id - ID0 */
|
#define KID_SWORD_ID0 0 /* индекс в атласе = sword_tbl.id - ID0 */
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -5,14 +5,23 @@ PROJ_ROOT := $(abspath $(CURDIR)/../../..)
|
|||||||
EXAMPLE := roomtest
|
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 ?= small
|
MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи)
|
||||||
# --w3 берёт ОДИН файл на флаг. В W3-резиденте держим ТОЛЬКО pop_bg —
|
# Резидента --w3 БОЛЬШЕ НЕТ: отрисовка уехала в БАНК 2 (pop_bg + pop_gdraw
|
||||||
# отрисовку тайлов: она большая и её зовёт лишь главный цикл. Всё
|
# в одной странице — вызовы между ними прямые, если функция не помечена
|
||||||
# остальное живёт в W1/W2, чтобы оставаться достижимым из будущих
|
# __banked). Так снято главное ограничение резидента: из банка его было
|
||||||
# __banked-модулей (стражи): из банка резидентная страница W3 не видна —
|
# не достать ни прямо, ни транзитивно, а банк→банк работает (трамплин
|
||||||
# ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4.
|
# сохраняет страницу окна на СТЕКЕ, проверено вложенной цепочкой
|
||||||
EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c
|
# банк1→банк2→банк1 в MAME 2026-07-30). Теперь в банки можно уводить и
|
||||||
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c
|
# логику, которая рисует. См. docs/layout_plan_v2.md §2, memory
|
||||||
|
# sdcc_banked_call_rules.
|
||||||
|
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c $(PROF_FLAGS)
|
||||||
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
|
# MAME = время фазы в растровых строках.
|
||||||
|
PROF ?= 1
|
||||||
|
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
||||||
|
EXTRA_SRCS := pop_state.c pop_kid.c pop_ctrl.c pop_level.c pop_trob.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
|
||||||
@@ -30,16 +39,36 @@ KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl
|
|||||||
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
|
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
|
||||||
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
|
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
|
||||||
KID_BIN := $(KID_DIR)/kid_data.bin
|
KID_BIN := $(KID_DIR)/kid_data.bin
|
||||||
|
GUARD_DIR := $(CURDIR)/../poc/res/guard
|
||||||
|
GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
|
||||||
|
$(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl
|
||||||
|
|
||||||
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(LVL_DATA)
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(PROJ_ROOT)/app.mk
|
||||||
|
|
||||||
|
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
|
||||||
|
# чтобы корень диска не зарастал десятками .atl.
|
||||||
|
# BG\ — фон (env/wall/fore/pot)
|
||||||
|
# KID\ — персонаж (kid0..27, палитра, меч, таблицы анимации)
|
||||||
|
# GUARD\ — стражи (появятся здесь же)
|
||||||
|
# LEVELS\ — уровни
|
||||||
|
hdd: $(EXAMPLE).exe
|
||||||
|
$(PROJ_ROOT)/toolchain/make_hdd.sh $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd \
|
||||||
|
$(EXAMPLE).exe \
|
||||||
|
$(foreach f,$(BG_DATA),BG:$(f)) \
|
||||||
|
$(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
|
||||||
|
$(foreach f,$(GUARD_DATA),GUARD:$(f)) \
|
||||||
|
LEVELS:$(LVL_DATA)
|
||||||
|
|
||||||
TC := $(PROJ_ROOT)/applications/PoP/toolchain
|
TC := $(PROJ_ROOT)/applications/PoP/toolchain
|
||||||
$(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py
|
$(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py
|
||||||
cd $(TC) && python3 pop_pack_bg.py
|
cd $(TC) && python3 pop_pack_bg.py
|
||||||
|
$(GUARD_DATA): $(TC)/pop_pack_guard.py
|
||||||
|
cd $(TC) && python3 pop_pack_guard.py
|
||||||
|
|
||||||
$(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
$(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
||||||
cd $(TC) && python3 pop_pack_kid.py
|
cd $(TC) && python3 pop_pack_kid.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: pop_bg.c pop_bg.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_gdraw.c 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)
|
||||||
|
|||||||
@@ -0,0 +1,499 @@
|
|||||||
|
/*
|
||||||
|
* guards.c — ЛОГИКА стража, собирается в БАНК (--bank 1=guards.c, окно W3).
|
||||||
|
*
|
||||||
|
* Правила банка (проверено пробником tests/w3bankgfx):
|
||||||
|
* - состояние держим в W1/W2 (pop_guard.c): статики банка линкуются в
|
||||||
|
* страницу банка и снаружи не читаются;
|
||||||
|
* - звать можно W1/W2-код (pop_map, pop_kid, pop_geom, libc, примитивы
|
||||||
|
* libbgi) — скобка _bgi_begin/_bgi_end возвращает страницу БАНКА;
|
||||||
|
* - звать НЕЛЬЗЯ резидент W3 (pop_bg) — ни напрямую, ни транзитивно:
|
||||||
|
* пока исполняется банк, резидентной страницы в адресном пространстве
|
||||||
|
* нет. Поэтому фон/окклюзию за стража рисует главный цикл.
|
||||||
|
*
|
||||||
|
* Порт: seg003 check_can_guard_see_kid, seg002 autocontrol_*, seg000
|
||||||
|
* play_guard_frame, seg006 char_opp_dist. ДИСПЕТЧЕР у соперника не свой:
|
||||||
|
* ИИ выставляет те же глобалы control_* (pop_ctrl.h), что и ввод игрока, а
|
||||||
|
* дальше работает общий pop_control().
|
||||||
|
*/
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "pop_ctrl.h"
|
||||||
|
#include "pop_map.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_trob.h"
|
||||||
|
#include "pop_state.h" /* pop_immortal — чит бессмертия */
|
||||||
|
|
||||||
|
#define TILE_MIDX 7 /* середина тайла в координатах персонажа */
|
||||||
|
#define SCREENSPACE_X 58 /* = pop_x_bump[FIRST_ONSCREEN_COLUMN] */
|
||||||
|
|
||||||
|
#define TILE_GATE 4
|
||||||
|
#define TILE_DOORTOP_FLOOR 7
|
||||||
|
#define TILE_LOOSE 11
|
||||||
|
#define TILE_DOORTOP 12
|
||||||
|
#define TILE_CHOMPER 18
|
||||||
|
#define TILE_WALL 20
|
||||||
|
|
||||||
|
/* tile_is_floor (seg004): не-пол — пустота, ковёр, верх двери, стена и
|
||||||
|
* «половинки» 26..29. Дубликат крошечного switch'а из pop_map: тянуть его
|
||||||
|
* через границу банка дороже, чем повторить. */
|
||||||
|
static uint8_t tile_is_floor(uint8_t t)
|
||||||
|
{
|
||||||
|
switch (t) {
|
||||||
|
case 0: case 9: case 12: case 20:
|
||||||
|
case 26: case 27: case 28: case 29:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Тайл в ряду Кида по X-координате персонажа (порт get_tile_at_kid,
|
||||||
|
* seg003:761): xpos − 7 делится на ширину тайла с округлением вниз. */
|
||||||
|
static int8_t tile_col; /* колонка последнего tile_at_kid */
|
||||||
|
|
||||||
|
static uint8_t tile_at_kid(int16_t xpos)
|
||||||
|
{
|
||||||
|
/* get_tile_div_mod_m7 (seg006:697): колонка = floor((xpos − 7 − 58)/14),
|
||||||
|
* СРАЗУ в координатах комнаты (58 = x_bump[FIRST_ONSCREEN_COLUMN]).
|
||||||
|
* Оригинал берёт её из tile_div_tbl, у нас — делением с округлением
|
||||||
|
* вниз, как в pop_map. */
|
||||||
|
int16_t x = xpos - 7 - SCREENSPACE_X;
|
||||||
|
int16_t col = x / TILE_SIZEX;
|
||||||
|
if (x % TILE_SIZEX < 0) --col;
|
||||||
|
tile_col = (int8_t)col;
|
||||||
|
return pop_tile_at(tile_col, Kid.curr_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check_can_guard_see_kid (seg003:688) — луч видимости по ряду.
|
||||||
|
* Результат в can_guard_see_kid: 0 не видит / 1 видит, но не пойдёт /
|
||||||
|
* 2 видит и пойдёт. */
|
||||||
|
void pop_check_can_guard_see_kid(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t kid_frame = Kid.frame;
|
||||||
|
int16_t left_pos, right_pos;
|
||||||
|
uint8_t tile;
|
||||||
|
|
||||||
|
if (!(Guard.charid != 0 &&
|
||||||
|
/* кадры 219..228 — подъём по лестнице: там страж Кида не видит */
|
||||||
|
kid_frame != 0 && (kid_frame < 219 || kid_frame >= 229) &&
|
||||||
|
Kid.alive < 0 && Guard.alive < 0 &&
|
||||||
|
Kid.room == Guard.room && Kid.curr_row == Guard.curr_row)) {
|
||||||
|
can_guard_see_kid = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
can_guard_see_kid = 2;
|
||||||
|
left_pos = pop_x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||||
|
right_pos = pop_x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||||
|
if (left_pos > right_pos) {
|
||||||
|
int16_t t = left_pos; left_pos = right_pos; right_pos = t;
|
||||||
|
}
|
||||||
|
/* Чомпер стоит у ЛЕВОГО края тайла — свой тайл он не перекрывает. */
|
||||||
|
if (tile_at_kid(left_pos) == TILE_CHOMPER)
|
||||||
|
left_pos += TILE_SIZEX;
|
||||||
|
/* Ворота — у ПРАВОГО края, поэтому крайний правый тайл не считается. */
|
||||||
|
if (tile_at_kid(right_pos) == TILE_GATE)
|
||||||
|
right_pos -= TILE_SIZEX;
|
||||||
|
|
||||||
|
while (left_pos <= right_pos) {
|
||||||
|
tile = tile_at_kid(left_pos);
|
||||||
|
/* Сквозь это не видно вовсе. */
|
||||||
|
if (tile == TILE_WALL || tile == TILE_DOORTOP_FLOOR || tile == TILE_DOORTOP) {
|
||||||
|
can_guard_see_kid = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Видно, но страж не пойдёт: провалится / попадёт под чомпер /
|
||||||
|
* упрётся в неоткрытые ворота / шагнёт в дыру. */
|
||||||
|
if (tile == TILE_LOOSE || tile == TILE_CHOMPER || !tile_is_floor(tile)) {
|
||||||
|
can_guard_see_kid = 1;
|
||||||
|
} else if (tile == TILE_GATE &&
|
||||||
|
pop_trob_modif(Kid.room)[Kid.curr_row * 10 + tile_col] < 112) {
|
||||||
|
can_guard_see_kid = 1; /* ворота подняты не до конца */
|
||||||
|
}
|
||||||
|
left_pos += TILE_SIZEX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Синтетический ввод (seg002:0706..) --------------------------- */
|
||||||
|
static void move_0_nothing(void)
|
||||||
|
{
|
||||||
|
control_shift = control_y = control_x = control_shift2 = 0;
|
||||||
|
control_down = control_up = control_backward = control_forward = 0;
|
||||||
|
}
|
||||||
|
static void move_1_forward(void)
|
||||||
|
{
|
||||||
|
control_x = POP_CONTROL_HELD_FORWARD;
|
||||||
|
control_forward = POP_CONTROL_HELD;
|
||||||
|
}
|
||||||
|
static void move_2_backward(void)
|
||||||
|
{
|
||||||
|
control_backward = POP_CONTROL_HELD;
|
||||||
|
control_x = POP_CONTROL_HELD_BACKWARD;
|
||||||
|
}
|
||||||
|
static void move_3_up(void)
|
||||||
|
{
|
||||||
|
control_y = POP_CONTROL_HELD_UP;
|
||||||
|
control_up = POP_CONTROL_HELD;
|
||||||
|
}
|
||||||
|
static void move_4_down(void)
|
||||||
|
{
|
||||||
|
control_down = POP_CONTROL_HELD;
|
||||||
|
control_y = POP_CONTROL_HELD_DOWN;
|
||||||
|
}
|
||||||
|
static void move_6_shift(void)
|
||||||
|
{
|
||||||
|
control_shift = POP_CONTROL_HELD;
|
||||||
|
control_shift2 = POP_CONTROL_HELD;
|
||||||
|
}
|
||||||
|
static void move_down_back(void)
|
||||||
|
{
|
||||||
|
control_down = POP_CONTROL_HELD;
|
||||||
|
move_2_backward();
|
||||||
|
}
|
||||||
|
static void move_down_forw(void)
|
||||||
|
{
|
||||||
|
control_down = POP_CONTROL_HELD;
|
||||||
|
move_1_forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* autocontrol_guard_inactive (seg002:710): страж с убранным мечом.
|
||||||
|
* Пока Кид за спиной — страж не реагирует; исключение — «шум» Кида
|
||||||
|
* (is_guard_notice), от которого страж оборачивается. */
|
||||||
|
static void autocontrol_guard_inactive(void)
|
||||||
|
{
|
||||||
|
int16_t distance;
|
||||||
|
if (Kid.alive >= 0) return;
|
||||||
|
distance = pop_char_opp_dist();
|
||||||
|
/* В оригинале условие записано как (word)distance < (word)-8 —
|
||||||
|
* беззнаковое сравнение, истинное для ВСЕГО, кроме диапазона −8..−1.
|
||||||
|
* То есть: Кид не «вплотную за спиной». */
|
||||||
|
if (Opp.curr_row != Char.curr_row || distance >= 0 || distance < -8) {
|
||||||
|
if (is_guard_notice) {
|
||||||
|
is_guard_notice = 0;
|
||||||
|
if (distance < 0) {
|
||||||
|
if (distance < -4) move_4_down(); /* обернуться на шум */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (distance < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (can_guard_see_kid) {
|
||||||
|
/* Увидел — переходит в боевую стойку (вниз+вперёд = достать меч). */
|
||||||
|
move_down_forw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Активный страж (меч обнажён), seg002:737.. -------------------- *
|
||||||
|
* Таблицы вероятностей по 12 градациям мастерства (seg002:26..38). Бросок
|
||||||
|
* — prandom(255) с СОБСТВЕННЫМ сидом: у нас последовательности разведены
|
||||||
|
* по доменам (кладка / анимация тайлов / бой), см. pop_geom.h. */
|
||||||
|
static const uint8_t STRIKEPROB[NUM_GUARD_SKILLS] = { 61,100, 61, 61, 61, 40,100,220, 0, 48, 32, 48};
|
||||||
|
static const uint8_t RESTRIKEPROB[NUM_GUARD_SKILLS] = { 0, 0, 0, 5, 5,175, 16, 8, 0,255,255,150};
|
||||||
|
static const uint8_t BLOCKPROB[NUM_GUARD_SKILLS] = { 0,150,150,200,200,255,200,250, 0,255,255,255};
|
||||||
|
static const uint8_t IMPBLOCKPROB[NUM_GUARD_SKILLS] = { 0, 61, 61,100,100,145,100,250, 0,145,255,175};
|
||||||
|
static const uint8_t ADVPROB[NUM_GUARD_SKILLS] = {255,200,200,200,255,255,200, 0, 0,255,100,100};
|
||||||
|
|
||||||
|
/* Бросок «сработало ли»: в оригинале prob > prandom(255). */
|
||||||
|
static uint8_t roll(uint8_t prob)
|
||||||
|
{
|
||||||
|
return (uint8_t)(prob > (uint8_t)pop_prandom(&pop_fight_seed, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* guard_advance (seg002:0AF5): наступать. Мастерство 0 наступает всегда,
|
||||||
|
* остальные — только пока Кид не замахнулся. */
|
||||||
|
static void guard_advance(void)
|
||||||
|
{
|
||||||
|
if (guard_skill == 0 || kid_sword_strike == 0) {
|
||||||
|
if (roll(ADVPROB[guard_skill])) move_1_forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* guard_block (seg002:0B1D): блокировать замах Кида. Если Кид только что
|
||||||
|
* пробил блок (justblocked) — своя, «импровизационная» вероятность. */
|
||||||
|
static void guard_block(void)
|
||||||
|
{
|
||||||
|
uint8_t of = Opp.frame;
|
||||||
|
if (of == FRAME_152_STRIKE_2 || of == FRAME_153_STRIKE_3 ||
|
||||||
|
of == FRAME_162_BLOCK_TO_STRIKE) {
|
||||||
|
if (roll(justblocked ? IMPBLOCKPROB[guard_skill]
|
||||||
|
: BLOCKPROB[guard_skill]))
|
||||||
|
move_3_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* guard_strike (seg002:0B73): бить. По замаху Кида не бьём (кадры 169/151);
|
||||||
|
* из своего парирования — по отдельной вероятности «добить». */
|
||||||
|
static void guard_strike(void)
|
||||||
|
{
|
||||||
|
uint8_t of = Opp.frame, cf = Char.frame;
|
||||||
|
if (of == FRAME_169_BEGIN_BLOCK || of == FRAME_151_STRIKE_1) return;
|
||||||
|
if (cf == FRAME_161_PARRY || cf == FRAME_150_PARRY) {
|
||||||
|
if (roll(RESTRIKEPROB[guard_skill])) move_6_shift();
|
||||||
|
} else {
|
||||||
|
if (roll(STRIKEPROB[guard_skill])) move_6_shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* autocontrol_guard_kid_armed (seg002:0AC1): Кид с мечом. Вплотную и
|
||||||
|
* далеко — наступаем; в «рабочей» дистанции 10..28 сначала пробуем блок, и
|
||||||
|
* только отдышавшись бьём. */
|
||||||
|
static void autocontrol_guard_kid_armed(int16_t distance)
|
||||||
|
{
|
||||||
|
if (distance < 10 || distance >= 29) {
|
||||||
|
guard_advance();
|
||||||
|
} else {
|
||||||
|
guard_block();
|
||||||
|
if (guard_refrac == 0) {
|
||||||
|
if (distance < 12 || distance >= 29) guard_advance();
|
||||||
|
else guard_strike();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* autocontrol_guard_kid_in_sight (seg002:0A93). */
|
||||||
|
static void autocontrol_guard_kid_in_sight(int16_t distance)
|
||||||
|
{
|
||||||
|
if (Opp.sword == SWORD_2_DRAWN) {
|
||||||
|
autocontrol_guard_kid_armed(distance);
|
||||||
|
} else if (guard_refrac == 0) {
|
||||||
|
/* Кид безоружен: подойти и рубить. */
|
||||||
|
if (distance < 29) move_6_shift();
|
||||||
|
else move_1_forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Тайл перед стражем / через один (порт get_tile_infrontof_char и
|
||||||
|
* get_tile_infrontof2_char, seg004) — но для Char, а не для Kid. */
|
||||||
|
static uint8_t tile_infront(int8_t step)
|
||||||
|
{
|
||||||
|
int8_t d = (int8_t)(Char.direction < 0 ? -step : step);
|
||||||
|
return pop_tile_at((int8_t)(Char.curr_col + d), Char.curr_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* autocontrol_guard_kid_far (seg002:09CB): идём, если впереди есть пол хотя
|
||||||
|
* бы через тайл; иначе пятимся (не шагать в пропасть). */
|
||||||
|
static void autocontrol_guard_kid_far(void)
|
||||||
|
{
|
||||||
|
if (tile_is_floor(tile_infront(1)) || tile_is_floor(tile_infront(2)))
|
||||||
|
move_1_forward();
|
||||||
|
else
|
||||||
|
move_2_backward();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* autocontrol_guard_active (seg002:737). */
|
||||||
|
static void autocontrol_guard_active(void)
|
||||||
|
{
|
||||||
|
uint8_t cf = Char.frame;
|
||||||
|
uint8_t of;
|
||||||
|
int16_t distance;
|
||||||
|
|
||||||
|
/* Кадр 166 (неактивная стойка) и всё, что ниже 150, — не боевые позы. */
|
||||||
|
if (cf == FRAME_166_STAND_INACTIVE || cf < 150 || can_guard_see_kid == 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (can_guard_see_kid == 0) {
|
||||||
|
/* TODO: droppedout -> guard_follows_kid_down (seg002:09F8) — вместе
|
||||||
|
* с физикой стража; скелета (charid 4) ветка тоже не берёт. */
|
||||||
|
if (Char.charid != CHARID_4_SKELETON) move_down_back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* can_guard_see_kid == 2 */
|
||||||
|
of = Opp.frame;
|
||||||
|
distance = pop_char_opp_dist();
|
||||||
|
/* Кид падает/приземляется в отскоке — не мешаем ему, ждём. */
|
||||||
|
if (distance >= 12 && of >= 102 && of < 118 && Opp.action == 5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (distance < 35) {
|
||||||
|
if ((Char.sword < SWORD_2_DRAWN && distance < 8) || distance < 12) {
|
||||||
|
/* Слишком близко: если смотрим в одну сторону — развернуться. */
|
||||||
|
if (Char.direction == Opp.direction) move_2_backward();
|
||||||
|
else move_1_forward();
|
||||||
|
} else {
|
||||||
|
autocontrol_guard_kid_in_sight(distance);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guard_refrac != 0) return;
|
||||||
|
if (Char.direction != Opp.direction) {
|
||||||
|
/* Кид разбежался — встречаем ударом, а не шагом. */
|
||||||
|
if (of >= 7 && of < 15) { /* бег */
|
||||||
|
if (distance < 40) move_6_shift();
|
||||||
|
return;
|
||||||
|
} else if (of >= 34 && of < 44) { /* разбег в прыжок */
|
||||||
|
if (distance < 50) move_6_shift();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
autocontrol_guard_kid_far();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* autocontrol_guard (seg002:701) + autocontrol_opponent (seg002:628). */
|
||||||
|
static void autocontrol_opponent(void)
|
||||||
|
{
|
||||||
|
move_0_nothing();
|
||||||
|
/* Таймеры боя убывают раз в кадр — здесь, как в оригинале. */
|
||||||
|
if (justblocked) --justblocked;
|
||||||
|
if (kid_sword_strike) --kid_sword_strike;
|
||||||
|
if (guard_refrac) --guard_refrac;
|
||||||
|
if (Char.sword < SWORD_2_DRAWN) autocontrol_guard_inactive();
|
||||||
|
else autocontrol_guard_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Урон мечом (seg002:0CD4..0E1F) -------------------------------- */
|
||||||
|
#define ACTION_99_HURT 99
|
||||||
|
#define ACTION_1_RUN 1
|
||||||
|
#define FRAME_154_POKING 154
|
||||||
|
#define SEQ_69_ATTACK_WAS_PARRIED 69
|
||||||
|
#define SEQ_74_HIT_BY_SWORD 74
|
||||||
|
#define SEQ_85_STABBED_TO_DEATH 85
|
||||||
|
|
||||||
|
/* refractimer (seg002:36) — «отдышка» стража после того, как его ранили. */
|
||||||
|
static const uint8_t REFRACTIMER[NUM_GUARD_SKILLS] =
|
||||||
|
{ 16, 16, 16, 16, 8, 8, 8, 8, 0, 8, 0, 0};
|
||||||
|
|
||||||
|
/* take_hp (seg006): отнять count HP у АКТИВНОГО персонажа через дельту.
|
||||||
|
* Возврат 1 — HP кончились. Чит бессмертия ловим здесь: анимацию удара
|
||||||
|
* Кид получает, а HP не теряет. */
|
||||||
|
static uint8_t take_hp(uint8_t count)
|
||||||
|
{
|
||||||
|
if (Char.charid == CHARID_0_KID) {
|
||||||
|
if (pop_immortal) return 0;
|
||||||
|
if (count >= hitp_curr) { hitp_delta = (int8_t)-hitp_curr; return 1; }
|
||||||
|
hitp_delta = (int8_t)-(int8_t)count;
|
||||||
|
} else {
|
||||||
|
if (count >= guardhp_curr) { guardhp_delta = (int8_t)-guardhp_curr; return 1; }
|
||||||
|
guardhp_delta = (int8_t)-(int8_t)count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hurt_by_sword (seg002): применить попадание к АКТИВНОМУ персонажу.
|
||||||
|
* Без меча в руке любое попадание смертельно; с мечом — минус 1 HP и кадр
|
||||||
|
* «получил удар».
|
||||||
|
* УПРОЩЕНИЕ: ветку «сбит с уступа» (seq_81, когда сзади пусто и до кромки
|
||||||
|
* меньше 4) не портируем — ей нужны тайловые запросы ОТ Char, а pop_map
|
||||||
|
* пока умеет только от Kid. На ровном полу (тайл сзади не пустой) оригинал
|
||||||
|
* идёт ровно нашей веткой. */
|
||||||
|
static void hurt_by_sword(void)
|
||||||
|
{
|
||||||
|
if (Char.alive >= 0) return;
|
||||||
|
/* Чит бессмертия перекрывает и «безоружного закалывают насмерть»:
|
||||||
|
* без этого Кид гибнет с одного удара мимо стойки, и чит бесполезен
|
||||||
|
* ровно там, где нужен. Анимацию удара оставляем. */
|
||||||
|
if (Char.charid == CHARID_0_KID && pop_immortal) {
|
||||||
|
pop_char_set_seq(SEQ_74_HIT_BY_SWORD);
|
||||||
|
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||||
|
Char.fall_y = 0;
|
||||||
|
play_seq();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Char.sword != SWORD_2_DRAWN) {
|
||||||
|
take_hp(100);
|
||||||
|
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH);
|
||||||
|
} else if (Char.charid != CHARID_4_SKELETON && take_hp(1)) {
|
||||||
|
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH); /* HP кончились */
|
||||||
|
} else {
|
||||||
|
pop_char_set_seq(SEQ_74_HIT_BY_SWORD);
|
||||||
|
}
|
||||||
|
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||||
|
Char.fall_y = 0;
|
||||||
|
play_seq(); /* TODO: звук 12/13 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check_hurting (seg002:0D56): достал ли АКТИВНЫЙ персонаж соперника.
|
||||||
|
* Бьют кадры 153/154; если соперник в этот момент парирует — удар отбит. */
|
||||||
|
static void check_hurting(void)
|
||||||
|
{
|
||||||
|
int16_t distance;
|
||||||
|
uint8_t cf, of, min_range;
|
||||||
|
|
||||||
|
if (Char.sword != SWORD_2_DRAWN) return;
|
||||||
|
if (Char.curr_row != Opp.curr_row) return;
|
||||||
|
cf = Char.frame;
|
||||||
|
if (cf != FRAME_153_STRIKE_3 && cf != FRAME_154_POKING) return;
|
||||||
|
|
||||||
|
distance = pop_char_opp_dist();
|
||||||
|
of = Opp.frame;
|
||||||
|
if (distance < 0 || distance >= 29 ||
|
||||||
|
(of != FRAME_161_PARRY && of != FRAME_150_PARRY)) {
|
||||||
|
/* Соперник НЕ парирует. */
|
||||||
|
if (cf == FRAME_154_POKING) {
|
||||||
|
min_range = (uint8_t)(Opp.sword < SWORD_2_DRAWN ? 8 : 12);
|
||||||
|
distance = pop_char_opp_dist();
|
||||||
|
if (distance >= (int16_t)min_range && distance < 29)
|
||||||
|
Opp.action = ACTION_99_HURT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Парировано. */
|
||||||
|
Opp.frame = FRAME_161_PARRY;
|
||||||
|
if (Char.charid != CHARID_0_KID) justblocked = 4;
|
||||||
|
pop_char_set_seq(SEQ_69_ATTACK_WAS_PARRIED);
|
||||||
|
play_seq();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check_sword_hurting (seg002:0D1A): прогнать проверку с ОБЕИХ сторон. */
|
||||||
|
void pop_check_sword_hurting(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t kf = Kid.frame;
|
||||||
|
if (kf == 0 || (kf >= 219 && kf < 229)) return; /* подъём по лестнице */
|
||||||
|
pop_loadshad_and_opp();
|
||||||
|
check_hurting();
|
||||||
|
pop_saveshad_and_opp();
|
||||||
|
pop_loadkid_and_opp();
|
||||||
|
check_hurting();
|
||||||
|
pop_savekid_and_opp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check_sword_hurt (seg002:0CD4): применить помеченные попадания. Если
|
||||||
|
* ранен страж — Киду в этом кадре урон уже не проходит (в оригинале так же:
|
||||||
|
* его action сбрасывается ДО проверки). */
|
||||||
|
void pop_check_sword_hurt(void) __banked
|
||||||
|
{
|
||||||
|
if (Guard.action == ACTION_99_HURT) {
|
||||||
|
if (Kid.action == ACTION_99_HURT) Kid.action = ACTION_1_RUN;
|
||||||
|
pop_loadshad();
|
||||||
|
hurt_by_sword();
|
||||||
|
pop_saveshad();
|
||||||
|
guard_refrac = (int8_t)REFRACTIMER[guard_skill];
|
||||||
|
} else if (Kid.action == ACTION_99_HURT) {
|
||||||
|
pop_loadkid();
|
||||||
|
hurt_by_sword();
|
||||||
|
pop_savekid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* play_guard (seg006): ИИ + общий диспетчер. Отдельная ветка «HP кончились»:
|
||||||
|
* пока страж жив (alive < 0) и HP есть — обычный ход; как только HP дошли до
|
||||||
|
* нуля, он переводится в «умирает» (alive = 0), и дальше тот же control()
|
||||||
|
* ставит ему seq_71_dying. Уже умирающий сюда попадает с alive >= 0 и просто
|
||||||
|
* доигрывает последовательность. */
|
||||||
|
static void play_guard(void)
|
||||||
|
{
|
||||||
|
if (Char.alive < 0 && guardhp_curr == 0) {
|
||||||
|
Char.alive = 0;
|
||||||
|
/* on_guard_killed (seg006:1927) — у нас пока только звук победы,
|
||||||
|
* которого нет; для уровня 1 других эффектов там нет. */
|
||||||
|
}
|
||||||
|
autocontrol_opponent();
|
||||||
|
pop_control();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pop_guard_tick — порт play_guard_frame (seg000:1246) в объёме, который у
|
||||||
|
* нас уже есть: окно Char/Opp вокруг ИИ, диспетчера и интерпретатора
|
||||||
|
* последовательностей. Физика стража (fall_accel/коллизия/бамп/пики) —
|
||||||
|
* отдельным шагом. */
|
||||||
|
void pop_guard_tick(void) __banked
|
||||||
|
{
|
||||||
|
if (Guard.charid == 0) return; /* стража в комнате нет */
|
||||||
|
pop_loadshad_and_opp(); /* Char = Guard, Opp = Kid */
|
||||||
|
play_guard();
|
||||||
|
/* Анимация: та же seqtbl, что у Кида, но своя таблица кадров —
|
||||||
|
* выбирается по Char.charid внутри load_frame (seg006:0293). */
|
||||||
|
play_seq();
|
||||||
|
pop_saveshad();
|
||||||
|
}
|
||||||
@@ -18,6 +18,10 @@ typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe;
|
|||||||
/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */
|
/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */
|
||||||
#define KID_BIN_FRAMES_OFF 0
|
#define KID_BIN_FRAMES_OFF 0
|
||||||
#define KID_BIN_SEQTBL_OFF 1205
|
#define KID_BIN_SEQTBL_OFF 1205
|
||||||
|
/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс
|
||||||
|
* frame + add_frame - 149 (add_frame=70 для кадров 102..106). */
|
||||||
|
#define KID_NGFRAMES 41
|
||||||
|
#define KID_BIN_GFRAMES_OFF 3515
|
||||||
|
|
||||||
#define KID_NSEQ 115
|
#define KID_NSEQ 115
|
||||||
static const uint16_t kid_seq_off[KID_NSEQ] = {
|
static const uint16_t kid_seq_off[KID_NSEQ] = {
|
||||||
@@ -38,4 +42,31 @@ static const uint16_t kid_seq_off[KID_NSEQ] = {
|
|||||||
0x21FC,0x2240,0x2257,
|
0x21FC,0x2240,0x2257,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.
|
||||||
|
* Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.
|
||||||
|
* Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),
|
||||||
|
* x — «вперёд» по направлению персонажа. */
|
||||||
|
#define KID_NSWORD 51
|
||||||
|
#define KID_SWORD_ID_INIT { \
|
||||||
|
0xFF,0x00,0x05,0x01,0x02,0x06,0x07,0x03,0x04,0x1E,0x08,0x09, \
|
||||||
|
0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x13,0x11, \
|
||||||
|
0x11,0x07,0x01,0x1C,0x08,0x04,0x04,0x0A,0x0D,0x0D,0x0D,0x14, \
|
||||||
|
0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0xFF,0xFF,0x1D,0x08,0x1F, \
|
||||||
|
0x20,0x21,0x08, \
|
||||||
|
}
|
||||||
|
#define KID_SWORD_DX_INIT { \
|
||||||
|
0,0,-9,7,17,7,0,17,16,12,13,7, \
|
||||||
|
10,10,22,28,13,0,0,21,14,21,22,7, \
|
||||||
|
15,0,7,14,7,6,9,11,24,19,21,7, \
|
||||||
|
14,14,14,28,28,21,14,14,21,0,8,14, \
|
||||||
|
14,7,8, \
|
||||||
|
}
|
||||||
|
#define KID_SWORD_DY_INIT { \
|
||||||
|
0,-9,-29,-25,-26,-14,-5,-16,-19,-9,-34,-25, \
|
||||||
|
-16,-11,-21,-23,-35,-38,-29,-19,-23,-22,-23,-13, \
|
||||||
|
-18,-8,-27,-28,-27,-23,-21,-18,-23,-23,-23,-32, \
|
||||||
|
-32,-31,-29,-28,-28,-25,-22,-25,-25,-16,-37,-24, \
|
||||||
|
-24,-14,-37, \
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "pop_bg.h"
|
#include "pop_bg.h"
|
||||||
|
#include "pop_state.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
||||||
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */
|
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */
|
||||||
|
|
||||||
@@ -24,22 +26,25 @@ static atlas_t fore_at;
|
|||||||
static atlas_t pot_at; /* chtab_1: зелья (склянка + пузырёк) */
|
static atlas_t pot_at; /* chtab_1: зелья (склянка + пузырёк) */
|
||||||
|
|
||||||
static const char *const ENV_ATL[5] = {
|
static const char *const ENV_ATL[5] = {
|
||||||
"pop_env0.atl", "pop_env1.atl", "pop_env2.atl",
|
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон,
|
||||||
"pop_env3.atl", "pop_env4.atl"
|
* KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска
|
||||||
|
* зарастает десятками .atl. */
|
||||||
|
"BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl",
|
||||||
|
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
|
||||||
};
|
};
|
||||||
|
|
||||||
int pop_bg_load(void)
|
int pop_bg_load(void) __banked
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 5; i++)
|
for (i = 0; i < 5; i++)
|
||||||
if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1;
|
if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1;
|
||||||
if (atlas_load(&wall_at, "pop_wall.atl") != 0) return -1;
|
if (atlas_load(&wall_at, "BG\\pop_wall.atl") != 0) return -1;
|
||||||
if (atlas_load(&fore_at, "pop_fore.atl") != 0) return -1;
|
if (atlas_load(&fore_at, "BG\\pop_fore.atl") != 0) return -1;
|
||||||
if (atlas_load(&pot_at, "pop_pot.atl") != 0) return -1;
|
if (atlas_load(&pot_at, "BG\\pop_pot.atl") != 0) return -1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_bg_free(void)
|
void pop_bg_free(void) __banked
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 5; i++)
|
for (i = 0; i < 5; i++)
|
||||||
@@ -58,33 +63,73 @@ static int g_clip_top;
|
|||||||
/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x,
|
/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x,
|
||||||
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0).
|
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0).
|
||||||
* При g_clip_top рисуем только часть кадра ниже границы (gfx_blit_part). */
|
* При g_clip_top рисуем только часть кадра ниже границы (gfx_blit_part). */
|
||||||
|
/* ОКНО FORE-СЛОЯ (экранные координаты, полуинтервал). Пока оно включено,
|
||||||
|
* blit_b рисует только ту часть куска, что попадает в окно.
|
||||||
|
*
|
||||||
|
* Зачем: fore-слой существует РОВНО для того, чтобы вернуть куски тайлов
|
||||||
|
* поверх нарисованного персонажа — за пределами его прямоугольника в
|
||||||
|
* видеопамяти и так лежит правильный фон. А рисовал он тайлы ЦЕЛИКОМ:
|
||||||
|
* стена (код 20) — блок 64×63 = 4032 пикселя, и таких кусков за проход
|
||||||
|
* до девяти. Замер в MAME 2026-07-29 (комната 3, Kid + страж): проход
|
||||||
|
* поверх Kid — 402 816 тактов, поверх стража — 431 964, вместе 78 % всей
|
||||||
|
* работы кадра при бюджете 430 000 тактов на кадр. С окном большинство
|
||||||
|
* кусков отсекается целиком, остальные режутся до пересечения. */
|
||||||
|
static int fclip_x0, fclip_y0, fclip_x1, fclip_y1;
|
||||||
|
static uint8_t fclip_on;
|
||||||
|
|
||||||
|
void pop_fore_set_clip(int x, int y, int w, int h) __banked
|
||||||
|
{
|
||||||
|
fclip_x0 = x; fclip_y0 = y;
|
||||||
|
fclip_x1 = x + w; fclip_y1 = y + h;
|
||||||
|
}
|
||||||
|
|
||||||
static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||||
{
|
{
|
||||||
const uint8_t *img;
|
const uint8_t *img;
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
int top;
|
int top, dx, dy, dw, dh, sx = 0, sy = 0;
|
||||||
if (idx >= a->count)
|
if (idx >= a->count)
|
||||||
return;
|
return;
|
||||||
|
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
|
||||||
|
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
|
||||||
|
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
|
||||||
|
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
|
||||||
|
* atlas_image, ни за map/unmap. */
|
||||||
|
if (fclip_on &&
|
||||||
|
(x >= fclip_x1 || x + 64 <= fclip_x0 ||
|
||||||
|
ybottom + POP_YOFF + 1 <= fclip_y0 ||
|
||||||
|
ybottom + POP_YOFF - 64 >= fclip_y1))
|
||||||
|
return;
|
||||||
img = (const uint8_t *)atlas_image(a, idx);
|
img = (const uint8_t *)atlas_image(a, idx);
|
||||||
gfx_w0_map(a->page);
|
gfx_w0_map(a->page);
|
||||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||||||
if (w && h) {
|
dx = x; dy = top; dw = (int)w; dh = (int)h;
|
||||||
if (g_clip_top && top < g_clip_top) {
|
if (g_clip_top && dy < g_clip_top) { /* полоса у потолка */
|
||||||
int sy = g_clip_top - top; /* пропустить верхние строки */
|
sy = g_clip_top - dy; dy = g_clip_top; dh -= sy;
|
||||||
if (sy < (int)h)
|
}
|
||||||
gfx_blit_part(x, g_clip_top, img, 0, sy, (int)w, (int)h - sy);
|
if (fclip_on) { /* окно fore-слоя */
|
||||||
} else if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
if (dx < fclip_x0) { int d = fclip_x0 - dx; sx += d; dx += d; dw -= d; }
|
||||||
x + (int)w <= 320 && top + (int)h <= 256) {
|
if (dy < fclip_y0) { int d = fclip_y0 - dy; sy += d; dy += d; dh -= d; }
|
||||||
/* Спрайт целиком на экране — линейное ядро без клипа: ~2.9×
|
if (dx + dw > fclip_x1) dw = fclip_x1 - dx;
|
||||||
* быстрее (замер: 4617 против 13288 тактов на спрайт 32×3;
|
if (dy + dh > fclip_y1) dh = fclip_y1 - dy;
|
||||||
* общее ядро gfx_blit платит за клип/16-бит/split, а не за
|
}
|
||||||
* пиксели). W3-скобку ставит сама libbgi: из кода, собранного
|
if (w && h && dw > 0 && dh > 0) {
|
||||||
* с --w3, её вызывать нельзя (окно W3 уходит под видеобанк). */
|
/* Целиком на экране — линейное ядро без клипа: ~2.9× быстрее
|
||||||
gfx_blit_noclip(x, top, img);
|
* (замер: 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 {
|
} else {
|
||||||
gfx_blit(x, top, img); /* край экрана — с клипом */
|
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана — с клипом */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
@@ -125,7 +170,7 @@ static void ov_mark(int x, int y, int w, int h)
|
|||||||
ov_pending = 3; /* обе страницы */
|
ov_pending = 3; /* обе страницы */
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_fore_heal(void)
|
void pop_fore_heal(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t pg = gfx_get_draw_page() & 1;
|
uint8_t pg = gfx_get_draw_page() & 1;
|
||||||
if (!(ov_pending & (1 << pg))) return;
|
if (!(ov_pending & (1 << pg))) return;
|
||||||
@@ -249,12 +294,12 @@ static uint8_t g_room;
|
|||||||
* floorright, когда тайл нижнего ряда стал видимым (empty после падения
|
* floorright, когда тайл нижнего ряда стал видимым (empty после падения
|
||||||
* loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты
|
* loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты
|
||||||
* снизу (кромка уровня = стена). */
|
* снизу (кромка уровня = стена). */
|
||||||
void pop_room_set_below(const uint8_t *below_row0_fg) { g_below = below_row0_fg; }
|
void pop_room_set_below(const uint8_t *below_row0_fg) __banked { g_below = below_row0_fg; }
|
||||||
|
|
||||||
/* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки
|
/* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки
|
||||||
* у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
|
* у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
|
||||||
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
|
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
|
||||||
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
|
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) __banked
|
||||||
{ g_above = above_row2_fg; g_abovemod = above_row2_mod; }
|
{ g_above = above_row2_fg; g_abovemod = above_row2_mod; }
|
||||||
|
|
||||||
static uint8_t tile_code(int row, int col)
|
static uint8_t tile_code(int row, int col)
|
||||||
@@ -309,13 +354,9 @@ static uint8_t wall_modifier(int row, int col)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */
|
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */
|
||||||
static unsigned long rnd_seed;
|
static pop_rnd_t rnd_seed;
|
||||||
|
|
||||||
static uint16_t prandom(uint16_t maxv)
|
#define prandom(maxv) pop_prandom(&rnd_seed, (maxv))
|
||||||
{
|
|
||||||
rnd_seed = rnd_seed * 214013UL + 2531011UL;
|
|
||||||
return (uint16_t)((uint16_t)(rnd_seed >> 16) % (uint16_t)(maxv + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */
|
/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */
|
||||||
static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
|
static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
|
||||||
@@ -338,65 +379,135 @@ static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
|
|||||||
wall_b(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a, dby - RPOS[dv]);
|
wall_b(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a, dby - RPOS[dv]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
|
||||||
|
/* КЭШ РАСКЛАДКИ КЛАДКИ. Серия prandom для тайла зависит только от
|
||||||
|
* (комната, ряд, колонка) — пока мы в комнате, это КОНСТАНТА. А
|
||||||
|
* wall_pattern зовётся из fore-слоя КАЖДЫЙ кадр, и на Z80 это самая
|
||||||
|
* дорогая вещь в кадре: замер в MAME 2026-07-29 (комната 3) — 18 вызовов
|
||||||
|
* pop_prandom за один fore-проход, ~10 700 тактов каждый (32-битный LCG:
|
||||||
|
* __mullong ~8 000 + __moduint), то есть 193 К из 341 К всего прохода при
|
||||||
|
* бюджете кадра ~430 К. Кэшируем не сид, а ГОТОВЫЕ РЕШЕНИЯ (что рисовать
|
||||||
|
* и с каким смещением) — тогда в кадре не остаётся ни одного prandom.
|
||||||
|
*
|
||||||
|
* Порядок вызовов prandom воспроизведён один в один, включая то, что
|
||||||
|
* значение метки берётся ТОЛЬКО когда сработало её условие: иначе
|
||||||
|
* последовательность разъедется и кладка станет другой. which_part=0
|
||||||
|
* использует префикс той же серии, поэтому кэш общий.
|
||||||
|
*
|
||||||
|
* Упаковка: wp_a = mid_div | mid_off<<1 | bot_div<<4 | bot_off<<5;
|
||||||
|
* wp_b = rndblk | right<<1 | right_val<<2 | left<<4 | left_val<<5.
|
||||||
|
* Индекс — (row+1)*10+col, ряды −1..2. */
|
||||||
|
#define WP_N 40
|
||||||
|
static uint8_t wp_a[WP_N], wp_b[WP_N], wp_ok[WP_N];
|
||||||
|
|
||||||
|
static void wall_pattern_reset(void) /* смена комнаты — сид другой */
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < WP_N; i++) wp_ok[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Выходы wall_rnd — в файловых статиках: возвращать девять значений
|
||||||
|
* через указатели на Z80 дороже самой функции. */
|
||||||
|
static uint8_t wr_mid_div, wr_mid_off, wr_bot_div, wr_bot_off;
|
||||||
|
static uint8_t wr_rndblk, wr_right, wr_rightv, wr_left, wr_leftv;
|
||||||
|
|
||||||
|
static void wall_rnd(int row, int col)
|
||||||
|
{
|
||||||
|
/* Кэшируем только штатную сетку: колонка вне 0..9 дала бы индекс
|
||||||
|
* соседнего РЯДА и чужую раскладку (тихий баг вместо промаха). */
|
||||||
|
int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1;
|
||||||
|
uint8_t a, b, bg;
|
||||||
|
pop_rnd_t saved;
|
||||||
|
if (idx >= 0 && idx < WP_N && wp_ok[idx]) {
|
||||||
|
a = wp_a[idx]; b = wp_b[idx];
|
||||||
|
wr_mid_div = (uint8_t)(a & 1); wr_mid_off = (uint8_t)((a >> 1) & 7);
|
||||||
|
wr_bot_div = (uint8_t)((a >> 4) & 1); wr_bot_off = (uint8_t)((a >> 5) & 7);
|
||||||
|
wr_rndblk = (uint8_t)(b & 1);
|
||||||
|
wr_right = (uint8_t)((b >> 1) & 1); wr_rightv = (uint8_t)((b >> 2) & 3);
|
||||||
|
wr_left = (uint8_t)((b >> 4) & 1); wr_leftv = (uint8_t)((b >> 5) & 7);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saved = rnd_seed;
|
||||||
|
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
|
||||||
|
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
|
||||||
|
pop_prandom_set(rnd_seed, g_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
||||||
|
prandom(1); /* сброс */
|
||||||
|
wr_mid_div = (uint8_t)prandom(1);
|
||||||
|
wr_mid_off = (uint8_t)prandom(4);
|
||||||
|
wr_bot_div = (uint8_t)prandom(1);
|
||||||
|
wr_bot_off = (uint8_t)prandom(4);
|
||||||
|
wr_rndblk = wr_right = wr_left = 0;
|
||||||
|
wr_rightv = wr_leftv = 0;
|
||||||
|
bg = wall_modifier(row, col);
|
||||||
|
if (bg == 3) { /* WWW */
|
||||||
|
wr_rndblk = (uint8_t)(prandom(4) == 0);
|
||||||
|
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)prandom(3); }
|
||||||
|
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(4); }
|
||||||
|
} else if (bg == 0) { /* SWS */
|
||||||
|
if (prandom(6) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(1); }
|
||||||
|
} else if (bg == 1) { /* SWW */
|
||||||
|
wr_rndblk = (uint8_t)(prandom(4) == 0);
|
||||||
|
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)prandom(3); }
|
||||||
|
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(3); }
|
||||||
|
} else { /* WWS (bg==2) */
|
||||||
|
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)(prandom(1) + 2); }
|
||||||
|
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(4); }
|
||||||
|
}
|
||||||
|
rnd_seed = saved;
|
||||||
|
if (idx >= 0 && idx < WP_N) {
|
||||||
|
wp_a[idx] = (uint8_t)(wr_mid_div | (wr_mid_off << 1) |
|
||||||
|
(wr_bot_div << 4) | (wr_bot_off << 5));
|
||||||
|
wp_b[idx] = (uint8_t)(wr_rndblk | (wr_right << 1) | (wr_rightv << 2) |
|
||||||
|
(wr_left << 4) | (wr_leftv << 5));
|
||||||
|
wp_ok[idx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
|
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
|
||||||
static void wall_pattern(int row, int col, int which_part)
|
static void wall_pattern(int row, int col, int which_part)
|
||||||
{
|
{
|
||||||
int xh = COL_XH[col];
|
int xh = COL_XH[col];
|
||||||
int dby = 63 * row + 65;
|
int dby = 63 * row + 65;
|
||||||
unsigned long saved = rnd_seed;
|
uint8_t bg = wall_modifier(row, col);
|
||||||
uint16_t mid_div, mid_off, bot_div, bot_off;
|
|
||||||
uint8_t bg;
|
|
||||||
|
|
||||||
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
|
|
||||||
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
|
|
||||||
rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
|
||||||
prandom(1); /* сброс */
|
|
||||||
mid_div = prandom(1);
|
|
||||||
mid_off = prandom(4);
|
|
||||||
bot_div = prandom(1);
|
|
||||||
bot_off = prandom(4);
|
|
||||||
bg = wall_modifier(row, col);
|
|
||||||
|
|
||||||
|
wall_rnd(row, col);
|
||||||
if (bg == 3) { /* WWW */
|
if (bg == 3) { /* WWW */
|
||||||
if (which_part) {
|
if (which_part) {
|
||||||
if (prandom(4) == 0)
|
if (wr_rndblk)
|
||||||
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
|
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
|
||||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
|
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
|
||||||
}
|
}
|
||||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby);
|
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
|
||||||
if (which_part) {
|
if (which_part) {
|
||||||
if (prandom(4) == 0)
|
if (wr_right)
|
||||||
draw_right_mark(prandom(3), mid_off, row, col);
|
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||||||
if (prandom(4) == 0)
|
if (wr_left)
|
||||||
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col);
|
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||||
}
|
}
|
||||||
} else if (bg == 0) { /* SWS */
|
} else if (bg == 0) { /* SWS */
|
||||||
if (which_part) {
|
if (which_part && wr_left)
|
||||||
if (prandom(6) == 0)
|
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||||
draw_left_mark(prandom(1), mid_off - mid_div, bot_off - bot_div, row, col);
|
|
||||||
}
|
|
||||||
} else if (bg == 1) { /* SWW */
|
} else if (bg == 1) { /* SWW */
|
||||||
if (which_part) {
|
if (which_part) {
|
||||||
if (prandom(4) == 0)
|
if (wr_rndblk)
|
||||||
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
|
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
|
||||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
|
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
|
||||||
if (prandom(4) == 0)
|
if (wr_right)
|
||||||
draw_right_mark(prandom(3), mid_off, row, col);
|
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||||||
if (prandom(4) == 0)
|
if (wr_left)
|
||||||
draw_left_mark(prandom(3), mid_off - mid_div, bot_off - bot_div, row, col);
|
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||||
}
|
}
|
||||||
} else { /* WWS (bg==2) */
|
} else { /* WWS (bg==2) */
|
||||||
if (which_part)
|
if (which_part)
|
||||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
|
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
|
||||||
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby);
|
wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
|
||||||
if (which_part) {
|
if (which_part) {
|
||||||
if (prandom(4) == 0)
|
if (wr_right)
|
||||||
draw_right_mark((uint16_t)(prandom(1) + 2), mid_off, row, col);
|
draw_right_mark(wr_rightv, wr_mid_off, row, col);
|
||||||
if (prandom(4) == 0)
|
if (wr_left)
|
||||||
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col);
|
draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rnd_seed = saved;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* topright тайла снизу-слева (seg008.c:410). */
|
/* topright тайла снизу-слева (seg008.c:410). */
|
||||||
@@ -636,7 +747,7 @@ static void draw_tile(int row, int col)
|
|||||||
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
|
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
|
||||||
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
|
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
|
||||||
* внутри draw_tile). */
|
* внутри draw_tile). */
|
||||||
void pop_loose_shake_draw(int row, int col)
|
void pop_loose_shake_draw(int row, int col) __banked
|
||||||
{
|
{
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
||||||
@@ -658,7 +769,7 @@ void pop_loose_shake_draw(int row, int col)
|
|||||||
* читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать
|
* читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать
|
||||||
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
||||||
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
||||||
void pop_spike_redraw(int row, int col)
|
void pop_spike_redraw(int row, int col) __banked
|
||||||
{
|
{
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
||||||
@@ -685,7 +796,7 @@ void pop_spike_redraw(int row, int col)
|
|||||||
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
||||||
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
||||||
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
||||||
void pop_room_redraw_seam_left(void)
|
void pop_room_redraw_seam_left(void) __banked
|
||||||
{
|
{
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
@@ -707,7 +818,7 @@ void pop_room_redraw_seam_left(void)
|
|||||||
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
||||||
#define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */
|
#define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */
|
||||||
|
|
||||||
void pop_ceil_shake_draw(int col)
|
void pop_ceil_shake_draw(int col) __banked
|
||||||
{
|
{
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
|
heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
|
||||||
@@ -719,7 +830,7 @@ void pop_ceil_shake_draw(int col)
|
|||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_ceil_bake_empty(int col)
|
void pop_ceil_bake_empty(int col) __banked
|
||||||
{
|
{
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
@@ -742,7 +853,7 @@ void pop_ceil_bake_empty(int col)
|
|||||||
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
||||||
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
||||||
* (pot_size<<2) − 14 (seg008:668). */
|
* (pot_size<<2) − 14 (seg008:668). */
|
||||||
void pop_torch_draw(int row, int col, uint8_t modif)
|
void pop_torch_draw(int row, int col, uint8_t modif) __banked
|
||||||
{
|
{
|
||||||
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1
|
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1
|
||||||
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
||||||
@@ -759,7 +870,7 @@ void pop_torch_draw(int row, int col, uint8_t modif)
|
|||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_potion_draw(int row, int col, uint8_t modif)
|
void pop_potion_draw(int row, int col, uint8_t modif) __banked
|
||||||
{
|
{
|
||||||
uint8_t type = potion_type(modif);
|
uint8_t type = potion_type(modif);
|
||||||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||||||
@@ -777,7 +888,7 @@ void pop_potion_draw(int row, int col, uint8_t modif)
|
|||||||
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
||||||
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
||||||
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
|
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
|
||||||
void pop_floor_bake(int row, int col)
|
void pop_floor_bake(int row, int col) __banked
|
||||||
{
|
{
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
int yb = POP_YOFF + 63 * row;
|
int yb = POP_YOFF + 63 * row;
|
||||||
@@ -795,13 +906,13 @@ void pop_floor_bake(int row, int col)
|
|||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_button_redraw(int row, int col) { pop_floor_bake(row, col); }
|
void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
|
||||||
|
|
||||||
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
||||||
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
||||||
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
||||||
* Куски двери непрозрачные — wipe не нужен. */
|
* Куски двери непрозрачные — wipe не нужен. */
|
||||||
void pop_leveldoor_redraw(int row, int col)
|
void pop_leveldoor_redraw(int row, int col) __banked
|
||||||
{
|
{
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
draw_tile(row, col);
|
draw_tile(row, col);
|
||||||
@@ -814,7 +925,7 @@ void pop_leveldoor_redraw(int row, int col)
|
|||||||
* рисовал loose-правый-край env_b(42); теперь пусто → чистая грань).
|
* рисовал loose-правый-край env_b(42); теперь пусто → чистая грань).
|
||||||
* g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
* g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
||||||
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
||||||
void pop_loose_bake_empty(int row, int col)
|
void pop_loose_bake_empty(int row, int col) __banked
|
||||||
{
|
{
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
int xr = COL_XH[col + 1] * 8;
|
int xr = COL_XH[col + 1] * 8;
|
||||||
@@ -877,7 +988,8 @@ static mob_t mobs[MOB_MAX];
|
|||||||
|
|
||||||
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
||||||
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
||||||
uint8_t pop_loose_landed;
|
/* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
|
||||||
|
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
|
||||||
|
|
||||||
/* y_loose_land / y_something (seg007): [row+1], row = −1..3. Ряд −1 —
|
/* y_loose_land / y_something (seg007): [row+1], row = −1..3. Ряд −1 —
|
||||||
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
|
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
|
||||||
@@ -886,7 +998,7 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
|
|||||||
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
||||||
#define MOB_Y_NONE 0x7FFF
|
#define MOB_Y_NONE 0x7FFF
|
||||||
|
|
||||||
void pop_loose_mob_spawn(int row, int col)
|
void pop_loose_mob_spawn(int row, int col) __banked
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
mob_t *m = 0;
|
mob_t *m = 0;
|
||||||
@@ -923,7 +1035,7 @@ static int8_t obj_col, obj_row;
|
|||||||
|
|
||||||
static void mob_render(mob_t *m, uint8_t pg);
|
static void mob_render(mob_t *m, uint8_t pg);
|
||||||
|
|
||||||
void pop_loose_mob_reset(void)
|
void pop_loose_mob_reset(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
for (i = 0; i < MOB_MAX; i++) {
|
for (i = 0; i < MOB_MAX; i++) {
|
||||||
@@ -988,7 +1100,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
|||||||
mob_render(m, pg);
|
mob_render(m, pg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_loose_mob_tick(void)
|
void pop_loose_mob_tick(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
uint8_t pg = gfx_get_draw_page() & 1, i;
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
@@ -1038,7 +1150,7 @@ static void mob_render(mob_t *m, uint8_t pg)
|
|||||||
|
|
||||||
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
|
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
|
||||||
* после kid_draw, до fore-слоя. */
|
* после kid_draw, до fore-слоя. */
|
||||||
void pop_loose_mob_draw_over(void)
|
void pop_loose_mob_draw_over(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
uint8_t pg = gfx_get_draw_page() & 1, i;
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
@@ -1047,7 +1159,7 @@ void pop_loose_mob_draw_over(void)
|
|||||||
|
|
||||||
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
|
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
|
||||||
* y (низ куска). 0 — куска нет. */
|
* y (низ куска). 0 — куска нет. */
|
||||||
uint8_t pop_loose_mob_pos(int *col, int *y)
|
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
@@ -1062,7 +1174,7 @@ uint8_t pop_loose_mob_pos(int *col, int *y)
|
|||||||
* кадрах падения — гейт по флагу). */
|
* кадрах падения — гейт по флагу). */
|
||||||
static uint8_t border_dirty;
|
static uint8_t border_dirty;
|
||||||
|
|
||||||
void pop_clip_sprite(int x, int w, int sy_top, int h)
|
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
|
||||||
{
|
{
|
||||||
int sy_bot = sy_top + h;
|
int sy_bot = sy_top + h;
|
||||||
(void)x; (void)w;
|
(void)x; (void)w;
|
||||||
@@ -1074,7 +1186,7 @@ void pop_clip_sprite(int x, int w, int sy_top, int h)
|
|||||||
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
|
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
|
||||||
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
|
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
|
||||||
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
|
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
|
||||||
void pop_room_clip_borders(void)
|
void pop_room_clip_borders(void) __banked
|
||||||
{
|
{
|
||||||
if (!border_dirty) return;
|
if (!border_dirty) return;
|
||||||
border_dirty--;
|
border_dirty--;
|
||||||
@@ -1084,10 +1196,11 @@ void pop_room_clip_borders(void)
|
|||||||
|
|
||||||
void pop_room_draw(uint8_t room_num,
|
void pop_room_draw(uint8_t room_num,
|
||||||
const uint8_t *fg, const uint8_t *bg,
|
const uint8_t *fg, const uint8_t *bg,
|
||||||
const uint8_t *lcol_fg, const uint8_t *lcol_bg)
|
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked
|
||||||
{
|
{
|
||||||
int row, col;
|
int row, col;
|
||||||
g_room = room_num;
|
g_room = room_num;
|
||||||
|
wall_pattern_reset(); /* кэш кладки привязан к номеру комнаты */
|
||||||
g_fg = fg; g_bg = bg;
|
g_fg = fg; g_bg = bg;
|
||||||
g_lfg = lcol_fg; g_lbg = lcol_bg;
|
g_lfg = lcol_fg; g_lbg = lcol_bg;
|
||||||
|
|
||||||
@@ -1125,10 +1238,7 @@ static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка (
|
|||||||
return (int8_t)xh;
|
return (int8_t)xh;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=63) */
|
#define y_to_row(y) pop_y_to_row((int16_t)(y)) /* общий (pop_geom) */
|
||||||
{
|
|
||||||
return (int8_t)((y + 60) / 63 % 4 - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fore_only_tile(int row, int col)
|
static void fore_only_tile(int row, int col)
|
||||||
{
|
{
|
||||||
@@ -1180,8 +1290,25 @@ static void ceil_over_kid_tile(int col)
|
|||||||
fore_only_tile(-1, col); /* draw_tile_fore */
|
fore_only_tile(-1, col); /* draw_tile_fore */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Пересекается ли габарит тайла с окном fore-слоя. Шаг тайла — 32 px
|
||||||
|
* (COL_XH[col]*8 = col*32), куски выходят вправо максимум на +8 (дивайдеры
|
||||||
|
* и метки рисуются в ячейке правого соседа) и вверх на 42+высота кадра.
|
||||||
|
* Даёт отсев ЦЕЛОГО тайла ДО обращения к атласу: спрайт 27..40 px шириной
|
||||||
|
* задевает не все тайлы футпринта, а платили за каждый (atlas_image +
|
||||||
|
* gfx_w0_map + разбор заголовка на КАЖДЫЙ кусок). */
|
||||||
|
static uint8_t tile_in_fclip(int row, int col)
|
||||||
|
{
|
||||||
|
int x0, y0;
|
||||||
|
if (!fclip_on) return 1;
|
||||||
|
x0 = COL_XH[col] * 8;
|
||||||
|
y0 = POP_YOFF + 63 * row;
|
||||||
|
return (uint8_t)(x0 < fclip_x1 && x0 + 40 > fclip_x0 &&
|
||||||
|
y0 - 8 < fclip_y1 && y0 + 70 > fclip_y0);
|
||||||
|
}
|
||||||
|
|
||||||
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
||||||
{
|
{
|
||||||
|
if (!tile_in_fclip(row, col)) return;
|
||||||
/* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал
|
/* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал
|
||||||
* кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в
|
* кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в
|
||||||
* backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ
|
* backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ
|
||||||
@@ -1214,7 +1341,9 @@ static void climb_overlay_tile(int row, int col, uint8_t fidx)
|
|||||||
{
|
{
|
||||||
/* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую
|
/* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую
|
||||||
* Kid спускается/подтягивается, закрашивается целым тайлом */
|
* Kid спускается/подтягивается, закрашивается целым тайлом */
|
||||||
uint8_t code = tile_code_drawn(row, col);
|
uint8_t code;
|
||||||
|
if (!tile_in_fclip(row, col)) return;
|
||||||
|
code = tile_code_drawn(row, col);
|
||||||
int x, dmy, dby;
|
int x, dmy, dby;
|
||||||
const piece *t;
|
const piece *t;
|
||||||
if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
|
if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
|
||||||
@@ -1304,8 +1433,9 @@ static void overlay_mid_tile(int row, int col)
|
|||||||
* сразу), поэтому порядок эмулируется явной проверкой. */
|
* сразу), поэтому порядок эмулируется явной проверкой. */
|
||||||
static void other_overlay_tile(int row, int col)
|
static void other_overlay_tile(int row, int col)
|
||||||
{
|
{
|
||||||
uint8_t code = tile_code(row, col);
|
uint8_t code, left;
|
||||||
uint8_t left;
|
if (!tile_in_fclip(row, col)) return;
|
||||||
|
code = tile_code(row, col);
|
||||||
/* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для
|
/* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для
|
||||||
* него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань
|
* него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань
|
||||||
* пустого соседа). В оригинале она уходит в y-сортируемый midtable и
|
* пустого соседа). В оригинале она уходит в y-сортируемый midtable и
|
||||||
@@ -1328,13 +1458,20 @@ static void other_overlay_tile(int row, int col)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
/* Футпринт персонажа в тайлах (порт set_char_collision, seg006:0723).
|
||||||
int8_t dir, uint8_t frame, uint8_t action)
|
* Выделено из pop_fore_over_kid, потому что тем же счётом пользуется страж
|
||||||
|
* (pop_fore_over_char): в оригинале это общий код для любого Char.
|
||||||
|
* Результат — в файловых статиках, а не через 6 указателей: на Z80 такой
|
||||||
|
* возврат стоил бы дороже самой функции. */
|
||||||
|
static int8_t fp_cL, fp_cR, fp_cLraw, fp_rT, fp_rB, fp_rTraw;
|
||||||
|
|
||||||
|
static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||||
|
int8_t dir)
|
||||||
{
|
{
|
||||||
int wh = (int)((w + 1) >> 1); /* char_width_half */
|
int wh = (int)((w + 1) >> 1); /* char_width_half */
|
||||||
int xl = obj_x / 2 + 58; /* char_x_left (58-база) */
|
int xl = obj_x / 2 + 58; /* char_x_left (58-база) */
|
||||||
int xr;
|
int xr;
|
||||||
int8_t cL, cR, cLraw, rT, rB, r, c, rTraw;
|
int8_t cL, cR, cLraw, rT, rB, rTraw;
|
||||||
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||||||
xr = xl + wh;
|
xr = xl + wh;
|
||||||
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
|
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
|
||||||
@@ -1362,6 +1499,18 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
|||||||
* (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим
|
* (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим
|
||||||
* включение ряда rB-1. */
|
* включение ряда rB-1. */
|
||||||
if (rB > 0 && rT >= rB) rT = rB - 1;
|
if (rB > 0 && rT >= rB) rT = rB - 1;
|
||||||
|
fp_cL = cL; fp_cR = cR; fp_cLraw = cLraw;
|
||||||
|
fp_rT = rT; fp_rB = rB; fp_rTraw = rTraw;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||||
|
int8_t dir, uint8_t frame, uint8_t action) __banked
|
||||||
|
{
|
||||||
|
int8_t cL, cR, cLraw, rT, rB, rTraw, r, c;
|
||||||
|
fclip_on = 1; /* окно = прямоугольник спрайта */
|
||||||
|
char_footprint(obj_x, obj_y, w, h, dir);
|
||||||
|
cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw;
|
||||||
|
rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw;
|
||||||
/* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт
|
/* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт
|
||||||
* Kid попадает в midtable — база порядка окклюзии в other_overlay_tile.
|
* Kid попадает в midtable — база порядка окклюзии в other_overlay_tile.
|
||||||
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
|
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
|
||||||
@@ -1429,4 +1578,37 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
|||||||
g_clip_top = 0;
|
g_clip_top = 0;
|
||||||
}
|
}
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
fclip_on = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Передние грани тайлов поверх ПРОИЗВОЛЬНОГО персонажа (страж, дальше —
|
||||||
|
* тень/скелет). В оригинале любой Char — запись midtable, а foretable
|
||||||
|
* рисуется после всех midtable (draw_tile_fore, seg008:690), поэтому
|
||||||
|
* колонна и кромка пола перекрывают всех одинаково.
|
||||||
|
*
|
||||||
|
* Отличие от pop_fore_over_kid: только слой fore (+ полоса потолка).
|
||||||
|
* Оверлеи тела пола (draw_other_overlay) завязаны на позы Kid — вис,
|
||||||
|
* полёт, подъём, — которых у стража нет: он ходит и дерётся на полу
|
||||||
|
* (seq_77..seq_84). Появятся падающие/висящие стражи — тогда и общий
|
||||||
|
* порт redraw_at_char2, а не догадки сейчас. */
|
||||||
|
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||||
|
int8_t dir) __banked
|
||||||
|
{
|
||||||
|
int8_t r, c;
|
||||||
|
fclip_on = 1; /* окно = прямоугольник спрайта */
|
||||||
|
char_footprint(obj_x, obj_y, w, h, dir);
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
for (r = fp_rT; r <= fp_rB; r++)
|
||||||
|
for (c = fp_cL; c <= fp_cR; c++)
|
||||||
|
fore_tile(r, c);
|
||||||
|
if (fp_rTraw < 0) {
|
||||||
|
g_clip_top = POP_YOFF;
|
||||||
|
for (c = fp_cL; c <= fp_cR; c++) {
|
||||||
|
ov_mark(COL_XH[c] * 8, 0, 64, CEIL_BAND_H);
|
||||||
|
ceil_over_kid_tile(c);
|
||||||
|
}
|
||||||
|
g_clip_top = 0;
|
||||||
|
}
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
fclip_on = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,13 +25,26 @@
|
|||||||
/* Экранный y нижней границы поля (ниже — статус-полоса + борт → клип). */
|
/* Экранный y нижней границы поля (ниже — статус-полоса + борт → клип). */
|
||||||
#define POP_CLIP_BOTTOM (POP_YOFF + POP_PLAYFIELD_H)
|
#define POP_CLIP_BOTTOM (POP_YOFF + POP_PLAYFIELD_H)
|
||||||
|
|
||||||
|
/* Спрайт целиком на экране И укладывается в 8-битные параметры noclip-
|
||||||
|
* примитивов libbgi? Условие входа в gfx_blit_cols_part_noclip: клипающий
|
||||||
|
* вариант платит ~5.6 К тактов подготовки на КАЖДЫЙ вызов, независимо от
|
||||||
|
* того, вылезает край или нет.
|
||||||
|
* static inline, а не чистый inline: SDCC оставляет мёртвое тело в каждом TU
|
||||||
|
* (memory sdcc_inline_codegen_findings), но здесь это ~30 байт на два TU —
|
||||||
|
* дешевле риска неразрешённой ссылки. */
|
||||||
|
static inline uint8_t pop_onscreen_cols(int x, int y, uint16_t w, uint16_t h)
|
||||||
|
{
|
||||||
|
return (uint8_t)(x >= 0 && y >= 0 && w < 256 && h < 256 &&
|
||||||
|
x + (int)w <= 320 && y + (int)h <= 256);
|
||||||
|
}
|
||||||
|
|
||||||
/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load
|
/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load
|
||||||
* трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ
|
* трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ
|
||||||
* после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */
|
* после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */
|
||||||
int pop_bg_load(void);
|
int pop_bg_load(void) __banked;
|
||||||
|
|
||||||
/* Освободить EMM-страницы атласов. */
|
/* Освободить EMM-страницы атласов. */
|
||||||
void pop_bg_free(void);
|
void pop_bg_free(void) __banked;
|
||||||
|
|
||||||
/* Нарисовать комнату: fg[30]/bg[30] — foretable-коды/backtable-модификаторы
|
/* Нарисовать комнату: fg[30]/bg[30] — foretable-коды/backtable-модификаторы
|
||||||
* (по строкам, row*10+col). lcol_fg/lcol_bg — 3 тайла правого столбца
|
* (по строкам, row*10+col). lcol_fg/lcol_bg — 3 тайла правого столбца
|
||||||
@@ -39,97 +52,114 @@ void pop_bg_free(void);
|
|||||||
* тогда левая кромка = стена. room_num — номер комнаты (зерно стен). */
|
* тогда левая кромка = стена. room_num — номер комнаты (зерно стен). */
|
||||||
void pop_room_draw(uint8_t room_num,
|
void pop_room_draw(uint8_t room_num,
|
||||||
const uint8_t *fg, const uint8_t *bg,
|
const uint8_t *fg, const uint8_t *bg,
|
||||||
const uint8_t *lcol_fg, const uint8_t *lcol_bg);
|
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked;
|
||||||
|
|
||||||
/* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright,
|
/* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright,
|
||||||
* когда тайл нижнего ряда стал empty (после падения loose): «стена вниз»
|
* когда тайл нижнего ряда стал empty (после падения loose): «стена вниз»
|
||||||
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
|
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
|
||||||
void pop_room_set_below(const uint8_t *below_row0_fg);
|
void pop_room_set_below(const uint8_t *below_row0_fg) __banked;
|
||||||
|
|
||||||
/* Задать ряд 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);
|
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) __banked;
|
||||||
|
|
||||||
/* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него —
|
/* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него —
|
||||||
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
||||||
* спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики
|
* спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики
|
||||||
* берутся из pop_kid (kid_fp_*). См. redraw_at_char/set_char_collision. */
|
* берутся из pop_kid (kid_fp_*). См. redraw_at_char/set_char_collision. */
|
||||||
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||||
int8_t dir, uint8_t frame, uint8_t action);
|
int8_t dir, uint8_t frame, uint8_t action) __banked;
|
||||||
|
|
||||||
|
/* Окно fore-слоя: прямоугольник СПРАЙТА (экранные координаты, y уже с
|
||||||
|
* POP_YOFF), поверх которого fore-проход возвращает куски тайлов. Ставит
|
||||||
|
* тот, кто нарисовал спрайт (kid_draw / pop_guard_draw) — он один знает
|
||||||
|
* итоговый габарит (у Kid — с учётом клинка и обрезки clip_char). Вне
|
||||||
|
* fore-проходов окно игнорируется: фон рисуется целиком. Мотивация и
|
||||||
|
* замер — в pop_bg.c у fclip_on. */
|
||||||
|
void pop_fore_set_clip(int x, int y, int w, int h) __banked;
|
||||||
|
|
||||||
|
/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка,
|
||||||
|
* без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата
|
||||||
|
* (до ×8/7), как у kid_fp_obj_x(). */
|
||||||
|
/* __banked: отрисовка стража уехала в СВОЙ банк (pop_gdraw.c), поэтому
|
||||||
|
* вызов стал кроссбанковым и обязан идти через трамплин. Зовётся раз в
|
||||||
|
* кадр, других вызывающих нет — цена одного трамплина здесь не видна. */
|
||||||
|
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||||
|
int8_t dir) __banked;
|
||||||
|
|
||||||
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
|
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
|
||||||
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
|
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
|
||||||
* прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице
|
* прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице
|
||||||
* дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */
|
* дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */
|
||||||
void pop_fore_heal(void);
|
void pop_fore_heal(void) __banked;
|
||||||
|
|
||||||
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
|
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
|
||||||
* pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick.
|
* pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick.
|
||||||
* shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту
|
* shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту
|
||||||
* после падения (NORMAL, в ОЗУ-копию). */
|
* после падения (NORMAL, в ОЗУ-копию). */
|
||||||
void pop_loose_shake_draw(int row, int col);
|
void pop_loose_shake_draw(int row, int col) __banked;
|
||||||
void pop_loose_bake_empty(int row, int col);
|
void pop_loose_bake_empty(int row, int col) __banked;
|
||||||
|
|
||||||
/* Плита-ПОТОЛОК (loose ряда 2 комнаты СВЕРХУ, наш ряд −1): дрожащий кадр в
|
/* Плита-ПОТОЛОК (loose ряда 2 комнаты СВЕРХУ, наш ряд −1): дрожащий кадр в
|
||||||
* полосе у потолка / «запечь» пустоту (колодец наверх) после провала.
|
* полосе у потолка / «запечь» пустоту (колодец наверх) после провала.
|
||||||
* Состояние — pop_ceil_modif[10] в pop_map. */
|
* Состояние — pop_ceil_modif[10] в pop_map. */
|
||||||
void pop_ceil_shake_draw(int col);
|
void pop_ceil_shake_draw(int col) __banked;
|
||||||
void pop_ceil_bake_empty(int col);
|
void pop_ceil_bake_empty(int col) __banked;
|
||||||
|
|
||||||
/* Кусок приземлился (loose_land): 0 = нет, иначе tilepos+1 тайла, на который
|
/* Кусок приземлился (loose_land) — переехал в pop_state.h: переменную
|
||||||
* он лёг. Ставит pop_loose_mob_tick, разбирает pop_loose_tick (debris+knock). */
|
* читают по обе стороны банковой границы. */
|
||||||
extern uint8_t pop_loose_landed;
|
#include "pop_state.h"
|
||||||
|
|
||||||
/* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому
|
/* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому
|
||||||
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
|
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
|
||||||
void pop_spike_redraw(int row, int col);
|
void pop_spike_redraw(int row, int col) __banked;
|
||||||
|
|
||||||
/* Анимация факела/зелья (динамика каждый кадр; фон запечён без них):
|
/* Анимация факела/зелья (динамика каждый кадр; фон запечён без них):
|
||||||
* modif — живой room_modif тайла. Для факела col — колонка САМОГО факела
|
* modif — живой room_modif тайла. Для факела col — колонка САМОГО факела
|
||||||
* (пламя рисуется в ячейке правого соседа). */
|
* (пламя рисуется в ячейке правого соседа). */
|
||||||
void pop_torch_draw(int row, int col, uint8_t modif);
|
void pop_torch_draw(int row, int col, uint8_t modif) __banked;
|
||||||
void pop_potion_draw(int row, int col, uint8_t modif);
|
void pop_potion_draw(int row, int col, uint8_t modif) __banked;
|
||||||
|
|
||||||
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
|
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
|
||||||
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
|
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
|
||||||
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
|
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
|
||||||
void pop_room_redraw_seam_left(void);
|
void pop_room_redraw_seam_left(void) __banked;
|
||||||
|
|
||||||
/* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице
|
/* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице
|
||||||
* (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте:
|
* (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте:
|
||||||
* кнопка нажата/отжата, floor → debris от упавшей плиты. */
|
* кнопка нажата/отжата, floor → debris от упавшей плиты. */
|
||||||
void pop_floor_bake(int row, int col);
|
void pop_floor_bake(int row, int col) __banked;
|
||||||
void pop_button_redraw(int row, int col); /* = pop_floor_bake (кнопка) */
|
void pop_button_redraw(int row, int col) __banked; /* = pop_floor_bake (кнопка) */
|
||||||
|
|
||||||
/* Дверь уровня: перерисовать её ПРАВУЮ половину (row, col = правая) с
|
/* Дверь уровня: перерисовать её ПРАВУЮ половину (row, col = правая) с
|
||||||
* запечкой — створка анимируется (pop_trob animate_leveldoor). */
|
* запечкой — створка анимируется (pop_trob animate_leveldoor). */
|
||||||
void pop_leveldoor_redraw(int row, int col);
|
void pop_leveldoor_redraw(int row, int col) __banked;
|
||||||
|
|
||||||
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
|
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
|
||||||
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
|
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
|
||||||
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
|
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
|
||||||
void pop_loose_mob_spawn(int row, int col);
|
void pop_loose_mob_spawn(int row, int col) __banked;
|
||||||
void pop_loose_mob_tick(void);
|
void pop_loose_mob_tick(void) __banked;
|
||||||
void pop_loose_mob_reset(void); /* остановить кусок (смена комнаты) */
|
void pop_loose_mob_reset(void) __banked; /* остановить кусок (смена комнаты) */
|
||||||
|
|
||||||
/* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs,
|
/* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs,
|
||||||
* seg008:1561). Звать после kid_draw, перед fore-слоем. */
|
* seg008:1561). Звать после kid_draw, перед fore-слоем. */
|
||||||
void pop_loose_mob_draw_over(void);
|
void pop_loose_mob_draw_over(void) __banked;
|
||||||
|
|
||||||
/* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */
|
/* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */
|
||||||
uint8_t pop_loose_mob_pos(int *col, int *y);
|
uint8_t pop_loose_mob_pos(int *col, int *y) __banked;
|
||||||
|
|
||||||
/* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом
|
/* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом
|
||||||
* sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше
|
* sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше
|
||||||
* верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра.
|
* верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра.
|
||||||
* Звать СРАЗУ после блита падающего объекта (Kid/кусок). */
|
* Звать СРАЗУ после блита падающего объекта (Kid/кусок). */
|
||||||
void pop_clip_sprite(int x, int w, int sy_top, int h);
|
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-странице. */
|
* КАЖДЫЙ кадр ПОСЛЕ kid_draw+fore на back-странице. */
|
||||||
void pop_room_clip_borders(void);
|
void pop_room_clip_borders(void) __banked;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* pop_char.h — состояние ПЕРСОНАЖА, общее для Кида, стража и активной
|
||||||
|
* копии Char. Порт char_type (types.h:302).
|
||||||
|
*
|
||||||
|
* Зачем отдельным заголовком: интерпретатор последовательностей в
|
||||||
|
* оригинале один на всех (работает с Char, а вокруг стоят
|
||||||
|
* loadkid/savekid и loadshad/saveshad, seg006:809). Пока у Кида был свой
|
||||||
|
* усечённый тип, а у стража свой, общий интерпретатор был невозможен.
|
||||||
|
* Поля и их порядок — как в char_type, чтобы порт seg005/seg006 ложился
|
||||||
|
* один в один.
|
||||||
|
*/
|
||||||
|
#ifndef POP_CHAR_H
|
||||||
|
#define POP_CHAR_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t frame;
|
||||||
|
uint8_t x, y;
|
||||||
|
int8_t direction; /* -1 влево, 0 вправо */
|
||||||
|
int8_t curr_col, curr_row;
|
||||||
|
uint8_t action;
|
||||||
|
int8_t fall_x, fall_y;
|
||||||
|
uint8_t room;
|
||||||
|
uint8_t repeat;
|
||||||
|
uint8_t charid; /* 0 = Kid, 2 = страж (см. CHARID_* в pop_guard.h) */
|
||||||
|
uint8_t sword;
|
||||||
|
int8_t alive; /* -1 = жив (как в оригинале), 0 = убит */
|
||||||
|
uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */
|
||||||
|
} pop_char_t;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* pop_cheat.h — режим читов (порт cheats_enabled, seg000:111).
|
||||||
|
*
|
||||||
|
* В оригинале включается ключом командной строки megahit/debug и меняет
|
||||||
|
* набор доступных клавиш. У нас конфигурации/UI пока нет, поэтому это
|
||||||
|
* просто глобальный флаг: main ставит его при старте (на время разработки —
|
||||||
|
* ВКЛЮЧЁН). Клавиши читов обрабатывает главный цикл.
|
||||||
|
*
|
||||||
|
* Реализовано: K — убить стража (seg000:786). Остальные читы оригинала
|
||||||
|
* (время, перо, воскрешение, переход по комнатам) пока не портированы.
|
||||||
|
*/
|
||||||
|
#ifndef POP_CHEAT_H
|
||||||
|
#define POP_CHEAT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern uint8_t pop_cheats; /* 0 = выключены, иначе включены */
|
||||||
|
|
||||||
|
#define KBD_CHEAT_KILL 0x42 /* K (PS/2 set 2) — kill guard */
|
||||||
|
#define KBD_CHEAT_IMMO 0x43 /* I (PS/2 set 2) — бессмертие Кида (toggle) */
|
||||||
|
#define KBD_CHEAT_SWORD 0x1B /* S (PS/2 set 2) — выдать Киду меч */
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <kbd_raw.h>
|
#include <kbd_raw.h>
|
||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
|
#include "pop_guard.h" /* charid, состояние меча, seq стража */
|
||||||
|
|
||||||
/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
|
/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
|
||||||
* MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо. PS/2 set2
|
* MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо. PS/2 set2
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
#define SEQ_49_STAND_UP_FROM_CROUCH 49
|
#define SEQ_49_STAND_UP_FROM_CROUCH 49
|
||||||
#define SEQ_50_CROUCH 50
|
#define SEQ_50_CROUCH 50
|
||||||
#define SEQ_79_CROUCH_HOP 79
|
#define SEQ_79_CROUCH_HOP 79
|
||||||
|
#define SEQ_78_DRINK 78 /* выпить зелье */
|
||||||
#define SEQ_91_PICKUP_SWORD 91 /* поднять меч (pickupsword) */
|
#define SEQ_91_PICKUP_SWORD 91 /* поднять меч (pickupsword) */
|
||||||
#define SEQ_STEP_BASE 28 /* +distance(1..14) = seq 29..42 шаг-N */
|
#define SEQ_STEP_BASE 28 /* +distance(1..14) = seq 29..42 шаг-N */
|
||||||
#define SEQ_39_UNSAFE_STEP 39 /* шаг за кромку (distance 11) */
|
#define SEQ_39_UNSAFE_STEP 39 /* шаг за кромку (distance 11) */
|
||||||
@@ -74,14 +76,21 @@
|
|||||||
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
|
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
|
||||||
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
|
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
|
||||||
* кадрами (rest/save). */
|
* кадрами (rest/save). */
|
||||||
static int8_t control_x, control_y, control_shift;
|
int8_t control_x, control_y, control_shift;
|
||||||
static int8_t control_forward, control_backward, control_up, control_down, control_shift2;
|
/* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт
|
||||||
|
* СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же
|
||||||
|
* control(), что и у игрока. Поэтому шина общая. */
|
||||||
|
int8_t control_forward, control_backward, control_up, control_down, control_shift2;
|
||||||
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
|
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
|
||||||
|
|
||||||
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
|
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
|
||||||
* Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
|
* Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
|
||||||
* не дублируются в этот TU). */
|
* не дублируются в этот TU). */
|
||||||
#define seqtbl_offset_char(seq) kid_set_seq(seq)
|
/* В оригинале seqtbl_offset_char работает с АКТИВНЫМ персонажем, и
|
||||||
|
* control() вызывается внутри окна Char. Раньше макрос вёл на
|
||||||
|
* kid_set_seq (пишет прямо в Kid) — после введения окна это ломало ввод:
|
||||||
|
* savekid затирал Kid копией Char со старой curr_seq. */
|
||||||
|
#define seqtbl_offset_char(seq) pop_char_set_seq(seq)
|
||||||
|
|
||||||
static int8_t release_arrows(void)
|
static int8_t release_arrows(void)
|
||||||
{
|
{
|
||||||
@@ -118,6 +127,11 @@ static uint8_t get_item_action(void)
|
|||||||
seqtbl_offset_char(SEQ_91_PICKUP_SWORD);
|
seqtbl_offset_char(SEQ_91_PICKUP_SWORD);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (a == 3) { /* зелье — выпить (get_item) */
|
||||||
|
control_shift2 = CONTROL_IGNORE;
|
||||||
|
seqtbl_offset_char(SEQ_78_DRINK);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +139,14 @@ static void run_jump(void)
|
|||||||
{
|
{
|
||||||
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
|
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
|
||||||
* полировка K3; K2b просто запускает run-jump. */
|
* полировка K3; K2b просто запускает run-jump. */
|
||||||
if (Kid.frame >= FRAME_7_RUN) {
|
if (Char.frame >= FRAME_7_RUN) {
|
||||||
control_up = release_arrows();
|
control_up = release_arrows();
|
||||||
seqtbl_offset_char(SEQ_4_RUN_JUMP);
|
seqtbl_offset_char(SEQ_4_RUN_JUMP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void safe_step(void); /* fwd */
|
static void safe_step(void); /* fwd */
|
||||||
|
static void draw_sword(void); /* fwd: нужен в control_standing */
|
||||||
|
|
||||||
static void forward_pressed(void)
|
static void forward_pressed(void)
|
||||||
{
|
{
|
||||||
@@ -190,15 +205,15 @@ static void safe_step(void)
|
|||||||
control_forward = CONTROL_IGNORE;
|
control_forward = CONTROL_IGNORE;
|
||||||
distance = pop_edge_distance();
|
distance = pop_edge_distance();
|
||||||
if (distance) {
|
if (distance) {
|
||||||
Kid.repeat = 1;
|
Char.repeat = 1;
|
||||||
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
|
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
|
||||||
} else if (pop_edge_type() == EDGE_WALL) {
|
} else if (pop_edge_type() == EDGE_WALL) {
|
||||||
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
|
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
|
||||||
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
|
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
|
||||||
Kid.repeat = 1;
|
Char.repeat = 1;
|
||||||
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
|
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
|
||||||
} else if (Kid.repeat != 0) {
|
} else if (Char.repeat != 0) {
|
||||||
Kid.repeat = 0;
|
Char.repeat = 0;
|
||||||
seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
|
seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
|
||||||
} else {
|
} else {
|
||||||
seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */
|
seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */
|
||||||
@@ -225,7 +240,32 @@ static void control_standing(void)
|
|||||||
if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD &&
|
if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD &&
|
||||||
get_item_action())
|
get_item_action())
|
||||||
return;
|
return;
|
||||||
/* Kid charid_0, без меча: остаётся нижняя часть control_standing. */
|
/* Ветку «charid != kid && вниз+вперёд -> draw_sword» не портируем: у нас
|
||||||
|
* соперник до control_standing не доходит, его перехватывает
|
||||||
|
* control_guard_inactive в самом диспетчере (порядок как в seg005). */
|
||||||
|
|
||||||
|
/* seg005:352 — Кид с мечом ВИДИТ активного стража: достаёт клинок сам.
|
||||||
|
* Без этого игрок стоит с мечом в ножнах, пока его рубят. */
|
||||||
|
if (pop_have_sword) {
|
||||||
|
if (offguard != 0 && control_shift >= CONTROL_RELEASED) {
|
||||||
|
/* Меч убран намеренно (down в бою) — не доставать, пока не
|
||||||
|
* отпустят Shift; в оригинале это переход на loc_6213. */
|
||||||
|
} else if (can_guard_see_kid >= 2) {
|
||||||
|
int16_t d = pop_char_opp_dist();
|
||||||
|
if (d >= -10 && d < 90) {
|
||||||
|
holding_sword = 1;
|
||||||
|
/* (word)d < (word)-6 — беззнаковое: истинно для ВСЕГО, кроме
|
||||||
|
* −6..−1. То есть «страж вплотную за спиной» -> отступить,
|
||||||
|
* иначе достать меч. */
|
||||||
|
if (d >= -6 && d < 0) { back_pressed(); return; }
|
||||||
|
draw_sword();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offguard = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (control_shift == CONTROL_HELD) {
|
if (control_shift == CONTROL_HELD) {
|
||||||
if (control_backward == CONTROL_HELD) back_pressed();
|
if (control_backward == CONTROL_HELD) back_pressed();
|
||||||
else if (control_up == CONTROL_HELD) up_pressed();
|
else if (control_up == CONTROL_HELD) up_pressed();
|
||||||
@@ -266,7 +306,7 @@ static void control_running(void)
|
|||||||
/* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
|
/* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
|
||||||
* seq_47 при беге в стену (как оригинал), с корректной позицией. */
|
* seq_47 при беге в стену (как оригинал), с корректной позицией. */
|
||||||
if (control_x == CONTROL_RELEASED &&
|
if (control_x == CONTROL_RELEASED &&
|
||||||
(Kid.frame == FRAME_7_RUN || Kid.frame == FRAME_11_RUN)) {
|
(Char.frame == FRAME_7_RUN || Char.frame == FRAME_11_RUN)) {
|
||||||
control_forward = release_arrows();
|
control_forward = release_arrows();
|
||||||
seqtbl_offset_char(SEQ_13_STOP_RUN);
|
seqtbl_offset_char(SEQ_13_STOP_RUN);
|
||||||
} else if (control_x == CONTROL_HELD_BACKWARD) {
|
} else if (control_x == CONTROL_HELD_BACKWARD) {
|
||||||
@@ -317,21 +357,216 @@ static void control_jumpup(void)
|
|||||||
standing_jump();
|
standing_jump();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */
|
/* draw_sword (seg005:945): достать меч. Кид переходит в seq_55 (подъём
|
||||||
static void control(void)
|
* клинка со звуком), соперник — сразу в стойку en garde (seq_90). Стрелки
|
||||||
|
* гасятся, чтобы «вперёд», которым меч достали, не сработало второй раз
|
||||||
|
* уже как выпад. */
|
||||||
|
static void draw_sword(void)
|
||||||
{
|
{
|
||||||
uint8_t f = Kid.frame;
|
uint8_t seq_id = SEQ_55_DRAW_SWORD;
|
||||||
|
control_forward = control_shift2 = release_arrows();
|
||||||
|
if (Char.charid == CHARID_0_KID) {
|
||||||
|
offguard = 0; /* TODO: play_sound(19) — звука пока нет */
|
||||||
|
} else if (Char.charid != CHARID_1_SHADOW) {
|
||||||
|
seq_id = SEQ_90_EN_GARDE; /* соперник встаёт сразу в стойку */
|
||||||
|
}
|
||||||
|
Char.sword = SWORD_2_DRAWN;
|
||||||
|
seqtbl_offset_char(seq_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control_guard_inactive (seg006:2123): неактивный страж реагирует ТОЛЬКО в
|
||||||
|
* кадре 166 (стойка) и только на «вниз»: вниз+вперёд — достать меч,
|
||||||
|
* вниз без «вперёд» — развернуться (stand flipped). */
|
||||||
|
static void control_guard_inactive(void)
|
||||||
|
{
|
||||||
|
if (Char.frame == FRAME_166_STAND_INACTIVE && control_down == CONTROL_HELD) {
|
||||||
|
if (control_forward == CONTROL_HELD) {
|
||||||
|
draw_sword();
|
||||||
|
} else {
|
||||||
|
control_down = CONTROL_IGNORE;
|
||||||
|
seqtbl_offset_char(SEQ_80_STAND_FLIPPED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Боёвка (seg005:964..) ---------------------------------------- *
|
||||||
|
* Всё это ветки ОБЩЕГО диспетчера: у стража тот же control(), просто
|
||||||
|
* control_* ему выставляет ИИ (autocontrol_*, guards.c), а не клавиатура. */
|
||||||
|
|
||||||
|
/* back_with_sword (seg005): шаг назад — только из стоек. */
|
||||||
|
static void back_with_sword(void)
|
||||||
|
{
|
||||||
|
uint8_t f = Char.frame;
|
||||||
|
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
|
||||||
|
f == FRAME_171_STAND_WITH_SWORD) {
|
||||||
|
control_backward = CONTROL_IGNORE; /* без авто-повтора */
|
||||||
|
seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* forward_with_sword (seg005): шаг вперёд; у стража своя последовательность. */
|
||||||
|
static void forward_with_sword(void)
|
||||||
|
{
|
||||||
|
uint8_t f = Char.frame;
|
||||||
|
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
|
||||||
|
f == FRAME_171_STAND_WITH_SWORD) {
|
||||||
|
control_forward = CONTROL_IGNORE;
|
||||||
|
seqtbl_offset_char(Char.charid != CHARID_0_KID ? SEQ_56_GUARD_FORWARD
|
||||||
|
: SEQ_86_FORWARD_WITH_SWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sword_strike (seg005): выпад из стойки/шага либо добивание после парирования. */
|
||||||
|
static void sword_strike(void)
|
||||||
|
{
|
||||||
|
uint8_t f = Char.frame;
|
||||||
|
uint8_t seq_id;
|
||||||
|
if (f == FRAME_157_WALK_WITH_SWORD || f == FRAME_158_STAND_WITH_SWORD ||
|
||||||
|
f == FRAME_170_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD ||
|
||||||
|
f == FRAME_165_WALK_WITH_SWORD) {
|
||||||
|
seq_id = (Char.charid == CHARID_0_KID) ? SEQ_75_STRIKE : SEQ_58_GUARD_STRIKE;
|
||||||
|
} else if (f == FRAME_150_PARRY || f == FRAME_161_PARRY) {
|
||||||
|
seq_id = SEQ_66_STRIKE_AFTER_PARRY;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
control_shift2 = CONTROL_IGNORE;
|
||||||
|
seqtbl_offset_char(seq_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parry (seg005): блок. Условие сильно разное у Кида и у соперника —
|
||||||
|
* соперник блокирует ТОЛЬКО на кадре 152 (второй кадр удара), а Кид ещё и
|
||||||
|
* по 153 (и тогда последовательность прокручивается сразу). */
|
||||||
|
static void parry(void)
|
||||||
|
{
|
||||||
|
uint8_t cf = Char.frame, of = Opp.frame;
|
||||||
|
uint8_t seq_id = SEQ_62_PARRY;
|
||||||
|
uint8_t do_play_seq = 0;
|
||||||
|
|
||||||
|
if (cf == FRAME_158_STAND_WITH_SWORD || cf == FRAME_170_STAND_WITH_SWORD ||
|
||||||
|
cf == FRAME_171_STAND_WITH_SWORD || cf == FRAME_168_BACK ||
|
||||||
|
cf == FRAME_165_WALK_WITH_SWORD) {
|
||||||
|
if (pop_char_opp_dist() >= 32 && Char.charid != CHARID_0_KID) {
|
||||||
|
back_with_sword();
|
||||||
|
return;
|
||||||
|
} else if (Char.charid == CHARID_0_KID) {
|
||||||
|
if (of == FRAME_168_BACK) return;
|
||||||
|
if (of != FRAME_151_STRIKE_1 && of != FRAME_152_STRIKE_2 &&
|
||||||
|
of != FRAME_162_BLOCK_TO_STRIKE) {
|
||||||
|
if (of == FRAME_153_STRIKE_3) do_play_seq = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (of != FRAME_152_STRIKE_2) return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cf != FRAME_167_BLOCKED) return;
|
||||||
|
seq_id = SEQ_61_PARRY_AFTER_STRIKE;
|
||||||
|
}
|
||||||
|
control_up = CONTROL_IGNORE;
|
||||||
|
seqtbl_offset_char(seq_id);
|
||||||
|
if (do_play_seq) play_seq();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* swordfight (seg005:0CDB) — разбор ввода в бою. Порядок веток важен:
|
||||||
|
* удар старше «убрать меч», тот старше блока, блок старше шагов. */
|
||||||
|
static void swordfight(void)
|
||||||
|
{
|
||||||
|
uint8_t f = Char.frame;
|
||||||
|
uint8_t seq_id;
|
||||||
|
|
||||||
|
if (f == FRAME_161_PARRY && control_shift2 >= CONTROL_RELEASED) {
|
||||||
|
seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD); /* отскок после парирования */
|
||||||
|
return;
|
||||||
|
} else if (control_shift2 == CONTROL_HELD) {
|
||||||
|
if (Char.charid == CHARID_0_KID) kid_sword_strike = 15;
|
||||||
|
sword_strike();
|
||||||
|
if (control_shift2 == CONTROL_IGNORE) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control_down == CONTROL_HELD) {
|
||||||
|
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
|
||||||
|
f == FRAME_171_STAND_WITH_SWORD) {
|
||||||
|
control_down = CONTROL_IGNORE;
|
||||||
|
Char.sword = SWORD_0_SHEATHED;
|
||||||
|
if (Char.charid == CHARID_0_KID) {
|
||||||
|
offguard = 1; /* убрал намеренно — сразу не доставать */
|
||||||
|
guard_refrac = 9; /* и страж на это время придержан */
|
||||||
|
holding_sword = 0;
|
||||||
|
seq_id = SEQ_93_PUT_SWORD_AWAY_FAST;
|
||||||
|
} else if (Char.charid == CHARID_1_SHADOW) {
|
||||||
|
seq_id = SEQ_92_PUT_SWORD_AWAY;
|
||||||
|
} else {
|
||||||
|
seq_id = SEQ_87_GUARD_BECOME_INACT; /* Кид ушёл из виду */
|
||||||
|
}
|
||||||
|
seqtbl_offset_char(seq_id);
|
||||||
|
}
|
||||||
|
} else if (control_up == CONTROL_HELD) {
|
||||||
|
parry();
|
||||||
|
} else if (control_forward == CONTROL_HELD) {
|
||||||
|
forward_with_sword();
|
||||||
|
} else if (control_backward == CONTROL_HELD) {
|
||||||
|
back_with_sword();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control_with_sword (seg005:964): пока соперник виден и близко — бой;
|
||||||
|
* иначе страж убирает меч, а Кид просто стоит. */
|
||||||
|
static void control_with_sword(void)
|
||||||
|
{
|
||||||
|
if (Char.action >= 2) return; /* actions_2_hang_climb и выше */
|
||||||
|
if (pop_tile_at(Char.curr_col, Char.curr_row) == 11 /* loose */ ||
|
||||||
|
can_guard_see_kid >= 2) {
|
||||||
|
int16_t d = pop_char_opp_dist();
|
||||||
|
if (d < 90) {
|
||||||
|
if (d >= 0) { swordfight(); return; }
|
||||||
|
/* Соперник за спиной: далеко — развернуться, вплотную — драться. */
|
||||||
|
if (d < -4) { seqtbl_offset_char(SEQ_60_TURN_WITH_SWORD); return; }
|
||||||
|
swordfight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Не видит / далеко. */
|
||||||
|
if (Char.charid < CHARID_2_GUARD) {
|
||||||
|
if (Char.frame == FRAME_171_STAND_WITH_SWORD) {
|
||||||
|
Char.sword = SWORD_0_SHEATHED;
|
||||||
|
seqtbl_offset_char(SEQ_92_PUT_SWORD_AWAY); /* страж убит */
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
swordfight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control() — диспетчер по кадру (seg005.c:252) для АКТИВНОГО персонажа. */
|
||||||
|
void pop_control(void)
|
||||||
|
{
|
||||||
|
uint8_t f = Char.frame;
|
||||||
|
/* seg005 control(), самая первая ветка: персонаж УМИРАЕТ (alive >= 0).
|
||||||
|
* Из любой стойки он валится в seq_71_dying; остальное управление в
|
||||||
|
* этом состоянии не обрабатывается. Через это же место проходит чит K
|
||||||
|
* (pop_guard_kill ставит Guard.alive = 0). */
|
||||||
|
if (Char.alive >= 0) {
|
||||||
|
if (f == FRAME_15_STAND || f == FRAME_166_STAND_INACTIVE ||
|
||||||
|
f == FRAME_158_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD)
|
||||||
|
seqtbl_offset_char(SEQ_71_DYING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
|
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
|
||||||
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
|
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
|
||||||
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
|
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
|
||||||
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
|
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
|
||||||
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
|
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
|
||||||
* пару пикселей меньше, чем должен»). */
|
* пару пикселей меньше, чем должен»). */
|
||||||
if (Kid.action == 5 || Kid.action == 4) { /* bumped / in_freefall */
|
if (Char.action == 5 || Char.action == 4) { /* bumped / in_freefall */
|
||||||
release_arrows();
|
release_arrows();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
|
/* Порядок веток — как в seg005: меч старше charid, charid старше кадров.
|
||||||
|
* Пока Kid безоружен, обе ветки — про стража. */
|
||||||
|
if (Char.sword == SWORD_2_DRAWN)
|
||||||
|
control_with_sword();
|
||||||
|
else if (Char.charid >= CHARID_2_GUARD)
|
||||||
|
control_guard_inactive();
|
||||||
|
else if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
|
||||||
control_standing();
|
control_standing();
|
||||||
else if (f == FRAME_48_TURN)
|
else if (f == FRAME_48_TURN)
|
||||||
control_turning();
|
control_turning();
|
||||||
@@ -406,12 +641,12 @@ static void flip_control_x(void)
|
|||||||
/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
|
/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
|
||||||
static void user_control(void)
|
static void user_control(void)
|
||||||
{
|
{
|
||||||
if (Kid.direction >= DIR_RIGHT) {
|
if (Char.direction >= DIR_RIGHT) {
|
||||||
flip_control_x();
|
flip_control_x();
|
||||||
control();
|
pop_control();
|
||||||
flip_control_x();
|
flip_control_x();
|
||||||
} else {
|
} else {
|
||||||
control();
|
pop_control();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +686,12 @@ void pop_ctrl_tick(void)
|
|||||||
rest_ctrl_1();
|
rest_ctrl_1();
|
||||||
read_input();
|
read_input();
|
||||||
read_user_control();
|
read_user_control();
|
||||||
user_control(); /* -> control(): может сменить Kid.curr_seq */
|
/* Окно Char вокруг диспетчера: control() работает с АКТИВНЫМ
|
||||||
|
* персонажем (как в оригинале), поэтому ввод игрока применяется к
|
||||||
|
* Киду только внутри loadkid/savekid. Кадр не перезагружаем —
|
||||||
|
* play_seq в kid_tick сделает это следующим шагом. */
|
||||||
|
pop_loadkid();
|
||||||
|
user_control(); /* -> pop_control(): может сменить Char.curr_seq */
|
||||||
|
pop_savekid_state();
|
||||||
save_ctrl_1();
|
save_ctrl_1();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,27 @@ void pop_ctrl_tick(void);
|
|||||||
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
|
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
|
||||||
int8_t pop_ctrl_shift_held(void);
|
int8_t pop_ctrl_shift_held(void);
|
||||||
|
|
||||||
|
/* ---- Шина синтетического ввода (порт глобалов control_* из seg005) ----
|
||||||
|
* В оригинале ИИ соперника (autocontrol_*, seg002) не имеет своего
|
||||||
|
* диспетчера: он ВЫСТАВЛЯЕТ эти переменные, а дальше исполняется тот же
|
||||||
|
* control(), что и для игрока. Значения — CONTROL_RELEASED/IGNORE/HELD. */
|
||||||
|
#define POP_CONTROL_RELEASED 0
|
||||||
|
#define POP_CONTROL_IGNORE 1
|
||||||
|
#define POP_CONTROL_HELD (-1)
|
||||||
|
/* Оси ЭТОГО кадра (абсолютные): X = -1 влево / +1 вправо, Y = -1 вверх /
|
||||||
|
* +1 вниз. Для ИИ «вперёд/назад» уже в системе персонажа, поэтому оси он
|
||||||
|
* заполняет как есть, без flip_control_x. */
|
||||||
|
extern int8_t control_x, control_y, control_shift;
|
||||||
|
#define POP_CONTROL_HELD_FORWARD (-1)
|
||||||
|
#define POP_CONTROL_HELD_BACKWARD 1
|
||||||
|
#define POP_CONTROL_HELD_UP (-1)
|
||||||
|
#define POP_CONTROL_HELD_DOWN 1
|
||||||
|
extern int8_t control_forward, control_backward, control_up, control_down,
|
||||||
|
control_shift2;
|
||||||
|
|
||||||
|
/* Диспетчер (seg005:252) для АКТИВНОГО персонажа Char. Игрок идёт через
|
||||||
|
* pop_ctrl_tick (тот делает user_control с зеркалом «вперёд»), страж — из
|
||||||
|
* своего тика напрямую. */
|
||||||
|
void pop_control(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* 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_map.h" /* hitp_curr/hitp_max — HP Кида */
|
||||||
|
#include "kid_data.h"
|
||||||
|
|
||||||
|
/* ---- Атласы стража (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];
|
||||||
|
|
||||||
|
int pop_guard_load(void) __banked
|
||||||
|
{
|
||||||
|
static const char *const names[GUARD_PAGES] = {
|
||||||
|
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
|
||||||
|
"GUARD\\g3.atl", "GUARD\\g4.atl"
|
||||||
|
};
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < GUARD_PAGES; i++)
|
||||||
|
if (atlas_load(&gp[i], names[i]) != 0) return -1;
|
||||||
|
g_ok = 1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_guard_heal(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t p = gfx_get_draw_page() & 1;
|
||||||
|
if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
|
||||||
|
if (svalid[p]) gfx_heal(sx_l[p], sy_l[p] + POP_YOFF, sw_l[p], sh_l[p]);
|
||||||
|
gvalid[p] = svalid[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;
|
||||||
|
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
|
||||||
|
if (flip) bx -= (int)w;
|
||||||
|
top = obj_y - (int)h + 1;
|
||||||
|
p = gfx_get_draw_page() & 1;
|
||||||
|
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_part(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1;
|
||||||
|
clip_x = bx; clip_y = top; clip_w = w; clip_h = h;
|
||||||
|
}
|
||||||
|
/* Клинок стража — тот же 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Индикаторы 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,236 @@
|
|||||||
|
/*
|
||||||
|
* pop_geom.c — общая геометрия + PRNG оригинала. См. pop_geom.h.
|
||||||
|
*/
|
||||||
|
#include "pop_geom.h"
|
||||||
|
|
||||||
|
/* data.h: шаг 14 (ширина тайла в координатах персонажа), [5] = col 0. */
|
||||||
|
const int16_t pop_x_bump[20] = {
|
||||||
|
-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
|
||||||
|
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
|
||||||
|
};
|
||||||
|
const int16_t pop_y_land[5] = {-8, 55, 118, 181, 244};
|
||||||
|
|
||||||
|
int8_t pop_y_to_row(int16_t y)
|
||||||
|
{
|
||||||
|
return (int8_t)((y + 60) / TILE_SIZEY % 4 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уложить значение в диапазон 0..maxv. Вызовы оригинала — prandom(1),
|
||||||
|
* prandom(255), prandom(0xFF): делитель степень двойки, то есть маска, а не
|
||||||
|
* деление. __moduint на Z80 стоит заметно дороже проверки n & (n-1). */
|
||||||
|
static uint16_t pop_rnd_fit(uint16_t v, uint16_t maxv)
|
||||||
|
{
|
||||||
|
uint16_t n = (uint16_t)(maxv + 1);
|
||||||
|
if ((uint16_t)(n & (uint16_t)(n - 1)) == 0)
|
||||||
|
return (uint16_t)(v & (uint16_t)(n - 1));
|
||||||
|
return (uint16_t)(v % n);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if POP_PRANDOM_EXACT
|
||||||
|
/* ---- LCG оригинала: s = s*214013 + 2531011 (seg009:321) ------------ *
|
||||||
|
*
|
||||||
|
* ЕДИНСТВЕННОЕ место в порте на ассемблере, и вот почему. На Z80 нет
|
||||||
|
* умножения, а SDCC для 32-битного `s * 214013` зовёт __mullong — 8 430
|
||||||
|
* тактов на вызов (замер в MAME). Написанное на C через 16-битные
|
||||||
|
* половины это ~3 500. Здесь ~1 000, при полностью той же
|
||||||
|
* последовательности, что в SDLPoP. Альтернатива «взять генератор
|
||||||
|
* попроще» (8-битный RND Apple II, xorshift) экономит меньше и ломает
|
||||||
|
* совместимость с эталоном, по которому сверяем картинку.
|
||||||
|
*
|
||||||
|
* Приём: 214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3 — схема Горнера по
|
||||||
|
* РАЗРЕЖЕННОЙ записи константы. Вместо 12 сложений (по числу единиц в
|
||||||
|
* 0x343FD) — 17 удвоений, ТРИ сложения и одно вычитание. Величина 3*s,
|
||||||
|
* нужная в конце, попадается по дороге на втором шаге — её и сохраняем.
|
||||||
|
*
|
||||||
|
* ABI __sdcccall(1): arg1 (указатель на сид) -> HL, возврат -> DE. IX не
|
||||||
|
* используется, сохранять нечего. Клоббер AF/BC/DE/HL. Аккумулятор —
|
||||||
|
* DE:HL (DE старшее слово); s.lo живёт в BC, s.hi и 3*s — в статиках
|
||||||
|
* (регистров на всё не хватает).
|
||||||
|
*/
|
||||||
|
uint16_t pop_pr_ptr; /* адрес сида */
|
||||||
|
uint16_t pop_pr_sh; /* старшее слово исходного сида */
|
||||||
|
uint16_t pop_pr_3s_lo, pop_pr_3s_hi; /* 3*s, нужное в конце */
|
||||||
|
|
||||||
|
static uint16_t pop_lcg_step(pop_rnd_t *seed) __naked
|
||||||
|
{
|
||||||
|
(void)seed;
|
||||||
|
__asm
|
||||||
|
ld (_pop_pr_ptr), hl
|
||||||
|
ld c, (hl)
|
||||||
|
inc hl
|
||||||
|
ld b, (hl) ; BC = s.lo
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
ld (_pop_pr_sh), a
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
ld (_pop_pr_sh + 1), a ; (pop_pr_sh) = s.hi
|
||||||
|
|
||||||
|
ld h, b
|
||||||
|
ld l, c
|
||||||
|
ld de, (_pop_pr_sh) ; акк = s
|
||||||
|
|
||||||
|
;; акк = акк*2 + s => 3s
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, bc
|
||||||
|
ex de, hl
|
||||||
|
ld a, (_pop_pr_sh) ; LD A,(nn) флаги НЕ трогает
|
||||||
|
adc a, l
|
||||||
|
ld l, a
|
||||||
|
ld a, (_pop_pr_sh + 1)
|
||||||
|
adc a, h
|
||||||
|
ld h, a
|
||||||
|
ex de, hl
|
||||||
|
ld (_pop_pr_3s_lo), hl
|
||||||
|
ld (_pop_pr_3s_hi), de
|
||||||
|
|
||||||
|
;; акк = акк*4 + s => 13s
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, bc
|
||||||
|
ex de, hl
|
||||||
|
ld a, (_pop_pr_sh)
|
||||||
|
adc a, l
|
||||||
|
ld l, a
|
||||||
|
ld a, (_pop_pr_sh + 1)
|
||||||
|
adc a, h
|
||||||
|
ld h, a
|
||||||
|
ex de, hl
|
||||||
|
|
||||||
|
;; акк = акк*16 + s => 209s
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, bc
|
||||||
|
ex de, hl
|
||||||
|
ld a, (_pop_pr_sh)
|
||||||
|
adc a, l
|
||||||
|
ld l, a
|
||||||
|
ld a, (_pop_pr_sh + 1)
|
||||||
|
adc a, h
|
||||||
|
ld h, a
|
||||||
|
ex de, hl
|
||||||
|
|
||||||
|
;; акк *= 1024 => 214016s (10 удвоений)
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
add hl, hl
|
||||||
|
rl e
|
||||||
|
rl d
|
||||||
|
|
||||||
|
;; акк -= 3s => 214013s
|
||||||
|
ld bc, (_pop_pr_3s_lo)
|
||||||
|
or a ; CY = 0
|
||||||
|
sbc hl, bc
|
||||||
|
ld bc, (_pop_pr_3s_hi) ; LD BC,(nn) флаги НЕ трогает
|
||||||
|
ex de, hl
|
||||||
|
sbc hl, bc
|
||||||
|
ex de, hl
|
||||||
|
|
||||||
|
;; акк += 2531011 = 0x0026_9EC3
|
||||||
|
ld bc, #0x9EC3
|
||||||
|
add hl, bc
|
||||||
|
ld bc, #0x0026
|
||||||
|
ex de, hl
|
||||||
|
adc hl, bc
|
||||||
|
ex de, hl
|
||||||
|
|
||||||
|
;; сохранить сид, вернуть СТАРШЕЕ слово в DE
|
||||||
|
push de
|
||||||
|
ex de, hl ; DE = младшее слово
|
||||||
|
ld hl, (_pop_pr_ptr)
|
||||||
|
ld (hl), e
|
||||||
|
inc hl
|
||||||
|
ld (hl), d
|
||||||
|
inc hl
|
||||||
|
pop de ; DE = старшее слово
|
||||||
|
ld (hl), e
|
||||||
|
inc hl
|
||||||
|
ld (hl), d
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv)
|
||||||
|
{
|
||||||
|
return pop_rnd_fit(pop_lcg_step(seed), maxv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* !POP_PRANDOM_EXACT — генератор попроще, НЕ совместимый с эталоном */
|
||||||
|
|
||||||
|
/* xorshift16 + шаг Вейля. Зачем не LCG оригинала: тот 32-битный, и даже
|
||||||
|
* без __mullong (см. ветку выше) стоит ~7 000 тактов на вызов — при двух
|
||||||
|
* вызовах за кадр это 3 % бюджета на генератор случайных чисел. Здесь
|
||||||
|
* умножений нет вовсе, только сдвиги и xor.
|
||||||
|
*
|
||||||
|
* Почему НЕ 8-битный RND Apple II (RNDseed := 5*RNDseed + 23 mod 256,
|
||||||
|
* Prince-of-Persia-Apple-II): он ещё быстрее, но у LCG по модулю 256
|
||||||
|
* младшие биты вырождены — бит 0 просто чередуется. Наши вызовы это
|
||||||
|
* видят: раскладка кладки берёт prandom(1) (один бит) и prandom(4), и
|
||||||
|
* вместо шума получилась бы правильная шахматка. У xorshift такой
|
||||||
|
* структуры нет, а стоит он примерно столько же.
|
||||||
|
*
|
||||||
|
* Шаг Вейля (+0x9E37, нечётный) нужен ещё и потому, что у чистого
|
||||||
|
* xorshift ноль — неподвижная точка, а сид кладки (номер комнаты + ряд +
|
||||||
|
* колонка) вполне может оказаться нулём.
|
||||||
|
*
|
||||||
|
* ПОСЛЕДСТВИЕ: последовательность отличается от SDLPoP — другая (но
|
||||||
|
* статистически такая же) раскладка кладки и другие броски в боёвке.
|
||||||
|
* Поэтому по умолчанию собирается НЕ эта ветка: после переписывания LCG
|
||||||
|
* на ассемблер выигрыш от смены генератора почти исчез, а сверять
|
||||||
|
* картинку с эталоном стало важнее. Ветка оставлена как запасной ход. */
|
||||||
|
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv)
|
||||||
|
{
|
||||||
|
uint16_t s = seed->lo;
|
||||||
|
s ^= (uint16_t)(s << 7);
|
||||||
|
s ^= (uint16_t)(s >> 9);
|
||||||
|
s ^= (uint16_t)(s << 8);
|
||||||
|
s = (uint16_t)(s + 0x9E37u);
|
||||||
|
seed->lo = s;
|
||||||
|
seed->hi = 0; /* старшая половина не используется */
|
||||||
|
/* Отдаём СТАРШИЙ байт: у него нет короткопериодической структуры
|
||||||
|
* младших бит, а вызывающие берут от результата именно младшие. */
|
||||||
|
return pop_rnd_fit((uint16_t)(s >> 8), maxv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* pop_geom.h — общая геометрия PoP и PRNG оригинала.
|
||||||
|
*
|
||||||
|
* Сюда сведено то, что раньше дублировалось по модулям (x_bump/y_land в
|
||||||
|
* pop_kid и pop_map, y_to_row в pop_bg и pop_map, 32-битный LCG в pop_bg и
|
||||||
|
* pop_trob). Кроме экономии это ФУНДАМЕНТ bank-safe API: модуль живёт в
|
||||||
|
* W1/W2 и не трогает графику, поэтому его может звать и главный цикл, и
|
||||||
|
* резидент W3, и будущий __banked-код стражей (docs/layout_plan_v2.md §4).
|
||||||
|
*/
|
||||||
|
#ifndef POP_GEOM_H
|
||||||
|
#define POP_GEOM_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define FIRST_ONSCREEN_COLUMN 5 /* индекс col 0 комнаты в x_bump */
|
||||||
|
#define TILE_SIZEX 14 /* ширина тайла в координатах персонажа */
|
||||||
|
#define TILE_SIZEY 63 /* высота ряда */
|
||||||
|
|
||||||
|
/* x_bump (data.h): левая граница колонки в координатах персонажа; колонка
|
||||||
|
* комнаты col = x_bump[col + FIRST_ONSCREEN_COLUMN]. y_land: пол ряда
|
||||||
|
* (индекс row+1; [0] — ряд «над комнатой»). */
|
||||||
|
extern const int16_t pop_x_bump[20];
|
||||||
|
extern const int16_t pop_y_land[5];
|
||||||
|
|
||||||
|
/* y_to_row_mod4 (seg006): ряд по координате y с оборотом mod 4 (−1 = полоса
|
||||||
|
* над комнатой, 3 = под комнатой). */
|
||||||
|
int8_t pop_y_to_row(int16_t y);
|
||||||
|
|
||||||
|
/* prandom (порт seg009:321). Сид ВНЕШНИЙ: у стен (раскладка кладки) и у
|
||||||
|
* анимаций тайлов (фаза факелов) свои последовательности, смешивать их
|
||||||
|
* нельзя.
|
||||||
|
*
|
||||||
|
* ДВА генератора, выбор через POP_PRANDOM_EXACT (см. pop_geom.c):
|
||||||
|
* 1 (по умолчанию) — LCG оригинала (s*214013+2531011) БИТ-В-БИТ: та же
|
||||||
|
* раскладка кладки и те же броски, что в SDLPoP, поэтому по картинке
|
||||||
|
* можно сверяться с эталоном;
|
||||||
|
* 0 — xorshift16 + шаг Вейля, без единого умножения. Дешевле ровно на
|
||||||
|
* 5 646 тактов за кадр, 1.3 % бюджета (замер A/B в MAME, комната 3,
|
||||||
|
* 175 кадров: 403 632 против 397 986). Последовательность другая —
|
||||||
|
* статистически эквивалентная, но не та же.
|
||||||
|
*
|
||||||
|
* Обе версии считают 16-битными половинами, а не unsigned long: на 32-бит
|
||||||
|
* умножении SDCC зовёт __mullong, и это 8 430 тактов на вызов (замер в
|
||||||
|
* MAME, breakpoint на __mullong).
|
||||||
|
*
|
||||||
|
* ЗАДЕЛ: разрыв между версиями определяется не алгоритмом, а кодогенерацией
|
||||||
|
* SDCC — те же генераторы на Z80-ассемблере укладываются в 86..148 тактов.
|
||||||
|
* Если процент понадобится, сначала переписать в asm ОРИГИНАЛЬНЫЙ LCG (там
|
||||||
|
* умножение на константу), а менять генератор — только если и этого мало. */
|
||||||
|
typedef struct { uint16_t lo, hi; } pop_rnd_t;
|
||||||
|
|
||||||
|
#ifndef POP_PRANDOM_EXACT
|
||||||
|
#define POP_PRANDOM_EXACT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv);
|
||||||
|
|
||||||
|
/* Сид из 16-битного числа (старшая половина = 0). */
|
||||||
|
#define pop_prandom_set(s, v) do { (s).lo = (uint16_t)(v); (s).hi = 0; } while (0)
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* pop_guard.c — СОСТОЯНИЕ и ЛОГИКА стража (W1/W2). См. pop_guard.h.
|
||||||
|
*
|
||||||
|
* Здесь только то, что должно оставаться достижимым из банка (guards.c):
|
||||||
|
* поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы.
|
||||||
|
* ОТРИСОВКА — в pop_gdraw.c, резиденте W3 (её зовёт лишь главный цикл).
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_level.h"
|
||||||
|
#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */
|
||||||
|
#include "pop_map.h" /* hitp_* — HP Кида */
|
||||||
|
#include "kid_data.h"
|
||||||
|
|
||||||
|
pop_char_t Guard;
|
||||||
|
uint8_t guardhp_curr, guardhp_max;
|
||||||
|
int8_t guardhp_delta;
|
||||||
|
int8_t can_guard_see_kid;
|
||||||
|
int8_t is_guard_notice;
|
||||||
|
uint8_t guard_skill;
|
||||||
|
int8_t guard_refrac, justblocked, kid_sword_strike;
|
||||||
|
int8_t offguard, holding_sword;
|
||||||
|
|
||||||
|
/* Сид бросков боёвки. В оригинале генератор один на всё, у нас
|
||||||
|
* последовательности разведены по доменам (кладка / анимация тайлов / бой),
|
||||||
|
* см. pop_geom.h — иначе перерисовка стены сбивала бы броски стража. */
|
||||||
|
pop_rnd_t pop_fight_seed;
|
||||||
|
|
||||||
|
/* Кадр стража (порт cur_frame для Char). Глобальный, а не статик: его
|
||||||
|
* заполняет логика (W1/W2), а читает отрисовка из резидента W3. */
|
||||||
|
kframe pop_gframe;
|
||||||
|
|
||||||
|
uint8_t pop_guard_present(void)
|
||||||
|
{
|
||||||
|
return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_guard_reset(void)
|
||||||
|
{
|
||||||
|
Guard.charid = 0;
|
||||||
|
Guard.alive = 0;
|
||||||
|
Guard.direction = 0;
|
||||||
|
guardhp_curr = 0;
|
||||||
|
guardhp_delta = 0;
|
||||||
|
can_guard_see_kid = 0;
|
||||||
|
is_guard_notice = 0;
|
||||||
|
guard_refrac = justblocked = kid_sword_strike = 0;
|
||||||
|
offguard = holding_sword = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* kill guard cheat (seg000:786):
|
||||||
|
* if (Guard.charid != charid_4_skeleton) {
|
||||||
|
* guardhp_delta = -guardhp_curr;
|
||||||
|
* Guard.alive = 0;
|
||||||
|
* }
|
||||||
|
* Скелета чит не убивает — он и в оригинале бессмертен. */
|
||||||
|
void pop_guard_kill(void)
|
||||||
|
{
|
||||||
|
if (Guard.charid == CHARID_4_SKELETON) return;
|
||||||
|
if (!pop_guard_present()) return;
|
||||||
|
/* Как в оригинале: HP НЕ обнуляем здесь — ставим дельту, её применит
|
||||||
|
* pop_do_delta_hp. alive = 0 переводит стража в «умирает», а дальше
|
||||||
|
* общий control() ставит ему seq_71_dying. */
|
||||||
|
guardhp_delta = (int8_t)-guardhp_curr;
|
||||||
|
Guard.alive = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do_delta_hp (seg003): применить накопленную за кадр дельту HP и обнулить
|
||||||
|
* её. Звать раз в кадр ПОСЛЕ тиков персонажей, как в play_frame. */
|
||||||
|
void pop_do_delta_hp(void)
|
||||||
|
{
|
||||||
|
int16_t hp;
|
||||||
|
if (hitp_delta) {
|
||||||
|
hp = (int16_t)hitp_curr + hitp_delta;
|
||||||
|
if (hp < 0) hp = 0;
|
||||||
|
if (hp > (int16_t)hitp_max) hp = (int16_t)hitp_max;
|
||||||
|
hitp_curr = (uint8_t)hp;
|
||||||
|
pop_kid_hurt = 1; /* -> «брызги» поверх Кида */
|
||||||
|
hitp_delta = 0;
|
||||||
|
}
|
||||||
|
if (guardhp_delta) {
|
||||||
|
hp = (int16_t)guardhp_curr + guardhp_delta;
|
||||||
|
if (hp < 0) hp = 0;
|
||||||
|
if (hp > (int16_t)guardhp_max) hp = (int16_t)guardhp_max;
|
||||||
|
guardhp_curr = (uint8_t)hp;
|
||||||
|
guardhp_delta = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* load_frame для стража (seg006:0293 ветка charid_2_guard): таблица у
|
||||||
|
* стражей СВОЯ (frame_tbl_guard), индекс = frame + add_frame − 149, где
|
||||||
|
* add_frame = 70 для кадров 102..106 (seg006:535). */
|
||||||
|
void pop_guard_load_frame(void)
|
||||||
|
{
|
||||||
|
int16_t idx = (int16_t)Guard.frame;
|
||||||
|
if (idx >= 102 && idx < 107) idx += 70; /* add_frame */
|
||||||
|
idx -= 149;
|
||||||
|
if (idx < 0 || idx >= KID_NGFRAMES) { pop_gframe.image = 255; return; }
|
||||||
|
pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&pop_gframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* leave_guard (seg002:02F5): вызывать ПЕРЕД сменой комнаты, пока Guard ещё
|
||||||
|
* держит стража уходящей комнаты. Мёртвого запоминаем вместе с
|
||||||
|
* последовательностью — тогда при возврате он лежит там же, а не встаёт
|
||||||
|
* живым. */
|
||||||
|
void pop_guard_leave(void)
|
||||||
|
{
|
||||||
|
if (Guard.charid == 0) return; /* стража не было */
|
||||||
|
pop_guard_state_save(Guard.room,
|
||||||
|
(uint8_t)(Guard.curr_row * 10), /* get_tilepos(0,row) */
|
||||||
|
Guard.direction, Guard.x, guard_skill,
|
||||||
|
Guard.curr_seq,
|
||||||
|
(uint8_t)(Guard.alive >= 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_guard_enter(uint8_t room)
|
||||||
|
{
|
||||||
|
uint8_t tile, color, skill;
|
||||||
|
int8_t dir;
|
||||||
|
pop_guard_reset();
|
||||||
|
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
|
||||||
|
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
|
||||||
|
* колонки тайла, а не берётся из файла. */
|
||||||
|
Guard.room = room;
|
||||||
|
Guard.curr_row = (int8_t)(tile / 10);
|
||||||
|
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
|
||||||
|
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
||||||
|
Guard.curr_col = (int8_t)(tile % 10);
|
||||||
|
Guard.direction = dir;
|
||||||
|
Guard.charid = CHARID_2_GUARD;
|
||||||
|
Guard.sword = 0; /* sword_0_sheathed */
|
||||||
|
Guard.alive = -1;
|
||||||
|
Guard.action = 1; /* actions_1_run_jump */
|
||||||
|
Guard.fall_x = Guard.fall_y = 0;
|
||||||
|
/* enter_guard (seg002:214): мастерство из данных уровня, 12 градаций;
|
||||||
|
* всё, что вне диапазона, оригинал сводит к 3. */
|
||||||
|
guard_skill = (uint8_t)(skill < NUM_GUARD_SKILLS ? skill : 3);
|
||||||
|
/* get_guard_hp (seg002:54): extrastrength[skill] + tbl_guard_hp[уровень].
|
||||||
|
* Уровень 1 -> 3; extrastrength ненулевой только у skill 4. */
|
||||||
|
guardhp_max = guardhp_curr = (uint8_t)(3 + (guard_skill == 4 ? 1 : 0));
|
||||||
|
guardhp_delta = 0;
|
||||||
|
/* enter_guard (seg002:0208). Живого поднимаем стандартной стойкой; у
|
||||||
|
* ЗАПОМНЕННОГО МЁРТВОГО берём сохранённую последовательность — он лежит
|
||||||
|
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
|
||||||
|
{
|
||||||
|
uint16_t saved_seq = pop_guard_state_seq(room);
|
||||||
|
/* У ЗАПОМНЕННОГО трупа X берём сохранённую: pos_guards (seg003:620)
|
||||||
|
* пересчитывает её из колонки только при загрузке уровня, а дальше
|
||||||
|
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
|
||||||
|
* тайла. */
|
||||||
|
if (saved_seq) Guard.x = pop_guard_state_x(room);
|
||||||
|
pop_loadshad();
|
||||||
|
if (saved_seq) Char.curr_seq = saved_seq;
|
||||||
|
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
|
||||||
|
pop_saveshad();
|
||||||
|
pop_guard_play(); /* до первого кадра */
|
||||||
|
/* enter_guard (seg002:218): кадр смерти -> страж поднят ТРУПОМ. */
|
||||||
|
if (Guard.frame == 185 || Guard.frame == 177 || Guard.frame == 178) {
|
||||||
|
Guard.alive = 1;
|
||||||
|
guardhp_curr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* pop_guard.h — СОСТОЯНИЕ стража и точки входа в его логику.
|
||||||
|
*
|
||||||
|
* Разделение по окнам (docs/layout_plan_v2.md §4, подтверждено пробником
|
||||||
|
* tests/w3bankgfx):
|
||||||
|
* - СОСТОЯНИЕ живёт здесь, в W1/W2. Писучие статики __banked-модуля
|
||||||
|
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
|
||||||
|
* только код;
|
||||||
|
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
|
||||||
|
* - ОТРИСОВКА стража — в pop_gdraw.c, РЕЗИДЕНТЕ W3 (--w3): её зовёт
|
||||||
|
* только главный цикл, а из банка резидент недостижим. Так код
|
||||||
|
* отрисовки не занимает дефицитные W1/W2 (куча).
|
||||||
|
*
|
||||||
|
* Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006
|
||||||
|
* ложился один в один.
|
||||||
|
*/
|
||||||
|
#ifndef POP_GUARD_H
|
||||||
|
#define POP_GUARD_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "kid_data.h" /* kframe */
|
||||||
|
#include "pop_char.h"
|
||||||
|
#include "pop_geom.h" /* pop_rnd_t — сид бросков боёвки */
|
||||||
|
|
||||||
|
/* seqids (types.h:1052+) — последовательности боя и стойки. */
|
||||||
|
#define SEQ_55_DRAW_SWORD 55
|
||||||
|
#define SEQ_56_GUARD_FORWARD 56 /* шаг вперёд с мечом (страж) */
|
||||||
|
#define SEQ_57_BACK_WITH_SWORD 57
|
||||||
|
#define SEQ_58_GUARD_STRIKE 58
|
||||||
|
#define SEQ_60_TURN_WITH_SWORD 60
|
||||||
|
#define SEQ_61_PARRY_AFTER_STRIKE 61
|
||||||
|
#define SEQ_62_PARRY 62
|
||||||
|
#define SEQ_66_STRIKE_AFTER_PARRY 66
|
||||||
|
#define SEQ_71_DYING 71
|
||||||
|
#define SEQ_75_STRIKE 75 /* удар (Кид) */
|
||||||
|
#define SEQ_77_GUARD_STAND_INACTIVE 77
|
||||||
|
#define SEQ_80_STAND_FLIPPED 80
|
||||||
|
#define SEQ_86_FORWARD_WITH_SWORD 86 /* шаг вперёд с мечом (Кид) */
|
||||||
|
#define SEQ_87_GUARD_BECOME_INACT 87
|
||||||
|
#define SEQ_90_EN_GARDE 90
|
||||||
|
#define SEQ_92_PUT_SWORD_AWAY 92
|
||||||
|
#define SEQ_93_PUT_SWORD_AWAY_FAST 93
|
||||||
|
|
||||||
|
/* frameids (types.h:911+) и состояния меча (types.h:332). */
|
||||||
|
#define FRAME_150_PARRY 150
|
||||||
|
#define FRAME_151_STRIKE_1 151
|
||||||
|
#define FRAME_152_STRIKE_2 152
|
||||||
|
#define FRAME_153_STRIKE_3 153
|
||||||
|
#define FRAME_157_WALK_WITH_SWORD 157
|
||||||
|
#define FRAME_158_STAND_WITH_SWORD 158
|
||||||
|
#define FRAME_161_PARRY 161
|
||||||
|
#define FRAME_162_BLOCK_TO_STRIKE 162
|
||||||
|
#define FRAME_165_WALK_WITH_SWORD 165
|
||||||
|
#define FRAME_166_STAND_INACTIVE 166
|
||||||
|
#define FRAME_167_BLOCKED 167
|
||||||
|
#define FRAME_168_BACK 168
|
||||||
|
#define FRAME_169_BEGIN_BLOCK 169
|
||||||
|
#define FRAME_170_STAND_WITH_SWORD 170
|
||||||
|
#define FRAME_171_STAND_WITH_SWORD 171
|
||||||
|
#define SWORD_0_SHEATHED 0
|
||||||
|
#define SWORD_2_DRAWN 2
|
||||||
|
|
||||||
|
/* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */
|
||||||
|
#define CHARID_0_KID 0
|
||||||
|
#define CHARID_1_SHADOW 1
|
||||||
|
#define CHARID_2_GUARD 2
|
||||||
|
#define CHARID_4_SKELETON 4
|
||||||
|
#define CHARID_5_PRINCESS 5
|
||||||
|
|
||||||
|
/* Градаций мастерства стража (seg002: таблицы вероятностей по 12 записей). */
|
||||||
|
#define NUM_GUARD_SKILLS 12
|
||||||
|
|
||||||
|
/* Тип персонажа — в pop_char.h (общий с Кидом и Char). */
|
||||||
|
|
||||||
|
extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */
|
||||||
|
extern uint8_t guardhp_curr, guardhp_max;
|
||||||
|
extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */
|
||||||
|
|
||||||
|
/* Видит ли страж Кида (порт can_guard_see_kid, seg003:688):
|
||||||
|
* 0 — не видит, 1 — видит, но не пойдёт (между ними loose/чомпер/дыра),
|
||||||
|
* 2 — видит и пойдёт. Считает pop_check_can_guard_see_kid (банк) раз в
|
||||||
|
* кадр ДО логики персонажей; читают ИИ (autocontrol_*) и боёвка. */
|
||||||
|
extern int8_t can_guard_see_kid;
|
||||||
|
|
||||||
|
/* Кид «нашумел» (порт is_guard_notice, seg000:364): заставляет неактивного
|
||||||
|
* стража обернуться, даже если Кид у него за спиной. Ставит источник шума,
|
||||||
|
* снимает сам ИИ. */
|
||||||
|
extern int8_t is_guard_notice;
|
||||||
|
|
||||||
|
/* Боевые таймеры/уровень мастерства (seg000:295..311). Все убывают раз в
|
||||||
|
* кадр в autocontrol_opponent — как в оригинале.
|
||||||
|
* guard_skill — 0..11, из данных уровня; индекс в таблицы вероятностей;
|
||||||
|
* guard_refrac — «отдышка» стража: пока >0, он не атакует и не наступает;
|
||||||
|
* justblocked — Кид только что парировал (влияет на вероятность блока);
|
||||||
|
* kid_sword_strike — Кид только что бил (стража это придерживает). */
|
||||||
|
extern uint8_t guard_skill;
|
||||||
|
extern int8_t guard_refrac, justblocked, kid_sword_strike;
|
||||||
|
|
||||||
|
/* Состояние меча КИДА (seg005/seg006):
|
||||||
|
* offguard — меч убран НАМЕРЕННО (вниз в бою): пока стоит, Кид не
|
||||||
|
* достаёт клинок автоматически, даже видя стража;
|
||||||
|
* holding_sword — клинок в руке (для индикатора HP стража). */
|
||||||
|
extern int8_t offguard, holding_sword;
|
||||||
|
|
||||||
|
/* Сид бросков боёвки (свой домен последовательности, см. pop_geom.h). */
|
||||||
|
extern pop_rnd_t pop_fight_seed;
|
||||||
|
|
||||||
|
/* Луч видимости стража — раз в кадр, ДО тиков персонажей (в оригинале
|
||||||
|
* check_can_guard_see_kid стоит в play_frame перед play_kid_frame). */
|
||||||
|
void pop_check_can_guard_see_kid(void) __banked;
|
||||||
|
|
||||||
|
/* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в
|
||||||
|
* кадре как в play_frame: hurting -> hurt -> do_delta_hp. */
|
||||||
|
void pop_check_sword_hurting(void) __banked;
|
||||||
|
void pop_check_sword_hurt(void) __banked;
|
||||||
|
|
||||||
|
/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2,
|
||||||
|
* читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */
|
||||||
|
extern kframe pop_gframe;
|
||||||
|
void pop_guard_load_frame(void);
|
||||||
|
|
||||||
|
/* Есть ли живой страж в отрисованной комнате (для главного цикла). */
|
||||||
|
uint8_t pop_guard_present(void);
|
||||||
|
|
||||||
|
/* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid.
|
||||||
|
* 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */
|
||||||
|
int pop_guard_load(void) __banked;
|
||||||
|
void pop_guard_free(void) __banked;
|
||||||
|
|
||||||
|
/* Вход в комнату: поднять стража из данных уровня (порт enter_guard,
|
||||||
|
* seg002:0112) либо убрать его, если в комнате стража нет. */
|
||||||
|
void pop_guard_enter(uint8_t room);
|
||||||
|
|
||||||
|
/* Запомнить стража УХОДЯЩЕЙ комнаты (порт leave_guard). Звать в начале
|
||||||
|
* enter_room, пока Guard ещё держит прежнего. */
|
||||||
|
void pop_guard_leave(void);
|
||||||
|
|
||||||
|
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
|
||||||
|
* как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
|
||||||
|
* (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
|
||||||
|
/* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл
|
||||||
|
* ПОСЛЕ персонажей — она ничего не перекрывает. */
|
||||||
|
void pop_hp_draw(void) __banked;
|
||||||
|
/* Полоса стёрта перерисовкой фона (вход в комнату) — обновить обе страницы. */
|
||||||
|
void pop_hp_invalidate(void) __banked;
|
||||||
|
|
||||||
|
void pop_guard_heal(void) __banked;
|
||||||
|
void pop_guard_draw(void) __banked;
|
||||||
|
|
||||||
|
/* Убрать стража со сцены (смена комнаты / инициализация). */
|
||||||
|
void pop_guard_reset(void);
|
||||||
|
|
||||||
|
/* Чит «убить стража» (seg000:786, K): скелета не берёт. Вызывать только
|
||||||
|
* при включённых читах — проверку делает вызывающий. */
|
||||||
|
void pop_guard_kill(void);
|
||||||
|
|
||||||
|
/* do_delta_hp (seg003): применить дельту HP, накопленную за кадр. Звать
|
||||||
|
* раз в кадр ПОСЛЕ тиков персонажей (в оригинале — в play_frame). */
|
||||||
|
void pop_do_delta_hp(void);
|
||||||
|
|
||||||
|
/* Тик логики стража — В БАНКЕ (guards.c). Состояние берёт отсюда. */
|
||||||
|
void pop_guard_tick(void) __banked;
|
||||||
|
|
||||||
|
#endif
|
||||||
+332
-106
@@ -10,57 +10,117 @@
|
|||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
|
#include "pop_guard.h" /* Guard/pop_gframe/CHARID_* — интерпретатор общий */
|
||||||
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
|
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
|
||||||
#include "pop_map.h" /* pop_clip_char_top (clip_char) */
|
#include "pop_map.h" /* pop_clip_char_top (clip_char) */
|
||||||
|
#include "pop_geom.h"
|
||||||
#include "kid_data.h"
|
#include "kid_data.h"
|
||||||
|
|
||||||
kid_t Kid;
|
pop_char_t Kid;
|
||||||
|
|
||||||
/* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN
|
/* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN
|
||||||
* в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */
|
* в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */
|
||||||
int8_t knock;
|
int8_t knock;
|
||||||
|
|
||||||
/* Координатные таблицы (data.h). */
|
|
||||||
static const uint8_t x_bump[20] = {
|
|
||||||
(uint8_t)-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
|
|
||||||
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
|
|
||||||
};
|
|
||||||
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
|
|
||||||
#define FIRST_ONSCREEN_COLUMN 5
|
|
||||||
#define TILE_SIZEX 14
|
|
||||||
|
|
||||||
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */
|
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */
|
||||||
#define KID_MAXPAGES 28
|
#define KID_MAXPAGES 28
|
||||||
static atlas_t kidp[KID_MAXPAGES];
|
static atlas_t kidp[KID_MAXPAGES];
|
||||||
static uint8_t kid_npages;
|
static uint8_t kid_npages;
|
||||||
|
|
||||||
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): отдельный
|
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): клинок —
|
||||||
* спрайт клинка поверх Kid в кадрах «нашёл меч»/убирание в ножны (229..237).
|
* ОТДЕЛЬНЫЙ спрайт поверх персонажа, один и тот же атлас у Кида и у стража.
|
||||||
* Из sword_tbl взяты только строки 35..42 — те, что адресуют эти кадры (бой
|
* Таблица sword_tbl (kid_data.h, генерится из seg006) индексируется
|
||||||
* не портирован); индекс в атласе = sword_frame − 35. Смещения таблицы —
|
* sword_frame = кадр.sword & 0x3F; id 255 = клинка в этом кадре нет.
|
||||||
* в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ calc_screen_x_coord. */
|
* Смещения — в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ
|
||||||
#define SWORD_SF_LO 35
|
* calc_screen_x_coord, x — «вперёд» по направлению персонажа. */
|
||||||
#define SWORD_SF_HI 42
|
static const uint8_t SWORD_ID[KID_NSWORD] = KID_SWORD_ID_INIT;
|
||||||
static const int8_t SWORD_DX[8] = { 7, 14, 14, 14, 28, 28, 21, 14 };
|
static const int8_t SWORD_DX[KID_NSWORD] = KID_SWORD_DX_INIT;
|
||||||
static const int8_t SWORD_DY[8] = {-32,-32,-31,-29,-28,-28,-25,-22 };
|
static const int8_t SWORD_DY[KID_NSWORD] = KID_SWORD_DY_INIT;
|
||||||
static atlas_t swordp;
|
static atlas_t swordp;
|
||||||
static uint8_t sword_ok;
|
static uint8_t sword_ok;
|
||||||
|
|
||||||
int pop_sword_load(void)
|
int pop_sword_load(void)
|
||||||
{
|
{
|
||||||
if (atlas_load(&swordp, "sword.atl") != 0) return -1;
|
if (atlas_load(&swordp, "KID\\sword.atl") != 0) return -1;
|
||||||
sword_ok = 1;
|
sword_ok = 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в
|
||||||
|
* кадрах подъёма/ножен, ИЛИ держит меч, ИЛИ это ЖИВОЙ страж (у стража меч
|
||||||
|
* в руке всегда, даже пока sword ещё sheathed). Страницу W0 оставляем
|
||||||
|
* замапленной — вызывающий делает gfx_w0_unmap сам. */
|
||||||
|
uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
||||||
|
int obj_x, int obj_y,
|
||||||
|
int *rx, int *ry, uint16_t *rw, uint16_t *rh)
|
||||||
|
{
|
||||||
|
uint8_t sf, id, flip;
|
||||||
|
const uint8_t *simg;
|
||||||
|
uint16_t w, h;
|
||||||
|
int bx, top;
|
||||||
|
|
||||||
|
if (!sword_ok) return 0;
|
||||||
|
if (!((ch->frame >= 229 && ch->frame < 238) ||
|
||||||
|
ch->sword != 0 ||
|
||||||
|
(ch->charid == CHARID_2_GUARD && ch->alive < 0)))
|
||||||
|
return 0;
|
||||||
|
sf = (uint8_t)(fr->sword & 0x3F);
|
||||||
|
if (sf == 0 || sf >= KID_NSWORD) return 0;
|
||||||
|
id = SWORD_ID[sf];
|
||||||
|
if (id == 0xFF) return 0;
|
||||||
|
|
||||||
|
simg = (const uint8_t *)atlas_image(&swordp, id);
|
||||||
|
gfx_w0_map(swordp.page);
|
||||||
|
w = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8));
|
||||||
|
h = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8));
|
||||||
|
if (!w || !h) return 0;
|
||||||
|
|
||||||
|
flip = (uint8_t)(ch->direction >= 0);
|
||||||
|
bx = obj_x + ((ch->direction < 0) ? -(int)SWORD_DX[sf] : (int)SWORD_DX[sf]);
|
||||||
|
top = obj_y + (int)SWORD_DY[sf] - (int)h + 1;
|
||||||
|
if (flip) bx -= (int)w;
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
|
||||||
|
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
|
||||||
|
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||||||
|
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip, 0, 0);
|
||||||
|
else
|
||||||
|
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, 0, 0);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
*rx = bx; *ry = top; *rw = w; *rh = h;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
|
||||||
|
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
|
||||||
|
* Кида, а держать вторую копию раскладки в pop_gdraw ни к чему. */
|
||||||
|
void pop_kid_img_blit(uint8_t image, int x, int y)
|
||||||
|
{
|
||||||
|
uint8_t pg = (uint8_t)(image >> 3); /* раскладка атласов: 8 спрайтов/стр. */
|
||||||
|
uint8_t idx = (uint8_t)(image & 7);
|
||||||
|
const uint8_t *img;
|
||||||
|
uint16_t w, h;
|
||||||
|
if (pg >= kid_npages) return;
|
||||||
|
img = (const uint8_t *)atlas_image(&kidp[pg], idx);
|
||||||
|
gfx_w0_map(kidp[pg].page);
|
||||||
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
|
/* Атласы Кида хранятся COLUMN-MAJOR (transpose_cols в pop_pack_kid —
|
||||||
|
* ради бесплатного горизонтального флипа), поэтому блит обязан быть
|
||||||
|
* колоночным. Row-major gfx_blit_noclip выдал бы марку транспонированной. */
|
||||||
|
if (w && h) gfx_blit_cols_part_noclip(x, y, img, 0, 0, 0);
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
int pop_kid_load(uint8_t npages)
|
int pop_kid_load(uint8_t npages)
|
||||||
{
|
{
|
||||||
static const char *const names[KID_MAXPAGES] = {
|
static const char *const names[KID_MAXPAGES] = {
|
||||||
"kid0.atl","kid1.atl","kid2.atl","kid3.atl","kid4.atl","kid5.atl",
|
"KID\\kid0.atl","KID\\kid1.atl","KID\\kid2.atl","KID\\kid3.atl","KID\\kid4.atl","KID\\kid5.atl",
|
||||||
"kid6.atl","kid7.atl","kid8.atl","kid9.atl","kid10.atl","kid11.atl",
|
"KID\\kid6.atl","KID\\kid7.atl","KID\\kid8.atl","KID\\kid9.atl","KID\\kid10.atl","KID\\kid11.atl",
|
||||||
"kid12.atl","kid13.atl","kid14.atl","kid15.atl","kid16.atl","kid17.atl",
|
"KID\\kid12.atl","KID\\kid13.atl","KID\\kid14.atl","KID\\kid15.atl","KID\\kid16.atl","KID\\kid17.atl",
|
||||||
"kid18.atl","kid19.atl","kid20.atl","kid21.atl","kid22.atl","kid23.atl",
|
"KID\\kid18.atl","KID\\kid19.atl","KID\\kid20.atl","KID\\kid21.atl","KID\\kid22.atl","KID\\kid23.atl",
|
||||||
"kid24.atl","kid25.atl","kid26.atl","kid27.atl"
|
"KID\\kid24.atl","KID\\kid25.atl","KID\\kid26.atl","KID\\kid27.atl"
|
||||||
};
|
};
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
||||||
@@ -105,6 +165,16 @@ static const uint8_t *kd_frame_ptr(uint8_t i)
|
|||||||
off += (uint16_t)(KD_DATA_OFF + KID_BIN_FRAMES_OFF);
|
off += (uint16_t)(KD_DATA_OFF + KID_BIN_FRAMES_OFF);
|
||||||
return (const uint8_t *)off;
|
return (const uint8_t *)off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* То же для таблицы кадров СТРАЖА (frame_tbl_guard дописана в ту же
|
||||||
|
* страницу данных, см. pop_extract_kid_data.py). */
|
||||||
|
static const uint8_t *kd_gframe_ptr(uint8_t i)
|
||||||
|
{
|
||||||
|
uint16_t off = (uint16_t)i;
|
||||||
|
off += (uint16_t)(off << 2);
|
||||||
|
off += (uint16_t)(KD_DATA_OFF + KID_BIN_GFRAMES_OFF);
|
||||||
|
return (const uint8_t *)off;
|
||||||
|
}
|
||||||
#define KD_SEQ(cs) (*((const uint8_t *)(KD_DATA_OFF + KID_BIN_SEQTBL_OFF) + \
|
#define KD_SEQ(cs) (*((const uint8_t *)(KD_DATA_OFF + KID_BIN_SEQTBL_OFF) + \
|
||||||
(uint16_t)((cs) - SEQTBL_BASE)))
|
(uint16_t)((cs) - SEQTBL_BASE)))
|
||||||
/* Байт seqtbl по адресу оригинала (база SEQTBL_BASE); ТОЛЬКО под замапленной
|
/* Байт seqtbl по адресу оригинала (база SEQTBL_BASE); ТОЛЬКО под замапленной
|
||||||
@@ -113,8 +183,76 @@ static const uint8_t *kd_frame_ptr(uint8_t i)
|
|||||||
|
|
||||||
static uint8_t kdat_blk, kdat_page, kdat_ok;
|
static uint8_t kdat_blk, kdat_page, kdat_ok;
|
||||||
|
|
||||||
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы. */
|
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы.
|
||||||
|
* Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на
|
||||||
|
* выходе из play_seq его надо сохранить в кэш своего персонажа: страж
|
||||||
|
* тикает ПОСЛЕ Кида, и без разделения kid_draw взял бы кадр стража. */
|
||||||
static kframe cur_frame;
|
static kframe cur_frame;
|
||||||
|
static kframe kid_frame; /* кадр Kid для kid_draw */
|
||||||
|
|
||||||
|
/* Char (seg006): АКТИВНЫЙ персонаж. Интерпретатор последовательностей в
|
||||||
|
* оригинале работает не с Kid и не с Guard, а с этой копией, а вокруг
|
||||||
|
* стоят loadkid/savekid и loadshad/saveshad (seg006:809..825). Копия, а
|
||||||
|
* не указатель, — и так же, как в оригинале, и быстрее на Z80: обращения
|
||||||
|
* в горячем цикле идут абсолютной адресацией глобала, а 16-байтовое
|
||||||
|
* копирование платится один раз на переключение персонажа. */
|
||||||
|
pop_char_t Char;
|
||||||
|
|
||||||
|
/* Opp (seg006): СОПЕРНИК активного персонажа — вторая копия, которую ставят
|
||||||
|
* рядом с Char (loadkid_and_opp / loadshad_and_opp, seg006:809). Через неё
|
||||||
|
* ИИ и боёвка меряют дистанцию (char_opp_dist), не зная, кто именно перед
|
||||||
|
* ними: для стража Opp = Kid, для Кида Opp = Guard. */
|
||||||
|
pop_char_t Opp;
|
||||||
|
|
||||||
|
/* Снимок последовательности на момент входа в окно Char: по нему
|
||||||
|
* pop_savekid_state отличает «последовательность сменил ДИСПЕТЧЕР (в Char)»
|
||||||
|
* от «её сменил pop_map (прямо в Kid)». */
|
||||||
|
static uint16_t kid_seq_at_load;
|
||||||
|
|
||||||
|
void pop_loadkid(void) { Char = Kid; kid_seq_at_load = Kid.curr_seq; }
|
||||||
|
void pop_savekid(void) { Kid = Char; }
|
||||||
|
/* Выгрузка после control(): копируем ТОЛЬКО то, что диспетчер реально
|
||||||
|
* меняет у активного персонажа — последовательность и состояние меча.
|
||||||
|
*
|
||||||
|
* Почему не `Kid = Char`, как делает savekid оригинала: у нас часть
|
||||||
|
* действий control() исполняет pop_map (pop_down_action, pop_jump_up_seq,
|
||||||
|
* safe_step...), и он пишет ПРЯМО в Kid — там ещё не портирован переход на
|
||||||
|
* Char. Полное копирование затирало эти правки: Kid терял выравнивание x
|
||||||
|
* и ряд, и спуск с уступа через вис не срабатывал — персонаж просто
|
||||||
|
* приседал. Когда pop_map переведём на Char, здесь вернётся Kid = Char. */
|
||||||
|
void pop_savekid_state(void)
|
||||||
|
{
|
||||||
|
/* Последовательность мог сменить КАЖДЫЙ из двоих: диспетчер — в Char
|
||||||
|
* (pop_char_set_seq), pop_map — прямо в Kid (kid_set_seq: спуск с
|
||||||
|
* уступа SEQ_68, зацеп, осторожный шаг). Решение pop_map старше:
|
||||||
|
* диспетчер про эти переходы не знает и, копируя Char вслепую, затирал
|
||||||
|
* их — спуск в проём не срабатывал, а персонаж даже не приседал, потому
|
||||||
|
* что pop_down_action уже вернул «обработано». */
|
||||||
|
uint8_t map_changed = (uint8_t)(Kid.curr_seq != kid_seq_at_load);
|
||||||
|
if (!map_changed) Kid.curr_seq = Char.curr_seq;
|
||||||
|
Kid.sword = Char.sword;
|
||||||
|
}
|
||||||
|
void pop_loadshad(void) { Char = Guard; }
|
||||||
|
void pop_saveshad(void) { Guard = Char; }
|
||||||
|
void pop_loadshad_and_opp(void) { Char = Guard; Opp = Kid; }
|
||||||
|
void pop_saveshad_and_opp(void) { Guard = Char; Kid = Opp; }
|
||||||
|
void pop_loadkid_and_opp(void) { Char = Kid; Opp = Guard; }
|
||||||
|
void pop_savekid_and_opp(void) { Kid = Char; Guard = Opp; }
|
||||||
|
|
||||||
|
/* char_opp_dist (seg006:2135): >0 — соперник ПЕРЕД Char, <0 — за спиной.
|
||||||
|
* +13 при встречных направлениях: у стоящих лицом друг к другу «ноль»
|
||||||
|
* приходится на ширину тайла между ними. Живёт здесь, а не в guards.c,
|
||||||
|
* потому что нужен по обе стороны банковой границы — и ИИ, и диспетчеру
|
||||||
|
* боёвки (pop_ctrl). */
|
||||||
|
int16_t pop_char_opp_dist(void)
|
||||||
|
{
|
||||||
|
int16_t distance;
|
||||||
|
if (Char.room != Opp.room) return 999;
|
||||||
|
distance = (int16_t)Opp.x - (int16_t)Char.x;
|
||||||
|
if (Char.direction < 0) distance = -distance;
|
||||||
|
if (distance >= 0 && Char.direction != Opp.direction) distance += 13;
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
int pop_kid_data_load(const char *path)
|
int pop_kid_data_load(const char *path)
|
||||||
{
|
{
|
||||||
@@ -152,22 +290,60 @@ void pop_kid_data_free(void)
|
|||||||
if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; }
|
if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Прочитать кадр из ЛЮБОЙ таблицы страницы данных (у Kid и у стража они
|
||||||
|
* разные: KID_BIN_FRAMES_OFF / KID_BIN_GFRAMES_OFF). Сама маппит страницу,
|
||||||
|
* поэтому годится для вызова извне (pop_guard) — но НЕ в горячем цикле. */
|
||||||
|
void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5)
|
||||||
|
{
|
||||||
|
const uint8_t *f;
|
||||||
|
uint16_t off = (uint16_t)idx;
|
||||||
|
if (!kdat_ok) { out5[0] = 255; return; }
|
||||||
|
off += (uint16_t)(off << 2); /* idx*5, без умножения */
|
||||||
|
off += (uint16_t)(KD_DATA_OFF + tbl_off);
|
||||||
|
gfx_w0_map(kdat_page);
|
||||||
|
f = (const uint8_t *)off;
|
||||||
|
out5[0] = f[0]; out5[1] = f[1]; out5[2] = f[2];
|
||||||
|
out5[3] = f[3]; out5[4] = f[4];
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
/* load_frame (seg006:0293): скопировать кадр Kid.frame из таблицы в
|
/* load_frame (seg006:0293): скопировать кадр Kid.frame из таблицы в
|
||||||
* cur_frame. Звать ТОЛЬКО когда страница данных замаплена в W0. */
|
* cur_frame. Звать ТОЛЬКО когда страница данных замаплена в W0. */
|
||||||
static void load_frame(void)
|
static void load_frame(void)
|
||||||
{
|
{
|
||||||
const uint8_t *f = kd_frame_ptr(Kid.frame);
|
/* Таблица кадров зависит от персонажа (seg006:0293): у Kid своя, у
|
||||||
|
* стража frame_tbl_guard с индексом frame + add_frame − 149, где
|
||||||
|
* add_frame = 70 для кадров 102..106 (seg006:535). */
|
||||||
|
const uint8_t *f;
|
||||||
|
if (Char.charid == CHARID_2_GUARD) {
|
||||||
|
int16_t idx = (int16_t)Char.frame;
|
||||||
|
if (idx >= 102 && idx < 107) idx += 70;
|
||||||
|
idx -= 149;
|
||||||
|
if (idx < 0 || idx >= KID_NGFRAMES) { cur_frame.image = 255; return; }
|
||||||
|
f = kd_gframe_ptr((uint8_t)idx);
|
||||||
|
} else {
|
||||||
|
f = kd_frame_ptr(Char.frame);
|
||||||
|
}
|
||||||
cur_frame.image = f[0];
|
cur_frame.image = f[0];
|
||||||
cur_frame.dx = (int8_t)f[1];
|
cur_frame.dx = (int8_t)f[1];
|
||||||
cur_frame.dy = (int8_t)f[2];
|
cur_frame.dy = (int8_t)f[2];
|
||||||
cur_frame.flags = f[3];
|
cur_frame.flags = f[3];
|
||||||
cur_frame.sword = f[4];
|
cur_frame.sword = f[4];
|
||||||
|
/* Кэш кадра для ОТРИСОВКИ раскладываем ЗДЕСЬ, по charid активного
|
||||||
|
* персонажа, а не в save*-функциях. Раньше кэш писали pop_savekid/
|
||||||
|
* pop_saveshad, и любое окно Char БЕЗ play_seq (окна боёвки
|
||||||
|
* check_sword_hurting / check_sword_hurt) записывало Киду кадр, который
|
||||||
|
* на самом деле принадлежал СТРАЖУ: kid_draw брал его image из атласа
|
||||||
|
* Кида и рисовал произвольную позу. Теперь владелец один — тот, для
|
||||||
|
* кого кадр реально загружен. */
|
||||||
|
if (Char.charid == CHARID_0_KID) kid_frame = cur_frame;
|
||||||
|
else pop_gframe = cur_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t char_dx_forward(int8_t dx)
|
static uint8_t char_dx_forward(int8_t dx)
|
||||||
{
|
{
|
||||||
if (Kid.direction < 0) dx = (int8_t)-dx;
|
if (Char.direction < 0) dx = (int8_t)-dx;
|
||||||
return (uint8_t)(Kid.x + dx);
|
return (uint8_t)(Char.x + dx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Аксессоры к текущему кадру (для pop_map): читают cur_frame, НЕ таблицу —
|
/* Аксессоры к текущему кадру (для pop_map): читают cur_frame, НЕ таблицу —
|
||||||
@@ -179,6 +355,11 @@ uint8_t kid_cur_flags(void) { return cur_frame.flags; }
|
|||||||
* pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */
|
* pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */
|
||||||
void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; }
|
void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; }
|
||||||
|
|
||||||
|
/* То же для АКТИВНОГО персонажа — внутри окна Char (порт
|
||||||
|
* seqtbl_offset_char, seg006). Нужен стражу: его последовательность
|
||||||
|
* ставится, когда Char уже загружен из Guard. */
|
||||||
|
void pop_char_set_seq(uint8_t seq_id) { Char.curr_seq = kid_seq_off[seq_id]; }
|
||||||
|
|
||||||
/* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0).
|
/* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0).
|
||||||
* Страница данных маппится в W0 на ВЕСЬ разбор — один map/unmap за вызов
|
* Страница данных маппится в W0 на ВЕСЬ разбор — один map/unmap за вызов
|
||||||
* (в тике, до отрисовки: конфликта с атласом в W0 нет). В конце —
|
* (в тике, до отрисовки: конфликта с атласом в W0 нет). В конце —
|
||||||
@@ -188,42 +369,42 @@ void play_seq(void)
|
|||||||
if (!kdat_ok) return;
|
if (!kdat_ok) return;
|
||||||
gfx_w0_map(kdat_page);
|
gfx_w0_map(kdat_page);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t cmd = SEQ(Kid.curr_seq);
|
uint8_t cmd = SEQ(Char.curr_seq);
|
||||||
Kid.curr_seq++;
|
Char.curr_seq++;
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case 0xFB: /* DX */
|
case 0xFB: /* DX */
|
||||||
Kid.x = char_dx_forward((int8_t)SEQ(Kid.curr_seq));
|
Char.x = char_dx_forward((int8_t)SEQ(Char.curr_seq));
|
||||||
Kid.curr_seq++;
|
Char.curr_seq++;
|
||||||
break;
|
break;
|
||||||
case 0xFA: /* DY */
|
case 0xFA: /* DY */
|
||||||
Kid.y = (uint8_t)(Kid.y + (int8_t)SEQ(Kid.curr_seq));
|
Char.y = (uint8_t)(Char.y + (int8_t)SEQ(Char.curr_seq));
|
||||||
Kid.curr_seq++;
|
Char.curr_seq++;
|
||||||
break;
|
break;
|
||||||
case 0xFE: /* FLIP */
|
case 0xFE: /* FLIP */
|
||||||
Kid.direction = (int8_t)~Kid.direction;
|
Char.direction = (int8_t)~Char.direction;
|
||||||
break;
|
break;
|
||||||
case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */
|
case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */
|
||||||
Kid.curr_seq += 2;
|
Char.curr_seq += 2;
|
||||||
break;
|
break;
|
||||||
case 0xFF: { /* JMP */
|
case 0xFF: { /* JMP */
|
||||||
uint16_t a = (uint16_t)(SEQ(Kid.curr_seq) |
|
uint16_t a = (uint16_t)(SEQ(Char.curr_seq) |
|
||||||
((uint16_t)SEQ((uint16_t)(Kid.curr_seq + 1)) << 8));
|
((uint16_t)SEQ((uint16_t)(Char.curr_seq + 1)) << 8));
|
||||||
Kid.curr_seq = a;
|
Char.curr_seq = a;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0xFD: /* UP */
|
case 0xFD: /* UP */
|
||||||
Kid.curr_row--;
|
Char.curr_row--;
|
||||||
break;
|
break;
|
||||||
case 0xFC: /* DOWN */
|
case 0xFC: /* DOWN */
|
||||||
Kid.curr_row++;
|
Char.curr_row++;
|
||||||
break;
|
break;
|
||||||
case 0xF9: /* ACTION */
|
case 0xF9: /* ACTION */
|
||||||
Kid.action = SEQ(Kid.curr_seq);
|
Char.action = SEQ(Char.curr_seq);
|
||||||
Kid.curr_seq++;
|
Char.curr_seq++;
|
||||||
break;
|
break;
|
||||||
case 0xF8: /* SET_FALL */
|
case 0xF8: /* SET_FALL */
|
||||||
Kid.fall_x = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++;
|
Char.fall_x = (int8_t)SEQ(Char.curr_seq); Char.curr_seq++;
|
||||||
Kid.fall_y = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++;
|
Char.fall_y = (int8_t)SEQ(Char.curr_seq); Char.curr_seq++;
|
||||||
break;
|
break;
|
||||||
case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */
|
case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */
|
||||||
knock = 1;
|
knock = 1;
|
||||||
@@ -232,20 +413,20 @@ void play_seq(void)
|
|||||||
knock = -1;
|
knock = -1;
|
||||||
break;
|
break;
|
||||||
case 0xF2: /* SOUND — потребить 1 байт */
|
case 0xF2: /* SOUND — потребить 1 байт */
|
||||||
Kid.curr_seq++;
|
Char.curr_seq++;
|
||||||
break;
|
break;
|
||||||
case 0xF1: /* END_LEVEL */
|
case 0xF1: /* END_LEVEL */
|
||||||
break;
|
break;
|
||||||
case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета
|
case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета
|
||||||
* (порт play_seq -> proc_get_object, seg006) */
|
* (порт play_seq -> proc_get_object, seg006) */
|
||||||
if (SEQ(Kid.curr_seq) == 1)
|
if (SEQ(Char.curr_seq) == 1)
|
||||||
pop_proc_get_object();
|
pop_proc_get_object();
|
||||||
Kid.curr_seq++;
|
Char.curr_seq++;
|
||||||
break;
|
break;
|
||||||
case 0xF6: /* DIE nop */
|
case 0xF6: /* DIE nop */
|
||||||
break;
|
break;
|
||||||
default: /* КАДР */
|
default: /* КАДР */
|
||||||
Kid.frame = cmd;
|
Char.frame = cmd;
|
||||||
load_frame(); /* cur_frame := таблица[frame] (страница ещё в W0) */
|
load_frame(); /* cur_frame := таблица[frame] (страница ещё в W0) */
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
return;
|
return;
|
||||||
@@ -253,22 +434,30 @@ void play_seq(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Прокрутить последовательность КОНКРЕТНОГО персонажа (порт связки
|
||||||
|
* loadkid/play_seq/savekid, seg006). Звать вместо голого play_seq: тот
|
||||||
|
* работает с Char и сам по себе не знает, чей это персонаж. */
|
||||||
|
void pop_kid_play(void) { pop_loadkid(); play_seq(); pop_savekid(); }
|
||||||
|
void pop_guard_play(void) { pop_loadshad(); play_seq(); pop_saveshad(); }
|
||||||
|
|
||||||
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir)
|
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir)
|
||||||
{
|
{
|
||||||
Kid.curr_col = col;
|
Kid.curr_col = col;
|
||||||
Kid.curr_row = row;
|
Kid.curr_row = row;
|
||||||
Kid.direction = dir;
|
Kid.direction = dir;
|
||||||
Kid.x = (uint8_t)(x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
Kid.x = (uint8_t)(pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
||||||
Kid.y = (uint8_t)y_land[row + 1];
|
Kid.y = (uint8_t)pop_y_land[row + 1];
|
||||||
Kid.fall_x = Kid.fall_y = 0;
|
Kid.fall_x = Kid.fall_y = 0;
|
||||||
Kid.repeat = 0;
|
Kid.repeat = 0;
|
||||||
|
Kid.charid = 0; /* charid_0_kid: своя таблица кадров */
|
||||||
|
Kid.alive = -1; /* в оригинале ЖИВОЙ = alive < 0 */
|
||||||
Kid.curr_seq = kid_seq_off[seq_id];
|
Kid.curr_seq = kid_seq_off[seq_id];
|
||||||
play_seq(); /* до первого кадра */
|
pop_kid_play(); /* до первого кадра */
|
||||||
}
|
}
|
||||||
|
|
||||||
void kid_tick(void)
|
void kid_tick(void)
|
||||||
{
|
{
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy
|
/* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy
|
||||||
@@ -298,6 +487,64 @@ static int kid_lx[2], kid_ly[2];
|
|||||||
static uint16_t kid_lw[2], kid_lh[2];
|
static uint16_t kid_lw[2], kid_lh[2];
|
||||||
static uint8_t kid_lvalid[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):
|
/* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg):
|
||||||
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
|
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
|
||||||
static int kid_fp_x, kid_fp_y;
|
static int kid_fp_x, kid_fp_y;
|
||||||
@@ -341,23 +588,18 @@ void kid_draw_splash(void)
|
|||||||
int top = obj_y - (int)h + 1;
|
int top = obj_y - (int)h + 1;
|
||||||
if (flip) bx -= (int)w;
|
if (flip) bx -= (int)w;
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
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_blit_cols(bx, top + POP_YOFF, img, flip);
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
|
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
|
||||||
/* Прямоугольник splash расширяет область heal ЭТОЙ страницы: иначе
|
/* Брызги — накладной спрайт: свой прямоугольник heal (см.
|
||||||
* брызги останутся на экране после кадра. */
|
* kid_overlay_add), окно fore пересчитываем объединением. */
|
||||||
{
|
{
|
||||||
uint8_t dp = gfx_get_draw_page();
|
uint8_t dp = gfx_get_draw_page();
|
||||||
if (kid_lvalid[dp]) {
|
kid_overlay_add(dp, bx, top, w, h);
|
||||||
int x0 = kid_lx[dp] < bx ? kid_lx[dp] : bx;
|
kid_fore_clip(dp);
|
||||||
int y0 = kid_ly[dp] < top ? kid_ly[dp] : top;
|
|
||||||
int x1 = kid_lx[dp] + (int)kid_lw[dp];
|
|
||||||
int y1 = kid_ly[dp] + (int)kid_lh[dp];
|
|
||||||
if (bx + (int)w > x1) x1 = bx + (int)w;
|
|
||||||
if (top + (int)h > y1) y1 = top + (int)h;
|
|
||||||
kid_lx[dp] = x0; kid_ly[dp] = y0;
|
|
||||||
kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
@@ -369,17 +611,23 @@ void kid_heal(void)
|
|||||||
uint8_t p = gfx_get_draw_page();
|
uint8_t p = gfx_get_draw_page();
|
||||||
if (kid_lvalid[p])
|
if (kid_lvalid[p])
|
||||||
gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */
|
gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */
|
||||||
kid_lvalid[p] = 0;
|
if (kid_ovalid[p])
|
||||||
|
gfx_heal(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)
|
void kid_draw(void)
|
||||||
{
|
{
|
||||||
kframe fr = cur_frame; /* load_frame уже положил кадр (play_seq) */
|
kframe fr = kid_frame; /* кадр Kid, сохранённый savekid */
|
||||||
int fwd, obj_x, obj_y, top;
|
int fwd, obj_x, obj_y, top;
|
||||||
uint8_t page, idx;
|
uint8_t page, idx;
|
||||||
const uint8_t *img;
|
const uint8_t *img;
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
|
|
||||||
|
/* Окно fore-слоя по умолчанию пустое: на любом раннем выходе (нет
|
||||||
|
* кадра / страница не загружена) перекрывать нечего, и fore-проход
|
||||||
|
* не должен трогать экран. */
|
||||||
|
pop_fore_set_clip(0, 0, 0, 0);
|
||||||
if (fr.image == 255)
|
if (fr.image == 255)
|
||||||
return; /* пустой кадр */
|
return; /* пустой кадр */
|
||||||
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
|
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
|
||||||
@@ -424,8 +672,14 @@ void kid_draw(void)
|
|||||||
if (skip >= (int)h) skip = (int)h; /* обрезан целиком */
|
if (skip >= (int)h) skip = (int)h; /* обрезан целиком */
|
||||||
}
|
}
|
||||||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||||||
if (skip < (int)h) /* +YOFF: центрирование */
|
if (skip < (int)h) { /* +YOFF: центрирование */
|
||||||
|
if (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(bx, top + skip + POP_YOFF, img, flip, skip, 0);
|
gfx_blit_cols_part(bx, top + skip + POP_YOFF, img, flip, skip, 0);
|
||||||
|
}
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
|
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
|
||||||
* узко, только по прямоугольнику Кида и только при выходе. */
|
* узко, только по прямоугольнику Кида и только при выходе. */
|
||||||
@@ -438,44 +692,16 @@ void kid_draw(void)
|
|||||||
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
||||||
}
|
}
|
||||||
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
||||||
* Kid. Прямоугольник heal расширяем объединением — иначе клинок, выйдя
|
* Kid — со СВОИМ прямоугольником heal (kid_overlay_add). */
|
||||||
* за габарит Kid, оставит след. */
|
{
|
||||||
if (sword_ok && Kid.frame >= 229 && Kid.frame < 238) {
|
int sx, sy; uint16_t sw, sh;
|
||||||
uint8_t sf = (uint8_t)(fr.sword & 0x3F);
|
if (pop_sword_draw(&Kid, &fr, obj_x, obj_y, &sx, &sy, &sw, &sh))
|
||||||
if (sf >= SWORD_SF_LO && sf <= SWORD_SF_HI) {
|
kid_overlay_add(gfx_get_draw_page() & 1, sx, sy, sw, sh);
|
||||||
uint8_t si = (uint8_t)(sf - SWORD_SF_LO);
|
|
||||||
const uint8_t *simg = (const uint8_t *)atlas_image(&swordp, si);
|
|
||||||
uint16_t sw, sh;
|
|
||||||
gfx_w0_map(swordp.page);
|
|
||||||
sw = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8));
|
|
||||||
sh = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8));
|
|
||||||
if (sw && sh) {
|
|
||||||
uint8_t sflip = (Kid.direction >= 0);
|
|
||||||
int sbx = obj_x + ((Kid.direction < 0) ? -(int)SWORD_DX[si]
|
|
||||||
: (int)SWORD_DX[si]);
|
|
||||||
int stop = obj_y + (int)SWORD_DY[si] - (int)sh + 1;
|
|
||||||
uint8_t dp = gfx_get_draw_page() & 1;
|
|
||||||
if (sflip) sbx -= (int)sw;
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
gfx_blit_cols_part(sbx, stop + POP_YOFF, simg, sflip, 0, 0);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
if (!kid_lvalid[dp]) {
|
|
||||||
kid_lx[dp] = sbx; kid_ly[dp] = stop;
|
|
||||||
kid_lw[dp] = sw; kid_lh[dp] = sh;
|
|
||||||
kid_lvalid[dp] = 1;
|
|
||||||
} else {
|
|
||||||
int x0 = kid_lx[dp], y0 = kid_ly[dp];
|
|
||||||
int x1 = x0 + (int)kid_lw[dp], y1 = y0 + (int)kid_lh[dp];
|
|
||||||
if (sbx < x0) x0 = sbx;
|
|
||||||
if (stop < y0) y0 = stop;
|
|
||||||
if (sbx + (int)sw > x1) x1 = sbx + (int)sw;
|
|
||||||
if (stop + (int)sh > y1) y1 = stop + (int)sh;
|
|
||||||
kid_lx[dp] = x0; kid_ly[dp] = y0;
|
|
||||||
kid_lw[dp] = (uint16_t)(x1 - x0);
|
|
||||||
kid_lh[dp] = (uint16_t)(y1 - y0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
|
/* Окно fore-слоя = объединение «Kid + клинок» (с обрезкой clip_char):
|
||||||
|
* fore-проход вернёт куски тайлов ТОЛЬКО поверх него, а не целыми
|
||||||
|
* тайлами — см. pop_bg.c, fclip_on. Ставим и когда рисовать нечего:
|
||||||
|
* нулевое окно = fore-проход не трогает экран, что и правильно. */
|
||||||
|
kid_fore_clip(gfx_get_draw_page() & 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,11 @@
|
|||||||
#define POP_KID_H
|
#define POP_KID_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "pop_char.h"
|
||||||
|
#include "kid_data.h" /* kframe — тип кадра для pop_sword_draw */
|
||||||
|
|
||||||
/* Состояние персонажа (подмножество char_type, types.h). */
|
/* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */
|
||||||
typedef struct {
|
extern pop_char_t Kid;
|
||||||
uint8_t frame;
|
|
||||||
uint8_t x, y;
|
|
||||||
int8_t direction; /* -1 влево, 0 вправо */
|
|
||||||
int8_t curr_col, curr_row;
|
|
||||||
uint8_t action;
|
|
||||||
int8_t fall_x, fall_y;
|
|
||||||
uint8_t repeat;
|
|
||||||
uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */
|
|
||||||
} kid_t;
|
|
||||||
|
|
||||||
extern kid_t Kid;
|
|
||||||
|
|
||||||
/* Флаг сотрясения (seg006 knock): +1 = KNOCK_UP (ряд выше), -1 = KNOCK_DOWN
|
/* Флаг сотрясения (seg006 knock): +1 = KNOCK_UP (ряд выше), -1 = KNOCK_DOWN
|
||||||
* (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */
|
* (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */
|
||||||
@@ -39,6 +30,11 @@ int pop_kid_load(uint8_t npages);
|
|||||||
* первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт).
|
* первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт).
|
||||||
* Страница маппится в W0 внутри play_seq, раз за тик. */
|
* Страница маппится в W0 внутри play_seq, раз за тик. */
|
||||||
int pop_kid_data_load(const char *path);
|
int pop_kid_data_load(const char *path);
|
||||||
|
|
||||||
|
/* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF
|
||||||
|
* (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard).
|
||||||
|
* Маппит страницу сама; для горячего цикла есть cur_frame. */
|
||||||
|
void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5);
|
||||||
void pop_kid_data_free(void);
|
void pop_kid_data_free(void);
|
||||||
|
|
||||||
/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах
|
/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах
|
||||||
@@ -48,6 +44,15 @@ void pop_kid_data_free(void);
|
|||||||
int pop_sword_load(void);
|
int pop_sword_load(void);
|
||||||
void pop_kid_free(void);
|
void pop_kid_free(void);
|
||||||
|
|
||||||
|
/* Нарисовать клинок chtab_0 поверх персонажа (порт add_sword_to_objtable,
|
||||||
|
* seg006:1798) — общая точка для Кида и стража: меч в оригинале ОДИН на
|
||||||
|
* всех. obj_x/obj_y — та же опорная точка, что у спрайта персонажа.
|
||||||
|
* Возвращает 1 и прямоугольник клинка (для heal), 0 — клинка нет.
|
||||||
|
* Страницу W0 оставляет замапленной: вызывающий делает gfx_w0_unmap. */
|
||||||
|
uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
||||||
|
int obj_x, int obj_y,
|
||||||
|
int *rx, int *ry, uint16_t *rw, uint16_t *rh);
|
||||||
|
|
||||||
/* Инициализировать Kid: последовательность seq_id, клетка col/row,
|
/* Инициализировать Kid: последовательность seq_id, клетка col/row,
|
||||||
* направление dir (-1/0). Ставит x/y по x_bump/y_land. */
|
* направление dir (-1/0). Ставит x/y по x_bump/y_land. */
|
||||||
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir);
|
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir);
|
||||||
@@ -58,7 +63,34 @@ void kid_tick(void);
|
|||||||
|
|
||||||
/* play_seq напрямую (для pop_map start_fall — переход в fall-seq в тот же
|
/* play_seq напрямую (для pop_map start_fall — переход в fall-seq в тот же
|
||||||
* physics-шаг, как оригинал). */
|
* physics-шаг, как оригинал). */
|
||||||
void play_seq(void);
|
void play_seq(void); /* работает с Char — см. pop_kid_play ниже */
|
||||||
|
|
||||||
|
/* Активный персонаж (порт Char, seg006) и его загрузка/выгрузка.
|
||||||
|
* Интерпретатор последовательностей общий для Kid и стража: у них общая
|
||||||
|
* seqtbl, но РАЗНЫЕ таблицы кадров (load_frame выбирает по Char.charid). */
|
||||||
|
extern pop_char_t Char;
|
||||||
|
/* Соперник активного персонажа (порт Opp): ставится вместе с Char через
|
||||||
|
* pop_loadshad_and_opp. Читают ИИ стража и боёвка (char_opp_dist). */
|
||||||
|
extern pop_char_t Opp;
|
||||||
|
void pop_loadkid(void);
|
||||||
|
void pop_savekid(void);
|
||||||
|
void pop_savekid_state(void); /* Kid = Char, кадр не трогаем */
|
||||||
|
void pop_loadshad(void);
|
||||||
|
void pop_saveshad(void);
|
||||||
|
void pop_loadshad_and_opp(void); /* Char = Guard, Opp = Kid (seg006:841) */
|
||||||
|
void pop_saveshad_and_opp(void); /* Guard = Char, Kid = Opp */
|
||||||
|
void pop_loadkid_and_opp(void); /* Char = Kid, Opp = Guard */
|
||||||
|
void pop_savekid_and_opp(void); /* Kid = Char, Guard = Opp */
|
||||||
|
|
||||||
|
/* Дистанция до соперника в системе Char: >0 впереди, <0 за спиной,
|
||||||
|
* 999 — разные комнаты (порт char_opp_dist, seg006:2135). */
|
||||||
|
int16_t pop_char_opp_dist(void);
|
||||||
|
|
||||||
|
/* Прокрутить последовательность персонажа целиком (load + play_seq +
|
||||||
|
* save). Звать вместо голого play_seq. */
|
||||||
|
void pop_char_set_seq(uint8_t seq_id); /* seq активного персонажа */
|
||||||
|
void pop_kid_play(void);
|
||||||
|
void pop_guard_play(void);
|
||||||
|
|
||||||
/* Поля текущего кадра kid_frames[Kid.frame] (pop_map, без дублирования
|
/* Поля текущего кадра kid_frames[Kid.frame] (pop_map, без дублирования
|
||||||
* таблицы во втором TU). */
|
* таблицы во втором TU). */
|
||||||
@@ -85,6 +117,10 @@ void kid_draw(void);
|
|||||||
* звать СРАЗУ после kid_draw в кадре, когда HP убавилось (pop_kid_hurt). */
|
* звать СРАЗУ после kid_draw в кадре, когда HP убавилось (pop_kid_hurt). */
|
||||||
void kid_draw_splash(void);
|
void kid_draw_splash(void);
|
||||||
|
|
||||||
|
/* Блит спрайта chtab_2 по id, левым верхним углом в (x,y) — для полосы HP
|
||||||
|
* (id 216 полное деление / 217 пустое). */
|
||||||
|
void pop_kid_img_blit(uint8_t image, int x, int y);
|
||||||
|
|
||||||
/* Восстановить фон (из теневой копии) на месте ПРЕДЫДУЩЕГО кадра Kid —
|
/* Восстановить фон (из теневой копии) на месте ПРЕДЫДУЩЕГО кадра Kid —
|
||||||
* вместо перерисовки всего экрана. Звать ПЕРЕД kid_tick/kid_draw. */
|
* вместо перерисовки всего экрана. Звать ПЕРЕД kid_tick/kid_draw. */
|
||||||
void kid_heal(void);
|
void kid_heal(void);
|
||||||
|
|||||||
@@ -23,10 +23,22 @@ extern void _gfx_w0_isr(void);
|
|||||||
#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */
|
#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */
|
||||||
#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */
|
#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */
|
||||||
#define BP_LINKS 1952u
|
#define BP_LINKS 1952u
|
||||||
|
/* Стражи (level_type, types.h:228): по байту на комнату в каждом массиве. */
|
||||||
|
#define BP_GTILE 2119u
|
||||||
|
#define BP_GDIR 2143u
|
||||||
|
#define BP_GX 2167u
|
||||||
|
#define BP_GSEQLO 2191u
|
||||||
|
#define BP_GSKILL 2215u
|
||||||
|
#define BP_GSEQHI 2239u
|
||||||
|
#define BP_GCOLOR 2263u
|
||||||
#define BP_START 2112u
|
#define BP_START 2112u
|
||||||
#define ROOM_TILES 30u
|
#define ROOM_TILES 30u
|
||||||
#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */
|
#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */
|
||||||
|
|
||||||
|
/* fwd: инициализация живой копии стражей (тело ниже). */
|
||||||
|
static void gstate_init(void);
|
||||||
|
|
||||||
|
|
||||||
static uint8_t lvl_blk, lvl_page, lvl_ok;
|
static uint8_t lvl_blk, lvl_page, lvl_ok;
|
||||||
/* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */
|
/* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */
|
||||||
static uint8_t dl1[256], dl2[256];
|
static uint8_t dl1[256], dl2[256];
|
||||||
@@ -68,6 +80,7 @@ int pop_level_load(const char *path)
|
|||||||
|
|
||||||
if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; }
|
if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; }
|
||||||
lvl_ok = 1;
|
lvl_ok = 1;
|
||||||
|
gstate_init(); /* живая копия состояния стражей — из уровня */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +247,101 @@ static uint8_t start_byte(uint16_t off)
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат
|
||||||
|
* 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из
|
||||||
|
* колонки (pos_guards), поэтому guards_x из файла не читаем. */
|
||||||
|
/* ---- ЖИВОЕ состояние стражей (порт level.guards_*) ------------------ *
|
||||||
|
* В оригинале массивы стражей — часть структуры уровня в ОЗУ, и движок их
|
||||||
|
* ПЕРЕПИСЫВАЕТ: leave_guard при выходе из комнаты кладёт туда текущие
|
||||||
|
* позицию/направление/мастерство, а у МЁРТВОГО ещё и curr_seq. Благодаря
|
||||||
|
* этому, вернувшись, находишь труп там же, а не воскресшего стража.
|
||||||
|
* У нас уровень лежит в EMM-странице только на чтение, поэтому держим
|
||||||
|
* мутабельную копию в W2: 6 байт на комнату. */
|
||||||
|
#define GS_TILE 0
|
||||||
|
#define GS_DIR 1
|
||||||
|
#define GS_X 2
|
||||||
|
#define GS_SKILL 3
|
||||||
|
#define GS_SEQL 4
|
||||||
|
#define GS_SEQH 5
|
||||||
|
static uint8_t gstate[24 * 6];
|
||||||
|
|
||||||
|
static void gstate_init(void)
|
||||||
|
{
|
||||||
|
const uint8_t *p = (const uint8_t *)LVL_DATA_OFF;
|
||||||
|
uint8_t r;
|
||||||
|
gfx_w0_map(lvl_page);
|
||||||
|
for (r = 0; r < 24; r++) {
|
||||||
|
uint8_t *g = gstate + (uint16_t)r * 6;
|
||||||
|
g[GS_TILE] = p[BP_GTILE + r];
|
||||||
|
g[GS_DIR] = p[BP_GDIR + r];
|
||||||
|
g[GS_X] = p[BP_GX + r];
|
||||||
|
g[GS_SKILL] = p[BP_GSKILL + r];
|
||||||
|
/* guards_seq_lo/hi в ФАЙЛЕ не используются — там 0xFF во всех
|
||||||
|
* комнатах (оригинал чистит их в reset_level_unused_fields).
|
||||||
|
* Осмысленное значение туда кладёт только leave_guard, поэтому
|
||||||
|
* стартуем нулями: 0 = «поднимать стандартной стойкой».
|
||||||
|
* Без этого в интерпретатор уходил curr_seq = 0xFFFF и он вис. */
|
||||||
|
g[GS_SEQL] = 0;
|
||||||
|
g[GS_SEQH] = 0;
|
||||||
|
}
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* leave_guard (seg002:02F5): запомнить состояние стража комнаты. seq
|
||||||
|
* сохраняем ТОЛЬКО у мёртвого — у живого seq_hi = 0 означает «поднять
|
||||||
|
* стандартной стойкой», как в оригинале. */
|
||||||
|
void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x,
|
||||||
|
uint8_t skill, uint16_t seq, uint8_t dead)
|
||||||
|
{
|
||||||
|
uint8_t *g;
|
||||||
|
if (room < 1 || room > 24) return;
|
||||||
|
g = gstate + (uint16_t)(room - 1) * 6;
|
||||||
|
g[GS_TILE] = tile;
|
||||||
|
g[GS_DIR] = (uint8_t)dir;
|
||||||
|
g[GS_X] = x;
|
||||||
|
g[GS_SKILL] = skill;
|
||||||
|
if (dead) { g[GS_SEQL] = (uint8_t)seq; g[GS_SEQH] = (uint8_t)(seq >> 8); }
|
||||||
|
else { g[GS_SEQH] = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t pop_guard_state_seq(uint8_t room)
|
||||||
|
{
|
||||||
|
const uint8_t *g;
|
||||||
|
if (room < 1 || room > 24) return 0;
|
||||||
|
g = gstate + (uint16_t)(room - 1) * 6;
|
||||||
|
if (g[GS_SEQH] == 0) return 0; /* живой — стандартная стойка */
|
||||||
|
return (uint16_t)(g[GS_SEQL] | ((uint16_t)g[GS_SEQH] << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Запомненная X стража комнаты (для трупа: он лежит там, где лёг, а не в
|
||||||
|
* центре тайла). */
|
||||||
|
uint8_t pop_guard_state_x(uint8_t room)
|
||||||
|
{
|
||||||
|
if (room < 1 || room > 24) return 0;
|
||||||
|
return gstate[(uint16_t)(room - 1) * 6 + GS_X];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
|
||||||
|
uint8_t *color, uint8_t *skill)
|
||||||
|
{
|
||||||
|
const uint8_t *g;
|
||||||
|
uint8_t t;
|
||||||
|
if (!lvl_ok || room < 1 || room > 24) return 0;
|
||||||
|
/* Тайл/направление/мастерство — из ЖИВОЙ копии (её правит
|
||||||
|
* pop_guard_state_save); цвет статичен, его читаем со страницы. */
|
||||||
|
g = gstate + (uint16_t)(room - 1) * 6;
|
||||||
|
t = g[GS_TILE];
|
||||||
|
if (t < 30) {
|
||||||
|
*tile = t;
|
||||||
|
*dir = (int8_t)g[GS_DIR];
|
||||||
|
*skill = g[GS_SKILL];
|
||||||
|
gfx_w0_map(lvl_page);
|
||||||
|
*color = (uint8_t)(((const uint8_t *)LVL_DATA_OFF)[BP_GCOLOR + room - 1] & 0x0F);
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
return (uint8_t)(t < 30);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); }
|
uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); }
|
||||||
uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); }
|
uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); }
|
||||||
int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); }
|
int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); }
|
||||||
|
|||||||
@@ -73,6 +73,23 @@ void pop_doorlink2_set(uint8_t i, uint8_t v);
|
|||||||
int8_t pop_room_col_landing(uint8_t room, uint8_t col);
|
int8_t pop_room_col_landing(uint8_t room, uint8_t col);
|
||||||
|
|
||||||
/* Стартовая позиция уровня (блок @2112): комната / поза / направление. */
|
/* Стартовая позиция уровня (блок @2112): комната / поза / направление. */
|
||||||
|
/* Страж комнаты: 1 = есть (заполнит tile/dir/color/skill), 0 = нет.
|
||||||
|
* Порт enter_guard (seg002:0112). */
|
||||||
|
/* leave_guard (seg002:02F5): запомнить состояние стража комнаты в ЖИВОЙ
|
||||||
|
* копии массивов уровня. dead != 0 — сохранить и последовательность, тогда
|
||||||
|
* при возврате в комнату страж поднимется трупом там же, где лёг.
|
||||||
|
* tile == 30 (или больше) — «стража в комнате больше нет». */
|
||||||
|
void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x,
|
||||||
|
uint8_t skill, uint16_t seq, uint8_t dead);
|
||||||
|
|
||||||
|
/* Последовательность запомненного стража (0 — поднимать стандартной
|
||||||
|
* стойкой, как у живого). */
|
||||||
|
uint16_t pop_guard_state_seq(uint8_t room);
|
||||||
|
uint8_t pop_guard_state_x(uint8_t room);
|
||||||
|
|
||||||
|
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
|
||||||
|
uint8_t *color, uint8_t *skill);
|
||||||
|
|
||||||
uint8_t pop_level_start_room(void);
|
uint8_t pop_level_start_room(void);
|
||||||
uint8_t pop_level_start_pos(void);
|
uint8_t pop_level_start_pos(void);
|
||||||
int8_t pop_level_start_dir(void);
|
int8_t pop_level_start_dir(void);
|
||||||
|
|||||||
+134
-117
@@ -5,7 +5,7 @@
|
|||||||
* FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall();
|
* FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall();
|
||||||
* стена -> in_wall();
|
* стена -> in_wall();
|
||||||
* freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed),
|
* freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed),
|
||||||
* достиг y_land[curr_row+1] -> land()/inc_curr_row.
|
* достиг pop_y_land[curr_row+1] -> land()/inc_curr_row.
|
||||||
*
|
*
|
||||||
* Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX);
|
* Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX);
|
||||||
* col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 =
|
* col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 =
|
||||||
@@ -19,7 +19,16 @@
|
|||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
|
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
|
||||||
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */
|
#include "pop_geom.h" /* общая геометрия (x_bump/y_land/y_to_row) */
|
||||||
|
#include "pop_redraw.h" /* пометки перерисовки (порт set_redraw_*) */
|
||||||
|
/* pop_bg нужен ТОЛЬКО падающему куску (mob): spawn/tick/pos — это движущийся
|
||||||
|
* ОБЪЕКТ, а не перерисовка тайла (в оригинале он и живёт отдельно: mobs +
|
||||||
|
* draw_moving). Из-за него pop_loose_tick — единственная функция модуля,
|
||||||
|
* которую нельзя звать из __banked-кода; вся коллизия/физика/предметы, то
|
||||||
|
* есть то, что понадобится стражам, чиста от графики. Разделение mob на
|
||||||
|
* логику и отрисовку — задел следующей фазы. */
|
||||||
|
#include "pop_bg.h"
|
||||||
|
#include "pop_state.h"
|
||||||
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
|
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
|
||||||
|
|
||||||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||||||
@@ -81,23 +90,17 @@
|
|||||||
|
|
||||||
#define FRAME_NEEDS_FLOOR 0x40
|
#define FRAME_NEEDS_FLOOR 0x40
|
||||||
#define FRAME_WEIGHT_X 0x1F
|
#define FRAME_WEIGHT_X 0x1F
|
||||||
#define TILE_SIZEX 14
|
|
||||||
#define TILE_RIGHTX 13
|
#define TILE_RIGHTX 13
|
||||||
#define SCREENSPACE_X 58
|
#define SCREENSPACE_X 58
|
||||||
#define FALL_ACCEL 3
|
#define FALL_ACCEL 3
|
||||||
#define FALL_MAX 33
|
#define FALL_MAX 33
|
||||||
|
|
||||||
#define TILE_MIDX 7
|
#define TILE_MIDX 7
|
||||||
#define FIRST_ONSCREEN_COLUMN 5
|
|
||||||
#define FRAME_THIN 0x20
|
#define FRAME_THIN 0x20
|
||||||
|
|
||||||
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
|
|
||||||
static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */
|
static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */
|
||||||
static const int8_t dir_behind[2] = {1, -1}; /* [dir+1]: назад по направлению */
|
static const int8_t dir_behind[2] = {1, -1}; /* [dir+1]: назад по направлению */
|
||||||
static const int16_t x_bump[20] = {
|
|
||||||
-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
|
|
||||||
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
|
|
||||||
};
|
|
||||||
/* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */
|
/* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */
|
||||||
static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0}; /* wall_dist_from_left */
|
static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0}; /* wall_dist_from_left */
|
||||||
static const int8_t wall_dr[6] = {0, 0, 10, 13, 0, 0}; /* wall_dist_from_right */
|
static const int8_t wall_dr[6] = {0, 0, 10, 13, 0, 0}; /* wall_dist_from_right */
|
||||||
@@ -111,6 +114,12 @@ uint8_t pop_loose_fell; /* 0=нет; иначе tilepos+1 упавше
|
|||||||
uint8_t pop_kid_dead; /* 1 = Kid мёртв (напоролся на пики и т.п.) */
|
uint8_t pop_kid_dead; /* 1 = Kid мёртв (напоролся на пики и т.п.) */
|
||||||
uint8_t pop_kid_hurt; /* 1 = в этом кадре убавилось HP → «брызги» */
|
uint8_t pop_kid_hurt; /* 1 = в этом кадре убавилось HP → «брызги» */
|
||||||
uint8_t hitp_curr; /* текущее HP (0 = мёртв); pop_kid_hp_reset ставит старт */
|
uint8_t hitp_curr; /* текущее HP (0 = мёртв); pop_kid_hp_reset ставит старт */
|
||||||
|
uint8_t hitp_max; /* стартовое HP Кида (для индикатора) */
|
||||||
|
/* Дельта HP Кида за кадр (порт hitp_delta). Её ставит СВОРД-путь боёвки
|
||||||
|
* (guards.c), а применяет pop_do_delta_hp раз в кадр — как в оригинале.
|
||||||
|
* Путь пик/падения (take_hp ниже) правит hitp_curr сразу: он был написан и
|
||||||
|
* проверен до боёвки, и переводить его на дельту без нужды рискованно. */
|
||||||
|
int8_t hitp_delta;
|
||||||
static uint8_t g_room; /* номер ТЕКУЩЕЙ комнаты (для room_modif пик/ворот) */
|
static uint8_t g_room; /* номер ТЕКУЩЕЙ комнаты (для room_modif пик/ворот) */
|
||||||
static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ — loose→empty) */
|
static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ — loose→empty) */
|
||||||
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
||||||
@@ -142,20 +151,18 @@ static uint8_t g_lcol[6], g_rcol[6];
|
|||||||
* отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. —
|
* отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. —
|
||||||
* тряска, 1..11 — отсчёт до падения). */
|
* тряска, 1..11 — отсчёт до падения). */
|
||||||
uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */
|
uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */
|
||||||
static uint8_t ceil_rest[10]; /* «перерисовать ПОКОЙ» (2 = обе страницы) */
|
|
||||||
static uint8_t ceil_bake[10]; /* «запечь колодец» на второй странице */
|
|
||||||
static uint8_t land_bake, land_bake_pos; /* щебень от приземления — вторая страница */
|
|
||||||
static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */
|
static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */
|
||||||
uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */
|
uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */
|
||||||
|
|
||||||
uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */
|
uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */
|
||||||
|
|
||||||
void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; }
|
void pop_map_set_above(uint8_t *above_row2_fg) __banked { g_above = above_row2_fg; }
|
||||||
|
|
||||||
void pop_map_set(uint8_t *fg) { g_fg = fg; }
|
void pop_map_set(uint8_t *fg) __banked { g_fg = fg; }
|
||||||
|
|
||||||
void pop_map_set_room(uint8_t room) { g_room = room; }
|
void pop_map_set_room(uint8_t room) __banked { g_room = room; }
|
||||||
|
|
||||||
void pop_kid_hp_reset(void) { hitp_curr = 3; pop_kid_dead = 0; }
|
void pop_kid_hp_reset(void) __banked { hitp_curr = hitp_max = 3; hitp_delta = 0; pop_kid_dead = 0; }
|
||||||
|
|
||||||
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
|
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
|
||||||
static uint8_t take_hp(uint8_t count)
|
static uint8_t take_hp(uint8_t count)
|
||||||
@@ -166,7 +173,7 @@ static uint8_t take_hp(uint8_t count)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
|
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
|
||||||
const uint8_t *lcol_fg, const uint8_t *rcol_fg)
|
const uint8_t *lcol_fg, const uint8_t *rcol_fg) __banked
|
||||||
{
|
{
|
||||||
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
|
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
|
||||||
if (lcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; }
|
if (lcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; }
|
||||||
@@ -201,6 +208,16 @@ static uint8_t get_tile(int8_t col, int8_t row)
|
|||||||
return g_curr_tile;
|
return g_curr_tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Тайл ТЕКУЩЕЙ комнаты по клетке — наружу (порт get_tile(room,col,row) для
|
||||||
|
* случая room == текущая). Нужен лучу видимости стража
|
||||||
|
* (check_can_guard_see_kid, seg003:688): тот идёт по ряду между Кидом и
|
||||||
|
* стражем, а оба по условию в одной (= отрисованной) комнате. Колонки за
|
||||||
|
* пределами 0..9 get_tile разрешает через швы — здесь не используются. */
|
||||||
|
uint8_t pop_tile_at(int8_t col, int8_t row) __banked
|
||||||
|
{
|
||||||
|
return get_tile(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
|
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
|
||||||
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
|
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
|
||||||
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
|
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
|
||||||
@@ -367,7 +384,7 @@ static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol)
|
|||||||
uint8_t type = wall_type(tiletype);
|
uint8_t type = wall_type(tiletype);
|
||||||
int coll_left, cx;
|
int coll_left, cx;
|
||||||
if (type == 0) return 127;
|
if (type == 0) return 127;
|
||||||
coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
coll_left = pop_x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||||
cx = char_front_coll();
|
cx = char_front_coll();
|
||||||
if (Kid.direction < 0) /* лицом влево */
|
if (Kid.direction < 0) /* лицом влево */
|
||||||
return cx - (coll_left + TILE_RIGHTX - wall_dr[type]);
|
return cx - (coll_left + TILE_RIGHTX - wall_dr[type]);
|
||||||
@@ -423,7 +440,7 @@ static uint8_t fell_on_spikes(void)
|
|||||||
static void land(void)
|
static void land(void)
|
||||||
{
|
{
|
||||||
uint8_t seq;
|
uint8_t seq;
|
||||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
|
||||||
if (fell_on_spikes()) return; /* упал на вредные пики — смерть */
|
if (fell_on_spikes()) return; /* упал на вредные пики — смерть */
|
||||||
/* к краю пола — чуть назад (как оригинал) */
|
/* к краю пола — чуть назад (как оригинал) */
|
||||||
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
|
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
|
||||||
@@ -441,7 +458,7 @@ static void land(void)
|
|||||||
if (deadly || take_hp(1)) { /* 3+ этажа или последнее HP */
|
if (deadly || take_hp(1)) { /* 3+ этажа или последнее HP */
|
||||||
take_hp(100);
|
take_hp(100);
|
||||||
kid_set_seq(SEQ_22_CRUSHED);
|
kid_set_seq(SEQ_22_CRUSHED);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
pop_kid_dead = 1;
|
pop_kid_dead = 1;
|
||||||
return;
|
return;
|
||||||
@@ -450,7 +467,7 @@ static void land(void)
|
|||||||
}
|
}
|
||||||
Kid.fall_x = Kid.fall_y = 0;
|
Kid.fall_x = Kid.fall_y = 0;
|
||||||
kid_set_seq(seq);
|
kid_set_seq(seq);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +481,7 @@ static void start_fall(void)
|
|||||||
else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP;
|
else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP;
|
||||||
else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */
|
else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */
|
||||||
kid_set_seq(seq);
|
kid_set_seq(seq);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
/* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2).
|
/* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2).
|
||||||
* Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose)
|
* Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose)
|
||||||
@@ -518,7 +535,7 @@ static void check_grab(void)
|
|||||||
uint8_t old_x;
|
uint8_t old_x;
|
||||||
if (!pop_ctrl_shift_held()) return; /* Shift не зажат */
|
if (!pop_ctrl_shift_held()) return; /* Shift не зажат */
|
||||||
if ((uint8_t)Kid.fall_y >= 32) return; /* падает слишком быстро */
|
if ((uint8_t)Kid.fall_y >= 32) return; /* падает слишком быстро */
|
||||||
if ((uint16_t)y_land[Kid.curr_row + 1] > (uint16_t)(Kid.y + 25)) return;
|
if ((uint16_t)pop_y_land[Kid.curr_row + 1] > (uint16_t)(Kid.y + 25)) return;
|
||||||
old_x = Kid.x;
|
old_x = Kid.x;
|
||||||
Kid.x = (uint8_t)char_dx_forward(-8);
|
Kid.x = (uint8_t)char_dx_forward(-8);
|
||||||
determine_col();
|
determine_col();
|
||||||
@@ -527,10 +544,10 @@ static void check_grab(void)
|
|||||||
determine_col();
|
determine_col();
|
||||||
} else {
|
} else {
|
||||||
Kid.x = (uint8_t)char_dx_forward((int8_t)distance_to_edge_weight());
|
Kid.x = (uint8_t)char_dx_forward((int8_t)distance_to_edge_weight());
|
||||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
|
||||||
Kid.fall_y = 0;
|
Kid.fall_y = 0;
|
||||||
kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR);
|
kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
grab_timer = 12; /* (TODO: sound grab) */
|
grab_timer = 12; /* (TODO: sound grab) */
|
||||||
}
|
}
|
||||||
@@ -539,8 +556,8 @@ static void check_grab(void)
|
|||||||
static void do_fall(void)
|
static void do_fall(void)
|
||||||
{
|
{
|
||||||
uint8_t nrow = (uint8_t)(Kid.curr_row + 1);
|
uint8_t nrow = (uint8_t)(Kid.curr_row + 1);
|
||||||
if (nrow > 4) nrow = 4; /* защита y_land[] от выхода */
|
if (nrow > 4) nrow = 4; /* защита pop_y_land[] от выхода */
|
||||||
if ((uint16_t)y_land[nrow] > (uint16_t)Kid.y) {
|
if ((uint16_t)pop_y_land[nrow] > (uint16_t)Kid.y) {
|
||||||
check_grab(); /* ещё летит — попытка зацепа */
|
check_grab(); /* ещё летит — попытка зацепа */
|
||||||
} else {
|
} else {
|
||||||
if (get_tile_at_char() == TILE_WALL)
|
if (get_tile_at_char() == TILE_WALL)
|
||||||
@@ -587,7 +604,7 @@ static int edge_classify(int d)
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pop_edge_distance(void)
|
int pop_edge_distance(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t t; int d;
|
uint8_t t; int d;
|
||||||
determine_col();
|
determine_col();
|
||||||
@@ -629,7 +646,7 @@ int pop_edge_distance(void)
|
|||||||
return distance_to_edge_weight();
|
return distance_to_edge_weight();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t pop_edge_type(void) { return g_edge_type; }
|
uint8_t pop_edge_type(void) __banked { return g_edge_type; }
|
||||||
|
|
||||||
/* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- *
|
/* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- *
|
||||||
* pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в
|
* pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в
|
||||||
@@ -682,7 +699,7 @@ static uint8_t jump_up_or_grab(void)
|
|||||||
return grab_up_with_floor_behind();
|
return grab_up_with_floor_behind();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t pop_jump_up_seq(void)
|
uint8_t pop_jump_up_seq(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t through = get_tile_above_char();
|
uint8_t through = get_tile_above_char();
|
||||||
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
|
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
|
||||||
@@ -697,7 +714,7 @@ uint8_t pop_jump_up_seq(void)
|
|||||||
|
|
||||||
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
|
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
|
||||||
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
|
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
|
||||||
int pop_wall_ahead(void)
|
int pop_wall_ahead(void) __banked
|
||||||
{
|
{
|
||||||
int d = pop_edge_distance();
|
int d = pop_edge_distance();
|
||||||
return (g_edge_type == EDGE_WALL && d <= 2);
|
return (g_edge_type == EDGE_WALL && d <= 2);
|
||||||
@@ -708,7 +725,7 @@ int pop_wall_ahead(void)
|
|||||||
* тайловые предикаты + выбор seq (+ выравнивание Kid.x). */
|
* тайловые предикаты + выбор seq (+ выравнивание Kid.x). */
|
||||||
|
|
||||||
/* grab_timer истёк — можно подтягиваться? */
|
/* grab_timer истёк — можно подтягиваться? */
|
||||||
uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; }
|
uint8_t pop_hang_grab_ready(void) __banked { return grab_timer == 0; }
|
||||||
|
|
||||||
/* can_climb_up (seg005:09DF): тайл над персонажем — обычный подъём (seq_10)
|
/* can_climb_up (seg005:09DF): тайл над персонажем — обычный подъём (seq_10)
|
||||||
* либо подъём к ЗАКРЫТОЙ калитке/зеркалу/чомперу (seq_73 = «влез и съехал
|
* либо подъём к ЗАКРЫТОЙ калитке/зеркалу/чомперу (seq_73 = «влез и съехал
|
||||||
@@ -716,7 +733,7 @@ uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; }
|
|||||||
* curr_tile2 == tiles_4_gate && direction != right && modif >> 2 < 6
|
* curr_tile2 == tiles_4_gate && direction != right && modif >> 2 < 6
|
||||||
* — под поднятой решёткой (modif >> 2 >= 6) подъём обычный. Без проверки
|
* — под поднятой решёткой (modif >> 2 >= 6) подъём обычный. Без проверки
|
||||||
* модификатора Kid срывался и с открытых ворот. */
|
* модификатора Kid срывался и с открытых ворот. */
|
||||||
uint8_t pop_hang_climb_up_seq(void)
|
uint8_t pop_hang_climb_up_seq(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t t = get_tile_above_char();
|
uint8_t t = get_tile_above_char();
|
||||||
if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) ||
|
if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) ||
|
||||||
@@ -728,7 +745,7 @@ uint8_t pop_hang_climb_up_seq(void)
|
|||||||
|
|
||||||
/* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 =
|
/* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 =
|
||||||
* сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */
|
* сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */
|
||||||
int8_t pop_hang_wall(void)
|
int8_t pop_hang_wall(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t t = get_tile_at_char();
|
uint8_t t = get_tile_at_char();
|
||||||
if (Kid.action != ACT_HANGSTRAIGHT &&
|
if (Kid.action != ACT_HANGSTRAIGHT &&
|
||||||
@@ -743,7 +760,7 @@ int8_t pop_hang_wall(void)
|
|||||||
/* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под
|
/* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под
|
||||||
* персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7)
|
* персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7)
|
||||||
* и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */
|
* и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */
|
||||||
uint8_t pop_hang_fall_seq(void)
|
uint8_t pop_hang_fall_seq(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t behind = get_tile_behind_char();
|
uint8_t behind = get_tile_behind_char();
|
||||||
uint8_t at;
|
uint8_t at;
|
||||||
@@ -760,7 +777,7 @@ uint8_t pop_hang_fall_seq(void)
|
|||||||
* либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 =
|
* либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 =
|
||||||
* приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки
|
* приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки
|
||||||
* пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */
|
* пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */
|
||||||
uint8_t pop_down_action(void)
|
uint8_t pop_down_action(void) __banked
|
||||||
{
|
{
|
||||||
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) {
|
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) {
|
||||||
Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */
|
Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */
|
||||||
@@ -792,6 +809,9 @@ uint8_t pop_down_action(void)
|
|||||||
uint8_t pop_have_sword;
|
uint8_t pop_have_sword;
|
||||||
uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 поднятого */
|
uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 поднятого */
|
||||||
uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */
|
uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */
|
||||||
|
/* Цвет вспышки: 0 = ярко-жёлтая (меч), 1 = красная (зелье). В оригинале это
|
||||||
|
* flash_color; нам хватает двух значений, которые реально встречаются. */
|
||||||
|
uint8_t pop_flash_red;
|
||||||
static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */
|
static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */
|
||||||
|
|
||||||
/* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить
|
/* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить
|
||||||
@@ -805,7 +825,7 @@ static void do_pickup(int8_t obj_type, uint8_t tilepos)
|
|||||||
|
|
||||||
/* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из
|
/* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из
|
||||||
* play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */
|
* play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */
|
||||||
void pop_proc_get_object(void)
|
void pop_proc_get_object(void) __banked
|
||||||
{
|
{
|
||||||
if (pickup_obj_type == -1) {
|
if (pickup_obj_type == -1) {
|
||||||
pop_have_sword = 1; /* в оригинале have_sword = -1 */
|
pop_have_sword = 1; /* в оригинале have_sword = -1 */
|
||||||
@@ -815,13 +835,35 @@ void pop_proc_get_object(void)
|
|||||||
* чёрное поле экрана. У нас так же (gfx_pal_set 0), см. roomtest. */
|
* чёрное поле экрана. У нас так же (gfx_pal_set 0), см. roomtest. */
|
||||||
pop_flash_time = 8;
|
pop_flash_time = 8;
|
||||||
}
|
}
|
||||||
pickup_obj_type = 0; /* зелья (типы 1..6) не портированы */
|
else {
|
||||||
|
/* proc_get_object (seg006:1927), ветка зелий. Типы 3/4/6
|
||||||
|
* (перо/переворот/открыть все ворота) — свойства поздних уровней,
|
||||||
|
* их портируем вместе с этими уровнями. */
|
||||||
|
switch (pickup_obj_type) {
|
||||||
|
case 1: /* здоровье: +1 HP, красная вспышка */
|
||||||
|
if (hitp_curr != hitp_max) {
|
||||||
|
hitp_delta = 1;
|
||||||
|
pop_flash_red = 1;
|
||||||
|
pop_flash_time = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5: /* «злое» зелье: −1 HP */
|
||||||
|
hitp_delta = -1;
|
||||||
|
pop_flash_red = 1;
|
||||||
|
pop_flash_time = 2;
|
||||||
|
break;
|
||||||
|
default: /* 2 жизнь / 3 перо / 4 переворот /
|
||||||
|
* 6 открыть — TODO с их уровнями */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pickup_obj_type = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет
|
/* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет
|
||||||
* (управление идёт обычной веткой); 1 — присесть (caller зовёт crouch());
|
* (управление идёт обычной веткой); 1 — присесть (caller зовёт crouch());
|
||||||
* 2 — поднять меч (caller ставит seq_91). */
|
* 2 — поднять меч (caller ставит seq_91). */
|
||||||
uint8_t pop_get_item_action(void)
|
uint8_t pop_get_item_action(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t t;
|
uint8_t t;
|
||||||
int8_t icol;
|
int8_t icol;
|
||||||
@@ -844,9 +886,17 @@ uint8_t pop_get_item_action(void)
|
|||||||
Kid.x = (uint8_t)char_dx_forward((int8_t)((t == TILE_POTION) - 2));
|
Kid.x = (uint8_t)char_dx_forward((int8_t)((t == TILE_POTION) - 2));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (t != TILE_SWORD) return 0; /* питьё зелий не портировано */
|
{
|
||||||
do_pickup(-1, (uint8_t)(Kid.curr_row * 10 + icol));
|
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + icol);
|
||||||
return 2;
|
if (t == TILE_SWORD) {
|
||||||
|
do_pickup(-1, tp);
|
||||||
|
return 2; /* поднять меч (seq_91) */
|
||||||
|
}
|
||||||
|
/* Зелье: тип лежит в СТАРШИХ битах модификатора (get_item,
|
||||||
|
* seg005: do_pickup(curr_room_modif[curr_tilepos] >> 3)). */
|
||||||
|
do_pickup((int8_t)(pop_trob_modif(g_room)[tp] >> 3), tp);
|
||||||
|
return 3; /* пить (seq_78) */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Диспетчер ----------------------------------------------------- */
|
/* ---- Диспетчер ----------------------------------------------------- */
|
||||||
@@ -885,7 +935,7 @@ static void bumped_fall(void)
|
|||||||
Kid.fall_x = 0;
|
Kid.fall_x = 0;
|
||||||
} else {
|
} else {
|
||||||
kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */
|
kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,11 +950,11 @@ static void bumped_floor(void)
|
|||||||
uint8_t frame;
|
uint8_t frame;
|
||||||
/* Оригинал сравнивает БЕЗЗНАКОВО: если персонаж НИЖЕ уровня пола,
|
/* Оригинал сравнивает БЕЗЗНАКОВО: если персонаж НИЖЕ уровня пола,
|
||||||
* разность заворачивается в большое число и тоже даёт «падать». */
|
* разность заворачивается в большое число и тоже даёт «падать». */
|
||||||
if ((uint16_t)(y_land[Kid.curr_row + 1] - (int16_t)Kid.y) >= 15) {
|
if ((uint16_t)(pop_y_land[Kid.curr_row + 1] - (int16_t)Kid.y) >= 15) {
|
||||||
bumped_fall(); /* высоко над полом — в воздухе */
|
bumped_fall(); /* высоко над полом — в воздухе */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; /* ПРИЖАТЬ к полу */
|
Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1]; /* ПРИЖАТЬ к полу */
|
||||||
if (Kid.fall_y >= 22) { /* влетел быстро — только отжать */
|
if (Kid.fall_y >= 22) { /* влетел быстро — только отжать */
|
||||||
Kid.x = (uint8_t)char_dx_forward(-5);
|
Kid.x = (uint8_t)char_dx_forward(-5);
|
||||||
return;
|
return;
|
||||||
@@ -919,7 +969,7 @@ static void bumped_floor(void)
|
|||||||
kid_set_seq(SEQ_46_HARDBUMP);
|
kid_set_seq(SEQ_46_HARDBUMP);
|
||||||
else
|
else
|
||||||
kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */
|
kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять
|
/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять
|
||||||
@@ -942,7 +992,7 @@ static void check_bumped(void)
|
|||||||
* hang->hang_fall->seq_11 (правильное приземление). */
|
* hang->hang_fall->seq_11 (правильное приземление). */
|
||||||
if (Kid.frame >= 87 && Kid.frame < 100) return;
|
if (Kid.frame >= 87 && Kid.frame < 100) return;
|
||||||
/* Вне рядов комнаты (падение мимо пола): get_tile даёт WALL, а
|
/* Вне рядов комнаты (падение мимо пола): get_tile даёт WALL, а
|
||||||
* dist_from_wall_forward/x_bump[] — мусор с большим отриц. сдвигом →
|
* dist_from_wall_forward/pop_x_bump[] — мусор с большим отриц. сдвигом →
|
||||||
* underflow X и уход в стену. fell_out ловит do_fall. */
|
* underflow X и уход в стену. fell_out ловит do_fall. */
|
||||||
if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
|
if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
|
||||||
/* seg004 check_collisions: на кадрах разворота коллизии НЕ считаются
|
/* seg004 check_collisions: на кадрах разворота коллизии НЕ считаются
|
||||||
@@ -973,7 +1023,7 @@ static void check_bumped(void)
|
|||||||
{
|
{
|
||||||
uint8_t f = 0, prev, need;
|
uint8_t f = 0, prev, need;
|
||||||
if (wt) {
|
if (wt) {
|
||||||
int coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
int coll_left = pop_x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||||
int lw = wall_dl[wt] + coll_left; /* get_left_wall_xpos */
|
int lw = wall_dl[wt] + coll_left; /* get_left_wall_xpos */
|
||||||
int rw = coll_left - wall_dr[wt] + TILE_RIGHTX; /* get_right_wall_xpos */
|
int rw = coll_left - wall_dr[wt] + TILE_RIGHTX; /* get_right_wall_xpos */
|
||||||
int e = char_x_forward_edge(), wh = ((int)kid_fp_width() + 1) >> 1;
|
int e = char_x_forward_edge(), wh = ((int)kid_fp_width() + 1) >> 1;
|
||||||
@@ -1027,8 +1077,6 @@ static void check_bumped(void)
|
|||||||
* отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/
|
* отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/
|
||||||
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
|
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
|
||||||
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
|
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
|
||||||
static uint8_t loose_bake[30]; /* счётчик «запечь пусто» (2 = обе страницы) */
|
|
||||||
static uint8_t loose_rest[30]; /* счётчик «перерисовать ПОКОЙ» после тряски (2 = обе стр.) */
|
|
||||||
|
|
||||||
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
|
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
|
||||||
|
|
||||||
@@ -1115,7 +1163,7 @@ static void do_knock(int tile_row)
|
|||||||
/* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl-
|
/* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl-
|
||||||
* командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) →
|
* командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) →
|
||||||
* ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */
|
* ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */
|
||||||
void pop_check_knock(void)
|
void pop_check_knock(void) __banked
|
||||||
{
|
{
|
||||||
if (knock) {
|
if (knock) {
|
||||||
do_knock(Kid.curr_row - (knock > 0));
|
do_knock(Kid.curr_row - (knock > 0));
|
||||||
@@ -1126,23 +1174,18 @@ void pop_check_knock(void)
|
|||||||
/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest
|
/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest
|
||||||
* индексируются позицией тайла и не должны утечь из старой комнаты в новую.
|
* индексируются позицией тайла и не должны утечь из старой комнаты в новую.
|
||||||
* Плюс останавливает падающий кусок (pop_bg). */
|
* Плюс останавливает падающий кусок (pop_bg). */
|
||||||
void pop_loose_reset(void)
|
void pop_loose_reset(void) __banked
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 30; i++) {
|
for (i = 0; i < 30; i++) {
|
||||||
pop_loose_modif[i] = 0;
|
pop_loose_modif[i] = 0;
|
||||||
loose_bake[i] = 0;
|
|
||||||
loose_rest[i] = 0;
|
|
||||||
}
|
}
|
||||||
for (i = 0; i < 10; i++) {
|
for (i = 0; i < 10; i++) {
|
||||||
pop_ceil_modif[i] = 0;
|
pop_ceil_modif[i] = 0;
|
||||||
ceil_rest[i] = 0;
|
|
||||||
ceil_bake[i] = 0;
|
|
||||||
}
|
}
|
||||||
pop_ceil_fell = 0;
|
pop_ceil_fell = 0;
|
||||||
pop_debris_at = 0;
|
pop_debris_at = 0;
|
||||||
pop_loose_landed = 0;
|
pop_loose_landed = 0;
|
||||||
land_bake = 0;
|
|
||||||
pop_loose_mob_reset();
|
pop_loose_mob_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1159,18 +1202,18 @@ static void fell_on_your_head(void)
|
|||||||
uint8_t frame = Kid.frame, action = Kid.action;
|
uint8_t frame = Kid.frame, action = Kid.action;
|
||||||
if ((frame < 5 || frame >= 15) &&
|
if ((frame < 5 || frame >= 15) &&
|
||||||
(action < ACT_HANGCLIMB || action == ACT_TURN)) {
|
(action < ACT_HANGCLIMB || action == ACT_TURN)) {
|
||||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
|
||||||
pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */
|
pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */
|
||||||
if (take_hp(1)) {
|
if (take_hp(1)) {
|
||||||
kid_set_seq(SEQ_22_CRUSHED);
|
kid_set_seq(SEQ_22_CRUSHED);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
pop_kid_dead = 1;
|
pop_kid_dead = 1;
|
||||||
} else if (frame != FRAME_109_CROUCH) {
|
} else if (frame != FRAME_109_CROUCH) {
|
||||||
if (get_tile_behind_char() == TILE_EMPTY)
|
if (get_tile_behind_char() == TILE_EMPTY)
|
||||||
Kid.x = (uint8_t)char_dx_forward(-2);
|
Kid.x = (uint8_t)char_dx_forward(-2);
|
||||||
kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID);
|
kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1233,7 @@ static void check_loose_fall_on_kid(void)
|
|||||||
|
|
||||||
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
|
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
|
||||||
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
|
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
|
||||||
void pop_loose_tick(void)
|
void pop_loose_tick(void) __banked
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
|
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
|
||||||
@@ -1201,24 +1244,17 @@ void pop_loose_tick(void)
|
|||||||
* Инкремент+сравнение считают row/col без деления. */
|
* Инкремент+сравнение считают row/col без деления. */
|
||||||
uint8_t row = 0, col = 0;
|
uint8_t row = 0, col = 0;
|
||||||
for (pos = 0; pos < 30; pos++) {
|
for (pos = 0; pos < 30; pos++) {
|
||||||
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
|
if (pop_loose_modif[pos] != 0) {
|
||||||
pop_loose_bake_empty(row, col);
|
|
||||||
loose_bake[pos]--;
|
|
||||||
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
|
||||||
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
|
||||||
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
|
||||||
* страницы дабл-буфера — иначе на одной остаётся застывший
|
|
||||||
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
|
||||||
pop_loose_shake_draw(row, col);
|
|
||||||
loose_rest[pos]--;
|
|
||||||
} else if (pop_loose_modif[pos] != 0) {
|
|
||||||
uint8_t m = ++pop_loose_modif[pos];
|
uint8_t m = ++pop_loose_modif[pos];
|
||||||
if (m & 0x80) { /* тряска (do_knock) */
|
if (m & 0x80) { /* тряска (do_knock) */
|
||||||
if (m >= 0x84) { /* конец тряски: сброс + перерисовать покой */
|
if (m >= 0x84) { /* конец тряски: сброс + покой */
|
||||||
pop_loose_modif[pos] = 0;
|
pop_loose_modif[pos] = 0;
|
||||||
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */
|
/* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
|
||||||
|
* правая грань loose живёт в соседе). ОБЕ страницы —
|
||||||
|
* иначе на одной застывает дрожащий кадр → мерцание. */
|
||||||
|
pop_set_redraw(pos, POP_RD_LOOSE, 2);
|
||||||
} else {
|
} else {
|
||||||
pop_loose_shake_draw(row, col); /* дрожащий кадр */
|
pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
|
||||||
}
|
}
|
||||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||||
@@ -1228,12 +1264,11 @@ void pop_loose_tick(void)
|
|||||||
* (стена-вниз от тайла снизу + чёрный фон). */
|
* (стена-вниз от тайла снизу + чёрный фон). */
|
||||||
g_fg[pos] = TILE_EMPTY;
|
g_fg[pos] = TILE_EMPTY;
|
||||||
pop_loose_modif[pos] = 0;
|
pop_loose_modif[pos] = 0;
|
||||||
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
|
||||||
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
|
pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
|
||||||
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */
|
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
|
||||||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
|
|
||||||
} else { /* отсчёт 1..10 — дрожащий кадр */
|
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||||||
pop_loose_shake_draw(row, col);
|
pop_set_redraw(pos, POP_RD_LOOSE, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
||||||
@@ -1242,27 +1277,21 @@ void pop_loose_tick(void)
|
|||||||
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
|
||||||
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
|
||||||
for (col = 0; col < 10; col++) {
|
for (col = 0; col < 10; col++) {
|
||||||
if (ceil_rest[col]) {
|
if (pop_ceil_modif[col] != 0) {
|
||||||
pop_ceil_shake_draw(col); /* modif уже 0 → кадр покоя */
|
|
||||||
ceil_rest[col]--;
|
|
||||||
} else if (pop_ceil_modif[col] != 0) {
|
|
||||||
uint8_t m = ++pop_ceil_modif[col];
|
uint8_t m = ++pop_ceil_modif[col];
|
||||||
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
|
||||||
if (m >= 0x84) { pop_ceil_modif[col] = 0; ceil_rest[col] = 2; }
|
if (m >= 0x84) { pop_ceil_modif[col] = 0;
|
||||||
else pop_ceil_shake_draw(col);
|
pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
|
||||||
|
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
|
||||||
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
|
||||||
pop_ceil_modif[col] = 0;
|
pop_ceil_modif[col] = 0;
|
||||||
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
|
||||||
pop_ceil_bake_empty(col); /* колодец наверх на ТЕКУЩЕЙ странице */
|
pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
|
||||||
ceil_bake[col] = 1; /* + вторая страница следующим кадром */
|
|
||||||
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
|
||||||
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
|
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
|
||||||
} else {
|
} else {
|
||||||
pop_ceil_shake_draw(col); /* отсчёт 1..10 — дрожащий кадр */
|
pop_set_redraw_above(col, POP_RDA_CEIL, 1); /* отсчёт 1..10 */
|
||||||
}
|
}
|
||||||
} else if (ceil_bake[col]) {
|
|
||||||
pop_ceil_bake_empty(col);
|
|
||||||
ceil_bake[col]--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||||
@@ -1270,34 +1299,22 @@ void pop_loose_tick(void)
|
|||||||
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
|
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
|
||||||
* разбором нового: иначе при двух плитах, севших подряд, land_bake
|
* разбором нового: иначе при двух плитах, севших подряд, land_bake
|
||||||
* затирался новым и на одной странице оставался старый пол. */
|
* затирался новым и на одной странице оставался старый пол. */
|
||||||
if (land_bake && pop_loose_landed) {
|
|
||||||
uint8_t r = 0, c = land_bake_pos;
|
|
||||||
while (c >= 10) { c -= 10; r++; }
|
|
||||||
pop_floor_bake(r, c);
|
|
||||||
land_bake = 0;
|
|
||||||
}
|
|
||||||
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
|
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
|
||||||
uint8_t pos = (uint8_t)(pop_loose_landed - 1);
|
uint8_t pos = (uint8_t)(pop_loose_landed - 1);
|
||||||
uint8_t r = 0, c = pos;
|
uint8_t r = 0, c = pos;
|
||||||
pop_loose_landed = 0;
|
pop_loose_landed = 0;
|
||||||
while (c >= 10) { c -= 10; r++; }
|
while (c >= 10) { c -= 10; r++; }
|
||||||
g_fg[pos] = TILE_DEBRIS;
|
g_fg[pos] = TILE_DEBRIS;
|
||||||
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не bake_empty: та стирает и верх стены
|
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не LOOSE_GONE: та стирает и верх стены
|
||||||
* ряда ниже — оставался чёрный прямоугольник под тайлом). */
|
* ряда ниже — оставался чёрный прямоугольник под тайлом). Обе
|
||||||
pop_floor_bake(r, c);
|
* страницы дабл-буфера. */
|
||||||
land_bake_pos = pos; land_bake = 1; /* вторая страница дабл-буфера */
|
pop_set_redraw(pos, POP_RD_FLOOR, 2);
|
||||||
do_knock(r); /* удар плиты трясёт loose этого ряда */
|
do_knock(r); /* удар плиты трясёт loose этого ряда */
|
||||||
pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */
|
pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */
|
||||||
} else if (land_bake) {
|
|
||||||
uint8_t r = 0, c = land_bake_pos;
|
|
||||||
while (c >= 10) { c -= 10; r++; }
|
|
||||||
pop_floor_bake(r, c);
|
|
||||||
land_bake = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* y_to_row_mod4 (seg006): ряд по y с оборотом mod4 (TILE_SIZEY=63). */
|
|
||||||
static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
|
|
||||||
|
|
||||||
/* ---- clip_char (seg006:1749), первый блок: обрезка спрайта СВЕРХУ ----- *
|
/* ---- clip_char (seg006:1749), первый блок: обрезка спрайта СВЕРХУ ----- *
|
||||||
* Оригинал перед add_objtable кладёт в objtable прямоугольник клипа; блиттер
|
* Оригинал перед add_objtable кладёт в objtable прямоугольник клипа; блиттер
|
||||||
@@ -1313,7 +1330,7 @@ static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
|
|||||||
* check_spike_below колонки берутся get_tile_div_mod БЕЗ −7.
|
* check_spike_below колонки берутся get_tile_div_mod БЕЗ −7.
|
||||||
*
|
*
|
||||||
* Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */
|
* Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */
|
||||||
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h)
|
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked
|
||||||
{
|
{
|
||||||
static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 };
|
static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 };
|
||||||
int wh = ((int)w + 1) >> 1; /* char_width_half */
|
int wh = ((int)w + 1) >> 1; /* char_width_half */
|
||||||
@@ -1326,7 +1343,7 @@ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h)
|
|||||||
xr = xl + wh; /* char_x_right */
|
xr = xl + wh; /* char_x_right */
|
||||||
top_y = obj_y - (int)h + 1; /* char_top_y */
|
top_y = obj_y - (int)h + 1; /* char_top_y */
|
||||||
if (top_y >= 192) top_y = 0; /* весь спрайт ниже комнаты */
|
if (top_y >= 192) top_y = 0; /* весь спрайт ниже комнаты */
|
||||||
trow = y_to_row((int16_t)top_y); /* char_top_row */
|
trow = pop_y_to_row((int16_t)top_y); /* char_top_row */
|
||||||
cL = get_tile_div_mod(xl); if (cL < 0) cL = 0;
|
cL = get_tile_div_mod(xl); if (cL < 0) cL = 0;
|
||||||
cR = get_tile_div_mod(xr); if (cR > 9) cR = 9;
|
cR = get_tile_div_mod(xr); if (cR > 9) cR = 9;
|
||||||
|
|
||||||
@@ -1364,13 +1381,13 @@ static void check_leave_below(void)
|
|||||||
Kid.action != ACT_MIDAIR &&
|
Kid.action != ACT_MIDAIR &&
|
||||||
(int8_t)Kid.y < 10 && (int8_t)Kid.y > -16) {
|
(int8_t)Kid.y < 10 && (int8_t)Kid.y > -16) {
|
||||||
Kid.y = (uint8_t)(Kid.y + 189);
|
Kid.y = (uint8_t)(Kid.y + 189);
|
||||||
Kid.curr_row = y_to_row((int16_t)Kid.y);
|
Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
|
||||||
pop_leave_dir = 3;
|
pop_leave_dir = 3;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Kid.y >= 211) {
|
if (Kid.y >= 211) {
|
||||||
Kid.y = (uint8_t)(Kid.y - 189);
|
Kid.y = (uint8_t)(Kid.y - 189);
|
||||||
Kid.curr_row = y_to_row((int16_t)Kid.y);
|
Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
|
||||||
pop_fell_out = 1;
|
pop_fell_out = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1456,13 +1473,13 @@ static int is_spike_harmful(uint8_t tilepos)
|
|||||||
static void spiked(uint8_t tilepos)
|
static void spiked(uint8_t tilepos)
|
||||||
{
|
{
|
||||||
pop_trob_modif(g_room)[tilepos] = 0xFF;
|
pop_trob_modif(g_room)[tilepos] = 0xFF;
|
||||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
|
||||||
Kid.x = (uint8_t)(x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10);
|
Kid.x = (uint8_t)(pop_x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10);
|
||||||
Kid.x = (uint8_t)char_dx_forward(8);
|
Kid.x = (uint8_t)char_dx_forward(8);
|
||||||
Kid.fall_y = 0;
|
Kid.fall_y = 0;
|
||||||
take_hp(100); /* (TODO: sound_48_spiked) */
|
take_hp(100); /* (TODO: sound_48_spiked) */
|
||||||
kid_set_seq(SEQ_51_SPIKED);
|
kid_set_seq(SEQ_51_SPIKED);
|
||||||
play_seq();
|
pop_kid_play();
|
||||||
determine_col();
|
determine_col();
|
||||||
pop_kid_dead = 1;
|
pop_kid_dead = 1;
|
||||||
}
|
}
|
||||||
@@ -1514,7 +1531,7 @@ static void check_spike_below(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_phys_tick(void)
|
void pop_phys_tick(void) __banked
|
||||||
{
|
{
|
||||||
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
|
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
|
||||||
if (grab_timer) grab_timer--;
|
if (grab_timer) grab_timer--;
|
||||||
|
|||||||
@@ -17,25 +17,32 @@
|
|||||||
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив
|
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив
|
||||||
* МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию,
|
* МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию,
|
||||||
* что и в pop_room_draw, чтобы отрисовка видела изменения. */
|
* что и в pop_room_draw, чтобы отрисовка видела изменения. */
|
||||||
void pop_map_set(uint8_t *fg);
|
void pop_map_set(uint8_t *fg) __banked;
|
||||||
|
|
||||||
/* Связи комнаты (0=нет соседа; иначе номер) + кромки швов для коллизии:
|
/* Связи комнаты (0=нет соседа; иначе номер) + кромки швов для коллизии:
|
||||||
* lcol_fg[6] = col9 левого соседа (за col0) в [0..2] и его col8 (колонка −2)
|
* lcol_fg[6] = col9 левого соседа (за col0) в [0..2] и его col8 (колонка −2)
|
||||||
* в [3..5]; rcol_fg[6] симметрично (col0 / col1 = колонки 10 и 11). Вторая
|
* в [3..5]; rcol_fg[6] симметрично (col0 / col1 = колонки 10 и 11). Вторая
|
||||||
* колонка нужна, когда Kid стоит В шве и смотрит наружу. Звать в enter_room. */
|
* колонка нужна, когда Kid стоит В шве и смотрит наружу. Звать в enter_room. */
|
||||||
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
|
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
|
||||||
const uint8_t *lcol_fg, const uint8_t *rcol_fg);
|
const uint8_t *lcol_fg, const uint8_t *rcol_fg) __banked;
|
||||||
|
|
||||||
/* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob).
|
/* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob).
|
||||||
* Звать в enter_room. */
|
* Звать в enter_room. */
|
||||||
void pop_map_set_room(uint8_t room);
|
void pop_map_set_room(uint8_t room) __banked;
|
||||||
|
|
||||||
|
/* Тайл-код текущей комнаты по клетке (col 0..9, row 0..2) — порт
|
||||||
|
* get_tile(room,col,row) для текущей комнаты. Нужен лучу видимости стража
|
||||||
|
* (guards.c): он идёт по ряду между Кидом и стражем. */
|
||||||
|
uint8_t pop_tile_at(int8_t col, int8_t row) __banked;
|
||||||
|
|
||||||
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
|
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
|
||||||
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
|
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
|
||||||
* респавн). */
|
* респавн). */
|
||||||
extern uint8_t pop_kid_dead;
|
extern uint8_t pop_kid_dead;
|
||||||
extern uint8_t hitp_curr;
|
extern uint8_t hitp_curr;
|
||||||
void pop_kid_hp_reset(void);
|
extern uint8_t hitp_max; /* стартовое HP (для индикатора) */
|
||||||
|
extern int8_t hitp_delta; /* урон за кадр от меча; применяет pop_do_delta_hp */
|
||||||
|
void pop_kid_hp_reset(void) __banked;
|
||||||
|
|
||||||
/* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object).
|
/* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object).
|
||||||
* pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или
|
* pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или
|
||||||
@@ -46,8 +53,10 @@ extern uint8_t pop_item_taken;
|
|||||||
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
|
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
|
||||||
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
|
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
|
||||||
extern uint8_t pop_flash_time;
|
extern uint8_t pop_flash_time;
|
||||||
uint8_t pop_get_item_action(void); /* 0 нет / 1 присесть / 2 поднять меч */
|
/* Цвет вспышки: 0 = ярко-жёлтая (подобран меч), 1 = красная (зелье). */
|
||||||
void pop_proc_get_object(void); /* эффект — из play_seq (опкод GET_ITEM) */
|
extern uint8_t pop_flash_red;
|
||||||
|
uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч / 3 пить */
|
||||||
|
void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */
|
||||||
|
|
||||||
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
|
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
|
||||||
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
|
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
|
||||||
@@ -66,8 +75,8 @@ extern uint8_t pop_leave_timer;
|
|||||||
* на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back),
|
* на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back),
|
||||||
* рядом с kid-отрисовкой. */
|
* рядом с kid-отрисовкой. */
|
||||||
extern uint8_t pop_loose_modif[30];
|
extern uint8_t pop_loose_modif[30];
|
||||||
void pop_loose_tick(void);
|
void pop_loose_tick(void) __banked;
|
||||||
void pop_loose_reset(void); /* сброс loose-состояния при смене комнаты */
|
void pop_loose_reset(void) __banked; /* сброс loose-состояния при смене комнаты */
|
||||||
|
|
||||||
/* Установлен в tilepos+1 в кадре, когда loose-пол упал (кусок улетел ВНИЗ).
|
/* Установлен в tilepos+1 в кадре, когда loose-пол упал (кусок улетел ВНИЗ).
|
||||||
* Приложение: определить debris-приземление в комнате снизу. Снять (=0). */
|
* Приложение: определить debris-приземление в комнате снизу. Снять (=0). */
|
||||||
@@ -81,7 +90,7 @@ extern uint8_t pop_loose_fell;
|
|||||||
* pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности
|
* pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности
|
||||||
* между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты,
|
* между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты,
|
||||||
* pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */
|
* pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */
|
||||||
void pop_map_set_above(uint8_t *above_row2_fg);
|
void pop_map_set_above(uint8_t *above_row2_fg) __banked;
|
||||||
extern uint8_t pop_ceil_modif[10];
|
extern uint8_t pop_ceil_modif[10];
|
||||||
extern uint8_t pop_ceil_fell;
|
extern uint8_t pop_ceil_fell;
|
||||||
extern uint8_t pop_debris_at;
|
extern uint8_t pop_debris_at;
|
||||||
@@ -92,7 +101,7 @@ extern uint8_t pop_kid_hurt;
|
|||||||
|
|
||||||
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
|
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
|
||||||
* приземления/удара. Зовётся из pop_phys_tick. */
|
* приземления/удара. Зовётся из pop_phys_tick. */
|
||||||
void pop_check_knock(void);
|
void pop_check_knock(void) __banked;
|
||||||
|
|
||||||
/* Установлен в 1, когда Kid пересёк НИЖНЮЮ границу комнаты (провал в дыру
|
/* Установлен в 1, когда Kid пересёк НИЖНЮЮ границу комнаты (провал в дыру
|
||||||
* или спуск-зацеп с кромки). Координаты Kid УЖЕ репроецированы в кадр
|
* или спуск-зацеп с кромки). Координаты Kid УЖЕ репроецированы в кадр
|
||||||
@@ -104,14 +113,14 @@ extern uint8_t pop_fell_out;
|
|||||||
/* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed +
|
/* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed +
|
||||||
* determine_col + check_action (триггер падения / do_fall / приземление).
|
* determine_col + check_action (триггер падения / do_fall / приземление).
|
||||||
* Порядок как в оригинале (seg000 play_kid_frame). */
|
* Порядок как в оригинале (seg000 play_kid_frame). */
|
||||||
void pop_phys_tick(void);
|
void pop_phys_tick(void) __banked;
|
||||||
|
|
||||||
/* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл
|
/* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл
|
||||||
* над головой — стена/пол, всё выше линии своей полосы не рисуется (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 = клипа нет. Зовёт kid_draw. */
|
||||||
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h);
|
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked;
|
||||||
|
|
||||||
/* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после
|
/* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после
|
||||||
* pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */
|
* pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */
|
||||||
@@ -120,26 +129,26 @@ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h);
|
|||||||
#define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */
|
#define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */
|
||||||
|
|
||||||
/* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */
|
/* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */
|
||||||
int pop_edge_distance(void);
|
int pop_edge_distance(void) __banked;
|
||||||
uint8_t pop_edge_type(void);
|
uint8_t pop_edge_type(void) __banked;
|
||||||
|
|
||||||
/* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной
|
/* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной
|
||||||
* позиции (передний край у стены, симметрично) и возвращает 1. Для
|
* позиции (передний край у стены, симметрично) и возвращает 1. Для
|
||||||
* control_running (чистый стоп у стены). */
|
* control_running (чистый стоп у стены). */
|
||||||
int pop_wall_ahead(void);
|
int pop_wall_ahead(void) __banked;
|
||||||
|
|
||||||
/* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО
|
/* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО
|
||||||
* прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x.
|
* прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x.
|
||||||
* check_jump_up/grab_up (K4.4). control_up гасит caller. */
|
* check_jump_up/grab_up (K4.4). control_up гасит caller. */
|
||||||
uint8_t pop_jump_up_seq(void);
|
uint8_t pop_jump_up_seq(void) __banked;
|
||||||
|
|
||||||
/* ---- Вис/зацеп/спуск (K4.2/K4.3) — тайловые решения для pop_ctrl --- *
|
/* ---- Вис/зацеп/спуск (K4.2/K4.3) — тайловые решения для pop_ctrl --- *
|
||||||
* Управление (control_hanging/can_climb_up/hang_fall/down_pressed) в
|
* Управление (control_hanging/can_climb_up/hang_fall/down_pressed) в
|
||||||
* pop_ctrl; здесь — предикаты + выбор seq (+ выравнивание Kid.x). */
|
* pop_ctrl; здесь — предикаты + выбор seq (+ выравнивание Kid.x). */
|
||||||
uint8_t pop_hang_grab_ready(void); /* grab_timer истёк — можно climb-up */
|
uint8_t pop_hang_grab_ready(void) __banked; /* grab_timer истёк — можно climb-up */
|
||||||
uint8_t pop_hang_climb_up_seq(void); /* seq_10 / seq_73 (подтягивание) */
|
uint8_t pop_hang_climb_up_seq(void) __banked; /* seq_10 / seq_73 (подтягивание) */
|
||||||
int8_t pop_hang_wall(void); /* Shift в висе: 1=у стены,-1=fall,0=держ.*/
|
int8_t pop_hang_wall(void) __banked; /* Shift в висе: 1=у стены,-1=fall,0=держ.*/
|
||||||
uint8_t pop_hang_fall_seq(void); /* seq_23 / seq_11 (сорваться), +Kid.x */
|
uint8_t pop_hang_fall_seq(void) __banked; /* seq_23 / seq_11 (сорваться), +Kid.x */
|
||||||
uint8_t pop_down_action(void); /* 1=обработано(спуск/отступ), 0=присед */
|
uint8_t pop_down_action(void) __banked; /* 1=обработано(спуск/отступ), 0=присед */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* pop_redraw.c — пометки перерисовки тайлов и их разбор. См. pop_redraw.h.
|
||||||
|
*
|
||||||
|
* Живёт в W1/W2 (НЕ в резиденте W3): пометки ставит логика, которая должна
|
||||||
|
* оставаться достижимой из будущих __banked-модулей. Сам разбор зовёт
|
||||||
|
* pop_bg (резидент W3) — это законно, потому что разбор дёргает только
|
||||||
|
* главный цикл.
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pop_redraw.h"
|
||||||
|
#include "pop_bg.h"
|
||||||
|
|
||||||
|
#define NTILES 30
|
||||||
|
#define NCOLS 10
|
||||||
|
|
||||||
|
static uint8_t rd_kind[NTILES], rd_cnt[NTILES];
|
||||||
|
static uint8_t rda_kind[NCOLS], rda_cnt[NCOLS];
|
||||||
|
/* Сколько всего непустых пометок: без него проход по 40 ячейкам крутился бы
|
||||||
|
* КАЖДЫЙ кадр впустую (замер 2026-07-27 на прежнем rest-цикле: ~12 % кадра). */
|
||||||
|
static uint8_t rd_pending;
|
||||||
|
|
||||||
|
void pop_redraw_reset(void)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < NTILES; i++) { rd_kind[i] = 0; rd_cnt[i] = 0; }
|
||||||
|
for (i = 0; i < NCOLS; i++) { rda_kind[i] = 0; rda_cnt[i] = 0; }
|
||||||
|
rd_pending = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages)
|
||||||
|
{
|
||||||
|
if (tilepos >= NTILES) return;
|
||||||
|
if (!rd_cnt[tilepos]) rd_pending++;
|
||||||
|
rd_kind[tilepos] = kind;
|
||||||
|
if (pages > rd_cnt[tilepos]) rd_cnt[tilepos] = pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages)
|
||||||
|
{
|
||||||
|
if (col >= NCOLS) return;
|
||||||
|
if (!rda_cnt[col]) rd_pending++;
|
||||||
|
rda_kind[col] = kind;
|
||||||
|
if (pages > rda_cnt[col]) rda_cnt[col] = pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* redraw_needed (seg008:0178): пройти пометки и нарисовать. row/col ведём
|
||||||
|
* бегущими счётчиками, а не через i/10 и i%10 — у Z80 нет деления, и SDCC
|
||||||
|
* выносит __divuchar/__moduchar в вершину цикла как общее подвыражение
|
||||||
|
* аргументов (30*2 делений за кадр даже когда рисовать нечего). */
|
||||||
|
void pop_redraw_needed(void)
|
||||||
|
{
|
||||||
|
uint8_t i, row = 0, col = 0, left = 0;
|
||||||
|
if (!rd_pending) return;
|
||||||
|
for (i = 0; i < NTILES; i++) {
|
||||||
|
if (rd_cnt[i]) {
|
||||||
|
switch (rd_kind[i]) {
|
||||||
|
case POP_RD_SPIKE: pop_spike_redraw(row, col); break;
|
||||||
|
case POP_RD_LOOSE: pop_loose_shake_draw(row, col); break;
|
||||||
|
case POP_RD_LOOSE_GONE: pop_loose_bake_empty(row, col); break;
|
||||||
|
case POP_RD_FLOOR: pop_floor_bake(row, col); break;
|
||||||
|
case POP_RD_LEVELDOOR: pop_leveldoor_redraw(row, col); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (--rd_cnt[i]) left++;
|
||||||
|
else rd_kind[i] = 0;
|
||||||
|
}
|
||||||
|
if (++col == 10) { col = 0; row++; }
|
||||||
|
}
|
||||||
|
for (i = 0; i < NCOLS; i++) {
|
||||||
|
if (rda_cnt[i]) {
|
||||||
|
if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i);
|
||||||
|
else pop_ceil_bake_empty(i);
|
||||||
|
if (--rda_cnt[i]) left++;
|
||||||
|
else rda_kind[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rd_pending = left;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* pop_redraw.h — пометки «перерисовать тайл» и их разбор.
|
||||||
|
*
|
||||||
|
* Порт архитектуры оригинала: логика анимации НИЧЕГО не рисует, она ставит
|
||||||
|
* флаг (`set_redraw_full`/`set_wipe`/`redraw_20h`/`redraw_21h`, seg007), а
|
||||||
|
* отрисовка идёт отдельным проходом (`redraw_needed`, seg008:0178). У нас
|
||||||
|
* так же: pop_map/pop_trob зовут pop_set_redraw*, а pop_redraw_needed()
|
||||||
|
* (главный цикл) разбирает пометки и дёргает pop_bg.
|
||||||
|
*
|
||||||
|
* ЗАЧЕМ (кроме сходства с SDLPoP): из __banked-кода резидентная страница W3
|
||||||
|
* недостижима — ни напрямую, ни транзитивно (docs/layout_plan_v2.md §2 R2).
|
||||||
|
* Пока pop_map звал pop_bg, его нельзя было использовать из банка стражей.
|
||||||
|
* Пометки живут в W1/W2 и зовутся откуда угодно.
|
||||||
|
*
|
||||||
|
* ОТЛИЧИЕ ОТ ОРИГИНАЛА: у нас не один экран с dirty-rect, а две страницы
|
||||||
|
* дабл-буфера со своей ОЗУ-копией фона, поэтому счётчик пометки — это ЧИСЛО
|
||||||
|
* СТРАНИЦ (обычно 2), а вид перерисовки хранится явно: heal+рисовать поверх
|
||||||
|
* (транзиент) или запечь новый фон. В оригинале вид кодируется тем, в какой
|
||||||
|
* из таблиц redraw_frames_* стоит флаг.
|
||||||
|
*/
|
||||||
|
#ifndef POP_REDRAW_H
|
||||||
|
#define POP_REDRAW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Виды перерисовки тайла (row*10+col). */
|
||||||
|
#define POP_RD_SPIKE 1 /* пики: heal + оба тайла (redraw_21h) */
|
||||||
|
#define POP_RD_LOOSE 2 /* loose: дрожащий кадр / кадр покоя */
|
||||||
|
#define POP_RD_LOOSE_GONE 3 /* loose провалился: запечь пусто */
|
||||||
|
#define POP_RD_FLOOR 4 /* пол изменился: кнопка/щебень/поднятый меч */
|
||||||
|
#define POP_RD_LEVELDOOR 5 /* створка двери уровня (ПРАВАЯ половина) */
|
||||||
|
|
||||||
|
/* Виды перерисовки полосы у потолка (по колонке). */
|
||||||
|
#define POP_RDA_CEIL 1 /* плита-потолок: дрожащий кадр / покой */
|
||||||
|
#define POP_RDA_CEIL_GONE 2 /* плита-потолок провалилась: колодец */
|
||||||
|
|
||||||
|
/* Пометить тайл/колонку: kind — вид, pages — на скольких страницах
|
||||||
|
* дабл-буфера перерисовать (1 = только текущая, 2 = обе). Повторная
|
||||||
|
* пометка перекрывает вид и берёт максимум счётчика. */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и
|
||||||
|
* уменьшить счётчики. Звать из главного цикла в слое ФОНА — до kid_draw. */
|
||||||
|
void pop_redraw_needed(void);
|
||||||
|
|
||||||
|
/* Сбросить все пометки (смена комнаты/уровня). */
|
||||||
|
void pop_redraw_reset(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* pop_state.c — данные, которые читают ОБЕ стороны банковой границы.
|
||||||
|
*
|
||||||
|
* Зачем отдельный модуль: sprinter-cc собирает банковый модуль с
|
||||||
|
* --dataseg BANKn, поэтому ВСЯ его писучая память (глобалы, file-static,
|
||||||
|
* статики внутри функций) линкуется В СТРАНИЦУ БАНКА и снаружи не читается
|
||||||
|
* — проверено на .map (2026-07-30: глобал банкового модуля лёг по
|
||||||
|
* 0x0001C000, а не в _DATA). Значит любую переменную, к которой нужен
|
||||||
|
* доступ и из банка, и снаружи, надо держать в НЕбанковом модуле — здесь.
|
||||||
|
*
|
||||||
|
* Модуль обязан оставаться в W1/W2: его читают и главный цикл, и банки.
|
||||||
|
*/
|
||||||
|
#include "pop_state.h"
|
||||||
|
|
||||||
|
uint8_t pop_loose_landed;
|
||||||
|
|
||||||
|
/* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара,
|
||||||
|
* но HP не теряет — иначе проверять боёвку приходится с трёх попаданий. */
|
||||||
|
uint8_t pop_immortal;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* pop_state.h — состояние, общее для банков и W1/W2. См. pop_state.c.
|
||||||
|
*
|
||||||
|
* Правило: если переменную читают/пишут по обе стороны банковой границы —
|
||||||
|
* её место здесь, а НЕ в банковом модуле (там она уедет в страницу банка).
|
||||||
|
*/
|
||||||
|
#ifndef POP_STATE_H
|
||||||
|
#define POP_STATE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе
|
||||||
|
* tilepos+1 тайла, на который он лёг. Ставит отрисовка mob (банк),
|
||||||
|
* разбирает логика loose-полов (pop_map, W1/W2). */
|
||||||
|
extern uint8_t pop_loose_landed;
|
||||||
|
|
||||||
|
/* Чит «бессмертие» (I): Кида бьют, но HP не убавляется. Читают пути урона
|
||||||
|
* (боёвка в банке, пики в pop_map), переключает главный цикл. */
|
||||||
|
extern uint8_t pop_immortal;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -5,7 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
#include "pop_trob.h"
|
#include "pop_trob.h"
|
||||||
#include "pop_level.h"
|
#include "pop_level.h"
|
||||||
#include "pop_bg.h" /* pop_spike_redraw */
|
#include "pop_geom.h"
|
||||||
|
#include "pop_redraw.h" /* пометки перерисовки тайлов (вместо прямых вызовов) */
|
||||||
|
/* pop_bg нужен ТОЛЬКО для пламени факела и пузырька зелья: это не тайловая
|
||||||
|
* перерисовка, а покадровый оверлей поверх фона (в оригинале — свои
|
||||||
|
* add_backtable/add_foretable внутри draw_tile). Всё остальное идёт через
|
||||||
|
* пометки, поэтому pop_process_trobs — единственная функция модуля, которую
|
||||||
|
* нельзя звать из __banked-кода. */
|
||||||
|
#include "pop_bg.h"
|
||||||
|
|
||||||
/* Коды тайлов (fg & 0x1F), которые анимируются. */
|
/* Коды тайлов (fg & 0x1F), которые анимируются. */
|
||||||
#define TILE_SPIKE 0x02
|
#define TILE_SPIKE 0x02
|
||||||
@@ -30,11 +37,6 @@ static trob_t trobs[TROBS_MAX];
|
|||||||
static uint8_t trobs_count;
|
static uint8_t trobs_count;
|
||||||
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
|
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
|
||||||
|
|
||||||
/* Отложенная перерисовка «покоя» законченных пик/кнопок текущей комнаты
|
|
||||||
* (2 кадра = обе страницы дабл-буфера, как loose_rest). */
|
|
||||||
static uint8_t spike_rest[POP_ROOMTILES];
|
|
||||||
static uint8_t button_rest[POP_ROOMTILES];
|
|
||||||
|
|
||||||
/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
|
/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
|
||||||
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
|
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
|
||||||
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
|
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
|
||||||
@@ -43,36 +45,19 @@ static uint8_t button_rest[POP_ROOMTILES];
|
|||||||
* расхождение было именно в безусловном редрое). */
|
* расхождение было именно в безусловном редрое). */
|
||||||
static uint8_t trob_drawn[POP_ROOMTILES];
|
static uint8_t trob_drawn[POP_ROOMTILES];
|
||||||
|
|
||||||
/* Сколько тайлов ждут отложенного редроя (spike_rest/button_rest). Без
|
|
||||||
* него цикл по 30 тайлам крутился КАЖДЫЙ кадр впустую: замер в MAME
|
|
||||||
* (2026-07-27) — служебная часть pop_process_trobs ~50 000 тактов при
|
|
||||||
* кадре 430 080, то есть ~12 % кадра на пустой проход. */
|
|
||||||
static uint8_t rest_pending;
|
|
||||||
|
|
||||||
/* Дверь уровня: отложенный редрой ВТОРОЙ страницы дабл-буфера. Створка
|
|
||||||
* анимируется покадрово в back-страницу, поэтому последний кадр анимации
|
|
||||||
* попадает только на одну из двух страниц — вторая застревает на шаг раньше
|
|
||||||
* и дверь «дрожит» через кадр. ldoor_pos = tilepos ПРАВОЙ половины. */
|
|
||||||
static uint8_t ldoor_rest, ldoor_pos;
|
|
||||||
|
|
||||||
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
|
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
|
||||||
* выбора следующего кадра пламени. */
|
* выбора следующего кадра пламени. */
|
||||||
static unsigned long trob_seed;
|
static pop_rnd_t trob_seed;
|
||||||
|
|
||||||
static uint16_t trob_prandom(uint16_t maxv)
|
#define trob_prandom(maxv) pop_prandom(&trob_seed, (maxv))
|
||||||
{
|
|
||||||
trob_seed = trob_seed * 214013UL + 2531011UL;
|
|
||||||
return (uint16_t)((uint16_t)(trob_seed >> 16) % (uint16_t)(maxv + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void pop_trob_reset(void)
|
void pop_trob_reset(void)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
|
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
|
||||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
|
||||||
rest_pending = 0;
|
pop_redraw_reset();
|
||||||
ldoor_rest = 0;
|
|
||||||
trobs_count = 0;
|
trobs_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +101,8 @@ void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
|
|||||||
void pop_trob_room_changed(void)
|
void pop_trob_room_changed(void)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
|
||||||
rest_pending = 0;
|
pop_redraw_reset();
|
||||||
ldoor_rest = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
|
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
|
||||||
@@ -361,38 +345,35 @@ uint8_t pop_trob_room_active(uint8_t room)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t torch_tick; /* делитель скорости анимации пламени */
|
|
||||||
|
/* Темп анимации пламени в ЛОГИЧЕСКИХ кадрах (один логический кадр = 3
|
||||||
|
* vsync и соответствует игровому тику оригинала):
|
||||||
|
* 1 — кадр пламени каждый тик, как в оригинале; кадры при этом ровные по
|
||||||
|
* тактам, что нужно для счёта бюджета;
|
||||||
|
* 2 — вдвое реже (так было до 2026-07-30; пламя шло на половинной
|
||||||
|
* скорости, а бюджет кадра «прыгал» на 6.2 % от того, попал кадр на
|
||||||
|
* анимацию или нет). */
|
||||||
|
#define TORCH_ANIM_DIV 1
|
||||||
|
|
||||||
|
#if TORCH_ANIM_DIV > 1
|
||||||
|
static uint8_t torch_tick;
|
||||||
|
#define TORCH_ANIM_NOW ((uint8_t)(torch_tick % TORCH_ANIM_DIV) == 0)
|
||||||
|
#else
|
||||||
|
#define TORCH_ANIM_NOW 1
|
||||||
|
#endif
|
||||||
|
|
||||||
void pop_process_trobs(uint8_t cur_room)
|
void pop_process_trobs(uint8_t cur_room)
|
||||||
{
|
{
|
||||||
uint8_t i, w = 0;
|
uint8_t i, w = 0;
|
||||||
|
#if TORCH_ANIM_DIV > 1
|
||||||
torch_tick++;
|
torch_tick++;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
|
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
|
||||||
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
|
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
|
||||||
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
|
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
|
||||||
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
|
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
|
||||||
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
|
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
|
||||||
if (rest_pending) {
|
|
||||||
uint8_t row = 0, col = 0, left = 0;
|
|
||||||
for (i = 0; i < POP_ROOMTILES; i++) {
|
|
||||||
if (spike_rest[i]) {
|
|
||||||
pop_spike_redraw(row, col);
|
|
||||||
if (--spike_rest[i]) left++;
|
|
||||||
}
|
|
||||||
if (button_rest[i]) {
|
|
||||||
pop_button_redraw(row, col);
|
|
||||||
if (--button_rest[i]) left++;
|
|
||||||
}
|
|
||||||
if (++col == 10) { col = 0; row++; }
|
|
||||||
}
|
|
||||||
rest_pending = left;
|
|
||||||
}
|
|
||||||
if (ldoor_rest) { /* доложить финальный кадр створки на 2-ю страницу */
|
|
||||||
pop_leveldoor_redraw(ldoor_pos / 10, ldoor_pos % 10);
|
|
||||||
ldoor_rest--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trobs_count == 0) return;
|
if (trobs_count == 0) return;
|
||||||
|
|
||||||
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
|
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
|
||||||
@@ -430,11 +411,9 @@ void pop_process_trobs(uint8_t cur_room)
|
|||||||
break;
|
break;
|
||||||
case TILE_TORCH:
|
case TILE_TORCH:
|
||||||
case TILE_TORCH_DEBRIS:
|
case TILE_TORCH_DEBRIS:
|
||||||
/* animate_torch (seg007:03C1): кадр пламени; анимация вечная —
|
/* animate_torch (seg007:03C1): анимация вечная — type остаётся
|
||||||
* type остаётся 1, пока комната отрисована. Смена кадра — через
|
* 1, пока комната отрисована. Темп — TORCH_ANIM_DIV. */
|
||||||
* ЛОГИЧЕСКИЙ кадр (torch_tick): у нас кадр короче игрового тика
|
if (room == cur_room && TORCH_ANIM_NOW)
|
||||||
* оригинала, и пламя «кипело» вдвое быстрее нужного. */
|
|
||||||
if (room == cur_room && (torch_tick & 1) == 0)
|
|
||||||
mod[tp] = get_torch_frame(mod[tp]);
|
mod[tp] = get_torch_frame(mod[tp]);
|
||||||
break;
|
break;
|
||||||
case TILE_POTION:
|
case TILE_POTION:
|
||||||
@@ -477,24 +456,20 @@ void pop_process_trobs(uint8_t cur_room)
|
|||||||
* неизменной сигнатурой, и стёртое уже не возвращается: рядом с
|
* неизменной сигнатурой, и стёртое уже не возвращается: рядом с
|
||||||
* Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной
|
* Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной
|
||||||
* странице дабл-буфера из двух). */
|
* странице дабл-буфера из двух). */
|
||||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
/* Последний кадр (пика убралась, trob умирает) — пометить ОБЕ
|
||||||
pop_spike_redraw(trow, tcol);
|
* страницы: живой trob метит каждый кадр по одной. */
|
||||||
/* Последний кадр (пика убралась, trob умирает) — на ВТОРУЮ страницу
|
pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1));
|
||||||
* его уже никто не положит: ставим отложенный редрой. */
|
|
||||||
if (type < 0) { spike_rest[tp] = 1; rest_pending++; }
|
|
||||||
}
|
}
|
||||||
if (room == cur_room && code == TILE_LEVELDOOR) {
|
if (room == cur_room && code == TILE_LEVELDOOR) {
|
||||||
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
|
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
|
||||||
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
|
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
|
||||||
* kid_heal вернул бы из фона закрытую створку. Куски двери
|
* kid_heal вернул бы из фона закрытую створку. Куски двери
|
||||||
* непрозрачны, поэтому wipe не нужен. */
|
* непрозрачны, поэтому wipe не нужен. */
|
||||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
/* Створку рисует ПРАВАЯ половина двери; последний кадр анимации
|
||||||
if (tcol + 1 < 10) {
|
* помечаем на обе страницы, иначе дверь дрожит через кадр. */
|
||||||
pop_leveldoor_redraw(trow, tcol + 1);
|
if (tp % 10 + 1 < 10)
|
||||||
/* Анимация кончилась — этот кадр лёг только на ОДНУ страницу;
|
pop_set_redraw((uint8_t)(tp + 1), POP_RD_LEVELDOOR,
|
||||||
* повторить его на второй, иначе дверь дрожит через кадр. */
|
(uint8_t)(type < 0 ? 2 : 1));
|
||||||
if (type < 0) { ldoor_pos = (uint8_t)(tp + 1); ldoor_rest = 1; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (room == cur_room && code == TILE_SWORD) {
|
if (room == cur_room && code == TILE_SWORD) {
|
||||||
/* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1):
|
/* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1):
|
||||||
@@ -503,9 +478,7 @@ void pop_process_trobs(uint8_t cur_room)
|
|||||||
* следующим кадром через rest-цикл. */
|
* следующим кадром через rest-цикл. */
|
||||||
uint8_t sig = (uint8_t)(mod[tp] == 1);
|
uint8_t sig = (uint8_t)(mod[tp] == 1);
|
||||||
if (sig != trob_drawn[tp]) {
|
if (sig != trob_drawn[tp]) {
|
||||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
|
||||||
pop_button_redraw(trow, tcol);
|
|
||||||
button_rest[tp] = 1; rest_pending++;
|
|
||||||
trob_drawn[tp] = sig;
|
trob_drawn[tp] = sig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -517,9 +490,7 @@ void pop_process_trobs(uint8_t cur_room)
|
|||||||
* считаем только в момент редроя (редко), а не каждый кадр. */
|
* считаем только в момент редроя (редко), а не каждый кадр. */
|
||||||
uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
||||||
if (sig != trob_drawn[tp]) {
|
if (sig != trob_drawn[tp]) {
|
||||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
|
||||||
pop_button_redraw(trow, tcol);
|
|
||||||
button_rest[tp] = 1; rest_pending++;
|
|
||||||
trob_drawn[tp] = sig;
|
trob_drawn[tp] = sig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,48 @@
|
|||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
#include "pop_level.h"
|
#include "pop_level.h"
|
||||||
#include "pop_trob.h"
|
#include "pop_trob.h"
|
||||||
|
#include "pop_redraw.h"
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_cheat.h"
|
||||||
|
|
||||||
__sfr __at (0xFE) io_border;
|
__sfr __at (0xFE) io_border;
|
||||||
|
|
||||||
|
/* ПРОФИЛИРОВАНИЕ ПОЛОСАМИ БОРДЮРА (включено по умолчанию; make PROF=0 —
|
||||||
|
* выключить). Цвет бордюра держится, пока фаза считает, поэтому высота
|
||||||
|
* полосы на скриншоте = время фазы в растровых строках. Это единственный
|
||||||
|
* способ смотреть профиль без дебаггера: выборка PC вырождается (мост MAME
|
||||||
|
* отвечает из фреймового колбэка, все сэмплы падают в обработчик
|
||||||
|
* прерывания); точные такты — breakpoint'ы с {printf totalcycles; g}.
|
||||||
|
*
|
||||||
|
* ЦВЕТА: у бордюра Sprinter бит 0 (красный) ИГНОРИРУЕТСЯ, работают только
|
||||||
|
* бит 1 (синий) и бит 2 (зелёный) — значит различимых состояний ровно
|
||||||
|
* ЧЕТЫРЕ, и значения обязаны быть чётными. Отсюда фазы сгруппированы по
|
||||||
|
* четыре, а не по семь (нечётные номера раньше сливались с соседями и
|
||||||
|
* полосы не читались):
|
||||||
|
* 0 чёрный — ожидание vsync (запас кадра)
|
||||||
|
* 2 синий — ввод, heal и ЛОГИКА (kid_tick/физика/страж)
|
||||||
|
* 4 зелёный — слой ФОНА (loose/trobs/redraw_needed/шов)
|
||||||
|
* 6 циан — СПРАЙТЫ (страж, Kid, fore поверх них) */
|
||||||
|
#ifndef PROF_BORDER
|
||||||
|
#define PROF_BORDER 1
|
||||||
|
#endif
|
||||||
|
#if PROF_BORDER
|
||||||
|
#define PROF(n) io_border = (n)
|
||||||
|
#else
|
||||||
|
#define PROF(n) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
||||||
|
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
||||||
|
* (коллизия/физика), 4 = pop_gdraw (отрисовка стража + полоса HP).
|
||||||
|
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
||||||
|
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
||||||
|
* уровней, чомперы, зеркало. */
|
||||||
|
const uint8_t n_banks = 4;
|
||||||
|
|
||||||
|
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||||
|
uint8_t pop_cheats;
|
||||||
|
|
||||||
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
|
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
|
||||||
#define SEQ_STAND 2
|
#define SEQ_STAND 2
|
||||||
#define ROOM_1 1
|
#define ROOM_1 1
|
||||||
@@ -39,6 +78,7 @@ __sfr __at (0xFE) io_border;
|
|||||||
* Убирается закомментированием ОДНОЙ строки ниже. */
|
* Убирается закомментированием ОДНОЙ строки ниже. */
|
||||||
#define ROOMNAV 1
|
#define ROOMNAV 1
|
||||||
|
|
||||||
|
|
||||||
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
||||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||||
@@ -130,6 +170,7 @@ static void apply_overrides(uint8_t room, uint8_t *tiles, uint8_t base, uint8_t
|
|||||||
static void enter_room(uint8_t room)
|
static void enter_room(uint8_t room)
|
||||||
{
|
{
|
||||||
uint8_t pg;
|
uint8_t pg;
|
||||||
|
pop_guard_leave(); /* запомнить стража уходящей комнаты */
|
||||||
uint8_t *room_mod;
|
uint8_t *room_mod;
|
||||||
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, rcol_fg, rcol_bg, below_fg);
|
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, rcol_fg, rcol_bg, below_fg);
|
||||||
apply_overrides(room, room_fg, 0, 30); /* щебень/пустота от упавших плит */
|
apply_overrides(room, room_fg, 0, 30); /* щебень/пустота от упавших плит */
|
||||||
@@ -157,9 +198,16 @@ static void enter_room(uint8_t room)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pop_map_set_room(room);
|
pop_map_set_room(room);
|
||||||
|
/* Kid.room — как в оригинале: «комната персонажа». Полная модель
|
||||||
|
* (Kid.room ≠ отрисованной у шва) ещё не портирована, но сравнение
|
||||||
|
* Kid.room == Guard.room нужно уже сейчас: без него луч видимости
|
||||||
|
* стража никогда не срабатывает. */
|
||||||
|
Kid.room = room;
|
||||||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||||||
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||||||
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||||
|
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
|
||||||
|
pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */
|
||||||
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||||||
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
||||||
* а не по свежему backtable. Фаза P0. */
|
* а не по свежему backtable. Фаза P0. */
|
||||||
@@ -197,21 +245,25 @@ int main(void)
|
|||||||
#ifdef ROOMNAV
|
#ifdef ROOMNAV
|
||||||
uint8_t nav_prev = 0;
|
uint8_t nav_prev = 0;
|
||||||
#endif
|
#endif
|
||||||
|
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0;
|
||||||
|
|
||||||
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
|
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
|
||||||
puts("pop_bg_load failed");
|
puts("pop_bg_load failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (pop_kid_data_load("kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
pop_cheats = 1; /* режим разработки: читы включены */
|
||||||
|
pop_guard_reset();
|
||||||
|
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
||||||
printf("kid_data.bin load failed\n");
|
printf("kid_data.bin load failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
|
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
|
||||||
|
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
||||||
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
||||||
puts("kid atlas load failed");
|
puts("kid atlas load failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (pop_level_load("res2001.bin") != 0 && /* сырой уровень 1 в EMM-страницу */
|
if (pop_level_load("LEVELS\\res2001.bin") != 0 && /* сырой уровень 1 в EMM-страницу */
|
||||||
pop_level_load("a:\\res2001.bin") != 0) {
|
pop_level_load("a:\\res2001.bin") != 0) {
|
||||||
puts("level load failed");
|
puts("level load failed");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -219,7 +271,7 @@ int main(void)
|
|||||||
|
|
||||||
initgraph();
|
initgraph();
|
||||||
gfx_set_visible_page(0);
|
gfx_set_visible_page(0);
|
||||||
if (gfx_pal_fload(0, "kid.pal") < 0) /* единая игровая палитра */
|
if (gfx_pal_fload(0, "KID\\kid.pal") < 0) /* единая игровая палитра */
|
||||||
gfx_pal_fload(0, "a:\\kid.pal");
|
gfx_pal_fload(0, "a:\\kid.pal");
|
||||||
gfx_pal_sync(); /* палитра 0 -> плоскость 1 */
|
gfx_pal_sync(); /* палитра 0 -> плоскость 1 */
|
||||||
|
|
||||||
@@ -253,7 +305,7 @@ int main(void)
|
|||||||
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
||||||
* переключ). Выключение возвращает показ на страницу 0. */
|
* переключ). Выключение возвращает показ на страницу 0. */
|
||||||
|
|
||||||
io_border = 2; /* синий - начало */
|
PROF(2); /* ввод + heal */
|
||||||
|
|
||||||
uint8_t sp = kbd_raw_down(KBD_SPACE);
|
uint8_t sp = kbd_raw_down(KBD_SPACE);
|
||||||
if (sp && !sp_prev) {
|
if (sp && !sp_prev) {
|
||||||
@@ -270,8 +322,25 @@ 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;
|
||||||
|
{ /* Читы (seg000: switch под if (cheats_enabled)). Все — по
|
||||||
|
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
|
||||||
|
* K — убить стража;
|
||||||
|
* I — бессмертие Кида (наш отладочный, в оригинале его нет:
|
||||||
|
* иначе боёвку проверять получается ровно три раза);
|
||||||
|
* S — выдать меч, не бегая за ним в комнату 15. Штатно меч
|
||||||
|
* берётся с пола (pop_have_sword ставит подбор). */
|
||||||
|
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
|
||||||
|
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
|
||||||
|
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
|
||||||
|
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
||||||
|
if (pop_cheats && i && !immo_prev) pop_immortal = !pop_immortal;
|
||||||
|
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
|
||||||
|
cheat_prev = k;
|
||||||
|
immo_prev = i;
|
||||||
|
sword_prev = s;
|
||||||
|
}
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
io_border = 0; /* черный: ждём vsync */
|
PROF(0); /* чёрный: ждём vsync */
|
||||||
gfx_wait_vsync();
|
gfx_wait_vsync();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -311,11 +380,20 @@ int main(void)
|
|||||||
back = dbuf ? (uint8_t)(front ^ 1) : 0;
|
back = dbuf ? (uint8_t)(front ^ 1) : 0;
|
||||||
gfx_set_draw_page(back);
|
gfx_set_draw_page(back);
|
||||||
|
|
||||||
|
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||||
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
|
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
|
||||||
|
pop_guard_heal(); /* и прошлый кадр стража */
|
||||||
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
|
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
|
||||||
|
PROF(2); /* логика (тот же синий) */
|
||||||
kid_tick(); /* play_seq: следующий кадр */
|
kid_tick(); /* play_seq: следующий кадр */
|
||||||
pop_phys_tick(); /* K3: падение/приземление/стена */
|
pop_phys_tick(); /* K3: падение/приземление/стена */
|
||||||
|
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
|
||||||
|
/* Боёвка, порядок как в play_frame (seg000:872): сначала пометить
|
||||||
|
* попадания с обеих сторон, потом применить, потом свести HP. */
|
||||||
|
pop_check_sword_hurting();
|
||||||
|
pop_check_sword_hurt();
|
||||||
|
pop_do_delta_hp();
|
||||||
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
|
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
|
||||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||||
pop_fell_out = 0;
|
pop_fell_out = 0;
|
||||||
@@ -338,14 +416,14 @@ int main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
io_border = 4; /* зеленый */
|
PROF(4); /* слой фона */
|
||||||
|
|
||||||
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
||||||
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
||||||
io_border = 6; /* голубой */
|
pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
|
||||||
pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */
|
/* process_trobs — рисования внутри нет) */
|
||||||
/* слой фона — ДО Kid) */
|
pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */
|
||||||
io_border = 4; /* синий */
|
/* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
|
||||||
{ /* ворота соседа слева (room8) видны в col0 — */
|
{ /* ворота соседа слева (room8) видны в col0 — */
|
||||||
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
||||||
if (lft) {
|
if (lft) {
|
||||||
@@ -362,7 +440,6 @@ int main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_border = 2; /* зеленый */
|
|
||||||
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
||||||
dead_frames++;
|
dead_frames++;
|
||||||
if (dead_frames > RESPAWN_SETTLE &&
|
if (dead_frames > RESPAWN_SETTLE &&
|
||||||
@@ -375,13 +452,11 @@ int main(void)
|
|||||||
pop_kid_hp_reset();
|
pop_kid_hp_reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_border = 6; /* голубой */
|
|
||||||
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
|
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
|
||||||
uint8_t fpos = (uint8_t)(pop_loose_fell - 1);
|
uint8_t fpos = (uint8_t)(pop_loose_fell - 1);
|
||||||
uint8_t fcol = (uint8_t)(fpos % 10);
|
uint8_t fcol = (uint8_t)(fpos % 10);
|
||||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||||
pop_loose_fell = 0;
|
pop_loose_fell = 0;
|
||||||
io_border = 2; /* синий */
|
|
||||||
add_tile_override(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */
|
add_tile_override(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */
|
||||||
if (down) {
|
if (down) {
|
||||||
int8_t lrow = pop_room_col_landing(down, fcol);
|
int8_t lrow = pop_room_col_landing(down, fcol);
|
||||||
@@ -418,22 +493,29 @@ int main(void)
|
|||||||
* Приоритет как в оригинале: активный flash_time > кадр урона
|
* Приоритет как в оригинале: активный flash_time > кадр урона
|
||||||
* (hitp_delta<0 -> ярко-красный, ровно на один кадр). */
|
* (hitp_delta<0 -> ярко-красный, ровно на один кадр). */
|
||||||
if (pop_flash_time) {
|
if (pop_flash_time) {
|
||||||
flash_bg(255, 255, 85); /* color_14_brightyellow */
|
/* Цвет вспышки: меч — ярко-жёлтый, зелье — красный (flash_color
|
||||||
|
* оригинала; нам хватает этих двух). */
|
||||||
|
if (pop_flash_red) flash_bg(255, 0, 0); /* color_4_red */
|
||||||
|
else flash_bg(255, 255, 85); /* color_14_brightyellow */
|
||||||
pop_flash_time--;
|
pop_flash_time--;
|
||||||
|
if (!pop_flash_time) pop_flash_red = 0;
|
||||||
flash_on = 1;
|
flash_on = 1;
|
||||||
} else if (pop_kid_hurt) {
|
} else if (pop_kid_hurt) {
|
||||||
flash_bg(255, 85, 85); /* color_12_brightred */
|
flash_bg(255, 85, 85); /* color_12_brightred */
|
||||||
flash_on = 1;
|
flash_on = 1;
|
||||||
}
|
}
|
||||||
io_border = 4; /* зеленый */
|
|
||||||
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
||||||
|
PROF(6); /* спрайты: страж, Kid, fore поверх */
|
||||||
|
pop_guard_draw(); /* страж — ДО Kid (порядок как в оригинале) */
|
||||||
|
PROF(6); /* kid_draw (тот же циан) */
|
||||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||||
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
|
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
|
||||||
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
|
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
|
||||||
kid_draw_splash();
|
kid_draw_splash();
|
||||||
pop_kid_hurt = 0;
|
pop_kid_hurt = 0;
|
||||||
}
|
}
|
||||||
io_border = 0; /* черный */
|
pop_hp_draw(); /* полоса HP — только при изменении */
|
||||||
|
PROF(6); /* fore поверх Kid + борта */
|
||||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||||
kid_fp_width(), kid_fp_height(),
|
kid_fp_width(), kid_fp_height(),
|
||||||
Kid.direction, Kid.frame, Kid.action);
|
Kid.direction, Kid.frame, Kid.action);
|
||||||
@@ -443,7 +525,7 @@ int main(void)
|
|||||||
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
||||||
* set_visible_page переключает во время бланка (gfx.h паттерн). */
|
* set_visible_page переключает во время бланка (gfx.h паттерн). */
|
||||||
|
|
||||||
io_border = 0; /* черный: ждём vsync */
|
PROF(0); /* чёрный: ждём vsync */
|
||||||
|
|
||||||
/* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО
|
/* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО
|
||||||
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
||||||
@@ -488,6 +570,7 @@ int main(void)
|
|||||||
closegraph();
|
closegraph();
|
||||||
pop_bg_free();
|
pop_bg_free();
|
||||||
pop_kid_free();
|
pop_kid_free();
|
||||||
|
pop_guard_free();
|
||||||
pop_kid_data_free();
|
pop_kid_data_free();
|
||||||
pop_level_free();
|
pop_level_free();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ seqtbl адресуется базой SEQTBL_BASE=0x196E: элемент = kid_
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
SRC = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/src"
|
SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "SDLPoP", "src")
|
||||||
OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
"..", "poc", "res", "kid")
|
"..", "poc", "res", "kid")
|
||||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
@@ -26,10 +26,11 @@ def read(name):
|
|||||||
return open(os.path.join(SRC, name)).read()
|
return open(os.path.join(SRC, name)).read()
|
||||||
|
|
||||||
|
|
||||||
def parse_frame_table(txt):
|
def parse_frame_table(txt, name="frame_table_kid"):
|
||||||
"""frame_table_kid[] -> список (image, dx, dy, flags). Записи вида
|
"""frame_table_kid[]/frame_tbl_guard[] -> список (image, dx, dy, flags).
|
||||||
{ IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как OR-выражения."""
|
Записи вида { IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как
|
||||||
m = re.search(r'const frame_type frame_table_kid\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
OR-выражения."""
|
||||||
|
m = re.search(r'const frame_type ' + name + r'\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||||||
body = m.group(1)
|
body = m.group(1)
|
||||||
frames = []
|
frames = []
|
||||||
for row in re.findall(r'\{([^}]*)\}', body):
|
for row in re.findall(r'\{([^}]*)\}', body):
|
||||||
@@ -43,6 +44,18 @@ def parse_frame_table(txt):
|
|||||||
return frames
|
return frames
|
||||||
|
|
||||||
|
|
||||||
|
def parse_sword_tbl(txt):
|
||||||
|
"""sword_tbl[] (seg006:2047) -> список (id, x, y). id 255 = клинка нет.
|
||||||
|
Индекс — sword_frame (cur_frame.sword & 0x3F); один и тот же chtab_0 и у
|
||||||
|
Кида, и у стража."""
|
||||||
|
m = re.search(r'const sword_table_type sword_tbl\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||||||
|
rows = []
|
||||||
|
for row in re.findall(r'\{([^}]*)\}', m.group(1)):
|
||||||
|
a, b, c = [int(p.strip(), 0) for p in row.split(',')]
|
||||||
|
rows.append((a & 0xFF, b & 0xFF, c & 0xFF))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def parse_byte_array(txt, name):
|
def parse_byte_array(txt, name):
|
||||||
m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
|
m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
|
||||||
return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]
|
return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]
|
||||||
@@ -65,8 +78,10 @@ def main():
|
|||||||
seqc = read("seqtbl.c")
|
seqc = read("seqtbl.c")
|
||||||
|
|
||||||
frames = parse_frame_table(seg006)
|
frames = parse_frame_table(seg006)
|
||||||
|
gframes = parse_frame_table(seg006, "frame_tbl_guard")
|
||||||
seqtbl = parse_byte_array(seqc, "original_seqtbl")
|
seqtbl = parse_byte_array(seqc, "original_seqtbl")
|
||||||
offs = parse_word_array(seqc, "original_seqtbl_offsets")
|
offs = parse_word_array(seqc, "original_seqtbl_offsets")
|
||||||
|
swtbl = parse_sword_tbl(seg006)
|
||||||
|
|
||||||
with open(OUT, "w") as f:
|
with open(OUT, "w") as f:
|
||||||
f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n"
|
f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n"
|
||||||
@@ -90,13 +105,32 @@ def main():
|
|||||||
# раскладка kid_data.bin (смещения ОТ НАЧАЛА ДАННЫХ страницы)
|
# раскладка kid_data.bin (смещения ОТ НАЧАЛА ДАННЫХ страницы)
|
||||||
f.write("/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */\n")
|
f.write("/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */\n")
|
||||||
f.write("#define KID_BIN_FRAMES_OFF 0\n")
|
f.write("#define KID_BIN_FRAMES_OFF 0\n")
|
||||||
f.write("#define KID_BIN_SEQTBL_OFF %d\n\n" % (len(frames) * 5))
|
f.write("#define KID_BIN_SEQTBL_OFF %d\n" % (len(frames) * 5))
|
||||||
|
f.write("/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс\n"
|
||||||
|
" * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */\n")
|
||||||
|
f.write("#define KID_NGFRAMES %d\n" % len(gframes))
|
||||||
|
f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))
|
||||||
|
|
||||||
# seq offsets
|
# seq offsets
|
||||||
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("static const uint16_t kid_seq_off[KID_NSEQ] = {\n")
|
||||||
f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")
|
f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")
|
||||||
f.write("#endif\n")
|
|
||||||
|
# sword_tbl — макро-инициализаторами, а не готовыми массивами: так
|
||||||
|
# 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто
|
||||||
|
# включил заголовок.
|
||||||
|
f.write("/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.\n"
|
||||||
|
" * Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.\n"
|
||||||
|
" * Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),\n"
|
||||||
|
" * x — «вперёд» по направлению персонажа. */\n")
|
||||||
|
f.write("#define KID_NSWORD %d\n" % len(swtbl))
|
||||||
|
for i, (nm, fmt) in enumerate((("ID", "0x%02X"), ("DX", "%d"), ("DY", "%d"))):
|
||||||
|
# dx/dy — знаковые: печатаем со знаком, иначе int8_t = overflow
|
||||||
|
vals = [r[i] if i == 0 else (r[i] - 256 if r[i] > 127 else r[i])
|
||||||
|
for r in swtbl]
|
||||||
|
f.write("#define KID_SWORD_%s_INIT { \\\n" % nm)
|
||||||
|
f.write(emit_c(vals, 12, fmt).replace("\n", " \\\n") + " \\\n}\n")
|
||||||
|
f.write("\n#endif\n")
|
||||||
|
|
||||||
# kid_data.bin: кадры (по 5 байт) + seqtbl подряд
|
# kid_data.bin: кадры (по 5 байт) + seqtbl подряд
|
||||||
os.makedirs(OUT_BIN_DIR, exist_ok=True)
|
os.makedirs(OUT_BIN_DIR, exist_ok=True)
|
||||||
@@ -105,7 +139,9 @@ def main():
|
|||||||
for (img, dx, dy, fl, sw) in frames:
|
for (img, dx, dy, fl, sw) in frames:
|
||||||
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
||||||
b.write(bytes(seqtbl))
|
b.write(bytes(seqtbl))
|
||||||
print(f"kid_data.bin: {len(frames)*5 + len(seqtbl)} Б -> {binpath}")
|
for (img, dx, dy, fl, sw) in gframes:
|
||||||
|
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
||||||
|
print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5} Б (кадры Kid + seqtbl + кадры стража) -> {binpath}")
|
||||||
|
|
||||||
print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, "
|
print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, "
|
||||||
f"{len(offs)} seq-оффсетов -> {OUT}")
|
f"{len(offs)} seq-оффсетов -> {OUT}")
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
pop_pack_guard.py — упаковщик спрайтов СТРАЖА (chtab_5_guard, база 750).
|
||||||
|
|
||||||
|
Оригинал грузит стражей из отдельного .DAT по типу уровня
|
||||||
|
(GUARD/FAT/SKEL/VIZIER/SHADOW, seg000:1092); уровень 1 — обычный «guard»,
|
||||||
|
то есть data/GUARD/res(750+id).png. Палитра — НЕ из PNG: оригинал
|
||||||
|
подменяет её из res10.bin (guard_palettes, 7 палитр по 16 цветов, 6-бит) по
|
||||||
|
level.guards_color; на уровне 1 у обоих стражей color = 2.
|
||||||
|
|
||||||
|
Выход: poc/res/guard/g0..gN.atl (по 8 спрайтов на страницу, адресация
|
||||||
|
id>>3 / id&7 — как у Kid) + группа палитры 0x90..0x9F в общий kid.pal
|
||||||
|
(его собирает pop_pack_kid.py, см. GUARD_PAL_BASE там).
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, HERE)
|
||||||
|
import pop_pack_bg as B
|
||||||
|
from pop_pack_kid import transpose_cols
|
||||||
|
|
||||||
|
GUARD_DIR = os.path.join(HERE, "..", "SDLPoP", "data", "GUARD")
|
||||||
|
PAL_BIN = os.path.join(HERE, "..", "SDLPoP", "data", "PRINCE", "res10.bin")
|
||||||
|
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "guard")
|
||||||
|
PAL_BASE = 0x90 # слоты палитры Sprinter под стража
|
||||||
|
COLOR = 2 # level.guards_color обоих стражей уровня 1
|
||||||
|
SHIFT = 3 # g[id>>3], idx id&7 (спрайты крупные)
|
||||||
|
MASK = (1 << SHIFT) - 1
|
||||||
|
MAX_ID = 33 # атлас-индексы 0..33 = res751..res784
|
||||||
|
|
||||||
|
|
||||||
|
def load_guard(id):
|
||||||
|
"""Атлас-индекс id (== frame.image) -> res(751+id).png -> (w, h, 8bpp
|
||||||
|
COLUMN-MAJOR); 0 -> 0xFF (прозрачность).
|
||||||
|
|
||||||
|
Off-by-one как у Kid (см. pop_pack_kid.load_kid): load_chtab_from_file
|
||||||
|
зовётся с ресурсом 750, а images[i-1] = res(750+i), то есть images[0] =
|
||||||
|
res751. Рисование берёт get_image(chtab, obj_id+1 − 1) = images[image],
|
||||||
|
значит frame.image=N -> res(751+N). С res(750+N) страж стоял в позе
|
||||||
|
предыдущего кадра (замечено на frame_166_stand_inactive: image 17
|
||||||
|
рисовался как res767 — выпад — вместо res768). """
|
||||||
|
path = os.path.join(GUARD_DIR, f"res{751 + id}.png")
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return None
|
||||||
|
im = Image.open(path)
|
||||||
|
if im.mode != "P":
|
||||||
|
B.die(f"res{750+id}: mode {im.mode}, ожидался P")
|
||||||
|
src = im.tobytes()
|
||||||
|
rowmajor = bytes((0xFF if v == 0 else PAL_BASE + v) for v in src)
|
||||||
|
return im.width, im.height, transpose_cols(im.width, im.height, rowmajor)
|
||||||
|
|
||||||
|
|
||||||
|
def guard_palette():
|
||||||
|
"""16 цветов (r,g,b 0..63) из res10.bin для COLOR."""
|
||||||
|
b = open(PAL_BIN, "rb").read()
|
||||||
|
off = 0x30 * COLOR - 0x30
|
||||||
|
return [(b[off + i * 3], b[off + i * 3 + 1], b[off + i * 3 + 2]) for i in range(16)]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.makedirs(OUT_DIR, exist_ok=True)
|
||||||
|
pages = {}
|
||||||
|
n = 0
|
||||||
|
for id in range(0, MAX_ID + 1):
|
||||||
|
g = load_guard(id)
|
||||||
|
if g is None:
|
||||||
|
continue
|
||||||
|
pages.setdefault(id >> SHIFT, {})[id & MASK] = g
|
||||||
|
n += 1
|
||||||
|
if not pages:
|
||||||
|
B.die("нет спрайтов стража")
|
||||||
|
total = 0
|
||||||
|
for pg in range(max(pages) + 1):
|
||||||
|
nm = f"g{pg}.atl"
|
||||||
|
cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, nm), pages.get(pg, {}))
|
||||||
|
total += sz
|
||||||
|
print(f" {nm}: {len(pages.get(pg, {}))} спрайтов, каталог {cnt}, {sz} Б")
|
||||||
|
print(f"ИТОГО: {n} спрайтов стража, {max(pages)+1} EMM-страниц, {total} Б -> {OUT_DIR}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -25,7 +25,11 @@ KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDL
|
|||||||
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
||||||
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
||||||
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
||||||
SWORD_IDS = tuple(range(20, 28)) # sword_tbl для кадров 229..236 (найти-убрать)
|
GUARD_PAL_BASE = 0x90 # chtab_5 (страж) -> слоты 0x90..0x9F
|
||||||
|
# chtab_0 целиком: sword_tbl (seg006:2047) адресует id 0..33 — подъём меча,
|
||||||
|
# ножны И вся боёвка. Меч в оригинале ОДИН на всех: и Кид, и страж рисуют
|
||||||
|
# клинок из этого же chtab_0 (add_sword_to_objtable, seg006:1798).
|
||||||
|
SWORD_IDS = tuple(range(0, 34))
|
||||||
SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ)
|
SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ)
|
||||||
MASK = (1 << SHIFT) - 1
|
MASK = (1 << SHIFT) - 1
|
||||||
|
|
||||||
@@ -108,6 +112,14 @@ def build_palette(out_path):
|
|||||||
for i, (r, g, bl) in enumerate(_read16(path)):
|
for i, (r, g, bl) in enumerate(_read16(path)):
|
||||||
o = (base + i) * 4
|
o = (base + i) * 4
|
||||||
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
|
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
|
||||||
|
# Палитра стража берётся НЕ из .pal, а из res10.bin (guard_palettes:
|
||||||
|
# 7 палитр по 16 цветов, 6-бит) по level.guards_color; на уровне 1 у
|
||||||
|
# обоих стражей color = 2. См. toolchain/pop_pack_guard.py.
|
||||||
|
import pop_pack_guard as G
|
||||||
|
for i, (r, g, bl) in enumerate(G.guard_palette()):
|
||||||
|
o = (GUARD_PAL_BASE + i) * 4
|
||||||
|
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
|
||||||
|
|
||||||
with open(out_path, "wb") as f:
|
with open(out_path, "wb") as f:
|
||||||
f.write(pal)
|
f.write(pal)
|
||||||
|
|
||||||
@@ -141,8 +153,7 @@ def main():
|
|||||||
names.append(None)
|
names.append(None)
|
||||||
print(f" (kid стр {pg}: пусто)")
|
print(f" (kid стр {pg}: пусто)")
|
||||||
|
|
||||||
# chtab_0: меч в руке. Пакуем ТОЛЬКО кадры подъёма/ножен (sword_tbl
|
# chtab_0: меч в руке — весь chtab (id 0..33), индекс в атласе = id.
|
||||||
# 35..42 -> id 20..27); индекс в атласе = id - 20 (бой не портирован).
|
|
||||||
sw = {}
|
sw = {}
|
||||||
for i, id in enumerate(SWORD_IDS):
|
for i, id in enumerate(SWORD_IDS):
|
||||||
w, h, pix = load_sword(id)
|
w, h, pix = load_sword(id)
|
||||||
|
|||||||
+21
-2
@@ -23,6 +23,7 @@
|
|||||||
# CODE=W1|W2 DATA=W1|W2|SAME BANKED=W1|W3
|
# CODE=W1|W2 DATA=W1|W2|SAME BANKED=W1|W3
|
||||||
# Example: --memory-manual CODE=W2,DATA=W2,BANKED=W3
|
# Example: --memory-manual CODE=W2,DATA=W2,BANKED=W3
|
||||||
# -I PATH additional include path (repeatable)
|
# -I PATH additional include path (repeatable)
|
||||||
|
# -DNAME[=VAL] preprocessor define, passed to sdcc as is (repeatable)
|
||||||
# -L 0xADDR code load address (default: derived from --memory mode)
|
# -L 0xADDR code load address (default: derived from --memory mode)
|
||||||
# -E 0xADDR entry address (default: same as -L)
|
# -E 0xADDR entry address (default: same as -L)
|
||||||
# -S 0xADDR stack address (default: 0xBFFE)
|
# -S 0xADDR stack address (default: 0xBFFE)
|
||||||
@@ -32,6 +33,9 @@
|
|||||||
# --data-loc 0xN override SDCC --data-loc (default: derived from --memory)
|
# --data-loc 0xN override SDCC --data-loc (default: derived from --memory)
|
||||||
# -Wl FLAG extra linker flag (repeatable)
|
# -Wl FLAG extra linker flag (repeatable)
|
||||||
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
|
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
|
||||||
|
# --bank-data put bank modules' writable data INTO the bank page
|
||||||
|
# (default: it goes to the shared _DATA in W1/W2, which
|
||||||
|
# stays mapped and is visible from everywhere)
|
||||||
# automatically and adds -Wl-b_BANKN=0x{N}C000
|
# automatically and adds -Wl-b_BANKN=0x{N}C000
|
||||||
# --w3 FILE.c place FILE.c resident in window 3 (0xC000), called
|
# --w3 FILE.c place FILE.c resident in window 3 (0xC000), called
|
||||||
# DIRECTLY (no trampoline). Repeatable. Defaults to
|
# DIRECTLY (no trampoline). Repeatable. Defaults to
|
||||||
@@ -106,6 +110,8 @@ LD_EXTRA=()
|
|||||||
MKEXE_EXTRA=()
|
MKEXE_EXTRA=()
|
||||||
BANK_SPECS=() # entries like "1=engine.c"
|
BANK_SPECS=() # entries like "1=engine.c"
|
||||||
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
|
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
|
||||||
|
USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть
|
||||||
|
BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные банков — В СТРАНИЦУ банка
|
||||||
W3_RELS=() # заполняется при компиляции W3-модулей
|
W3_RELS=() # заполняется при компиляции W3-модулей
|
||||||
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
|
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
|
||||||
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
|
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
|
||||||
@@ -139,7 +145,9 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--w3) W3_SPECS+=("$2"); shift 2;;
|
--w3) W3_SPECS+=("$2"); shift 2;;
|
||||||
--stack-size) STACK_SIZE="$2"; shift 2;;
|
--stack-size) STACK_SIZE="$2"; shift 2;;
|
||||||
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
||||||
|
-D*) USER_DEFS+=("$1"); shift;;
|
||||||
--bank) BANK_SPECS+=("$2"); shift 2;;
|
--bank) BANK_SPECS+=("$2"); shift 2;;
|
||||||
|
--bank-data) BANK_LOCAL_DATA=1; shift;;
|
||||||
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
||||||
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
|
--max-allocs) MAX_ALLOCS="$2"; shift 2;;
|
||||||
--gfx) GFX_MODE="$2"; shift 2;;
|
--gfx) GFX_MODE="$2"; shift 2;;
|
||||||
@@ -366,7 +374,7 @@ run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
|
|||||||
|
|
||||||
# 2. user sources → .rel (HOME)
|
# 2. user sources → .rel (HOME)
|
||||||
USER_RELS=()
|
USER_RELS=()
|
||||||
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}")
|
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}" "${USER_DEFS[@]}")
|
||||||
[[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT)
|
[[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT)
|
||||||
[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS")
|
[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS")
|
||||||
for src in "${SOURCES[@]}"; do
|
for src in "${SOURCES[@]}"; do
|
||||||
@@ -420,8 +428,19 @@ if [[ "$CRT0_TYPE" == "banked" ]]; then
|
|||||||
bank_n="${spec%%=*}"
|
bank_n="${spec%%=*}"
|
||||||
bank_src="${spec#*=}"
|
bank_src="${spec#*=}"
|
||||||
rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
|
rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
|
||||||
|
# По умолчанию --dataseg НЕ задаём: писучие данные банкового
|
||||||
|
# модуля (глобалы, file-static, статики функций) остаются в
|
||||||
|
# общем _DATA, то есть в W1/W2 — они замаплены всегда и видны
|
||||||
|
# и банку, и остальному коду. С --dataseg BANKn они уезжают в
|
||||||
|
# СТРАНИЦУ БАНКА и снаружи не читаются (проверено на .map:
|
||||||
|
# глобал банкового модуля лёг по 0x0001C000): это экономит
|
||||||
|
# W1/W2, но требует ручного контроля видимости, поэтому
|
||||||
|
# включается явно через --bank-data.
|
||||||
|
bank_data_flags=()
|
||||||
|
[[ $BANK_LOCAL_DATA -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}")
|
||||||
run "$SDCC" "${CC_FLAGS[@]}" \
|
run "$SDCC" "${CC_FLAGS[@]}" \
|
||||||
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \
|
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" \
|
||||||
|
"${bank_data_flags[@]}" \
|
||||||
-c -o "$rel" "$bank_src"
|
-c -o "$rel" "$bank_src"
|
||||||
BANK_RELS+=("$rel")
|
BANK_RELS+=("$rel")
|
||||||
# Virtual address: bank_n in upper byte, BANK_LOW16 in low half.
|
# Virtual address: bank_n in upper byte, BANK_LOW16 in low half.
|
||||||
|
|||||||
+17
-10
@@ -1,5 +1,5 @@
|
|||||||
# Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
|
# Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
|
||||||
accfill 3772
|
accfill 3786
|
||||||
argv 3431
|
argv 3431
|
||||||
assrtest 3847
|
assrtest 3847
|
||||||
atlas 9193
|
atlas 9193
|
||||||
@@ -9,15 +9,15 @@ banked 1056
|
|||||||
bankedbg 1067
|
bankedbg 1067
|
||||||
banklocl 4683
|
banklocl 4683
|
||||||
banktest 3767
|
banktest 3767
|
||||||
bgi_img 7057
|
bgi_img 7200
|
||||||
bgitest 3737
|
bgitest 3751
|
||||||
bios_text 4461
|
bios_text 4461
|
||||||
blitperf 5860
|
blitperf 5860
|
||||||
blitw 4939
|
blitw 4939
|
||||||
cat 913
|
cat 913
|
||||||
cblstream 6217
|
cblstream 6431
|
||||||
cbltest 6366
|
cbltest 6580
|
||||||
cblwav 6434
|
cblwav 6648
|
||||||
conio 4605
|
conio 4605
|
||||||
conio2 3929
|
conio2 3929
|
||||||
dec_test 860
|
dec_test 860
|
||||||
@@ -27,12 +27,12 @@ fdmax 6023
|
|||||||
filetest 10547
|
filetest 10547
|
||||||
fpsdiv 4870
|
fpsdiv 4870
|
||||||
gets 509
|
gets 509
|
||||||
gfx_dbuf 5032
|
gfx_dbuf 5112
|
||||||
gfx_demo 4103
|
gfx_demo 4117
|
||||||
gfxbanks 6044
|
gfxbanks 6044
|
||||||
hello 4167
|
hello 4167
|
||||||
hello2 4244
|
hello2 4244
|
||||||
irqtest 6527
|
irqtest 6647
|
||||||
kbdraw 4911
|
kbdraw 4911
|
||||||
ls 4835
|
ls 4835
|
||||||
malloc 4461
|
malloc 4461
|
||||||
@@ -45,7 +45,8 @@ palfile 5406
|
|||||||
ptime 5744
|
ptime 5744
|
||||||
rpgprof 12082
|
rpgprof 12082
|
||||||
rpgwalk 10309
|
rpgwalk 10309
|
||||||
rt_test 4892
|
rt_test 5154
|
||||||
|
scroll 2974
|
||||||
seek 4188
|
seek 4188
|
||||||
simple 955
|
simple 955
|
||||||
solidt 11531
|
solidt 11531
|
||||||
@@ -60,4 +61,10 @@ strtest 1340
|
|||||||
text_palette 5024
|
text_palette 5024
|
||||||
timedir 5454
|
timedir 5454
|
||||||
w0page 8831
|
w0page 8831
|
||||||
|
w3bankgfx 5240
|
||||||
|
w3bgfx 5246
|
||||||
|
w3big 3673
|
||||||
|
w3huge 3700
|
||||||
|
w3probe 3556
|
||||||
|
w3tiny 3471
|
||||||
winrest 4459
|
winrest 4459
|
||||||
|
|||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* gfx_blit_cols_part_noclip — блит спрайта column-major БЕЗ клипа по экрану,
|
||||||
|
* с обрезкой сверху (как gfx_blit_cols_part). Пара к gfx_blit_cols_part
|
||||||
|
* ровно так же, как gfx_blit_part_noclip — к gfx_blit_part.
|
||||||
|
*
|
||||||
|
* Зачем: у персонажей PoP спрайт почти всегда целиком на экране, а общий
|
||||||
|
* gfx_blit_cols_part платит за проверку четырёх границ, пересчёт w/h/cx/sy и
|
||||||
|
* 16-битную арифметику независимо от того, вылезает край или нет — замер в
|
||||||
|
* MAME (roomtest, страж + клинок): 5 622 такта подготовки против 13 596 на
|
||||||
|
* сам accel-проход. Здесь та же колонка уходит в _bgi_blit_cols_raw без
|
||||||
|
* подготовки.
|
||||||
|
*
|
||||||
|
* Требования (проверяет ВЫЗЫВАЮЩИЙ, кода проверок здесь нет):
|
||||||
|
* - прямоугольник назначения целиком на экране;
|
||||||
|
* - ширина и высота кадра <= 255, y <= 255;
|
||||||
|
* - skip < высоты кадра.
|
||||||
|
* Не выполняется — зовите gfx_blit_cols_part.
|
||||||
|
*
|
||||||
|
* Флип, как и в gfx_blit_cols_part, бесплатный: подаём src с последней
|
||||||
|
* колонки и отрицательный страйд — accel сам кладёт её в левый x.
|
||||||
|
*
|
||||||
|
* W3-скобка ставится ЗДЕСЬ (код libbgi живёт в W1): из приложения,
|
||||||
|
* собранного с --w3, её вызывать нельзя — после _bgi_begin окно W3 занято
|
||||||
|
* видеобанком и код вызывающего исчезает из адресного пространства.
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
void gfx_blit_cols_part_noclip(int x, int y, const void *img, uint8_t flip,
|
||||||
|
uint8_t skip, uint8_t rows)
|
||||||
|
{
|
||||||
|
const uint8_t *p = (const uint8_t *)img;
|
||||||
|
uint8_t w = p[0]; /* ширина кадра (< 256) */
|
||||||
|
uint8_t fh = p[2]; /* высота кадра (< 256) */
|
||||||
|
const uint8_t *src = p + 4;
|
||||||
|
uint8_t h;
|
||||||
|
|
||||||
|
if (w == 0 || fh == 0 || skip >= fh) return;
|
||||||
|
h = (uint8_t)(fh - skip);
|
||||||
|
if (rows && rows < h) h = rows;
|
||||||
|
|
||||||
|
if (flip) src += (uint16_t)(w - 1) * fh; /* последняя колонка -> левый x */
|
||||||
|
src += skip;
|
||||||
|
|
||||||
|
_bgi_begin();
|
||||||
|
_bgi_blit_cols_raw(src, (uint8_t *)(_gfx_addr_base + (uint16_t)x),
|
||||||
|
w, h, flip ? -(int)fh : (int)fh, (uint8_t)y);
|
||||||
|
_bgi_end();
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* gfx_blit_part_noclip — блит ПОД-ПРЯМОУГОЛЬНИКА картинки getimage-формата
|
||||||
|
* БЕЗ клипа по экрану, в ТЕКУЩЕМ банке (gfx_set_bank). Пара к
|
||||||
|
* gfx_blit_noclip ровно так же, как gfx_blit_part — к gfx_blit.
|
||||||
|
*
|
||||||
|
* Зачем: тайловому движку часто нужно положить поверх спрайта не весь
|
||||||
|
* тайл, а только ту его часть, что перекрывает спрайт. Через
|
||||||
|
* gfx_blit_part это идёт общим ядром _gfx_blit_full (16-битная
|
||||||
|
* арифметика, клип, нарезка полос) — ~13 К тактов накладных на вызов
|
||||||
|
* независимо от размера; здесь тот же под-прямоугольник рисуется линейным
|
||||||
|
* спрайтовым ядром (~4.6 К на 32×3). Замер PoP roomtest 2026-07-29:
|
||||||
|
* fore-слой поверх персонажа рисовал 9 целых тайлов за 432 К тактов
|
||||||
|
* (стена = блок 64×63); с обрезкой по прямоугольнику спрайта тех же
|
||||||
|
* пикселей остаётся втрое меньше.
|
||||||
|
*
|
||||||
|
* Требования (проверяет ВЫЗЫВАЮЩИЙ, кода проверок здесь нет):
|
||||||
|
* - под-прямоугольник (sx,sy,w,h) лежит внутри картинки;
|
||||||
|
* - прямоугольник назначения целиком на экране;
|
||||||
|
* - w,h <= 255, y <= 255.
|
||||||
|
* Не выполняется — зовите gfx_blit_part.
|
||||||
|
*
|
||||||
|
* W3-скобка ставится ЗДЕСЬ (код libbgi живёт в W1): из приложения,
|
||||||
|
* собранного с --w3, её вызывать нельзя — после _bgi_begin окно W3 занято
|
||||||
|
* видеобанком и код вызывающего исчезает из адресного пространства.
|
||||||
|
*/
|
||||||
|
#include "../_bgi.h"
|
||||||
|
|
||||||
|
void gfx_blit_part_noclip(int x, int y, const void *img,
|
||||||
|
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h)
|
||||||
|
{
|
||||||
|
const uint8_t *p = (const uint8_t *)img;
|
||||||
|
uint16_t stride = (uint16_t)(p[0] | (p[1] << 8));
|
||||||
|
const uint8_t *src = p + 4 + (uint16_t)sy * stride + sx;
|
||||||
|
|
||||||
|
_bgi_begin();
|
||||||
|
_gfx_blit_sprite_noclip(x, y, src, stride, w, h);
|
||||||
|
_bgi_end();
|
||||||
|
}
|
||||||
@@ -105,6 +105,14 @@ void gfx_heal_noclip(int x, int y, uint8_t w, uint8_t h);
|
|||||||
void gfx_blit_part(int x, int y, const void *img,
|
void gfx_blit_part(int x, int y, const void *img,
|
||||||
int sx, int sy, int w, int h);
|
int sx, int sy, int w, int h);
|
||||||
|
|
||||||
|
/* Тот же под-прямоугольник БЕЗ клипа по экрану — линейным спрайтовым
|
||||||
|
* ядром (пара к gfx_blit_noclip). Вызывающий гарантирует: под-
|
||||||
|
* прямоугольник внутри img, назначение целиком на экране, w,h,y <= 255.
|
||||||
|
* Для тайловых движков, кладущих поверх спрайта только перекрывающую его
|
||||||
|
* часть тайла (PoP: fore-слой). */
|
||||||
|
void gfx_blit_part_noclip(int x, int y, const void *img,
|
||||||
|
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h);
|
||||||
|
|
||||||
/* Блит спрайта column-major (пиксели по колонкам) — вертикальным accel-
|
/* Блит спрайта column-major (пиксели по колонкам) — вертикальным accel-
|
||||||
* проходом. flip!=0 = горизонтальное зеркало (направление персонажа) без
|
* проходом. flip!=0 = горизонтальное зеркало (направление персонажа) без
|
||||||
* CPU-реверса/второй копии; зеркало ВНУТРИ футпринта [x,x+w) (для «hot-
|
* CPU-реверса/второй копии; зеркало ВНУТРИ футпринта [x,x+w) (для «hot-
|
||||||
@@ -120,6 +128,16 @@ void gfx_blit_cols(int x, int y, const void *img, uint8_t flip);
|
|||||||
void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip,
|
void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip,
|
||||||
int skip, int rows);
|
int skip, int rows);
|
||||||
|
|
||||||
|
/* То же БЕЗ клипа по экрану — пара к gfx_blit_cols_part так же, как
|
||||||
|
* gfx_blit_part_noclip к gfx_blit_part. Вызывающий гарантирует: спрайт
|
||||||
|
* целиком на экране, ширина/высота кадра и y <= 255, skip < высоты.
|
||||||
|
* rows = 0 — «до низа кадра». Экономит ~5.6 К тактов подготовки на вызов
|
||||||
|
* (замер PoP roomtest: подготовка 5 622 против 13 596 на сам accel-проход),
|
||||||
|
* что для персонажа со спрайтом-компаньоном (клинок) даёт заметную долю
|
||||||
|
* кадра. */
|
||||||
|
void gfx_blit_cols_part_noclip(int x, int y, const void *img, uint8_t flip,
|
||||||
|
uint8_t skip, uint8_t rows);
|
||||||
|
|
||||||
/* Восстановить прямоугольник экрана из ОЗУ-копии (стирание спрайта/
|
/* Восстановить прямоугольник экрана из ОЗУ-копии (стирание спрайта/
|
||||||
* оверлея, нарисованного банком с битом 2: 0x54/0x5C). Всегда
|
* оверлея, нарисованного банком с битом 2: 0x54/0x5C). Всегда
|
||||||
* работает банком GFX_BANK_NORMAL независимо от gfx_set_bank;
|
* работает банком GFX_BANK_NORMAL независимо от gfx_set_bank;
|
||||||
|
|||||||
+4
-4
@@ -30,10 +30,10 @@
|
|||||||
#define _IRQ_VEC_BUF_SIZE (256 + 257)
|
#define _IRQ_VEC_BUF_SIZE (256 + 257)
|
||||||
|
|
||||||
/* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в
|
/* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в
|
||||||
* W1). С запасом над реальным размером трамплина (244 Б после
|
* W1). С запасом над реальным размером трамплина (267 Б после
|
||||||
* FIFO-drain + overrun-recovery в raw-клавиатурной ветке, 2026-07-18 —
|
* упаковки held-карты клавиатуры в биты, 2026-07-30 — проверять
|
||||||
* проверять __irq_tramp_end при правках, запас держать ≥ 60 Б). */
|
* __irq_tramp_end при правках, запас держать >= 60 Б). */
|
||||||
#define _IRQ_TRAMP_BUF_SIZE 320
|
#define _IRQ_TRAMP_BUF_SIZE 336
|
||||||
|
|
||||||
extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
|
extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
|
||||||
|
|
||||||
|
|||||||
+31
-5
@@ -10,7 +10,8 @@
|
|||||||
* (порт данных 0x18 читается один раз, деструктивно — либо мы,
|
* (порт данных 0x18 читается один раз, деструктивно — либо мы,
|
||||||
* либо DSS, не оба), декодируется make/break (PS/2 Scan Code
|
* либо DSS, не оба), декодируется make/break (PS/2 Scan Code
|
||||||
* Set 2: 0xF0 — префикс отпускания, 0xE0 — префикс расширенной
|
* Set 2: 0xF0 — префикс отпускания, 0xE0 — префикс расширенной
|
||||||
* клавиши) в битовую карту `_kbdraw_down[]`, и к DSS НЕ идём
|
* клавиши) в битовую карту `_kbdraw_down[]` (бит на код, 64 Б),
|
||||||
|
* и к DSS НЕ идём
|
||||||
* (private RETI — тот же паттерн, что и у CBL ниже). Пока raw
|
* (private RETI — тот же паттерн, что и у CBL ниже). Пока raw
|
||||||
* открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>).
|
* открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>).
|
||||||
* - CBL (бит 7 порта 0xFE) — только при зарегистрированном хуке
|
* - CBL (бит 7 порта 0xFE) — только при зарегистрированном хуке
|
||||||
@@ -61,6 +62,7 @@ void _irq_tramp(void) __naked
|
|||||||
;; break-кода = клавиша «залипает» навсегда (найдено 2026-07-16).
|
;; break-кода = клавиша «залипает» навсегда (найдено 2026-07-16).
|
||||||
push hl
|
push hl
|
||||||
push de
|
push de
|
||||||
|
push bc ; маска бита строится в BC (см. ниже)
|
||||||
tr_kbd_drain:
|
tr_kbd_drain:
|
||||||
in a, (#_IRQ_SIO_DATA) ; читаем — тем самым дренируем байт
|
in a, (#_IRQ_SIO_DATA) ; читаем — тем самым дренируем байт
|
||||||
cp #0xF0
|
cp #0xF0
|
||||||
@@ -68,7 +70,27 @@ void _irq_tramp(void) __naked
|
|||||||
cp #0xE0
|
cp #0xE0
|
||||||
jr Z, tr_kbd_setext
|
jr Z, tr_kbd_setext
|
||||||
|
|
||||||
;; --- обычный байт кода: индекс в _kbdraw_down[код(+256 EXT)] ---
|
;; --- обычный байт кода: БИТ code&7 в байте _kbdraw_down[code>>3]
|
||||||
|
;; (карта упакована по битам: 64 Б вместо 512, см. _kbdraw.h).
|
||||||
|
;; Маску строим сдвигом, а НЕ таблицей: таблица потребовала бы
|
||||||
|
;; `ld hl,#метка` внутри трамплина, а он копируется в W2 побайтно
|
||||||
|
;; и обязан быть без абсолютных само-ссылок (см. шапку). ---
|
||||||
|
ld e, a ; E = код
|
||||||
|
and #7
|
||||||
|
ld b, a
|
||||||
|
inc b ; B = (код&7)+1 для djnz
|
||||||
|
ld a, #1
|
||||||
|
jr tr_kbd_mskdec
|
||||||
|
tr_kbd_mskloop:
|
||||||
|
add a, a
|
||||||
|
tr_kbd_mskdec:
|
||||||
|
djnz tr_kbd_mskloop ; A = 1 << (код&7)
|
||||||
|
ld c, a ; C = маска бита
|
||||||
|
ld a, e
|
||||||
|
rrca
|
||||||
|
rrca
|
||||||
|
rrca
|
||||||
|
and #0x1F ; A = код>>3 (код <= 255)
|
||||||
ld hl, #__kbdraw_down
|
ld hl, #__kbdraw_down
|
||||||
ld d, #0
|
ld d, #0
|
||||||
ld e, a
|
ld e, a
|
||||||
@@ -76,16 +98,19 @@ void _irq_tramp(void) __naked
|
|||||||
ld a, (__kbdraw_pending)
|
ld a, (__kbdraw_pending)
|
||||||
bit 1, a ; _KBDRAW_PEND_EXT?
|
bit 1, a ; _KBDRAW_PEND_EXT?
|
||||||
jr Z, tr_kbd_noext
|
jr Z, tr_kbd_noext
|
||||||
ld de, #256
|
ld de, #32 ; +256 кодов = +32 байта карты
|
||||||
add hl, de
|
add hl, de
|
||||||
tr_kbd_noext:
|
tr_kbd_noext:
|
||||||
ld a, (__kbdraw_pending)
|
ld a, (__kbdraw_pending)
|
||||||
bit 0, a ; _KBDRAW_PEND_BREAK?
|
bit 0, a ; _KBDRAW_PEND_BREAK?
|
||||||
jr Z, tr_kbd_mkdown
|
jr Z, tr_kbd_mkdown
|
||||||
xor a, a ; break → снять (0)
|
ld a, c
|
||||||
|
cpl ; break → сбросить бит
|
||||||
|
and a, (hl)
|
||||||
jr tr_kbd_wr
|
jr tr_kbd_wr
|
||||||
tr_kbd_mkdown:
|
tr_kbd_mkdown:
|
||||||
ld a, #1 ; make → взвести (1)
|
ld a, c ; make → взвести бит
|
||||||
|
or a, (hl)
|
||||||
tr_kbd_wr:
|
tr_kbd_wr:
|
||||||
ld (hl), a
|
ld (hl), a
|
||||||
xor a, a
|
xor a, a
|
||||||
@@ -120,6 +145,7 @@ void _irq_tramp(void) __naked
|
|||||||
ld a, #1
|
ld a, #1
|
||||||
ld (__kbdraw_overrun), a
|
ld (__kbdraw_overrun), a
|
||||||
tr_kbd_noovr:
|
tr_kbd_noovr:
|
||||||
|
pop bc
|
||||||
pop de
|
pop de
|
||||||
pop hl
|
pop hl
|
||||||
pop af
|
pop af
|
||||||
|
|||||||
+7
-2
@@ -23,7 +23,12 @@ extern uint8_t _kbdraw_pending; /* FSM декодера make/break, см. в
|
|||||||
extern uint8_t _kbdraw_overrun; /* 1 = ISR поймал Rx-overrun SIO (потерян
|
extern uint8_t _kbdraw_overrun; /* 1 = ISR поймал Rx-overrun SIO (потерян
|
||||||
* байт → возможно потерян break); снимает
|
* байт → возможно потерян break); снимает
|
||||||
* kbd_raw_sync (чистит _kbdraw_down) */
|
* kbd_raw_sync (чистит _kbdraw_down) */
|
||||||
extern uint8_t _kbdraw_down[512]; /* 0/1 по коду; +256 для расширенных
|
/* Held-состояние: БИТ на скан-код, код>>3 = байт, код&7 = номер бита;
|
||||||
* (префикс 0xE0) — см. kbd_raw_down */
|
* коды 0..255 обычные, 256..511 — расширенные (префикс 0xE0), т.е.
|
||||||
|
* смещение +32 байта. Упаковка вместо байта на код: 64 Б вместо 512 —
|
||||||
|
* заметная доля _DATA на 32-килобайтной раскладке (замер PoP roomtest
|
||||||
|
* 2026-07-30). Ведёт трамплин прерывания (libc/irq/_irq_tramp.c),
|
||||||
|
* читает kbd_raw_down(). */
|
||||||
|
extern uint8_t _kbdraw_down[64];
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ uint8_t _kbdraw_active;
|
|||||||
uint8_t _kbdraw_pending;
|
uint8_t _kbdraw_pending;
|
||||||
uint8_t _kbdraw_overrun; /* ISR взвёл при Rx-overrun SIO (потерян
|
uint8_t _kbdraw_overrun; /* ISR взвёл при Rx-overrun SIO (потерян
|
||||||
* break) — kbd_raw_sync снимет залипшее */
|
* break) — kbd_raw_sync снимет залипшее */
|
||||||
uint8_t _kbdraw_down[512];
|
uint8_t _kbdraw_down[64]; /* БИТ на скан-код (см. _kbdraw.h) */
|
||||||
|
|||||||
@@ -10,5 +10,5 @@
|
|||||||
uint8_t kbd_raw_down(uint16_t code)
|
uint8_t kbd_raw_down(uint16_t code)
|
||||||
{
|
{
|
||||||
if (code >= 512) return 0;
|
if (code >= 512) return 0;
|
||||||
return _kbdraw_down[code];
|
return (uint8_t)((_kbdraw_down[code >> 3] >> (code & 7)) & 1);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -36,7 +36,15 @@ void kbd_raw_sync(void)
|
|||||||
if (!_kbdraw_overrun) return;
|
if (!_kbdraw_overrun) return;
|
||||||
_kbdraw_overrun = 0;
|
_kbdraw_overrun = 0;
|
||||||
|
|
||||||
for (i = 0; i < 6; i++) save[i] = _kbdraw_down[_kbd_mod_codes[i]];
|
/* Карта упакована по битам (см. _kbdraw.h): сохраняем/возвращаем
|
||||||
|
* именно БИТ модификатора, а не байт. */
|
||||||
|
for (i = 0; i < 6; i++) {
|
||||||
|
uint16_t c = _kbd_mod_codes[i];
|
||||||
|
save[i] = (uint8_t)((_kbdraw_down[c >> 3] >> (c & 7)) & 1);
|
||||||
|
}
|
||||||
memset(_kbdraw_down, 0, sizeof(_kbdraw_down));
|
memset(_kbdraw_down, 0, sizeof(_kbdraw_down));
|
||||||
for (i = 0; i < 6; i++) _kbdraw_down[_kbd_mod_codes[i]] = save[i];
|
for (i = 0; i < 6; i++) {
|
||||||
|
uint16_t c = _kbd_mod_codes[i];
|
||||||
|
if (save[i]) _kbdraw_down[c >> 3] |= (uint8_t)(1 << (c & 7));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# w3bankgfx — пробник модели «huge + резидент W3 + банк W3 + графика».
|
||||||
|
#
|
||||||
|
# Проверяем ровно то, на чём стоит план раскладки PoP (docs/layout_plan_v2.md):
|
||||||
|
# R2 из __banked-кода резидентная страница W3 недостижима (НЕ зовём);
|
||||||
|
# R3 резидент -> банк работает через трамплин;
|
||||||
|
# R4 примитивы libbgi можно звать ИЗ БАНКА: _bgi_begin запоминает текущую
|
||||||
|
# страницу W3 (порт 0xE2) и _bgi_end её возвращает — значит банк
|
||||||
|
# переживает рисование и продолжает исполняться.
|
||||||
|
#
|
||||||
|
# Раскладка huge: CODE 0x4100 (W1), DATA 0x8000 (W2), банк 0x1C000 (W3),
|
||||||
|
# резидент --w3 делит W3 с банком (crt0 запоминает резидентную страницу).
|
||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
EXAMPLE := w3bgfx
|
||||||
|
MEMORY := huge
|
||||||
|
EXTRA_FLAGS := --gfx 256 --w3 res.c --bank 1=bank1.c
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
|
|
||||||
|
# Ожидаемый результат (проверено 2026-07-29, MAME dev):
|
||||||
|
# пять полос сверху вниз: синяя (резидент), бирюзовая (банк рисует сам),
|
||||||
|
# жёлтая (W1/W2-функция, вызванная из банка), ЗЕЛЁНАЯ (все проверки ок),
|
||||||
|
# синяя (резидент жив после возврата). Красная вместо зелёной = модель
|
||||||
|
# не подтвердилась. Диагностика в переменных bank_page_*/home_page_seen/
|
||||||
|
# res_page_seen: банк 0xF0, резидент 0xF3, страница W3 внутри банка не
|
||||||
|
# менялась вокруг рисования.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* bank1.c — __banked модуль (страница W3 подменяется трамплином на время
|
||||||
|
* вызова). Задача: проверить, что ИЗ БАНКА можно рисовать примитивами
|
||||||
|
* libbgi и звать функции W1/W2, и что после этого банк ЖИВ (его страница
|
||||||
|
* вернулась в W3).
|
||||||
|
*
|
||||||
|
* Резидент (res_draw) отсюда НЕ зовём сознательно: пока исполняется банк,
|
||||||
|
* резидентной страницы в адресном пространстве нет (правило R2).
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern uint8_t home_helper(uint8_t x); /* функция в W1/W2 (рисует сама) */
|
||||||
|
|
||||||
|
/* ВАЖНО: писучие статики __banked-модуля линкуются В СТРАНИЦУ БАНКА
|
||||||
|
* (адрес 0x1C000+), поэтому снаружи их не прочитать — из W1/W2 по 0xC000
|
||||||
|
* видна РЕЗИДЕНТНАЯ страница. Состояние банка держим в главном модуле. */
|
||||||
|
extern uint8_t bank_page_before, bank_page_after, bank_page_after_home;
|
||||||
|
extern uint8_t bank_alive;
|
||||||
|
|
||||||
|
/* Текущая страница W3 (порт 0xE2). ОТДЕЛЬНОЙ __naked-функцией: инлайновый
|
||||||
|
* `in a,(0xE2)` посреди тела затирает A, куда SDCC уже положил параметр —
|
||||||
|
* в первой версии пробника из-за этого цвет заливки становился номером
|
||||||
|
* страницы (0xF3 = чёрный), и «резидент не рисовал». */
|
||||||
|
static uint8_t w3_page(void) __naked
|
||||||
|
{
|
||||||
|
__asm__("in a, (#0xE2)\n\tret");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t bank_run(uint8_t arg) __banked
|
||||||
|
{
|
||||||
|
uint8_t r;
|
||||||
|
bank_page_before = w3_page();
|
||||||
|
|
||||||
|
/* 1) прямое рисование примитивом libbgi (он живёт в W1/W2 и сам
|
||||||
|
* открывает/закрывает W3-скобку) */
|
||||||
|
setfillstyle(SOLID_FILL, CYAN);
|
||||||
|
bar(70, 40, 120, 52);
|
||||||
|
bank_page_after = w3_page();
|
||||||
|
|
||||||
|
/* 2) вызов функции W1/W2, которая тоже рисует */
|
||||||
|
r = home_helper(arg);
|
||||||
|
bank_page_after_home = w3_page();
|
||||||
|
|
||||||
|
/* 3) банк ещё исполняется — значит его страница на месте */
|
||||||
|
bank_alive = 1;
|
||||||
|
return (uint8_t)(r + 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* res.c — РЕЗИДЕНТНЫЙ модуль окна W3 (--w3): лежит на 0xC000 и вызывается
|
||||||
|
* напрямую из W1/W2. Рисует через готовые примитивы libbgi — это законно:
|
||||||
|
* скобку _bgi_begin/_bgi_end они открывают внутри себя (сам W3-код открывать
|
||||||
|
* её НЕ имеет права, иначе исполняемая страница подменяется видеобанком).
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint8_t res_page_seen; /* какая страница была в W3 во время вызова */
|
||||||
|
|
||||||
|
/* Текущая страница W3 (порт 0xE2). ОТДЕЛЬНОЙ __naked-функцией: инлайновый
|
||||||
|
* `in a,(0xE2)` посреди тела затирает A, куда SDCC уже положил параметр —
|
||||||
|
* в первой версии пробника из-за этого цвет заливки становился номером
|
||||||
|
* страницы (0xF3 = чёрный), и «резидент не рисовал». */
|
||||||
|
static uint8_t w3_page(void) __naked
|
||||||
|
{
|
||||||
|
__asm__("in a, (#0xE2)\n\tret");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void res_draw(int y, int color)
|
||||||
|
{
|
||||||
|
res_page_seen = w3_page();
|
||||||
|
setfillstyle(SOLID_FILL, color);
|
||||||
|
bar(10, y, 60, y + 12);
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* w3bankgfx — пробник раскладки «huge + резидент W3 + банк W3 + графика».
|
||||||
|
* См. Makefile: проверяются правила R2/R3/R4 из
|
||||||
|
* applications/PoP/docs/layout_plan_v2.md §2.
|
||||||
|
*
|
||||||
|
* Итог рисуется полосами, чтобы читалось и глазом, и скриптом по VRAM:
|
||||||
|
* y=20 синяя — рисовал РЕЗИДЕНТ W3 (до банка)
|
||||||
|
* y=40 бирюз. — рисовал БАНК напрямую примитивом libbgi
|
||||||
|
* y=60 жёлтая — рисовала функция W1/W2, вызванная ИЗ БАНКА
|
||||||
|
* y=80 зелёная — все проверки прошли (иначе КРАСНАЯ)
|
||||||
|
* y=100 синяя — резидент W3 жив ПОСЛЕ возврата из банка
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
|
||||||
|
const uint8_t n_banks = 1; /* читает crt0_banked */
|
||||||
|
|
||||||
|
extern uint8_t res_page_seen;
|
||||||
|
uint8_t bank_page_before, bank_page_after, bank_page_after_home, bank_alive;
|
||||||
|
extern uint8_t bank_pages[];
|
||||||
|
|
||||||
|
void res_draw(int y, int color); /* резидент W3 (прямой вызов) */
|
||||||
|
uint8_t bank_run(uint8_t arg) __banked; /* банк (через трамплин) */
|
||||||
|
|
||||||
|
uint8_t home_page_seen;
|
||||||
|
|
||||||
|
/* Текущая страница W3 (порт 0xE2). ОТДЕЛЬНОЙ __naked-функцией: инлайновый
|
||||||
|
* `in a,(0xE2)` посреди тела затирает A, куда SDCC уже положил параметр —
|
||||||
|
* в первой версии пробника из-за этого цвет заливки становился номером
|
||||||
|
* страницы (0xF3 = чёрный), и «резидент не рисовал». */
|
||||||
|
static uint8_t w3_page(void) __naked
|
||||||
|
{
|
||||||
|
__asm__("in a, (#0xE2)\n\tret");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Функция W1/W2, которую зовёт БАНК. Рисует — то есть внутри неё libbgi
|
||||||
|
* подменит W3 на видеобанк и вернёт обратно страницу БАНКА, а не резидента. */
|
||||||
|
uint8_t home_helper(uint8_t x)
|
||||||
|
{
|
||||||
|
home_page_seen = w3_page();
|
||||||
|
setfillstyle(SOLID_FILL, YELLOW);
|
||||||
|
bar(130, 60, 180, 72);
|
||||||
|
return (uint8_t)(x * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
uint8_t ret, ok;
|
||||||
|
uint8_t res_page, bank_page;
|
||||||
|
|
||||||
|
initgraph();
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(0, 0, 319, 255);
|
||||||
|
|
||||||
|
res_draw(20, BLUE); /* R3-часть: W1/W2 -> резидент W3 */
|
||||||
|
res_page = res_page_seen;
|
||||||
|
|
||||||
|
ret = bank_run(21); /* R3: резидент/HOME -> банк */
|
||||||
|
bank_page = bank_page_before;
|
||||||
|
|
||||||
|
/* Критерии: банк дожил до конца, вернул r+1 = 21*2+1 = 43, страница W3
|
||||||
|
* ВНУТРИ банка не менялась вокруг рисования (её вернули), и она НЕ равна
|
||||||
|
* резидентной. */
|
||||||
|
ok = (uint8_t)(bank_alive == 1 && ret == 43 &&
|
||||||
|
bank_page_after == bank_page &&
|
||||||
|
bank_page_after_home == bank_page &&
|
||||||
|
home_page_seen == bank_page &&
|
||||||
|
bank_page != res_page);
|
||||||
|
|
||||||
|
setfillstyle(SOLID_FILL, ok ? GREEN : RED); /* зелёная / красная */
|
||||||
|
bar(10, 80, 60, 92);
|
||||||
|
|
||||||
|
res_draw(100, BLUE); /* резидент жив после возврата? */
|
||||||
|
|
||||||
|
printf("res_page=%02X bank_page=%02X after=%02X afterhome=%02X home=%02X ret=%u alive=%u ok=%u\n",
|
||||||
|
res_page, bank_page, bank_page_after, bank_page_after_home,
|
||||||
|
home_page_seen, ret, bank_alive, ok);
|
||||||
|
printf("bank_pages[1]=%02X\n", bank_pages[1]);
|
||||||
|
|
||||||
|
while (1) { } /* держим картинку для скрипта */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+41
-5
@@ -11,6 +11,9 @@
|
|||||||
#
|
#
|
||||||
# Использование:
|
# Использование:
|
||||||
# toolchain/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ...
|
# toolchain/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ...
|
||||||
|
# toolchain/make_hdd.sh out.chd prog.exe KID:kid0.atl GUARD:g0.atl ...
|
||||||
|
# — аргумент вида КАТАЛОГ:файл кладёт файл в подкаталог образа
|
||||||
|
# (каталог создаётся); без префикса файл идёт в корень.
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ $# -lt 2 ]; then
|
if [ $# -lt 2 ]; then
|
||||||
@@ -35,18 +38,51 @@ dd if=/dev/zero of="$RAW" bs=512 count=$SECTORS status=none
|
|||||||
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
||||||
export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1
|
export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1
|
||||||
|
|
||||||
# MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16.
|
# mtools собран с enable-raw-term и, когда ему нужно что-то спросить,
|
||||||
mpartition -I -c -t $CYLS -h $HEADS -s $SECS z:
|
# открывает УПРАВЛЯЮЩИЙ ТЕРМИНАЛ напрямую (/dev/tty) — в обход stdin.
|
||||||
mformat z:
|
# Поэтому ни `< /dev/null`, ни перенаправление stdio не спасают: из
|
||||||
|
# терминала сборка молча вставала навсегда на `mmd z:/BG` (подтверждено
|
||||||
|
# lsof: fd 0 = /dev/null, fd 4 = /dev/tty, процесс спит). Лечится только
|
||||||
|
# отсутствием управляющего терминала — запускаем mtools в новой сессии,
|
||||||
|
# тогда открыть /dev/tty нечего и mtools идёт неинтерактивным путём.
|
||||||
|
#
|
||||||
|
# setsid(1) в macOS нет, поэтому делаем то же самое питоном.
|
||||||
|
# Каналов, откуда mtools может ждать ответ, ДВА, и закрывать надо оба:
|
||||||
|
# /dev/tty — убирается новой сессией (os.setsid);
|
||||||
|
# stdin — убирается перенаправлением из /dev/null.
|
||||||
|
# Закрыть только один недостаточно: с одним лишь setsid mmd переключался на
|
||||||
|
# stdin и вис уже на нём (lsof: fd 0 = /dev/ttys002).
|
||||||
|
mt() {
|
||||||
|
python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' \
|
||||||
|
"$@" < /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16.
|
||||||
|
echo "--- разметка и формат образа ---"
|
||||||
|
mt mpartition -I -c -t $CYLS -h $HEADS -s $SECS z:
|
||||||
|
mt mformat z:
|
||||||
|
|
||||||
|
echo "--- копирование файлов ---"
|
||||||
|
|
||||||
|
# Аргумент может быть либо файлом (кладём в корень), либо парой
|
||||||
|
# КАТАЛОГ:файл — тогда каталог создаётся на образе и файл кладётся туда.
|
||||||
|
# Имена на Sprinter/DSS — 8.3, каталоги тоже (KID, GUARD, BG, LEVELS...).
|
||||||
for f in "$@"; do
|
for f in "$@"; do
|
||||||
mcopy -o "$f" z:
|
case "$f" in
|
||||||
|
*:*) dir="${f%%:*}"; src="${f#*:}"
|
||||||
|
# Каталог может уже быть — это не ошибка; stderr НЕ прячем, иначе
|
||||||
|
# непрошедший вопрос mtools снова окажется невидимым.
|
||||||
|
mt mmd "z:/$dir" >/dev/null 2>&1 || true
|
||||||
|
mt mcopy -o "$src" "z:/$dir/" ;;
|
||||||
|
*) mt mcopy -o "$f" z: ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "--- содержимое образа ---"
|
echo "--- содержимое образа ---"
|
||||||
mdir z:
|
mt mdir -/ z:
|
||||||
|
|
||||||
# RAW -> CHD (без сжатия = быстро; та же геометрия).
|
# RAW -> CHD (без сжатия = быстро; та же геометрия).
|
||||||
|
echo "--- конвертация RAW -> CHD ---"
|
||||||
chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \
|
chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \
|
||||||
-chs $CYLS,$HEADS,$SECS -ss 512 -f
|
-chs $CYLS,$HEADS,$SECS -ss 512 -f
|
||||||
echo "готово: $OUT_CHD"
|
echo "готово: $OUT_CHD"
|
||||||
|
|||||||
Reference in New Issue
Block a user