PoP: roomtest — объекты/обломки/переходы комнат + арт
Порт Prince of Persia (applications/PoP/roomtest): развитие уровня, объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка; планы (room_model/size_optimization), bug_list, pop_trob. Арт third_party/16x16-RPG-characters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@@ -0,0 +1,68 @@
|
|||||||
|
# PoP roomtest — модель `kid_room ≠ drawn_room` (баг #4)
|
||||||
|
|
||||||
|
Порт straddle-модели SDLPoP: персонаж может находиться в СОСЕДНЕЙ комнате,
|
||||||
|
пока на экране ещё ТЕКУЩАЯ (drawn_room). Источник истины — SDLPoP.
|
||||||
|
|
||||||
|
## Факты из SDLPoP (подтверждено чтением исходника)
|
||||||
|
|
||||||
|
- `Char.room` (реальная комната персонажа) ≠ `drawn_room` (отрисованная) —
|
||||||
|
штатное состояние.
|
||||||
|
- **Коллизия через ±140:** `xpos_in_drawn_room()` (seg004:0405) сдвигает
|
||||||
|
xpos на `±TILE_SIZEX*SCREEN_TILECOUNTX = ±140`, когда `curr_room` колонки
|
||||||
|
(`curr_row_coll_room[col]`) ≠ `drawn_room` (room_L/room_BL → −140,
|
||||||
|
room_R/room_BR → +140). Т.е. коллизия строится по РЕАЛЬНЫМ тайлам соседей.
|
||||||
|
- **Смена экрана:** `check_the_end()` (seg000:0FBD): `if (next_room!=0 &&
|
||||||
|
next_room!=drawn_room) { drawn_room=next_room; load_room_links; redraw }`.
|
||||||
|
`next_room` ставится в `exit_room()` (= `Char.room` ПОСЛЕ успешного
|
||||||
|
`leave_room`). Значит drawn_room следует за Char.room, но Char.room меняется
|
||||||
|
ТОЛЬКО при реальном пересечении шва (leave_room, seg002:0504) на «легальном»
|
||||||
|
кадре/действии (не turn/climb/standup).
|
||||||
|
- **Отрисовка левого соседа:** только `load_leftroom()` (col9 левого соседа в
|
||||||
|
левую кромку); правый сосед НЕ рисуется (изометрия). Окклюзия ворот на шве —
|
||||||
|
только левая (seg008:696).
|
||||||
|
- **Ceiling-полоса:** `draw_room` рисует доп. ряд из `room_A` (row2, draw_main_y
|
||||||
|
=-1). (Уже реализовано, баг #3.)
|
||||||
|
|
||||||
|
## Текущее состояние нашего движка (до #4)
|
||||||
|
|
||||||
|
`cur_room` (=drawn_room) ВСЕГДА == комната Kid. Шов подделан: Kid остаётся в
|
||||||
|
drawn_room с `curr_col=-1/10` + снапшоты соседей `g_lcol/g_rcol` (коллизия ±1
|
||||||
|
кол) / `lcol_bg` (openness ворот). Уход из комнаты — `pop_leave_dir`/`enter_room`
|
||||||
|
МГНОВЕННО при пересечении порога `char_x`. Отсюда #4: экран переключается
|
||||||
|
раньше, чем в оригинале (Kid должен «отступить» за кромку, оставив старую
|
||||||
|
комнату).
|
||||||
|
|
||||||
|
## План (инкременты, каждый проверяется в MAME)
|
||||||
|
|
||||||
|
### S1. Данные + рендер-смещение Kid
|
||||||
|
- Ввести `kid_room` (реальная комната Kid) отдельно от `cur_room`(=drawn_room).
|
||||||
|
- `kid_x_offset()` = разница комнат: kid_room == left(drawn) → лог. x Kid −140
|
||||||
|
(рисуется за левой кромкой); right → +140; равны → 0. (порт
|
||||||
|
xpos_in_drawn_room).
|
||||||
|
- `kid_draw`/heal/fore используют смещение (Kid рисуется частично за кромкой).
|
||||||
|
- Проверка: Kid у шва рисуется со сдвигом, экран не дёргается.
|
||||||
|
|
||||||
|
### S2. Коллизия по kid_room
|
||||||
|
- Коллизионный контекст (`g_fg`/edges/`g_room`/modif в pop_map) следует за
|
||||||
|
`kid_room`, а не за drawn_room. Когда kid_room≠drawn_room — грузим
|
||||||
|
соседа как коллизионную комнату (curr_col 0..9 в кадре kid_room).
|
||||||
|
- Отрисовка (room_fg и т.п.) остаётся по drawn_room.
|
||||||
|
- Порт `curr_row_coll_room[]`/`xpos_in_drawn_room` можно упростить: держим
|
||||||
|
ОДИН коллизионный room (kid_room) + существующие снапшоты кромок для ±1 кол.
|
||||||
|
|
||||||
|
### S3. Отложенная смена drawn_room
|
||||||
|
- Уход (`check_leave`/`check_leave_below`): ставит `kid_room=сосед`,
|
||||||
|
репроецирует Kid (x∓140, col∓10) — но drawn_room НЕ меняет сразу.
|
||||||
|
- `check_the_end`-эквивалент в главном цикле: `if (kid_room != drawn_room &&
|
||||||
|
<условие коммита>) enter_room(kid_room)`. Условие коммита — по SDLPoP:
|
||||||
|
как только Char.room сменилась легальным leave (не bumped/turn). Для
|
||||||
|
«bumped назад за кромку» drawn_room остаётся (симптом #4).
|
||||||
|
- Проверка сценариев #4/#5 в MAME.
|
||||||
|
|
||||||
|
### S4. Полировка
|
||||||
|
- Окклюзия/ceiling у шва при straddle, BUG-OCCL-1 (глубина), правый край.
|
||||||
|
|
||||||
|
## Связанные баги (bug_list.md)
|
||||||
|
BUG-CEIL-1 (руки при прыжке вверх), BUG-CEIL-2 (loose в потолке),
|
||||||
|
BUG-CEIL-3 (потолок над анимируемыми воротами), BUG-OCCL-1 (тень дальней
|
||||||
|
колонны). Memory: `pop_seam_room_model`.
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
# roomtest — план оптимизации по размеру + переход на huge/banking
|
||||||
|
|
||||||
|
Статус: **план для отдельной сессии** (2026-07-21). Документ самодостаточный
|
||||||
|
(рассчитан на старт с пустого контекста). Цель — освободить место: сейчас
|
||||||
|
`applications/PoP/roomtest` в режиме `small` почти упёрся в потолок 32 КБ.
|
||||||
|
|
||||||
|
Правило проекта (`applications/PoP/CLAUDE.md`): механику/раскладку памяти
|
||||||
|
сверять с исходником и с memory (`sprinter_memory_modes`, `sdcc_banking`,
|
||||||
|
`bank_local_data_pattern`, `pop_banking_architecture`). Перед оптимизацией —
|
||||||
|
`make size-check`-подобный замер до/после (здесь — руками по `.map`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Как мерить
|
||||||
|
|
||||||
|
- Сборка: `cd applications/PoP/roomtest && make roomtest.exe` (режим `small`,
|
||||||
|
`--gfx 256`). Карта символов — `.sprinter-cc-roomtest/roomtest.map`
|
||||||
|
(адреса сдвигаются при каждой пересборке!).
|
||||||
|
- Размеры областей — из `.map` (`_CODE`, `_DATA`, `_BSS`).
|
||||||
|
- Вклад модулей в `_CODE` — атрибуция диапазонов между символами по модулю
|
||||||
|
(скрипт-однострочник на python в истории; группировать символы `.map` по
|
||||||
|
3-й колонке-модулю и суммировать `addr[i+1]-addr[i]`).
|
||||||
|
- MAME-проверка после изменений раскладки ОБЯЗАТЕЛЬНА (режимы памяти —
|
||||||
|
типовой источник «молча ломается», см. `sprinter_memory_modes`).
|
||||||
|
|
||||||
|
## 1. ТЕКУЩЕЕ СОСТОЯНИЕ (замер 2026-07-21)
|
||||||
|
|
||||||
|
Режим `small` = единое пространство **W1+W2 = 0x4000..0xBFFF (32 КБ)**; CODE с
|
||||||
|
0x4100, DATA/BSS/heap цепляются ЗА CODE автоматически (`--data-loc 0` =
|
||||||
|
linker chains), стек — вверху W2.
|
||||||
|
|
||||||
|
| Область | Размер | Диапазон |
|
||||||
|
|---------|--------|----------|
|
||||||
|
| `_CODE` | ~27 250 Б (0x6A6F) | 0x4100–0xAB6F |
|
||||||
|
| `_HOME` | 227 Б | 0xAB6F |
|
||||||
|
| `_DATA` | 3 449 Б (0x0D79) | 0xAC78–0xB9F1 |
|
||||||
|
| `_BSS` | 290 Б | |
|
||||||
|
|
||||||
|
**Образ ≈ 31.2 КБ; до верха W2 (0xBFFF) остаётся ≈ 1.3 КБ на кучу+стек.**
|
||||||
|
Куча в roomtest почти не используется (атласы/уровень — в EMM-страницах),
|
||||||
|
но запас критично мал.
|
||||||
|
|
||||||
|
### Вклад модулей в _CODE (по .map, приблизительно)
|
||||||
|
```
|
||||||
|
7003 pop_bg (вся отрисовка тайлов/слоёв/wall_pattern)
|
||||||
|
6326 pop_kid (из них ~3745 Б — СТАТ. ТАБЛИЦЫ kid_data.h, см. ниже)
|
||||||
|
3762 pop_map (коллизия/физика/пики)
|
||||||
|
1455 pop_trob (кнопки/ворота/пики-каркас)
|
||||||
|
1224 pop_level (загрузка уровня, doorlink)
|
||||||
|
914 roomtest (главный цикл)
|
||||||
|
~7000 libc/libbgi (gfx_blit*, atlas_load, kbd_raw, open/read, irq, div/mul…)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Крупные СТАТИЧЕСКИЕ данные (сейчас в _CODE как `const`)
|
||||||
|
- **`kid_data.h` — самый большой кусок, ~3.7 КБ**, живёт в _CODE (атрибутируется
|
||||||
|
pop_kid):
|
||||||
|
- `kid_seqtbl[2310]` — байткод последовательностей (play_seq).
|
||||||
|
- `kid_frames[241]` × 5 Б = 1205 Б — таблица кадров (image,dx,dy,flags,sword).
|
||||||
|
- `kid_seq_off[115]` × 2 Б = 230 Б — смещения seq.
|
||||||
|
- `pop_bg`: `tile_table[31]`×12 = 372 Б + ~20 мелких const-таблиц (COL_XH,
|
||||||
|
WALL_FRAM_*, SPIKES_FRAM_{RIGHT,LEFT,FORE}, LOOSE_FRAM_*, DOOR_FRAM_SLICE,
|
||||||
|
BLUELINE_*, LPOS/RPOS, FLOOR_LEFT_OVERLAY) — суммарно ~0.5–0.7 КБ.
|
||||||
|
- `pop_map`: `x_bump[20]`, `y_land[5]`, `wall_dl/dr`, `dir_front/behind` — ~100 Б.
|
||||||
|
- В `_DATA` (W2, не CODE): `room_modif[24][30]`=720 Б + копии LINKLOC/LINKMAP=512 Б
|
||||||
|
(pop_trob/pop_level) + рабочие массивы roomtest.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. ПУТЬ A — оптимизация КОДА (без смены модели)
|
||||||
|
|
||||||
|
1. **Компиляторные флаги** (`bin/sprinter-cc`): попробовать `--opt-code-size`
|
||||||
|
у SDCC и подобрать `--max-allocs` (сейчас дефолт 100000; меньше = мельче код,
|
||||||
|
но медленнее компиляция; см. `mdview2_size_budget` — там `--max-allocs`
|
||||||
|
давал −1.4 КБ). Замерить каждый модуль отдельно.
|
||||||
|
2. **Дедуп подстановки нажатой кнопки**: логика `opener→floor / closer→stuck`
|
||||||
|
по таймеру связи ПРОДУБЛИРОВАНА в `draw_tile` и `fore_tile` (pop_bg.c).
|
||||||
|
Вынести в `static inline`/helper `subst_pressed_button(code,mod)`.
|
||||||
|
3. **wall_pattern / prandom** (pop_bg): 32-битный LCG (`unsigned long`) —
|
||||||
|
пользователь не любит 32-бит (см. `avoid_32bit_arith_z80`); но это PRNG
|
||||||
|
оригинала (нужен для совпадения раскладки стен) — трогать осторожно, только
|
||||||
|
если найдётся 16-битный эквивалент, дающий ТУ ЖЕ последовательность.
|
||||||
|
4. **Ревизия дублей**: `y_to_row` определён в pop_bg И pop_map; мелкие
|
||||||
|
геометрические хелперы дублируются — свести в один internal-модуль.
|
||||||
|
5. `/simplify`-проход по последним правкам Фазы B (pop_trob/pop_bg).
|
||||||
|
|
||||||
|
Ожидаемый выигрыш пути A: единицы–первые сотни байт на пункт; в сумме,
|
||||||
|
оптимистично, ~1–2 КБ. Недостаточно как единственная мера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. ПУТЬ B — вынос СТАТ. ДАННЫХ в EMM-страницы (с атласами / с level)
|
||||||
|
|
||||||
|
**Идея (по замечанию пользователя):** EMM-страницы атласов и уровня
|
||||||
|
использованы лишь частично (страница 16 КБ, данных меньше), в «хвосте» —
|
||||||
|
свободное место. Часть `const`-таблиц можно хранить ТАМ, а не в _CODE/_DATA,
|
||||||
|
если таблица читается ИМЕННО ТОГДА, когда нужная страница уже в W0.
|
||||||
|
|
||||||
|
**Механика W0:** атласы блитятся из W0 (`_gfx_w0_state`: `_gfx_w0_cur` —
|
||||||
|
спрайт-страница в W0; ISR-стаб `_gfx_w0_isr` возвращает её после прерывания).
|
||||||
|
Уровень (pop_level) маппит свою страницу в W0 на время извлечения
|
||||||
|
(`gfx_w0_map`/`gfx_w0_unmap`). → пока страница в W0, CPU может читать и
|
||||||
|
данные из неё по адресам 0x0000..0x3FFF.
|
||||||
|
|
||||||
|
**Категоризация таблиц по W0-контексту (задача сессии — уточнить по каждой):**
|
||||||
|
|
||||||
|
- **(a) Читается, когда в W0 АТЛАС** → хранить в свободном хвосте атлас-страницы.
|
||||||
|
Кандидаты — таблицы, которые нужны В МОМЕНТ блита конкретного атласа.
|
||||||
|
ГРАБЛИ: `draw_tile` читает `tile_table`/`COL_XH` ДО блита (чтобы решить, какой
|
||||||
|
спрайт/куда) — в этот момент в W0 может быть ДРУГАЯ страница (DSS/предыдущий
|
||||||
|
атлас). Т.е. большинство draw-таблиц читаются ВНЕ W0-атлас-контекста →
|
||||||
|
«в лоб» не переносятся. Нужен аудит КАЖДОГО чтения: гарантирована ли нужная
|
||||||
|
страница в W0 в этот тик.
|
||||||
|
- **(b) Читается, когда в W0 LEVEL** → хранить с уровнем (в его странице; там
|
||||||
|
~13.7 КБ свободно из 16). Кандидаты: константы декода doorlink, разбор
|
||||||
|
комнат — всё, что pop_level делает под `gfx_w0_map(lvl_page)`.
|
||||||
|
- **(c) Нужна и там, и там** → дублировать в обеих страницах ЛИБО оставить
|
||||||
|
резидентной (если дубли дороже экономии).
|
||||||
|
- **(d) Читается в чистой ЛОГИКЕ (W0 не важен)** → перенос требует ЯВНОГО
|
||||||
|
`gfx_w0_map` на каждое чтение (дорого, особенно в горячих циклах) → как
|
||||||
|
правило оставить резидентной.
|
||||||
|
|
||||||
|
**Отдельно `kid_data.h` (3.7 КБ — самый жирный кандидат):**
|
||||||
|
- `kid_frames`/`kid_seqtbl` читаются в `play_seq` (ЧИСТАЯ логика, каждый тик) И
|
||||||
|
в `kid_draw` (блит из kid-атласа, kid-страница в W0). Т.е. частично (a),
|
||||||
|
частично (d). Перенос всей таблицы в kid-атлас-страницу заставит `play_seq`
|
||||||
|
делать `gfx_w0_map` на каждый шаг байткода → замерить стоимость (может убить
|
||||||
|
бюджет спрайтов, см. `sprite_engine_perf`). Вариант: держать в EMM отдельной
|
||||||
|
страницей данных Kid и маппить один раз на кадр вокруг kid_tick+kid_draw.
|
||||||
|
- Это самый большой одиночный выигрыш (−3.7 КБ из _CODE), но и самый рискованный
|
||||||
|
по скорости — приоритетный к ПРОТОТИПИРОВАНИЮ и замеру.
|
||||||
|
|
||||||
|
**Паттерн переноса writable/const данных в банк/страницу:** см. memory
|
||||||
|
`bank_local_data_pattern` (--codeseg/--constseg/--dataseg BANKn + trampoline-fix
|
||||||
|
+ mkexe -p 0) и `sdcc_static_storage_gotcha`.
|
||||||
|
|
||||||
|
### 3.1 Свободное место в страницах (замер 2026-07-21, страница = 16384 Б)
|
||||||
|
```
|
||||||
|
BG-атласы: размер свободно
|
||||||
|
pop_env0.atl 10578 5806
|
||||||
|
pop_env1.atl 12449 3935 <- САМАЯ ТЕСНАЯ из bg
|
||||||
|
pop_env2.atl 10798 5586
|
||||||
|
pop_env3.atl 5032 11352 <- много места
|
||||||
|
pop_env4.atl 8498 7886
|
||||||
|
pop_wall.atl 11543 4841
|
||||||
|
pop_fore.atl 7763 8621
|
||||||
|
Kid-атласы (28 стр): free min=6161 max=15452 avg=9722
|
||||||
|
Level (res2001.bin): данные 2305, свободно ~13823 (16384 − 0x100 стаб − 2305)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Выводы по вместимости:**
|
||||||
|
- **Макс. данных в ОДНОМ атлас-банке = свободный хвост ЭТОЙ страницы** (см.
|
||||||
|
таблицу). Связывающее ограничение — самая тесная нужная страница (env1 =
|
||||||
|
3935 Б; не перегружать её).
|
||||||
|
- Если страница будет маппиться в **W0** — минус ~0x100 Б на ISR-стаб (как
|
||||||
|
level). Атлас-страницы стаб УЖЕ содержат (atlas_load патчит) → данные класть
|
||||||
|
в хвост ПОСЛЕ атласа.
|
||||||
|
- **`kid_data.h` (3.7 КБ) влезает в kid-страницу** (min free 6161) или в
|
||||||
|
отдельную выделенную страницу данных Kid — предпочтительно отдельную (маппить
|
||||||
|
раз на кадр, не конфликтуя с kid-атласами блита).
|
||||||
|
- **Level-таблицы** — вагон места в level-странице (~13.8 КБ).
|
||||||
|
- **BG draw-таблицы** (~0.7 КБ) влезут в env3/fore/env4 (много free), НО см.
|
||||||
|
граблю W0-контекста в §3(a) — читаются ли они, когда нужная страница в W0.
|
||||||
|
- **Выделенная страница ТОЛЬКО под данные** (не делить с атласом) = до ~16 КБ
|
||||||
|
(−0x100 стаб при W0-маппинге). EMM-бюджет это позволяет (см.
|
||||||
|
`sprinter_emm_budget`: 215/3440 КБ free на старте).
|
||||||
|
- **Принудительно уменьшать макс. атлас (репак мельче) — КРАЙНИЙ случай:** это
|
||||||
|
резко поднимет число атлас-банков (сейчас 5 env-страниц адресуются как id>>5;
|
||||||
|
дробление ломает эту адресацию и множит страницы). Сначала использовать
|
||||||
|
СУЩЕСТВУЮЩИЙ свободный хвост и отдельные data-страницы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. ПУТЬ C — переход на huge (banked code)
|
||||||
|
|
||||||
|
### 4.1 Что такое huge сейчас (`bin/sprinter-cc`, `runtime/crt0_banked`)
|
||||||
|
- `--memory huge`: `MODE_CODE_LOC=0x4100`, **`MODE_DATA_LOC=0x8000` (ФИКС.)**,
|
||||||
|
banked code в W3. crt0_banked, как crt0_small, авто-детектит W2.
|
||||||
|
Помечено `[TODO]` — не обкатано.
|
||||||
|
- Отличие от small: small цепляет DATA сразу за CODE (`--data-loc 0`); huge
|
||||||
|
ФИКСИРУЕТ DATA на 0x8000.
|
||||||
|
|
||||||
|
### 4.2 ТРЕБОВАНИЕ (по пользователю): huge должен переносить DATA динамически
|
||||||
|
Сейчас huge жёстко кладёт DATA на 0x8000. Если РЕЗИДЕНТНЫЙ CODE вылезет за
|
||||||
|
0x8000 (W1 = только 0x4000..0x7FFF ≈ 16 КБ; резидент > 16 КБ лезет в W2) →
|
||||||
|
коллизия с DATA. **Надо научить huge класть DATA динамически ЗА резидентным
|
||||||
|
CODE (как small: `--data-loc 0` + crt0 считает старт), а не на фикс 0x8000.**
|
||||||
|
Тогда huge = «small-раскладка резидента (W1+W2, DATA за CODE) + ДОП. код в
|
||||||
|
банках W3». Это первый пункт работ по huge.
|
||||||
|
|
||||||
|
### 4.3 КОНФЛИКТ: графика тоже хочет W3 (ключевой риск)
|
||||||
|
`pop_banking_architecture` прямо говорит: **графику нельзя в W3** (блиты/атласы
|
||||||
|
используют окна; см. §4.5). Поэтому в банки W3 можно выносить ТОЛЬКО
|
||||||
|
НЕ-графические блоки, и такой банк НЕ должен во время своего исполнения держать
|
||||||
|
графику в W3. Если W3-банкованная функция ЗОВЁТ графику (которой нужен W3),
|
||||||
|
трамплин обязан сохранить/восстановить банк вокруг вызова (проверить, что
|
||||||
|
banking-ABI это делает — `sdcc_banking`). Альтернатива без этого риска —
|
||||||
|
**big + BANK_W1** (банк кода в W1, не W3), рекомендованная в
|
||||||
|
`pop_banking_architecture` именно из-за W3-графики. Сессия должна выбрать:
|
||||||
|
huge(W3) с аккуратным save/restore ИЛИ big(BANK_W1).
|
||||||
|
|
||||||
|
### 4.4 Какие блоки МОЖНО вынести (не работают с графикой напрямую)
|
||||||
|
Замер graphics-ref по модулям (grep `gfx_|blit|env_b|wall_b|fore_b|setfillstyle|
|
||||||
|
bar(|GFX_BANK|initgraph`):
|
||||||
|
```
|
||||||
|
pop_bg.c : 83 — РЕЗИДЕНТ (вся отрисовка)
|
||||||
|
roomtest.c : 23 — РЕЗИДЕНТ (главный цикл + флип страниц)
|
||||||
|
pop_level.c : 17 — использует gfx_w0_map (W0, не W3-блиты) — ПОГРАНИЧНЫЙ
|
||||||
|
pop_kid.c : 12 — kid_draw = графика; НО play_seq — чистая логика (можно split)
|
||||||
|
pop_ctrl.c : 0 — КАНДИДАТ В БАНК (ввод/диспетчер control)
|
||||||
|
pop_map.c : 0 — КАНДИДАТ В БАНК (коллизия/физика, ~3.8 КБ) — лучший по объёму
|
||||||
|
pop_trob.c : 0 — КАНДИДАТ В БАНК (кнопки/ворота/пики-логика)
|
||||||
|
```
|
||||||
|
- **Лучшие кандидаты в W3-банк(и): pop_map + pop_trob + pop_ctrl** (нет прямой
|
||||||
|
графики; вместе ~5.3 КБ CODE). Освобождают резидент → он влезает в W1.
|
||||||
|
- **Осторожно с межбанковыми вызовами:** pop_map/pop_trob ЗОВУТ pop_bg
|
||||||
|
(перерисовка loose/пик/кнопок/шва) и pop_kid (play_seq/kid_set_seq). Это
|
||||||
|
кросс-банк вызовы через трамплин (`sdcc_banking`: стек +3 байта, виртуальный
|
||||||
|
24-битный адрес). Правило `pop_banking_architecture`: «один файл = один банк
|
||||||
|
= прямые вызовы», main резидентен. Проверить, что трамплин сохраняет W3
|
||||||
|
вокруг вызова в графический pop_bg (см. §4.3).
|
||||||
|
- **pop_kid split** (по желанию): вынести play_seq/seqtbl-интерпретатор
|
||||||
|
(логика + таблицы kid_data.h) в банк, оставить kid_draw/kid_heal резидентными.
|
||||||
|
Даёт и −код, и −данные из резидента, но требует аккуратного разделения TU
|
||||||
|
(1 функция = 1 модуль, см. `libc_one_function_per_module`).
|
||||||
|
- **pop_level: пограничный** — не блитит, но маппит уровень в W0; банковать
|
||||||
|
можно, если W0-логика совместима с трамплином (проверить ISR-стаб взаимодействие).
|
||||||
|
|
||||||
|
### 4.5 Почему графику нельзя в W3 (контекст)
|
||||||
|
Блиттер держит спрайт-страницу атласа в **W0** (`_gfx_w0_state`,
|
||||||
|
`_gfx_w0_isr`). Ускоритель/адресация видео — отдельная тема (см.
|
||||||
|
`sprinter_accelerator`, `sprinter_graphics`). W3 в banked-раскладке — окно
|
||||||
|
кода-банка; смешивать с окном, которое графика перемапливает, нельзя без
|
||||||
|
save/restore. Детально — `pop_banking_architecture`, `graphics_constraints`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. РЕКОМЕНДУЕМЫЙ ПОРЯДОК РАБОТ (для след. сессии)
|
||||||
|
|
||||||
|
1. **Замер-базлайн** (CODE/DATA/BSS + per-module) — зафиксировать до.
|
||||||
|
2. **Путь A** дешёвые пункты (флаги, дедуп кнопки, дедуп y_to_row) — быстрый −1..2 КБ.
|
||||||
|
3. **huge §4.2**: научить huge класть DATA динамически (как small) — инфраструктурный
|
||||||
|
пререквизит, без него банкинг не даст гибкости. Обкатать в MAME на текущем
|
||||||
|
резиденте (пока без выноса — просто huge-раскладка = small + пустой W3).
|
||||||
|
4. **huge §4.4**: вынести pop_map (+pop_trob, +pop_ctrl) в W3-банк(и); проверить
|
||||||
|
кросс-банк вызовы в pop_bg (§4.3) в MAME. ЛИБО выбрать big+BANK_W1.
|
||||||
|
5. **Путь B** (по остатку нужды): прототип выноса `kid_data.h` в EMM-страницу
|
||||||
|
Kid с маппингом раз на кадр; замерить скорость (`sprite_engine_perf`).
|
||||||
|
Затем аудит draw-таблиц по W0-контексту (§3 a/b/c/d).
|
||||||
|
|
||||||
|
## 6. Ссылки
|
||||||
|
- `bin/sprinter-cc` (§162+ — резолв memory-mode → CODE_LOC/DATA_LOC).
|
||||||
|
- `runtime/crt0_small.*`, `runtime/crt0_banked.*`, `runtime/bank.s`.
|
||||||
|
- memory: `sprinter_memory_modes`, `memory_modes_implemented`,
|
||||||
|
`setwin2_for_w2_alloc`, `sdcc_banking`, `bank_local_data_pattern`,
|
||||||
|
`pop_banking_architecture`, `avoid_32bit_arith_z80`,
|
||||||
|
`libc_one_function_per_module`, `sprite_engine_perf`, `mdview2_size_budget`.
|
||||||
|
- `applications/PoP/roomtest/bug_list.md` — открытые баги Фазы B (не блокируют
|
||||||
|
оптимизацию, но держать в уме при рефакторе pop_map/pop_bg).
|
||||||
@@ -6,8 +6,8 @@ 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 ?= small
|
||||||
EXTRA_FLAGS ?= --gfx 256
|
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c --w3 pop_map.c --w3 pop_bg.c
|
||||||
EXTRA_SRCS := pop_bg.c pop_kid.c pop_ctrl.c pop_map.c pop_level.c
|
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.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
|
||||||
@@ -33,4 +33,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
|||||||
kid_data.h: $(TC)/pop_extract_kid_data.py
|
kid_data.h: $(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 kid_data.h $(BG_DATA) $(KID_DATA) $(LVL_DATA)
|
$(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) $(LVL_DATA)
|
||||||
|
|||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# roomtest — список известных багов (Фаза B и смежное)
|
||||||
|
|
||||||
|
Найдено при тестировании Фазы B (кнопки/ворота) в MAME. **НЕ решаем сейчас**
|
||||||
|
— задача следующего этапа. Правило проекта: механику сверять с
|
||||||
|
`applications/PoP/SDLPoP/src/` ДО кодинга.
|
||||||
|
|
||||||
|
Статус на момент записи: 2026-07-21. Тестовый старт — комната 6 (`START_ROOM`
|
||||||
|
в `roomtest.c`). Кнопка room6(0,2, opener) → ворота room8(0,9), видны в левом
|
||||||
|
шве room6.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BUG-1. Боковой переход через ворота: Kid проваливается на row 1 — **Critical**
|
||||||
|
|
||||||
|
**Симптом:** при проходе через ОТКРЫТЫЕ ворота в соседнюю комнату (через шов)
|
||||||
|
Kid оказывается на ряду **row 1** вместо row 0 — «проваливается» на этаж ниже
|
||||||
|
(стоит в дверном проёме на неправильной высоте).
|
||||||
|
|
||||||
|
**Где:** `pop_map.c` `check_leave()` (порт seg002 leave_room) — боковой уход
|
||||||
|
`pop_leave_dir`; репроекция координат `Kid.x ∓= 140` есть, но Y/`curr_row`
|
||||||
|
при боковом переходе НЕ репроецируются (в отличие от `check_leave_below`,
|
||||||
|
где `Kid.y -= 189; curr_row = y_to_row`). При входе в соседнюю комнату у
|
||||||
|
шва Kid, видимо, попадает в позицию, где `determine_col`/физика следующего
|
||||||
|
кадра сбрасывают его на row 1 (нет пола на row 0 у входа, либо неверный y).
|
||||||
|
|
||||||
|
**Сверить с SDLPoP:** `goto_other_room` (seg002) — как именно
|
||||||
|
пересчитываются `Char.curr_row`/`Char.y` при side-переходе (left/right).
|
||||||
|
Для вертикального перехода мы это сделали, для бокового — нет.
|
||||||
|
|
||||||
|
## BUG-2. Возврат из комнаты назад: Kid отбрасывается обратно (ping-pong) — **Critical**
|
||||||
|
|
||||||
|
**Симптом:** после перехода в соседнюю комнату попытка сразу вернуться
|
||||||
|
обратно приводит к тому, что Kid снова закидывается в ту же комнату —
|
||||||
|
переход «пинг-понгует», выйти нельзя.
|
||||||
|
|
||||||
|
**Где:** `pop_map.c` `check_leave()`. После перехода Kid репроецирован к
|
||||||
|
ПРОТИВОПОЛОЖНОЙ кромке новой комнаты (`x ∓ 140`). На СЛЕДУЮЩЕМ же кадре
|
||||||
|
передний край снова за порогом ухода в обратную сторону → `pop_leave_dir`
|
||||||
|
взводится опять → мгновенный обратный переход. Нужен guard «только что
|
||||||
|
перешёл» (подавить повторный leave на N кадров / пока Kid не отойдёт от
|
||||||
|
кромки), как в оригинале (`next_room`/leave обрабатываются раз за кадр +
|
||||||
|
Kid входит достаточно внутрь).
|
||||||
|
|
||||||
|
**Сверить с SDLPoP:** порядок `leave_room`/`goto_other_room` + на какой
|
||||||
|
X ставится персонаж при входе (должен быть НЕ на самой кромке, иначе
|
||||||
|
мгновенный обратный триггер).
|
||||||
|
|
||||||
|
## BUG-3. Climb-up на тайл-кнопку: неправильная окклюзия (Kid поверх кнопки) — **Critical**
|
||||||
|
|
||||||
|
**Симптом:** когда Kid подтягивается (climb-up) на тайл, верх которого —
|
||||||
|
КНОПКА (opener/closer), а не обычный пол, отрисовка неверная: Kid рисуется
|
||||||
|
ПОВЕРХ кнопки, хотя передняя грань пола/кнопки должна перекрывать нижнюю
|
||||||
|
часть Kid во время подъёма.
|
||||||
|
|
||||||
|
**Где:** `pop_bg.c` `climb_overlay_tile()` — floor-overlay при подъёме
|
||||||
|
рисуется только для floor-подобных тайлов `code ∈ {1,3,5,19}`. Кнопка
|
||||||
|
(opener 0x0F / closer 0x06) в этот набор НЕ входит → пол-оверлей кнопки не
|
||||||
|
рисуется поверх Kid. Нужно: для нажатой кнопки (draw как floor/stuck)
|
||||||
|
трактовать её как floor-тайл в climb-overlay (учесть подстановку из
|
||||||
|
`get_tile_to_draw`), и/или добавить opener/closer в набор.
|
||||||
|
|
||||||
|
**Сверить с SDLPoP:** `draw_floor_overlay` (seg008) + `get_tile_to_draw`
|
||||||
|
(нажатая кнопка → tiles_1_floor / tiles_5_stuck) — оверлей должен идти по
|
||||||
|
ПОДСТАВЛЕННОМУ типу тайла.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Оптимизации (отложено)
|
||||||
|
|
||||||
|
### OPT-1. Хирургический редрой левого шва (ворота соседа) — 2026-07-22
|
||||||
|
|
||||||
|
**Возможность:** `pop_room_redraw_seam_left()` (pop_bg.c) на каждое изменение
|
||||||
|
openness рисует `bar(BLACK)` по всему col0 + **полный `draw_tile(0,0)`**
|
||||||
|
(стены, `topright`, `wall_pattern` с `prandom()` — десятки блитов). Реально
|
||||||
|
анимируются только бары решётки — `draw_gate_back` (~9 `env_b`).
|
||||||
|
|
||||||
|
**Стоимость:** seam-блок (синий io_border) занимает ~30-50% кадрового периода,
|
||||||
|
**но только пока openness меняется** — т.е. во время открытия и медленного
|
||||||
|
авто-закрытия (~5 сек после схода с кнопки). В покое (ворота открыты-держим
|
||||||
|
кнопку / закрыты / FF) — 0%. Замерено в MAME: брейк на
|
||||||
|
`_pop_room_redraw_seam_left` (0x5A54) срабатывает ⟺ сегмент дорогой.
|
||||||
|
|
||||||
|
**Приём** (как у пик, но сложнее — heal НЕ годится: печёные бары устаревшие,
|
||||||
|
поэтому и стоит `bar(BLACK)`+redraw): `bar(BLACK)` только по полосе баров
|
||||||
|
(x=0, gate_top..gate_bot) + `draw_gate_back(lmod,...)` + дорисовать статику
|
||||||
|
тайла (0,0), задетую полосой. Ожидаемо срезает бóльшую часть 30-50%.
|
||||||
|
**Пиксель-чувствительно** — полоса баров пересекается со статикой col0,
|
||||||
|
обязательна выверка в MAME по кромкам.
|
||||||
|
|
||||||
|
**Решение (2026-07-22):** оставляем как есть — стоимость транзиентная, в бюджет
|
||||||
|
помещаемся. Делать, если упрёмся в кадровый бюджет на сценах с воротами.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Исправлено
|
||||||
|
|
||||||
|
- **Шов ворот жёг 50% кадра в покое (закрытая решётка)** — ИСПРАВЛЕНО
|
||||||
|
2026-07-22. Причина — **баг кодогенератора SDCC z80** (memory
|
||||||
|
`sdcc_z80_cmp_store_a_bug`): `if (m[9] != seam_sig) seam_sig = m[9];`
|
||||||
|
компилировался в `sub (seam_sig)` (A ← разность) + `ld (seam_sig),a` —
|
||||||
|
сохранял РАЗНОСТЬ `m[9]-seam_sig`, не `m[9]`. `seam_sig` осциллировала
|
||||||
|
(напр. 25↔231), `m[9]!=seam_sig` истинно каждый кадр → `draw_tile(0,0)`
|
||||||
|
каждый кадр даже у неподвижной решётки. Фикс: store-до-сравнения
|
||||||
|
(`seam_sig = g;` из чистого `g` ДО `sub`), подтверждён в .asm. Прочёс всех
|
||||||
|
модулей PoP: других случайных compare-then-store нет.
|
||||||
|
|
||||||
|
- **Пики: 2×полный `draw_tile` на кадр анимации** → хирургический редрой,
|
||||||
|
2026-07-22. `pop_spike_redraw`: `heal_off` уже возвращает всю печёную
|
||||||
|
статику (база 127, пол, грани); поверх анимируются только два острия
|
||||||
|
(`SPIKES_FRAM_LEFT` в своей ячейке + `SPIKES_FRAM_RIGHT` в соседней).
|
||||||
|
Заменили 2×`draw_tile` на 2×`env_b` — пиксель-в-пиксель тот же результат,
|
||||||
|
в разы дешевле (шахта из нескольких пик больше не съедает полный кадр).
|
||||||
|
|
||||||
|
- **Отрисовка нажатой кнопки (0,2)/(0,3)** — ИСПРАВЛЕНО. Причина: `fore_tile`
|
||||||
|
(pop_bg.c, fore-слой поверх Kid) рисовал переднюю грань КНОПКИ (bottom_id
|
||||||
|
149) поверх уже нарисованной грани пола (43) — «остаток нажатой кнопки».
|
||||||
|
Фикс: `fore_tile` применяет ту же подстановку нажатой кнопки, что и
|
||||||
|
`draw_tile` (opener→floor / closer→stuck при таймере связи >1). Плюс
|
||||||
|
`pop_button_redraw` — wipe своей ячейки + правой грани (дальний угол в
|
||||||
|
0,3), низ строго yb+64 (не залезать в стену ряда 1).
|
||||||
|
|
||||||
|
## Заметки
|
||||||
|
|
||||||
|
- Тестовые клавиши осторожного шага: **J** = шаг влево, **L** = шаг вправо
|
||||||
|
(эмуляция Shift+стрелка), см. `pop_ctrl.c` `KBD_DBG_STEP*`. Первый шаг в
|
||||||
|
сторону = разворот (как в оригинале safe_step), движение со второго.
|
||||||
|
- Респавн после смерти — по **↑** (или авто через `RESPAWN_DELAY`).
|
||||||
|
- `START_ROOM=6` — временный тестовый старт; вернуть на
|
||||||
|
`pop_level_start_room()` после отладки объектов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BUG-CEIL-1. Прыжок вверх: руки Kid рисуются ПОВЕРХ потолка — **Medium**
|
||||||
|
|
||||||
|
**Симптом (2026-07-22):** при прыжке вверх (SEQ up, кадры 67..79) руки/голова
|
||||||
|
Kid заходят в полосу кладки у потолка (row -1) и рисуются ПОВЕРХ неё — потолок
|
||||||
|
должен окклюдить руки (как передняя грань).
|
||||||
|
|
||||||
|
**Где:** `pop_bg.c` — полоса потолка (`draw_tile(-1)`) сейчас только фон
|
||||||
|
(запечена в pop_room_draw), не окклюдер поверх Kid. Нужен ceiling-overlay в
|
||||||
|
`pop_fore_over_kid` (перерисовать срез полосы над Kid поверх спрайта), по
|
||||||
|
аналогии с fore-тайлами. Инфраструктура `g_clip_top`/`gfx_blit_part` уже есть.
|
||||||
|
|
||||||
|
## BUG-CEIL-2. Тряска/разбитие loose-плиты в потолке (row -1) не работает — **Medium**
|
||||||
|
|
||||||
|
**Симптом (2026-07-22):** loose-плита в ряду 2 верхнего соседа (room5 (2,5) →
|
||||||
|
потолок room6 над (0,5)) при прыжках Kid на (0,5) НЕ трясётся и не разбивается.
|
||||||
|
|
||||||
|
**Где:** верхний сосед не «живой» — `above_fg` берётся из статики уровня,
|
||||||
|
loose-состояние соседней комнаты не тянем (per-room loose/modif не персистятся,
|
||||||
|
P0). check_press/knock работают только по текущей комнате. Нужно: (а) пробой
|
||||||
|
loose сверху через границу комнаты, (б) персистентное loose-состояние соседа +
|
||||||
|
его отрисовка в полосе. Пока полоса рисует loose статически (кадр покоя).
|
||||||
|
|
||||||
|
## BUG-OCCL-1. Тень дальней колонны перекрывает Kid в (0,4)-(0,5) — **Medium**
|
||||||
|
|
||||||
|
**Симптом (2026-07-22):** Kid, стоя/прыгая у (0,4)-(0,5), частично перекрыт
|
||||||
|
тёмной штриховкой — это ТЕНЬ/боковая грань ДАЛЬНЕЙ колонны (сзади по глубине),
|
||||||
|
которая НЕ должна окклюдить персонажа. Over-occlusion fore-слоя.
|
||||||
|
|
||||||
|
**Где:** `pop_bg.c` `pop_fore_over_kid`/`fore_tile` — рисует fore футпринт-тайлов
|
||||||
|
без учёта глубины (какая грань РЕАЛЬНО перед Kid). Порт seg003 redraw_at_char
|
||||||
|
окклюдит только передние грани; дальняя колонна (за Kid) не должна. Сверить с
|
||||||
|
SDLPoP redraw_at_char (какие тайлы/грани идут в fore vs back по y-сортировке).
|
||||||
|
|
||||||
|
## BUG-CEIL-3. Анимация ворот стирает потолок над ними — **Medium**
|
||||||
|
|
||||||
|
**Симптом (2026-07-22):** при опускании/поднятии решётки шва (анимация openness)
|
||||||
|
полоса кладки у потолка НАД воротами пропадает (см. потолок над левым швом).
|
||||||
|
|
||||||
|
**Где:** `pop_bg.c` `pop_room_redraw_seam_left` (и `pop_button_redraw`/
|
||||||
|
`pop_spike_redraw` — общий класс): чёрный `bar` по col0 row0 стирает потолочную
|
||||||
|
полосу (row-1), а redraw рисует только сам гейт, не восстанавливая полосу.
|
||||||
|
Нужно: при перерисовке шва/тайла у потолка перерисовать и срез потолочной полосы
|
||||||
|
(draw_tile(-1,col) с g_clip_top) над затронутыми колонками.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# BUG-SEAM-PINGPONG (#4). Пинг-понг drawn_room у шва с закрытыми воротами — **РЕШЁН 2026-07-22**
|
||||||
|
|
||||||
|
Статус: 2026-07-22, вечер. НАСТОЯЩИЙ корень найден потиковой трассой ЖИВОГО
|
||||||
|
SDLPoP 1.23 (lldb-брейкпоинты на leave_room/bumped/safe_step с логом Char +
|
||||||
|
char_x_left/right; fixes выключены = vanilla). Все прежние гипотезы ниже
|
||||||
|
оставлены для истории — они НЕ были причиной.
|
||||||
|
|
||||||
|
## КОРЕНЬ (подтверждён трассой + исходником)
|
||||||
|
|
||||||
|
`set_char_collision` (seg006:0723): `char_x_right = obj_x/2 + 58`, где
|
||||||
|
`load_frame_to_obj` (seg008:1728) считает `obj_x = 2*char_dx_forward(dx) - 116`
|
||||||
|
и **добавляет +1** для кадров «чётного пикселя»:
|
||||||
|
`if ((sbyte)(cur_frame.flags ^ obj_direction) >= 0) ++obj_x;`
|
||||||
|
(бит 0x80 флагов кадра XOR направление; вправо: +1 если бит НЕ стоит).
|
||||||
|
Деление `obj_x/2` — C-усечение К НУЛЮ, поэтому при `e = x+dx <= 57`
|
||||||
|
(obj_x < 0, зона левого шва) поправка +1 даёт `char_x_right = e+1`, а при
|
||||||
|
e >= 58 формула сокращается к чистому `e`.
|
||||||
|
|
||||||
|
Итог: Kid, осевший после отскока от ворот шва на x=57 (frame15, флаги 0x43 —
|
||||||
|
бит 0x80 не стоит), имеет **char_x_right = 58** и порога leave-left (<=57)
|
||||||
|
НЕ достигает. Наш движок считал передний край как `Kid.x + dx` без поправки
|
||||||
|
→ 57 → ложный leave → пинг-понг.
|
||||||
|
|
||||||
|
Эталонный цикл SDLPoP (сн normalized по трассе): стойка x=61 → тап вправо →
|
||||||
|
safe_step(d=0) → step, на первом dx(1) x=62 → bump (edge-триггер) → align 61 →
|
||||||
|
seq47 dx(-4) → **x=57** (скрыт за кромкой) → кадры 50/51/52 (cxr 61/60/58, у
|
||||||
|
всех бит 0x80 снят, e>57 — без сдвига) → стойка cxr=58 → leave НЕ срабатывает;
|
||||||
|
тап → safe_step d=3 → x=60 (1/3 видно); тап → step1 → x=61 (2/3 видно); тап →
|
||||||
|
bump → 57 … по кругу. Char.room и drawn_room НЕ меняются вообще.
|
||||||
|
|
||||||
|
## Фикс (pop_map.c)
|
||||||
|
|
||||||
|
`char_x_forward_edge()`: `e = char_dx_forward(dx); if (((flags ^ (dir<0 ?
|
||||||
|
0x80 : 0)) & 0x80) == 0 && e <= 57) e++;` — используется в `char_front_coll`
|
||||||
|
(коллизия/bump/edge_distance) и в `check_leave` (порог ухода). Плюс порт
|
||||||
|
doortop-гарда leave-right из leave_room (тайл (9,row) = doortop → правого
|
||||||
|
выхода нет). `pop_leave_timer` (exit_room_timer) оставлен — он реален в
|
||||||
|
seg002/seg003.
|
||||||
|
|
||||||
|
## Гипотезы, ОТПАВШИЕ после трассы (история диагноза)
|
||||||
|
|
||||||
|
## Симптом (подтверждён пользователем + инструментально)
|
||||||
|
|
||||||
|
Kid стоит за решёткой закрытых ворот шва (левый сосед room8 виден в кромке
|
||||||
|
room6). При удержании/нажатии ВПРАВО экран пинг-понгует между двумя
|
||||||
|
состояниями:
|
||||||
|
- **A**: показывается room6, Kid у левой кромки за решёткой (спрайт виден на 2/3);
|
||||||
|
- **B**: показывается room8, Kid у его правой кромки.
|
||||||
|
|
||||||
|
Эталон SDLPoP (описан пользователем): drawn_room **всегда остаётся room6**,
|
||||||
|
Kid осциллирует у кромки (1/3→2/3→отступил→по кругу), в room8 экран НЕ
|
||||||
|
переключается. То есть у нас лишний свитч A→B.
|
||||||
|
|
||||||
|
## Тестовый контекст
|
||||||
|
|
||||||
|
START_ROOM=6. Ворота room8(0,9) закрыты, видны в col0 room6. Kid-структура
|
||||||
|
kframe: см. pop_kid.h (frame,x,y,direction,curr_col,curr_row,action,...).
|
||||||
|
Адрес _Kid и статиков — в .sprinter-cc-roomtest/roomtest.map (сдвигается от
|
||||||
|
сборки к сборке). MAME: memory `mame_mcp_bridge`/`mame_hdd_test_disk`.
|
||||||
|
Отладочные: J/L — короткий шаг влево/вправо (эмуляция Shift+стрелка).
|
||||||
|
|
||||||
|
## Инструментальный диагноз (watchpoint на pop_leave_dir)
|
||||||
|
|
||||||
|
В момент лишнего свитча A→B: `pop_leave_dir=1 (LEFT)`, Kid **frame=15 (СТОЯ,
|
||||||
|
не transient!), Kid.x=57** (до репроекции +140). То есть:
|
||||||
|
- `char_x_right = char_dx_forward(kid_cur_dx()) = Kid.x + frame15.dx = 57+0 = 57`.
|
||||||
|
- Порог leave-left (взгляд вправо): `char_x_right <= 57` → срабатывает РОВНО на 57.
|
||||||
|
- Грань ворот (где их держит коллизия) = **61** (`wall_dist_from_left[1]=10 +
|
||||||
|
coll_tile_left_xpos=51`). Между 57 и 61 — **зазор 4px**: Kid НЕ удержан
|
||||||
|
воротами (d=61−57=4≥0 → check_bumped не бампит), но уже на пороге ухода.
|
||||||
|
- Kid оседает на 57 из-за recoil отскока: seq_47 = `act(bumped), dx(-4),
|
||||||
|
frame_50, 51, 52`; SEQ_DX(−4) двигает Char.x на −4 суммарно; frame_50.dx=4
|
||||||
|
компенсирует ТОЛЬКО точку коллизии НА кадре 50, но при возврате в стойку
|
||||||
|
(frame15, dx=0) `char_x_right = Char.x = aligned−4 = 57`.
|
||||||
|
|
||||||
|
## Что ИСКЛЮЧЕНО (сверено с исходниками SDLPoP, НЕ причина)
|
||||||
|
|
||||||
|
- Формула char_x: `char_x_right = obj_x/2+58 = Char.x+frame.dx` (seg006
|
||||||
|
set_char_collision) — совпадает с нашим char_dx_forward.
|
||||||
|
- Позиция грани ворот: `get_left_wall_xpos = wall_dist_from_left[1](10) +
|
||||||
|
xpos_in_drawn_room(x_bump[9+5])+7 = 10+(184−140)+7 = 61` — совпадает с нашим
|
||||||
|
(`x_bump[−1+5]=44`, +7, +10 = 61).
|
||||||
|
- Порог leave: SDLPoP leave_room looking-right `char_x_right<=57` — совпадает.
|
||||||
|
- Данные кадров: frame_50 (image=49,dx=4,flags=0x67) и frame_15
|
||||||
|
(image=14,dx=0,flags=0x43,sword=9) — БАЙТ-В-БАЙТ как в SDLPoP frame_table_kid.
|
||||||
|
- seq_47 (act bumped, dx(-4), frame 50/51/52) — совпадает.
|
||||||
|
|
||||||
|
## Почему точечные фиксы НЕ работают
|
||||||
|
|
||||||
|
- Гард в check_leave (подавить leave на закрытых воротах) — это ОТСЕБЯТИНА,
|
||||||
|
не SDLPoP (в leave_room такого нет); откачено.
|
||||||
|
- `exit_room_timer=2` (порт seg002 exit_room — РЕАЛЬНЫЙ механизм, оставлен как
|
||||||
|
pop_leave_timer): блокирует leave 2 кадра после входа в комнату. НЕ спасает:
|
||||||
|
положение Char.x=57 **устойчивое** (Kid стоит), а не transient — после
|
||||||
|
истечения таймера leave срабатывает.
|
||||||
|
- По коду SDLPoP raw-рассуждением раз за разом выходит, что leave-left ДОЛЖЕН
|
||||||
|
сработать и там (Char.x=57), но эталон показывает, что нет. Значит различие
|
||||||
|
не в этих кусках, а в ДИНАМИКЕ: где именно оседает Char.x у шва И как
|
||||||
|
вычисляется drawn_room.
|
||||||
|
|
||||||
|
## КОРЕНЬ (гипотеза)
|
||||||
|
|
||||||
|
Наш движок держит `drawn_room == комната_Kid` ВСЕГДА, а шов подделан:
|
||||||
|
- коллизия — самодельные снапшоты кромок соседа (g_lcol/g_rcol, 1 колонка) +
|
||||||
|
`x_bump[tcol=-1]`, а НЕ порт `curr_row_coll_room[]`/`get_all_coll_row`/
|
||||||
|
`xpos_in_drawn_room` из seg004;
|
||||||
|
- уход — мгновенный `enter_room(сосед)` при пороге char_x, а НЕ отложенный
|
||||||
|
drawn_room через `next_room`/`check_the_end`.
|
||||||
|
|
||||||
|
Из-за упрощённой коллизии Char.x у шва оседает иначе, чем в SDLPoP (у нас ровно
|
||||||
|
57 = порог), и/или drawn_room переключается там, где в оригинале нет.
|
||||||
|
|
||||||
|
## План S2/S3 (порт coll_room + отложенный drawn_room) — НЕ потребовался для
|
||||||
|
## этого бага (трасса показала: в эталоне Char.room/drawn_room вообще не
|
||||||
|
## меняются). Остаётся как задел под стражей/двух персонажей в кадре.
|
||||||
|
|
||||||
|
Правило проекта: SDLPoP — источник истины, переводить 1:1, НЕ рассуждать «как
|
||||||
|
должно быть». Нужно портировать связку целиком:
|
||||||
|
|
||||||
|
1. **Раздельные комнаты.** `Char.room` (реальная комната персонажа) ≠
|
||||||
|
`drawn_room` (отрисованная). У нас уже есть `kid_room` (S1) + рендер-смещение
|
||||||
|
`pop_kid_set_render_dx(∓140)` (порт xpos_in_drawn_room для СПРАЙТА).
|
||||||
|
|
||||||
|
2. **Коллизия по Char.room через coll_room (seg004, S2).** Портировать
|
||||||
|
`check_collisions` → `get_row_collision_data` (строки curr/below/above) →
|
||||||
|
`get_left_wall_xpos`/`get_right_wall_xpos` (по `xpos_in_drawn_room(x_bump[
|
||||||
|
col+FIRST_ONSCREEN_COLUMN])`, ±140 когда колонка принадлежит соседу) →
|
||||||
|
массивы `curr_row_coll_room[]`/`_flags[]`, `bump_col_left_of_wall`/
|
||||||
|
`bump_col_right_of_wall` → `check_bumped_look_left/right` → `bumped()`.
|
||||||
|
Тайлы берутся из РЕАЛЬНОЙ комнаты колонки (get_tile(room,col,row) для любой
|
||||||
|
комнаты), НЕ из снапшота. Именно это определяет, где Char.x оседает у шва.
|
||||||
|
|
||||||
|
3. **Отрисовка по drawn_room, персонаж со сдвигом.** Рисуем drawn_room; Kid
|
||||||
|
(и будущие стражи) — со смещением ±140, если их комната ≠ drawn_room.
|
||||||
|
|
||||||
|
4. **Отложенная смена drawn_room (seg002/seg000, S3).** Уход =
|
||||||
|
`leave_room` (seg002:0504, ПОРТ ЦЕЛИКОМ, включая up/down/side, guard'ы
|
||||||
|
climb/standup/turn, doortop-check для right) → `goto_other_room`
|
||||||
|
(seg002:0486: Char.room=сосед, x±140/y±189). `exit_room` (seg002:03C7):
|
||||||
|
`exit_room_timer` + `next_room = Char.room`. `check_the_end` (seg000:0FBD):
|
||||||
|
`if (next_room != 0 && next_room != drawn_room) drawn_room = next_room;
|
||||||
|
redraw`. drawn_room меняется ТОЛЬКО так, а не по нашему прямому enter_room.
|
||||||
|
|
||||||
|
Ключевой момент, который надо переводить БУКВАЛЬНО (а не выводить): порядок
|
||||||
|
вызовов в play_frame (seg000:863 `play_frame`: … play_kid_frame → exit_room →
|
||||||
|
check_the_end) + set_char_collision перед leave_room в exit_room.
|
||||||
|
|
||||||
|
Карта сегментов: seg002 (leave/goto/exit_room), seg004 (коллизия/coll_room/
|
||||||
|
bumped), seg006 (set_char_collision/play_seq/frame_table_kid), seg000
|
||||||
|
(play_frame/check_the_end/draw). Наши файлы: pop_map.c (коллизия/физика/leave),
|
||||||
|
roomtest.c (enter_room/drawn_room/главный цикл), pop_kid.c (кадры/рендер-сдвиг).
|
||||||
|
|
||||||
|
Реализовано на 2026-07-22: S1 (kid_room + render_dx) + pop_leave_timer.
|
||||||
|
S2/S3 отложены — баг закрыт odd-pixel поправкой (см. КОРЕНЬ выше).
|
||||||
|
Память: [[pop_seam_room_model]], [[sdlpop_odd_pixel_char_x]].
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "pop_bg.h"
|
#include "pop_bg.h"
|
||||||
|
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
||||||
|
|
||||||
/* ---- Атласы (прямая адресация) ------------------------------------- */
|
/* ---- Атласы (прямая адресация) ------------------------------------- */
|
||||||
static atlas_t env[5];
|
static atlas_t env[5];
|
||||||
@@ -44,20 +45,36 @@ void pop_bg_free(void)
|
|||||||
atlas_free(&fore_at);
|
atlas_free(&fore_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра с screen_y <
|
||||||
|
* g_clip_top не рисуются (частичным блитом со сдвигом источника). Нужно
|
||||||
|
* полосе кладки у потолка (row<0): высокие кадры не должны лезть в верхний
|
||||||
|
* борт (за пределы игрового поля). 0 — без клипа. */
|
||||||
|
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). */
|
||||||
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;
|
||||||
if (idx >= a->count)
|
if (idx >= a->count)
|
||||||
return;
|
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));
|
||||||
if (w && h)
|
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||||||
gfx_blit(x, ybottom - (int)h + 1 + POP_YOFF, img); /* +YOFF: центрирование */
|
if (w && h) {
|
||||||
|
if (g_clip_top && top < g_clip_top) {
|
||||||
|
int sy = g_clip_top - top; /* пропустить верхние строки */
|
||||||
|
if (sy < (int)h)
|
||||||
|
gfx_blit_part(x, g_clip_top, img, 0, sy, (int)w, (int)h - sy);
|
||||||
|
} else {
|
||||||
|
gfx_blit(x, top, img);
|
||||||
|
}
|
||||||
|
}
|
||||||
gfx_w0_unmap();
|
gfx_w0_unmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +133,8 @@ static const uint8_t WALL_FRAM_BOTTOM[4] = {7, 9, 5, 3};
|
|||||||
static const uint8_t WALL_FRAM_MAIN[4] = {8, 10, 6, 4};
|
static const uint8_t WALL_FRAM_MAIN[4] = {8, 10, 6, 4};
|
||||||
static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52};
|
static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52};
|
||||||
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
|
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
|
||||||
|
static const uint8_t SPIKES_FRAM_LEFT[10] = {0, 128, 129, 130, 131, 132, 131, 129, 128, 0};
|
||||||
|
static const uint8_t SPIKES_FRAM_FORE[10] = {0, 139, 140, 141, 142, 143, 142, 140, 139, 0};
|
||||||
static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126};
|
static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126};
|
||||||
static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
|
static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
|
||||||
static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45};
|
static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45};
|
||||||
@@ -133,6 +152,7 @@ static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
|||||||
/* ---- Текущая комната (globals для tile_code/mod) ------------------- */
|
/* ---- Текущая комната (globals для tile_code/mod) ------------------- */
|
||||||
static const uint8_t *g_fg, *g_bg, *g_lfg, *g_lbg;
|
static const uint8_t *g_fg, *g_bg, *g_lfg, *g_lbg;
|
||||||
static const uint8_t *g_below; /* row0-коды комнаты СНИЗУ (для topright под нижним рядом) */
|
static const uint8_t *g_below; /* row0-коды комнаты СНИЗУ (для topright под нижним рядом) */
|
||||||
|
static const uint8_t *g_above, *g_abovemod; /* row2 fg/mod комнаты СВЕРХУ (полоса кладки у потолка) */
|
||||||
static uint8_t g_room;
|
static uint8_t g_room;
|
||||||
|
|
||||||
/* Задать ряд 0 комнаты снизу (fg-коды) — нужен для draw_tile_topright/
|
/* Задать ряд 0 комнаты снизу (fg-коды) — нужен для draw_tile_topright/
|
||||||
@@ -141,13 +161,22 @@ static uint8_t g_room;
|
|||||||
* снизу (кромка уровня = стена). */
|
* снизу (кромка уровня = стена). */
|
||||||
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) { g_below = below_row0_fg; }
|
||||||
|
|
||||||
|
/* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки
|
||||||
|
* у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
|
||||||
|
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
|
||||||
|
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
|
||||||
|
{ 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)
|
||||||
{
|
{
|
||||||
if (col < 0) {
|
if (col < 0) {
|
||||||
if (g_lfg && row >= 0 && row < 3) return g_lfg[row];
|
if (g_lfg && row >= 0 && row < 3) return g_lfg[row];
|
||||||
return 20; /* уровневая кромка = стена */
|
return 20; /* уровневая кромка = стена */
|
||||||
}
|
}
|
||||||
if (row < 0) return 1; /* верхняя кромка = пол */
|
if (row < 0) { /* верхняя полоса: ряд 2 комнаты сверху */
|
||||||
|
if (row == -1 && g_above && col < 10) return g_above[col];
|
||||||
|
return 1; /* нет комнаты сверху → пол (кромка) */
|
||||||
|
}
|
||||||
if (row > 2 || col > 9) return 20;
|
if (row > 2 || col > 9) return 20;
|
||||||
return g_fg[row * 10 + col];
|
return g_fg[row * 10 + col];
|
||||||
}
|
}
|
||||||
@@ -158,6 +187,7 @@ static uint8_t tile_mod(int row, int col)
|
|||||||
if (g_lbg && row >= 0 && row < 3) return g_lbg[row];
|
if (g_lbg && row >= 0 && row < 3) return g_lbg[row];
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (row == -1 && g_abovemod && col < 10) return g_abovemod[col];
|
||||||
if (row < 0 || row > 2 || col > 9) return 0;
|
if (row < 0 || row > 2 || col > 9) return 0;
|
||||||
return g_bg[row * 10 + col];
|
return g_bg[row * 10 + col];
|
||||||
}
|
}
|
||||||
@@ -213,7 +243,9 @@ static void wall_pattern(int row, int col, int which_part)
|
|||||||
uint16_t mid_div, mid_off, bot_div, bot_off;
|
uint16_t mid_div, mid_off, bot_div, bot_off;
|
||||||
uint8_t bg;
|
uint8_t bg;
|
||||||
|
|
||||||
rnd_seed = (unsigned long)(g_room + TBL_LINE[row] + col);
|
/* Верхняя полоса (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); /* сброс */
|
prandom(1); /* сброс */
|
||||||
mid_div = prandom(1);
|
mid_div = prandom(1);
|
||||||
mid_off = prandom(4);
|
mid_off = prandom(4);
|
||||||
@@ -321,6 +353,14 @@ static void draw_tile(int row, int col)
|
|||||||
uint8_t code = tile_code(row, col);
|
uint8_t code = tile_code(row, col);
|
||||||
uint8_t lcode = tile_code(row, col - 1);
|
uint8_t lcode = tile_code(row, col - 1);
|
||||||
uint8_t lmod = tile_mod(row, col - 1);
|
uint8_t lmod = tile_mod(row, col - 1);
|
||||||
|
/* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener
|
||||||
|
* (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05).
|
||||||
|
* Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого
|
||||||
|
* соседа (его правая грань). */
|
||||||
|
if (code == 0x0F) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||||
|
else if (code == 0x06) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||||
|
if (lcode == 0x0F) { if ((pop_doorlink2(lmod) & 0x1F) > 1) { lcode = 1; lmod = 0; } }
|
||||||
|
else if (lcode == 0x06) { if ((pop_doorlink2(lmod) & 0x1F) > 1) lcode = 5; }
|
||||||
int xh = COL_XH[col];
|
int xh = COL_XH[col];
|
||||||
int x = xh * 8;
|
int x = xh * 8;
|
||||||
int dby = 63 * row + 65;
|
int dby = 63 * row + 65;
|
||||||
@@ -367,7 +407,10 @@ static void draw_tile(int row, int col)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части --- */
|
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
||||||
|
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||||||
|
* отрицательного индекса в pop_loose_modif. --- */
|
||||||
|
if (row >= 0) {
|
||||||
if (lcode == 4) { /* gate: решётка */
|
if (lcode == 4) { /* gate: решётка */
|
||||||
draw_gate_back(lmod, xh, dby, dmy);
|
draw_gate_back(lmod, xh, dby, dmy);
|
||||||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||||
@@ -377,6 +420,7 @@ static void draw_tile(int row, int col)
|
|||||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||||
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||||||
if (code == 20) {
|
if (code == 20) {
|
||||||
@@ -387,12 +431,16 @@ static void draw_tile(int row, int col)
|
|||||||
env_b(t->bottom_id, x, dby);
|
env_b(t->bottom_id, x, dby);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif) --- */
|
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
|
||||||
|
* верхней полосе (row<0) loose ряда 2 верхнего соседа рисуем статически
|
||||||
|
* (кадр покоя 43): анимацию/разбитость соседа мы не тянем. --- */
|
||||||
if (code == 11)
|
if (code == 11)
|
||||||
env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])], x, dby);
|
env_b(row >= 0 ? LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])]
|
||||||
|
: LOOSE_FRAM_BOTTOM[0], x, dby);
|
||||||
|
|
||||||
/* --- draw_tile_base (seg008.c:611) --- */
|
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ
|
||||||
{
|
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
|
||||||
|
if (row >= 0) {
|
||||||
uint8_t base_id = t->base_id;
|
uint8_t base_id = t->base_id;
|
||||||
if (code == 11) /* loose: левая грань (анимир.) */
|
if (code == 11) /* loose: левая грань (анимир.) */
|
||||||
base_id = LOOSE_FRAM_LEFT[get_loose_frame(pop_loose_modif[row * 10 + col])];
|
base_id = LOOSE_FRAM_LEFT[get_loose_frame(pop_loose_modif[row * 10 + col])];
|
||||||
@@ -400,6 +448,17 @@ static void draw_tile(int row, int col)
|
|||||||
env_b(base_id, x, t->base_y + dmy);
|
env_b(base_id, x, t->base_y + dmy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
|
||||||
|
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
|
||||||
|
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
||||||
|
* пустая. */
|
||||||
|
if (row >= 0 && code == 2) {
|
||||||
|
uint8_t sm = tile_mod(row, col);
|
||||||
|
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||||||
|
if (sf > 0 && sf < 10 && SPIKES_FRAM_LEFT[sf])
|
||||||
|
env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||||
|
}
|
||||||
|
|
||||||
/* --- draw_tile_fore (seg008.c:690) --- */
|
/* --- draw_tile_fore (seg008.c:690) --- */
|
||||||
if (code == 20) {
|
if (code == 20) {
|
||||||
wmod = wall_modifier(row, col);
|
wmod = wall_modifier(row, col);
|
||||||
@@ -431,6 +490,75 @@ void pop_loose_shake_draw(int row, int col)
|
|||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Перерисовать пики тайла (row,col) на back-странице по ЖИВОМУ modif
|
||||||
|
* (pop_trob). Кадр выдвижения рисует ПРАВЫЙ сосед (draw_tile ветка lcode==2
|
||||||
|
* читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать
|
||||||
|
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
||||||
|
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
||||||
|
void pop_spike_redraw(int row, int col)
|
||||||
|
{
|
||||||
|
int x = COL_XH[col] * 8;
|
||||||
|
int dmy = 63 * row + 62; /* как в draw_tile (dby-3) */
|
||||||
|
uint8_t sm = g_bg[row * 10 + col]; /* живой modif пики (room_modif) */
|
||||||
|
uint8_t sf = (sm & 0x80) ? 5 : sm; /* видимый кадр (hold 0x80..0x8F → 5) */
|
||||||
|
/* Шипы выдвигаются выше линии пола (env_b на dmy-7) — heal с запасом
|
||||||
|
* вверх (63*row+34 .. +74) покрывает и остриё, и сам пол; ширина 64 =
|
||||||
|
* тайл + правый сосед (остриё рисуется в его ячейке).
|
||||||
|
* heal вернул из ОЗУ-копии ВЕСЬ печёный статичный фон (база пик 127, пол,
|
||||||
|
* грани соседей — всё, что рисовал бы полный draw_tile при modif=0).
|
||||||
|
* Единственное, что реально анимируется — два спрайта острия (LEFT в своей
|
||||||
|
* ячейке + RIGHT в соседней, оба по sf). Поэтому вместо 2×draw_tile
|
||||||
|
* (десятки блитов статики поверх идентичного фона = идемпотентно) рисуем
|
||||||
|
* РОВНО эти два острия — пиксель-в-пиксель тот же результат, во много раз
|
||||||
|
* дешевле (порт redraw_21h: draw_tile_anim + draw_tile_anim_right). */
|
||||||
|
heal_off(x, 63 * row + 34, 64, 40);
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
if (sf > 0 && sf < 10) {
|
||||||
|
if (SPIKES_FRAM_LEFT[sf]) /* остриё в своей ячейке */
|
||||||
|
env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||||
|
if (col + 1 < 10 && SPIKES_FRAM_RIGHT[sf]) /* остриё в ячейке соседа */
|
||||||
|
env_b(SPIKES_FRAM_RIGHT[sf], COL_XH[col + 1] * 8, dmy - 7);
|
||||||
|
}
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
|
||||||
|
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
|
||||||
|
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = g_lbg[0]).
|
||||||
|
* g_lbg (lcol_bg) должен указывать на живой modif (roomtest обновляет из
|
||||||
|
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
||||||
|
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
||||||
|
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
||||||
|
void pop_room_redraw_seam_left(void)
|
||||||
|
{
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(0, POP_YOFF, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
|
||||||
|
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
||||||
|
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
||||||
|
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
|
||||||
|
void pop_button_redraw(int row, int col)
|
||||||
|
{
|
||||||
|
int x = COL_XH[col] * 8;
|
||||||
|
int yb = POP_YOFF + 63 * row;
|
||||||
|
/* Wipe СВОЕЙ ячейки + правой грани кнопки (заходит в 0,3) + перерисовка
|
||||||
|
* пола. НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда 1
|
||||||
|
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(x, yb + 26, x + 59, yb + 64);
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
draw_tile(row, col);
|
||||||
|
if (col + 1 < 10) draw_tile(row, col + 1);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
||||||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||||||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||||||
@@ -571,6 +699,14 @@ void pop_room_draw(uint8_t room_num,
|
|||||||
for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */
|
for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */
|
||||||
for (col = 0; col < 10; col++)
|
for (col = 0; col < 10; col++)
|
||||||
draw_tile(row, col);
|
draw_tile(row, col);
|
||||||
|
/* Полоса кладки у потолка (seg008 draw_room: доп. ряд из room_A, row2
|
||||||
|
* при draw_main_y=-1): нижние/правые грани ряда 2 комнаты сверху. Кадры
|
||||||
|
* высокие — верх ушёл бы в борт (за поле); клип по верхней границе поля
|
||||||
|
* (POP_YOFF) рисует ТОЛЬКО срез у потолка, без зачернения борта. */
|
||||||
|
g_clip_top = POP_YOFF;
|
||||||
|
for (col = 0; col < 10; col++)
|
||||||
|
draw_tile(-1, col);
|
||||||
|
g_clip_top = 0;
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,7 +734,13 @@ static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=
|
|||||||
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
||||||
{
|
{
|
||||||
uint8_t code = tile_code(row, col);
|
uint8_t code = tile_code(row, col);
|
||||||
const piece *t = &tile_table[code];
|
const piece *t;
|
||||||
|
/* Нажатая кнопка → пол/stuck (как get_tile_to_draw), ИНАЧЕ fore рисует
|
||||||
|
* переднюю грань КНОПКИ (bottom_id 149) поверх Kid, перекрывая уже
|
||||||
|
* нарисованную грань пола 43 → «остаток нажатой кнопки» на грани. */
|
||||||
|
if (code == 0x0F) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||||
|
else if (code == 0x06) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||||
|
t = &tile_table[code];
|
||||||
int x = COL_XH[col] * 8;
|
int x = COL_XH[col] * 8;
|
||||||
int dmy = 63 * row + 62;
|
int dmy = 63 * row + 62;
|
||||||
int dby = dmy + 3; /* = 63*row+65 (draw_tile_bottom) */
|
int dby = dmy + 3; /* = 63*row+65 (draw_tile_bottom) */
|
||||||
@@ -611,6 +753,12 @@ static void fore_tile(int row, int col) /* передний слой одно
|
|||||||
} else {
|
} else {
|
||||||
if (t->bottom_id) /* передняя кромка пола (draw_tile_bottom) */
|
if (t->bottom_id) /* передняя кромка пола (draw_tile_bottom) */
|
||||||
env_b(t->bottom_id, x, dby);
|
env_b(t->bottom_id, x, dby);
|
||||||
|
if (code == 2) { /* пики: fore-кадр поверх Kid (напоротый) */
|
||||||
|
uint8_t sm = tile_mod(row, col);
|
||||||
|
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||||||
|
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
|
||||||
|
env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
|
||||||
|
}
|
||||||
if (t->fore_id) /* передние объекты: колонны/ворота/щебень */
|
if (t->fore_id) /* передние объекты: колонны/ворота/щебень */
|
||||||
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
||||||
}
|
}
|
||||||
@@ -665,10 +813,11 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
|||||||
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, rT, rB, r, c;
|
int8_t cL, cR, cLraw, rT, rB, r, c;
|
||||||
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||||||
xr = xl + wh;
|
xr = xl + wh;
|
||||||
cL = col_from_x(xl); if (cL < 0) cL = 0; else if (cL > 9) cL = 9;
|
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
|
||||||
|
cL = cLraw; if (cL < 0) cL = 0; else if (cL > 9) cL = 9;
|
||||||
cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9;
|
cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9;
|
||||||
/* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(obj_y),
|
/* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(obj_y),
|
||||||
* причём обёртка -1 (Kid НИЖЕ комнаты — climbdown/провал, obj_y в полосе
|
* причём обёртка -1 (Kid НИЖЕ комнаты — climbdown/провал, obj_y в полосе
|
||||||
@@ -695,6 +844,17 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
|||||||
for (r = rT; r <= rB; r++)
|
for (r = rT; r <= rB; r++)
|
||||||
for (c = cL; c <= cR; c++)
|
for (c = cL; c <= cR; c++)
|
||||||
fore_tile(r, c);
|
fore_tile(r, c);
|
||||||
|
/* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за
|
||||||
|
* ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) —
|
||||||
|
* перерисовать её бары ПОВЕРХ Kid. Иначе Kid, стоя за закрытыми воротами
|
||||||
|
* (col-1), рисуется поверх решётки. Бары = draw_gate_back по живому modif
|
||||||
|
* соседа (g_lbg = lcol_bg, openness). */
|
||||||
|
if (cLraw < 0 && g_lfg)
|
||||||
|
for (r = rT; r <= rB; r++)
|
||||||
|
if ((g_lfg[r] & 0x1F) == 4) {
|
||||||
|
int dby = 63 * r + 65;
|
||||||
|
draw_gate_back(g_lbg ? g_lbg[r] : 0, COL_XH[0], dby, dby - 3);
|
||||||
|
}
|
||||||
if (frame >= 137 && frame <= 144) { /* подъём: пол назначения поверх Kid */
|
if (frame >= 137 && frame <= 144) { /* подъём: пол назначения поверх Kid */
|
||||||
uint8_t fidx = (uint8_t)(frame - 137);
|
uint8_t fidx = (uint8_t)(frame - 137);
|
||||||
for (r = rT; r <= rB; r++)
|
for (r = rT; r <= rB; r++)
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ void pop_room_draw(uint8_t room_num,
|
|||||||
* рисуется по коду 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);
|
||||||
|
|
||||||
|
/* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы
|
||||||
|
* кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 0 — нет
|
||||||
|
* комнаты сверху (кромка уровня → пол). */
|
||||||
|
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod);
|
||||||
|
|
||||||
/* Перерисовать передний слой (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 влево). Метрики
|
||||||
@@ -60,6 +65,19 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
|||||||
void pop_loose_shake_draw(int row, int col);
|
void pop_loose_shake_draw(int row, int col);
|
||||||
void pop_loose_bake_empty(int row, int col);
|
void pop_loose_bake_empty(int row, int col);
|
||||||
|
|
||||||
|
/* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому
|
||||||
|
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
|
||||||
|
void pop_spike_redraw(int row, int col);
|
||||||
|
|
||||||
|
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
|
||||||
|
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
|
||||||
|
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
|
||||||
|
void pop_room_redraw_seam_left(void);
|
||||||
|
|
||||||
|
/* Кнопка (opener/closer) — перерисовать тайл (row,col) на back-странице:
|
||||||
|
* нажата/отжата по таймеру связи. Звать из pop_process_trobs. */
|
||||||
|
void pop_button_redraw(int row, int col);
|
||||||
|
|
||||||
/* Падающий кусок 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. */
|
||||||
|
|||||||
@@ -20,6 +20,12 @@
|
|||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
|
|
||||||
|
/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
|
||||||
|
* MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо. PS/2 set2
|
||||||
|
* scancodes. TODO: убрать после отладки. */
|
||||||
|
#define KBD_DBG_STEPL 0x3B /* J */
|
||||||
|
#define KBD_DBG_STEPR 0x4B /* L */
|
||||||
|
|
||||||
/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */
|
/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */
|
||||||
#define CONTROL_RELEASED 0
|
#define CONTROL_RELEASED 0
|
||||||
#define CONTROL_IGNORE 1
|
#define CONTROL_IGNORE 1
|
||||||
@@ -314,9 +320,14 @@ static void read_input(void)
|
|||||||
uint8_t r = kbd_raw_down(KBD_RIGHT);
|
uint8_t r = kbd_raw_down(KBD_RIGHT);
|
||||||
uint8_t u = kbd_raw_down(KBD_UP);
|
uint8_t u = kbd_raw_down(KBD_UP);
|
||||||
uint8_t d = kbd_raw_down(KBD_DOWN);
|
uint8_t d = kbd_raw_down(KBD_DOWN);
|
||||||
|
/* Отладочный осторожный шаг: J/L = Shift+влево/вправо (см. KBD_DBG_STEP*). */
|
||||||
|
uint8_t sl = kbd_raw_down(KBD_DBG_STEPL);
|
||||||
|
uint8_t sr = kbd_raw_down(KBD_DBG_STEPR);
|
||||||
|
if (sl) l = 1;
|
||||||
|
if (sr) r = 1;
|
||||||
control_x = r ? CONTROL_HELD_RIGHT : (l ? CONTROL_HELD_LEFT : CONTROL_RELEASED);
|
control_x = r ? CONTROL_HELD_RIGHT : (l ? CONTROL_HELD_LEFT : CONTROL_RELEASED);
|
||||||
control_y = d ? CONTROL_HELD_DOWN : (u ? CONTROL_HELD_UP : CONTROL_RELEASED);
|
control_y = d ? CONTROL_HELD_DOWN : (u ? CONTROL_HELD_UP : CONTROL_RELEASED);
|
||||||
control_shift = (kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))
|
control_shift = (sl || sr || kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))
|
||||||
? CONTROL_HELD : CONTROL_RELEASED;
|
? CONTROL_HELD : CONTROL_RELEASED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ static uint8_t kid_lvalid[2];
|
|||||||
* 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;
|
||||||
static uint16_t kid_fp_w, kid_fp_h;
|
static uint16_t kid_fp_w, kid_fp_h;
|
||||||
|
/* Рендер-смещение по ЛОГИЧЕСКОЙ X (straddle у шва, порт xpos_in_drawn_room):
|
||||||
|
* ∓140 когда комната Kid ≠ отрисованной; 0 обычно. */
|
||||||
|
static int kid_render_dx;
|
||||||
|
void pop_kid_set_render_dx(int dx) { kid_render_dx = dx; }
|
||||||
int kid_fp_obj_x(void) { return kid_fp_x; }
|
int kid_fp_obj_x(void) { return kid_fp_x; }
|
||||||
int kid_fp_obj_y(void) { return kid_fp_y; }
|
int kid_fp_obj_y(void) { return kid_fp_y; }
|
||||||
uint16_t kid_fp_width(void) { return kid_fp_w; }
|
uint16_t kid_fp_width(void) { return kid_fp_w; }
|
||||||
@@ -209,7 +213,10 @@ void kid_draw(void)
|
|||||||
|
|
||||||
if (fr.image == 255)
|
if (fr.image == 255)
|
||||||
return; /* пустой кадр */
|
return; /* пустой кадр */
|
||||||
fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx);
|
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
|
||||||
|
* отрисованной — спрайт за кромкой. Коллизия (Kid.x в pop_map) НЕ
|
||||||
|
* смещается: она в кадре комнаты Kid; смещение только рендерное. */
|
||||||
|
fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx) + kid_render_dx;
|
||||||
obj_x = (fwd << 1) - 116;
|
obj_x = (fwd << 1) - 116;
|
||||||
obj_y = (int)Kid.y + fr.dy;
|
obj_y = (int)Kid.y + fr.dy;
|
||||||
if ((int8_t)(fr.flags ^ Kid.direction) >= 0)
|
if ((int8_t)(fr.flags ^ Kid.direction) >= 0)
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ uint8_t kid_cur_flags(void);
|
|||||||
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
|
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
|
||||||
void kid_set_seq(uint8_t seq_id);
|
void kid_set_seq(uint8_t seq_id);
|
||||||
|
|
||||||
|
/* Рендер-смещение Kid по ЛОГИЧЕСКОЙ X (порт xpos_in_drawn_room, seg004):
|
||||||
|
* когда комната Kid ≠ отрисованной (straddle у шва), спрайт рисуется со
|
||||||
|
* сдвигом ∓140 (левый сосед −140 / правый +140), при этом Kid.x/коллизия
|
||||||
|
* остаются в кадре комнаты Kid. 0 — комнаты совпадают (обычный случай).
|
||||||
|
* Влияет на kid_draw и метрики футпринта (fore) — heal берёт запомненный
|
||||||
|
* прямоугольник, поэтому согласован автоматически. */
|
||||||
|
void pop_kid_set_render_dx(int dx);
|
||||||
|
|
||||||
/* Нарисовать текущий кадр Kid (банк GFX_BANK_SPRITE — видео-ОЗУ, без
|
/* Нарисовать текущий кадр Kid (банк GFX_BANK_SPRITE — видео-ОЗУ, без
|
||||||
* теневой копии; запоминает прямоугольник для kid_heal). */
|
* теневой копии; запоминает прямоугольник для kid_heal). */
|
||||||
void kid_draw(void);
|
void kid_draw(void);
|
||||||
|
|||||||
@@ -20,12 +20,16 @@ extern void _gfx_w0_isr(void);
|
|||||||
/* Смещения блоков blueprnt относительно начала данных (Table 6). */
|
/* Смещения блоков blueprnt относительно начала данных (Table 6). */
|
||||||
#define BP_FG 0u
|
#define BP_FG 0u
|
||||||
#define BP_BG 720u
|
#define BP_BG 720u
|
||||||
|
#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */
|
||||||
|
#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */
|
||||||
#define BP_LINKS 1952u
|
#define BP_LINKS 1952u
|
||||||
#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) /* минимум, чтобы прочитать старт */
|
||||||
|
|
||||||
static uint8_t lvl_blk, lvl_page, lvl_ok;
|
static uint8_t lvl_blk, lvl_page, lvl_ok;
|
||||||
|
/* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */
|
||||||
|
static uint8_t dl1[256], dl2[256];
|
||||||
|
|
||||||
int pop_level_load(const char *path)
|
int pop_level_load(const char *path)
|
||||||
{
|
{
|
||||||
@@ -47,11 +51,17 @@ int pop_level_load(const char *path)
|
|||||||
/* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */
|
/* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */
|
||||||
n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF);
|
n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF);
|
||||||
if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */
|
if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */
|
||||||
|
uint16_t i;
|
||||||
stub = (uint16_t)&_gfx_w0_isr;
|
stub = (uint16_t)&_gfx_w0_isr;
|
||||||
pg[0x38] = 0xC3; /* JP _gfx_w0_isr */
|
pg[0x38] = 0xC3; /* JP _gfx_w0_isr */
|
||||||
pg[0x39] = (uint8_t)(stub & 0xFF);
|
pg[0x39] = (uint8_t)(stub & 0xFF);
|
||||||
pg[0x3A] = (uint8_t)(stub >> 8);
|
pg[0x3A] = (uint8_t)(stub >> 8);
|
||||||
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI-хвост) */
|
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI-хвост) */
|
||||||
|
/* Скопировать таблицы дверных связей в W2 (страница ещё в W3). */
|
||||||
|
for (i = 0; i < 256; i++) {
|
||||||
|
dl1[i] = pg[LVL_DATA_OFF + BP_LINKLOC + i];
|
||||||
|
dl2[i] = pg[LVL_DATA_OFF + BP_LINKMAP + i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sprinter_page_w3(saved_w3);
|
sprinter_page_w3(saved_w3);
|
||||||
close(fd);
|
close(fd);
|
||||||
@@ -154,6 +164,49 @@ int8_t pop_room_col_landing(uint8_t room, uint8_t col)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t pop_doorlink1(uint8_t i) { return dl1[i]; }
|
||||||
|
uint8_t pop_doorlink2(uint8_t i) { return dl2[i]; }
|
||||||
|
void pop_doorlink2_set(uint8_t i, uint8_t v) { dl2[i] = v; }
|
||||||
|
|
||||||
|
uint8_t pop_level_tile(uint8_t room, uint8_t tilepos)
|
||||||
|
{
|
||||||
|
uint8_t v;
|
||||||
|
if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0;
|
||||||
|
gfx_w0_map(lvl_page);
|
||||||
|
v = (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F);
|
||||||
|
gfx_w0_unmap();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Батч-доступ: смаппить страницу уровня в W0 ОДИН раз, прочитать много
|
||||||
|
* тайлов через pop_level_tile_raw, размаппить один раз. Экономит N свапов
|
||||||
|
* окна 0 в горячих циклах (pop_process_trobs звал pop_level_tile на каждый
|
||||||
|
* trob каждый кадр). Между begin/end НЕЛЬЗЯ рисовать/звать код, которому
|
||||||
|
* нужно окно 0 в исходном состоянии — только сырые чтения уровня. */
|
||||||
|
void pop_level_access_begin(void) { if (lvl_ok) gfx_w0_map(lvl_page); }
|
||||||
|
void pop_level_access_end(void) { if (lvl_ok) gfx_w0_unmap(); }
|
||||||
|
|
||||||
|
uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos)
|
||||||
|
{
|
||||||
|
if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0;
|
||||||
|
return (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_level_room_bg(uint8_t room, uint8_t *out30)
|
||||||
|
{
|
||||||
|
const uint8_t *rbg;
|
||||||
|
uint8_t i;
|
||||||
|
if (!out30) return;
|
||||||
|
if (!lvl_ok || room < 1 || room > 24) {
|
||||||
|
for (i = 0; i < ROOM_TILES; i++) out30[i] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gfx_w0_map(lvl_page);
|
||||||
|
rbg = room_bg_ptr(room);
|
||||||
|
for (i = 0; i < ROOM_TILES; i++) out30[i] = rbg[i];
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t pop_room_link(uint8_t room, uint8_t side)
|
uint8_t pop_room_link(uint8_t room, uint8_t side)
|
||||||
{
|
{
|
||||||
uint8_t v;
|
uint8_t v;
|
||||||
|
|||||||
@@ -41,6 +41,29 @@ int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
|
|||||||
* 0, если стороны нет. Для переходов между комнатами (L2/L3). */
|
* 0, если стороны нет. Для переходов между комнатами (L2/L3). */
|
||||||
uint8_t pop_room_link(uint8_t room, uint8_t side);
|
uint8_t pop_room_link(uint8_t room, uint8_t side);
|
||||||
|
|
||||||
|
/* Тайл-код (fg & 0x1F) по комнате (1..24) и позиции tilepos (0..29,
|
||||||
|
* row*10+col); вне диапазона → 0 (empty). Для trob-диспетчера (pop_trob):
|
||||||
|
* тип анимируемого тайла в ЛЮБОЙ комнате, не только текущей. */
|
||||||
|
uint8_t pop_level_tile(uint8_t room, uint8_t tilepos);
|
||||||
|
|
||||||
|
/* Батч-доступ к странице уровня (один map/unmap окна 0 на много чтений).
|
||||||
|
* Между begin/end — только pop_level_tile_raw, без рисования. */
|
||||||
|
void pop_level_access_begin(void);
|
||||||
|
void pop_level_access_end(void);
|
||||||
|
uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos);
|
||||||
|
|
||||||
|
/* Скопировать backtable (модификаторы) комнаты room в out[30]. Исходное
|
||||||
|
* состояние per-room modifier (openness ворот, состояние пик и т.п.) для
|
||||||
|
* ленивой инициализации room_modif в pop_trob. Вне диапазона → нули. */
|
||||||
|
void pop_level_room_bg(uint8_t room, uint8_t *out30);
|
||||||
|
|
||||||
|
/* Таблицы связей дверей (LINKLOC@1440 / LINKMAP@1696, по 256 Б). Копируются
|
||||||
|
* в W2-RAM при загрузке (LINKMAP мутабельна — таймеры кнопок). i — индекс
|
||||||
|
* цепочки (модификатор кнопки). Декод — в pop_trob (get_doorlink_*). */
|
||||||
|
uint8_t pop_doorlink1(uint8_t i);
|
||||||
|
uint8_t pop_doorlink2(uint8_t i);
|
||||||
|
void pop_doorlink2_set(uint8_t i, uint8_t v);
|
||||||
|
|
||||||
/* Ряд приземления падающего loose-куска в колонке col комнаты room: первый
|
/* Ряд приземления падающего loose-куска в колонке col комнаты room: первый
|
||||||
* НЕ-пустой тайл сверху вниз; возвращает его ряд, если это floor (→ debris),
|
* НЕ-пустой тайл сверху вниз; возвращает его ряд, если это floor (→ debris),
|
||||||
* иначе -1 (пролетел/крушение о стену). */
|
* иначе -1 (пролетел/крушение о стену). */
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#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_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */
|
||||||
|
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
|
||||||
|
|
||||||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||||||
#define TILE_EMPTY 0
|
#define TILE_EMPTY 0
|
||||||
@@ -31,6 +32,9 @@
|
|||||||
#define TILE_WALL 20
|
#define TILE_WALL 20
|
||||||
#define TILE_LOOSE 11 /* проваливающийся пол */
|
#define TILE_LOOSE 11 /* проваливающийся пол */
|
||||||
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
|
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
|
||||||
|
#define TILE_SPIKE 2 /* пики */
|
||||||
|
#define TILE_CLOSER 6 /* кнопка-закрывалка (DROP) */
|
||||||
|
#define TILE_OPENER 15 /* кнопка-открывалка (RAISE, 0x0F) */
|
||||||
#define ACT_STAND 0
|
#define ACT_STAND 0
|
||||||
#define ACT_RUNJUMP 1
|
#define ACT_RUNJUMP 1
|
||||||
#define ACT_HANGCLIMB 2
|
#define ACT_HANGCLIMB 2
|
||||||
@@ -38,6 +42,7 @@
|
|||||||
#define ACT_FREEFALL 4
|
#define ACT_FREEFALL 4
|
||||||
#define ACT_BUMPED 5
|
#define ACT_BUMPED 5
|
||||||
#define ACT_HANGSTRAIGHT 6
|
#define ACT_HANGSTRAIGHT 6
|
||||||
|
#define ACT_TURN 7
|
||||||
|
|
||||||
#define SEQ_7_FALL 7 /* stepfall (stand/run/step/crouch) */
|
#define SEQ_7_FALL 7 /* stepfall (stand/run/step/crouch) */
|
||||||
#define SEQ_17_SOFT_LAND 17
|
#define SEQ_17_SOFT_LAND 17
|
||||||
@@ -58,6 +63,13 @@
|
|||||||
#define SEQ_23_RELEASE_LEDGE_FALL 23 /* отпустить уступ и падать */
|
#define SEQ_23_RELEASE_LEDGE_FALL 23 /* отпустить уступ и падать */
|
||||||
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
|
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
|
||||||
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
|
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
|
||||||
|
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
|
||||||
|
|
||||||
|
/* Кадры анимации для check_spiked (seg006:0658). */
|
||||||
|
#define FRAME_7_RUN 7
|
||||||
|
#define FRAME_26_STANDJUMP_LAND 26
|
||||||
|
#define FRAME_34_RUNJUMP_START 34
|
||||||
|
#define FRAME_43_RUNJUMP_LAND 43
|
||||||
|
|
||||||
#define FRAME_NEEDS_FLOOR 0x40
|
#define FRAME_NEEDS_FLOOR 0x40
|
||||||
#define FRAME_WEIGHT_X 0x1F
|
#define FRAME_WEIGHT_X 0x1F
|
||||||
@@ -86,7 +98,11 @@ static int8_t g_infrontx; /* колонка «перед персонаже
|
|||||||
/* ---- Карта комнаты ------------------------------------------------- */
|
/* ---- Карта комнаты ------------------------------------------------- */
|
||||||
uint8_t pop_fell_out; /* 1 = Kid пересёк НИЖНЮЮ границу (см. pop_map.h) */
|
uint8_t pop_fell_out; /* 1 = Kid пересёк НИЖНЮЮ границу (см. pop_map.h) */
|
||||||
uint8_t pop_leave_dir; /* уход вбок/вверх: 0=нет; 1=left,2=right,3=up (side+1) */
|
uint8_t pop_leave_dir; /* уход вбок/вверх: 0=нет; 1=left,2=right,3=up (side+1) */
|
||||||
|
uint8_t pop_leave_timer; /* exit_room_timer (seg002): >0 = leave заблокирован N кадров после входа в комнату */
|
||||||
uint8_t pop_loose_fell; /* 0=нет; иначе tilepos+1 упавшего loose (кусок улетел вниз) */
|
uint8_t pop_loose_fell; /* 0=нет; иначе tilepos+1 упавшего loose (кусок улетел вниз) */
|
||||||
|
uint8_t pop_kid_dead; /* 1 = Kid мёртв (напоролся на пики и т.п.) */
|
||||||
|
uint8_t hitp_curr; /* текущее HP (0 = мёртв); pop_kid_hp_reset ставит старт */
|
||||||
|
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 */
|
||||||
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
|
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
|
||||||
@@ -100,6 +116,18 @@ static uint8_t g_lcol[3], g_rcol[3];
|
|||||||
|
|
||||||
void pop_map_set(uint8_t *fg) { g_fg = fg; }
|
void pop_map_set(uint8_t *fg) { g_fg = fg; }
|
||||||
|
|
||||||
|
void pop_map_set_room(uint8_t room) { g_room = room; }
|
||||||
|
|
||||||
|
void pop_kid_hp_reset(void) { hitp_curr = 3; pop_kid_dead = 0; }
|
||||||
|
|
||||||
|
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
|
||||||
|
static uint8_t take_hp(uint8_t count)
|
||||||
|
{
|
||||||
|
if (count >= hitp_curr) { hitp_curr = 0; return 1; }
|
||||||
|
hitp_curr = (uint8_t)(hitp_curr - count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -123,6 +151,28 @@ static uint8_t get_tile(int8_t col, int8_t row)
|
|||||||
return g_curr_tile;
|
return g_curr_tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
|
||||||
|
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
|
||||||
|
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
|
||||||
|
* tile_is_floor это ПОЛ), поэтому Kid НЕ падает на них. Раньше get_tile ради
|
||||||
|
* проходимости подменял ворота шва на empty — но подмену видел и check_on_floor
|
||||||
|
* → start_fall (Kid проваливался при проходе через ОТКРЫТЫЕ ворота шва). Теперь
|
||||||
|
* проходимость живёт ТОЛЬКО в bump-логике (как в SDLPoP), не трогая проверку пола.
|
||||||
|
* Модификатор: шов слева (col=-1) — сосед g_link_l col9; шов справа (col=10) —
|
||||||
|
* g_link_r col0; ВНУТРИ комнаты (0..9) — текущая комната g_room (иначе после
|
||||||
|
* перехода Kid встаёт на ту же створку уже как на внутрикомнатную и его отбивает
|
||||||
|
* обратно). Открыто ⇔ (modif>>2)+6 >= char_height (бары выше головы). */
|
||||||
|
static uint8_t gate_passable(int8_t col, int8_t row)
|
||||||
|
{
|
||||||
|
uint8_t gm;
|
||||||
|
if (row < 0 || row > 2) return 0;
|
||||||
|
if (col == -1 && g_link_l) gm = pop_trob_modif(g_link_l)[row * 10 + 9];
|
||||||
|
else if (col == 10 && g_link_r) gm = pop_trob_modif(g_link_r)[row * 10 + 0];
|
||||||
|
else if (col >= 0 && col <= 9) gm = pop_trob_modif(g_room)[row * 10 + col];
|
||||||
|
else return 0;
|
||||||
|
return (uint8_t)((gm >> 2) + 6) >= (uint8_t)kid_fp_height();
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t tile_is_floor(uint8_t t)
|
static uint8_t tile_is_floor(uint8_t t)
|
||||||
{
|
{
|
||||||
switch (t) {
|
switch (t) {
|
||||||
@@ -225,13 +275,31 @@ static int distance_to_edge(int xpos)
|
|||||||
static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); }
|
static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); }
|
||||||
|
|
||||||
/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
|
/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
|
||||||
* Передний край персонажа char_x_*_coll = char_dx_forward(cur_frame.dx)
|
* Передний край персонажа = char_x_right (взгляд вправо) / char_x_left
|
||||||
* (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для
|
* (влево) из set_char_collision (seg006:0723): obj_x/2 + 58, где obj_x =
|
||||||
* THIN-кадров. dist_from_wall_forward — расстояние переднего края до грани
|
* 2*char_dx_forward(cur_frame.dx) - 116 и load_frame_to_obj (seg008:1728)
|
||||||
* стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */
|
* добавляет +1 для кадров «чётного пикселя» (бит 0x80 флагов XOR знак
|
||||||
|
* направления). Деление obj_x/2 в C усечено К НУЛЮ, поэтому в зоне левого
|
||||||
|
* шва (e<=57, obj_x<0) поправка +1 сдвигает край на пиксель ВПРАВО: e -> e+1.
|
||||||
|
* Это НЕ мелочь: Kid, осевший после отскока от ворот шва на x=57, имеет
|
||||||
|
* char_x_right=58 и НЕ пересекает порог leave-left (<=57) — иначе пинг-понг
|
||||||
|
* drawn_room у закрытых ворот (BUG-SEAM-PINGPONG; подтверждено потиковой
|
||||||
|
* трассой живого SDLPoP 1.23). При e>=58 формула сокращается к чистому
|
||||||
|
* char_dx_forward. ∓4 для THIN-кадров поверх. dist_from_wall_forward —
|
||||||
|
* расстояние переднего края до грани стены tiletype в колонке tcol: >0
|
||||||
|
* зазор, <=0 достал/зашёл (насколько). */
|
||||||
|
static int char_x_forward_edge(void)
|
||||||
|
{
|
||||||
|
int e = char_dx_forward(kid_cur_dx());
|
||||||
|
uint8_t fl = kid_cur_flags();
|
||||||
|
if (Kid.direction < 0) fl ^= 0x80;
|
||||||
|
if (!(fl & 0x80) && e <= 57) e++; /* odd pixel + C-трунк к нулю */
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
static int char_front_coll(void)
|
static int char_front_coll(void)
|
||||||
{
|
{
|
||||||
int cx = char_dx_forward(kid_cur_dx());
|
int cx = char_x_forward_edge();
|
||||||
if (kid_cur_flags() & FRAME_THIN)
|
if (kid_cur_flags() & FRAME_THIN)
|
||||||
cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */
|
cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */
|
||||||
return cx;
|
return cx;
|
||||||
@@ -270,10 +338,36 @@ static void in_wall(void)
|
|||||||
/* ---- Падение ------------------------------------------------------- */
|
/* ---- Падение ------------------------------------------------------- */
|
||||||
static void inc_curr_row(void) { Kid.curr_row++; }
|
static void inc_curr_row(void) { Kid.curr_row++; }
|
||||||
|
|
||||||
|
/* Пики (определены ниже, перед pop_phys_tick) — нужны в land() для смерти
|
||||||
|
* при ПАДЕНИИ на выдвинутые пики (seg005 loc_5EE6). */
|
||||||
|
static int is_spike_harmful(uint8_t tilepos);
|
||||||
|
static void spiked(uint8_t tilepos);
|
||||||
|
|
||||||
|
/* fell_on_spikes (seg005 loc_5EE6): при приземлении тайл ПОД персонажем =
|
||||||
|
* пика (или пика СЗАДИ у самой кромки) и вредна → напороться (смерть).
|
||||||
|
* Возвращает 1, если умер (land прерывается). */
|
||||||
|
static uint8_t fell_on_spikes(void)
|
||||||
|
{
|
||||||
|
uint8_t tp;
|
||||||
|
if (get_tile_at_char() == TILE_SPIKE) {
|
||||||
|
tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||||
|
} else if (distance_to_edge_weight() >= 12 &&
|
||||||
|
get_tile_behind_char() == TILE_SPIKE) {
|
||||||
|
int8_t bcol = (int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col);
|
||||||
|
if (bcol < 0 || bcol > 9) return 0;
|
||||||
|
tp = (uint8_t)(Kid.curr_row * 10 + bcol);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (is_spike_harmful(tp)) { spiked(tp); return 1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
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)y_land[Kid.curr_row + 1];
|
||||||
|
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)
|
||||||
Kid.x = (uint8_t)char_dx_forward(-3);
|
Kid.x = (uint8_t)char_dx_forward(-3);
|
||||||
@@ -618,7 +712,7 @@ static void check_action(void)
|
|||||||
* выравниваем, без рестарта. В падении/висе не трогаем. */
|
* выравниваем, без рестарта. В падении/висе не трогаем. */
|
||||||
static void check_bumped(void)
|
static void check_bumped(void)
|
||||||
{
|
{
|
||||||
uint8_t t; int8_t tcol; int d;
|
uint8_t t, wt; int8_t tcol; int d;
|
||||||
/* Не бампим в падении/висе/подтягивании (seg004 check_bumped guard'ы:
|
/* Не бампим в падении/висе/подтягивании (seg004 check_bumped guard'ы:
|
||||||
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
|
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
|
||||||
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
|
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
|
||||||
@@ -635,8 +729,19 @@ static void check_bumped(void)
|
|||||||
* 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;
|
||||||
t = get_tile_at_char(); tcol = Kid.curr_col;
|
t = get_tile_at_char(); tcol = Kid.curr_col;
|
||||||
if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; }
|
wt = wall_type(t);
|
||||||
if (!wall_type(t)) return;
|
/* seg004: bump проверяется по колонке СБОКУ от стены (bump_col_left/right_
|
||||||
|
* of_wall), а НЕ по тайлу ПОД персонажем. Стоя́ на односторонней стене
|
||||||
|
* (ворота = стена справа / зеркало = стена слева) лицом ПРОЧЬ от её
|
||||||
|
* блокирующей стороны — она не бампит. Иначе разворот на тайле ворот у
|
||||||
|
* шва выталкивает Kid к дальней грани ворот (сквозь закрытую решётку в
|
||||||
|
* соседнюю комнату — баг «телепорта» при развороте). */
|
||||||
|
if ((wt == 1 && Kid.direction < 0) || (wt == 2 && Kid.direction >= 0))
|
||||||
|
wt = 0;
|
||||||
|
if (!wt) { t = get_tile_infrontof_char(); tcol = g_infrontx; wt = wall_type(t); }
|
||||||
|
if (!wt) return;
|
||||||
|
if (t == TILE_GATE && gate_passable(tcol, Kid.curr_row))
|
||||||
|
return; /* открытые ворота (шов/внутри) — Kid проходит (не бамп) */
|
||||||
d = dist_from_wall_forward(t, tcol);
|
d = dist_from_wall_forward(t, tcol);
|
||||||
if (d >= 0) return; /* ещё не зашёл за грань */
|
if (d >= 0) return; /* ещё не зашёл за грань */
|
||||||
Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */
|
Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */
|
||||||
@@ -676,11 +781,20 @@ static void check_press(void)
|
|||||||
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR */
|
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR.
|
||||||
if (action != ACT_BUMPED && action >= ACT_HANGCLIMB) return;
|
* ACT_TURN обязателен (SDLPoP check_press: actions_7_turn): при развороте
|
||||||
|
* на кнопке иначе trigger_button не зовётся кадр-два → таймер связи падает
|
||||||
|
* <2 → кнопка ложно отжимается и жмётся снова. */
|
||||||
|
if (action != ACT_BUMPED && action != ACT_TURN && action >= ACT_HANGCLIMB) return;
|
||||||
if (!(kid_cur_flags() & FRAME_NEEDS_FLOOR)) return;
|
if (!(kid_cur_flags() & FRAME_NEEDS_FLOOR)) return;
|
||||||
if (get_tile_at_char() == TILE_LOOSE)
|
{
|
||||||
make_loose_fall(Kid.curr_row * 10 + Kid.curr_col, 1);
|
uint8_t t = get_tile_at_char();
|
||||||
|
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||||
|
if (t == TILE_OPENER || t == TILE_CLOSER)
|
||||||
|
pop_trigger_button(g_room, tp, t, pop_trob_modif(g_room)[tp]);
|
||||||
|
else if (t == TILE_LOOSE)
|
||||||
|
make_loose_fall(tp, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* do_knock (seg007:0FE0) + loose_make_shake (seg007:0FB4): сотрясение от
|
/* do_knock (seg007:0FE0) + loose_make_shake (seg007:0FB4): сотрясение от
|
||||||
@@ -729,29 +843,32 @@ void pop_loose_reset(void)
|
|||||||
void pop_loose_tick(void)
|
void pop_loose_tick(void)
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
|
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
|
||||||
|
* нет аппаратного деления, и SDCC выносил оба деления (__divuchar/
|
||||||
|
* __moduchar) в начало КАЖДОЙ итерации (общее подвыражение аргументов
|
||||||
|
* вызовов), т.е. 30*2=60 вызовов деления за кадр — даже в покое и в
|
||||||
|
* комнатах без loose-плит (условия if к тому моменту ещё не проверены).
|
||||||
|
* Инкремент+сравнение считают row/col без деления. */
|
||||||
|
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 (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
|
||||||
pop_loose_bake_empty(pos / 10, pos % 10);
|
pop_loose_bake_empty(row, col);
|
||||||
loose_bake[pos]--;
|
loose_bake[pos]--;
|
||||||
continue;
|
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||||||
}
|
|
||||||
if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
|
||||||
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
||||||
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
||||||
* страницы дабл-буфера — иначе на одной остаётся застывший
|
* страницы дабл-буфера — иначе на одной остаётся застывший
|
||||||
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
||||||
pop_loose_shake_draw(pos / 10, pos % 10);
|
pop_loose_shake_draw(row, col);
|
||||||
loose_rest[pos]--;
|
loose_rest[pos]--;
|
||||||
continue;
|
} else if (pop_loose_modif[pos] != 0) {
|
||||||
}
|
|
||||||
if (pop_loose_modif[pos] == 0) continue;
|
|
||||||
{
|
|
||||||
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; /* покой на ОБЕИХ страницах */
|
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */
|
||||||
continue;
|
} else {
|
||||||
|
pop_loose_shake_draw(row, col); /* дрожащий кадр */
|
||||||
}
|
}
|
||||||
} 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!).
|
||||||
@@ -761,14 +878,15 @@ 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(pos / 10, pos % 10); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||||||
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
|
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
|
||||||
pop_loose_mob_spawn(pos / 10, pos % 10); /* отрыв падающего куска (L4) */
|
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */
|
||||||
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
|
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
|
||||||
continue;
|
} else { /* отсчёт 1..10 — дрожащий кадр */
|
||||||
|
pop_loose_shake_draw(row, col);
|
||||||
}
|
}
|
||||||
pop_loose_shake_draw(pos / 10, pos % 10); /* дрожащий кадр */
|
|
||||||
}
|
}
|
||||||
|
if (++col == 10) { col = 0; row++; } /* row/col без деления */
|
||||||
}
|
}
|
||||||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||||
}
|
}
|
||||||
@@ -799,9 +917,27 @@ static void check_leave_below(void)
|
|||||||
* удержит у шва). */
|
* удержит у шва). */
|
||||||
static void check_leave(void)
|
static void check_leave(void)
|
||||||
{
|
{
|
||||||
int fx = char_dx_forward(kid_cur_dx());
|
int fx;
|
||||||
|
uint8_t f = Kid.frame;
|
||||||
|
/* seg002 leave_room: комнату НЕ покидаем во время разворота (action turn),
|
||||||
|
* подъёма-с-зацепа (climb, кадры 135..149) и вставания из приседа
|
||||||
|
* (110..119). Без этого гарда разворот у правого шва (взгляд флипается
|
||||||
|
* право→лево, char_x_left проскакивает >=198) телепортирует Kid сквозь
|
||||||
|
* закрытые ворота в соседнюю комнату (leave_room возвращает −1). */
|
||||||
|
if (Kid.action == ACT_TURN ||
|
||||||
|
(f >= 135 && f < 150) ||
|
||||||
|
(f >= 110 && f < 120))
|
||||||
|
return;
|
||||||
|
/* Передний край — ТОЛЬКО через char_x_forward_edge (obj_x-арифметика
|
||||||
|
* set_char_collision): в зоне шва (<=57) он на пиксель правее чистого
|
||||||
|
* Kid.x+dx, и Kid, осевший у ворот на x=57, порога leave-left НЕ достигает
|
||||||
|
* (эталон SDLPoP: экран не переключается, BUG-SEAM-PINGPONG). */
|
||||||
|
fx = char_x_forward_edge();
|
||||||
if (Kid.direction >= 0) { /* взгляд вправо: fx = char_x_right */
|
if (Kid.direction >= 0) { /* взгляд вправо: fx = char_x_right */
|
||||||
if (g_link_r && fx >= 201) {
|
uint8_t t9 = get_tile(9, Kid.curr_row);
|
||||||
|
if (g_link_r && fx >= 201 &&
|
||||||
|
t9 != TILE_DOORTOP_FLOOR && t9 != TILE_DOORTOP) {
|
||||||
|
/* leave_room (seg002): под doortop у правой кромки выхода нет */
|
||||||
Kid.x = (uint8_t)(Kid.x - 140); determine_col(); pop_leave_dir = 2;
|
Kid.x = (uint8_t)(Kid.x - 140); determine_col(); pop_leave_dir = 2;
|
||||||
} else if (g_link_l && fx <= 57) {
|
} else if (g_link_l && fx <= 57) {
|
||||||
Kid.x = (uint8_t)(Kid.x + 140); determine_col(); pop_leave_dir = 1;
|
Kid.x = (uint8_t)(Kid.x + 140); determine_col(); pop_leave_dir = 1;
|
||||||
@@ -815,8 +951,105 @@ static void check_leave(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Пики (spike, seg006/seg007) ---------------------------------- *
|
||||||
|
* Состояние пики в room_modif[g_room][tilepos] (pop_trob), автомат —
|
||||||
|
* animate_spike (pop_trob). Здесь: триггер выдвижения (check_spike_below),
|
||||||
|
* решение о смерти (check_spiked) + spiked(). */
|
||||||
|
|
||||||
|
/* start_anim_spike (seg007:08E3): взвести выдвижение, если пика в покое
|
||||||
|
* (modif 0 → trob type1) или ушла не полностью (modif<0, кроме 0xFF disabled
|
||||||
|
* → мгновенно 0x8F «вышли»). */
|
||||||
|
static void start_anim_spike(uint8_t tilepos)
|
||||||
|
{
|
||||||
|
uint8_t *mod = pop_trob_modif(g_room);
|
||||||
|
int8_t old;
|
||||||
|
if (!mod) return;
|
||||||
|
old = (int8_t)mod[tilepos];
|
||||||
|
if (old <= 0) {
|
||||||
|
if (old == 0)
|
||||||
|
pop_add_trob(g_room, tilepos, 1); /* (TODO: sound_49_spikes) */
|
||||||
|
else if ((uint8_t)old != 0xFF)
|
||||||
|
mod[tilepos] = 0x8F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_spike_harmful (seg007:1178): 0/-1 → 0 (безопасно); <0 → 1; 1..4 → 2;
|
||||||
|
* >=5 → 0. */
|
||||||
|
static int is_spike_harmful(uint8_t tilepos)
|
||||||
|
{
|
||||||
|
int8_t m = (int8_t)pop_trob_modif(g_room)[tilepos];
|
||||||
|
if (m == 0 || m == -1) return 0;
|
||||||
|
if (m < 0) return 1;
|
||||||
|
if (m < 5) return 2;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* spiked (seg005:0888): напоролся — пика становится безвредной (0xFF),
|
||||||
|
* выровнять по колонке, take_hp(100) = смерть, seq_51 (импейл). */
|
||||||
|
static void spiked(uint8_t tilepos)
|
||||||
|
{
|
||||||
|
pop_trob_modif(g_room)[tilepos] = 0xFF;
|
||||||
|
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
|
||||||
|
Kid.x = (uint8_t)(x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10);
|
||||||
|
Kid.x = (uint8_t)char_dx_forward(8);
|
||||||
|
Kid.fall_y = 0;
|
||||||
|
take_hp(100); /* (TODO: sound_48_spiked) */
|
||||||
|
kid_set_seq(SEQ_51_SPIKED);
|
||||||
|
play_seq();
|
||||||
|
determine_col();
|
||||||
|
pop_kid_dead = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check_spiked (seg006:0658): под персонажем пика И вредна И кадр
|
||||||
|
* бега(7..14)/старта прыжка(34..39) при harmful>=2, ЛИБО кадр приземления
|
||||||
|
* (43/26) при harmful!=0 → smert'. */
|
||||||
|
static void check_spiked(void)
|
||||||
|
{
|
||||||
|
uint8_t frame = Kid.frame, tp;
|
||||||
|
int h;
|
||||||
|
if (Kid.curr_col < 0 || Kid.curr_col > 9 || Kid.curr_row < 0 || Kid.curr_row > 2)
|
||||||
|
return;
|
||||||
|
if (get_tile_at_char() != TILE_SPIKE) return;
|
||||||
|
tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
|
||||||
|
h = is_spike_harmful(tp);
|
||||||
|
if ((h >= 2 && ((frame >= FRAME_7_RUN && frame < 15) ||
|
||||||
|
(frame >= FRAME_34_RUNJUMP_START && frame < 40))) ||
|
||||||
|
((frame == FRAME_43_RUNJUMP_LAND || frame == FRAME_26_STANDJUMP_LAND) && h != 0))
|
||||||
|
spiked(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check_spike_below (seg006:1199): для каждой колонки РЕАЛЬНОГО габарита
|
||||||
|
* спрайта (char_x_left..char_x_right, порт set_char_collision) скан ВНИЗ от
|
||||||
|
* curr_row через НЕ-floor тайлы; встретил пику → start_anim_spike. Габарит,
|
||||||
|
* а не «curr_col + колонка спереди»: иначе пики выдвигаются на тайл раньше
|
||||||
|
* (когда тело Кида ещё не над шахтой). Метрики кадра — kid_fp (габарит
|
||||||
|
* последнего kid_draw; отставание в 1 кадр несущественно, ошибка в позднюю
|
||||||
|
* сторону). */
|
||||||
|
static void check_spike_below(void)
|
||||||
|
{
|
||||||
|
int wh = ((int)kid_fp_width() + 1) >> 1; /* char_width_half */
|
||||||
|
int xl = kid_fp_obj_x() / 2 + 58; /* char_x_left */
|
||||||
|
int xr;
|
||||||
|
int8_t c0, c1, col;
|
||||||
|
if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
|
||||||
|
xr = xl + wh; /* char_x_right */
|
||||||
|
c0 = get_tile_div_mod_m7(xl);
|
||||||
|
c1 = get_tile_div_mod_m7(xr);
|
||||||
|
for (col = c0; col <= c1; col++) {
|
||||||
|
int8_t row = Kid.curr_row;
|
||||||
|
if (col < 0 || col > 9) continue;
|
||||||
|
for (;;) {
|
||||||
|
uint8_t t = get_tile(col, row);
|
||||||
|
if (t == TILE_SPIKE) { start_anim_spike((uint8_t)(row * 10 + col)); break; }
|
||||||
|
if (!tile_is_floor(t) && row <= 2) { row++; continue; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void pop_phys_tick(void)
|
void pop_phys_tick(void)
|
||||||
{
|
{
|
||||||
|
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
|
||||||
if (grab_timer) grab_timer--;
|
if (grab_timer) grab_timer--;
|
||||||
fall_accel();
|
fall_accel();
|
||||||
fall_speed();
|
fall_speed();
|
||||||
@@ -824,7 +1057,19 @@ void pop_phys_tick(void)
|
|||||||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||||||
check_action();
|
check_action();
|
||||||
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
|
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
|
||||||
|
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
|
||||||
|
check_spiked(); /* seg006: напоролся на вредные пики → смерть */
|
||||||
|
if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */
|
||||||
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
|
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
|
||||||
|
/* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой
|
||||||
|
* leave заблокирован. Без него транзиент отскока о ворота шва (seq_47
|
||||||
|
* роняет char_x за порог) сразу же выкидывает Kid в соседнюю комнату
|
||||||
|
* (ping-pong / баг #4). С таймером первый бамп попадает в окно блокировки,
|
||||||
|
* Kid оседает у грани (char_x стабильно > порога) — экран не перескакивает. */
|
||||||
|
if (pop_leave_timer) {
|
||||||
|
pop_leave_timer--;
|
||||||
|
} else {
|
||||||
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
|
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
|
||||||
check_leave(); /* seg002: уход вбок (право) → переход в соседнюю комнату */
|
check_leave(); /* seg002: уход вбок → переход в соседнюю комнату */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,28 @@ void pop_map_set(uint8_t *fg);
|
|||||||
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);
|
||||||
|
|
||||||
|
/* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob).
|
||||||
|
* Звать в enter_room. */
|
||||||
|
void pop_map_set_room(uint8_t room);
|
||||||
|
|
||||||
|
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
|
||||||
|
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
|
||||||
|
* респавн). */
|
||||||
|
extern uint8_t pop_kid_dead;
|
||||||
|
extern uint8_t hitp_curr;
|
||||||
|
void pop_kid_hp_reset(void);
|
||||||
|
|
||||||
/* Уход из комнаты ВБОК/ВВЕРХ: 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).
|
||||||
* Приложение переключает комнату по соответствующей связи и снимает флаг. */
|
* Приложение переключает комнату по соответствующей связи и снимает флаг. */
|
||||||
extern uint8_t pop_leave_dir;
|
extern uint8_t pop_leave_dir;
|
||||||
|
|
||||||
|
/* exit_room_timer (порт seg002 exit_room): пока >0, боковой/нижний leave
|
||||||
|
* заблокирован (декремент в pop_phys_tick). Приложение ставит =2 при входе
|
||||||
|
* в комнату (enter_room) — 2 кадра защиты от повторного/транзиентного leave
|
||||||
|
* (ping-pong у шва, транзиент отскока о ворота, баг #4/BUG-2). */
|
||||||
|
extern uint8_t pop_leave_timer;
|
||||||
|
|
||||||
/* Loose floors (проваливающиеся полы). pop_loose_modif[30] — состояние на
|
/* Loose floors (проваливающиеся полы). pop_loose_modif[30] — состояние на
|
||||||
* тайл (0 покой / 0x80.. тряска / 1..11 отсчёт падения); читает pop_bg при
|
* тайл (0 покой / 0x80.. тряска / 1..11 отсчёт падения); читает pop_bg при
|
||||||
* отрисовке. pop_loose_tick() — анимация + перерисовка изменившихся тайлов
|
* отрисовке. pop_loose_tick() — анимация + перерисовка изменившихся тайлов
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* pop_trob.c — P0-каркас: room_modif + список trob + диспетчер анимаций.
|
||||||
|
* Порт SDLPoP seg007 (process_trobs/animate_tile/add_trob/animate_spike).
|
||||||
|
* См. pop_trob.h.
|
||||||
|
*/
|
||||||
|
#include "pop_trob.h"
|
||||||
|
#include "pop_level.h"
|
||||||
|
#include "pop_bg.h" /* pop_spike_redraw */
|
||||||
|
|
||||||
|
/* Коды тайлов (fg & 0x1F), которые анимируются. */
|
||||||
|
#define TILE_SPIKE 0x02
|
||||||
|
#define TILE_GATE 0x04
|
||||||
|
#define TILE_CLOSER 0x06
|
||||||
|
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
|
||||||
|
#define TILE_OPENER 0x0F
|
||||||
|
|
||||||
|
/* ---- Персистентное per-room modifier-состояние -------------------- */
|
||||||
|
static uint8_t room_modif[POP_NROOMS][POP_ROOMTILES];
|
||||||
|
static uint8_t room_seen[POP_NROOMS]; /* modif проинициализирован из уровня */
|
||||||
|
|
||||||
|
/* ---- Список анимируемых тайлов ------------------------------------ */
|
||||||
|
typedef struct { uint8_t room, tilepos; int8_t type; } trob_t;
|
||||||
|
#define TROBS_MAX 24
|
||||||
|
static trob_t trobs[TROBS_MAX];
|
||||||
|
static uint8_t trobs_count;
|
||||||
|
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:
|
||||||
|
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
|
||||||
|
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
|
||||||
|
* стоящим Кидом перерисовывалась КАЖДЫЙ кадр (2×draw_tile ≈ 50% кадра в
|
||||||
|
* статике), т.к. её trob висит, пока Кид стоит (это верно, как в SDLPoP —
|
||||||
|
* расхождение было именно в безусловном редрое). */
|
||||||
|
static uint8_t trob_drawn[POP_ROOMTILES];
|
||||||
|
|
||||||
|
void pop_trob_reset(void)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
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; }
|
||||||
|
trobs_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *pop_trob_modif(uint8_t room)
|
||||||
|
{
|
||||||
|
if (room < 1 || room > POP_NROOMS) return 0;
|
||||||
|
if (!room_seen[room - 1]) {
|
||||||
|
pop_level_room_bg(room, room_modif[room - 1]);
|
||||||
|
room_seen[room - 1] = 1;
|
||||||
|
}
|
||||||
|
return room_modif[room - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_trob(uint8_t room, uint8_t tilepos)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < trobs_count; i++)
|
||||||
|
if (trobs[i].room == room && trobs[i].tilepos == tilepos) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
|
||||||
|
{
|
||||||
|
int f = find_trob(room, tilepos);
|
||||||
|
if (f >= 0) { trobs[f].type = type; return; }
|
||||||
|
if (trobs_count >= TROBS_MAX) return;
|
||||||
|
trobs[trobs_count].room = room;
|
||||||
|
trobs[trobs_count].tilepos = tilepos;
|
||||||
|
trobs[trobs_count].type = type;
|
||||||
|
trobs_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_trob_room_changed(void)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* animate_spike (seg007:04D3): автомат по modifier. *m — modifier тайла,
|
||||||
|
* *type — тип trob (>=0 активен, <0 = удалить). Выдвиг: ++modif (1..4),
|
||||||
|
* на 5 → 0x8F (полностью вышли, безвредны), на 9 → 0 + конец. Убирание
|
||||||
|
* (флаг 0x80): --modif; когда 0x7F-часть обнулилась → modif=6 (начать
|
||||||
|
* визуальный уход 6..8). 0xFF = disabled. */
|
||||||
|
static void animate_spike(uint8_t *m, int8_t *type)
|
||||||
|
{
|
||||||
|
uint8_t mod = *m;
|
||||||
|
if (*type >= 0) {
|
||||||
|
if (mod == 0xFF) return; /* disabled — не двигать */
|
||||||
|
if (mod & 0x80) {
|
||||||
|
--mod;
|
||||||
|
if (mod & 0x7F) { *m = mod; return; }
|
||||||
|
mod = 6;
|
||||||
|
} else {
|
||||||
|
++mod;
|
||||||
|
if (mod == 5) mod = 0x8F;
|
||||||
|
else if (mod == 9) { mod = 0; *type = -1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*m = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Кнопки/ворота (seg007) --------------------------------------- *
|
||||||
|
* Таблицы связей LINKLOC/LINKMAP — в pop_level (мутабельная копия в W2).
|
||||||
|
* doorlink1 (d1) = LINKLOC, doorlink2 (d2) = LINKMAP. */
|
||||||
|
static uint8_t get_doorlink_timer(uint8_t i) { return (uint8_t)(pop_doorlink2(i) & 0x1F); }
|
||||||
|
static void set_doorlink_timer(uint8_t i, uint8_t v)
|
||||||
|
{
|
||||||
|
pop_doorlink2_set(i, (uint8_t)((pop_doorlink2(i) & 0xE0) | (v & 0x1F)));
|
||||||
|
}
|
||||||
|
static uint8_t get_doorlink_tile(uint8_t i) { return (uint8_t)(pop_doorlink1(i) & 0x1F); }
|
||||||
|
static uint8_t get_doorlink_next(uint8_t i) { return (uint8_t)(!(pop_doorlink1(i) & 0x80)); }
|
||||||
|
static uint8_t get_doorlink_room(uint8_t i)
|
||||||
|
{
|
||||||
|
return (uint8_t)(((pop_doorlink1(i) & 0x60) >> 5) + ((pop_doorlink2(i) & 0xE0) >> 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* trigger_gate (seg007:092C): изменить openness ворот по типу кнопки.
|
||||||
|
* Возвращает тип анимации для add_trob (или -1 = ничего). */
|
||||||
|
static int8_t trigger_gate(uint8_t room, uint8_t tilepos, uint8_t button_type)
|
||||||
|
{
|
||||||
|
uint8_t *mod = pop_trob_modif(room);
|
||||||
|
uint8_t modifier = mod[tilepos];
|
||||||
|
if (button_type == TILE_OPENER) {
|
||||||
|
if (modifier == 0xFF) return -1; /* уже насовсем открыт */
|
||||||
|
if (modifier >= 188) { mod[tilepos] = 238; return -1; } /* открыт → держать */
|
||||||
|
mod[tilepos] = (uint8_t)((modifier + 3) & 0xFC);
|
||||||
|
return 1; /* обычное открытие */
|
||||||
|
} else if (button_type == TILE_DEBRIS) { /* насовсем (died_on_button) */
|
||||||
|
if (modifier < 188) return 2;
|
||||||
|
mod[tilepos] = 0xFF;
|
||||||
|
return -1;
|
||||||
|
} else { /* closer */
|
||||||
|
if (modifier != 0) return 3; /* быстро закрыть */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* trigger_1 (seg007:0999): цель = ворота → trigger_gate; иначе -1 (leveldoor
|
||||||
|
* и прочее — отложено). */
|
||||||
|
static int8_t trigger_1(uint8_t target_type, uint8_t room, uint8_t tilepos, uint8_t button_type)
|
||||||
|
{
|
||||||
|
if (target_type == TILE_GATE)
|
||||||
|
return trigger_gate(room, tilepos, button_type);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do_trigger_list (seg007:09E5): обойти цепочку LINKLOC от index, для каждой
|
||||||
|
* цели добавить trob (если trigger_1 >= 0). */
|
||||||
|
static void do_trigger_list(uint8_t index, uint8_t button_type)
|
||||||
|
{
|
||||||
|
for (;;) {
|
||||||
|
uint8_t room = get_doorlink_room(index);
|
||||||
|
uint8_t tilepos = get_doorlink_tile(index);
|
||||||
|
uint8_t target_type = pop_level_tile(room, tilepos);
|
||||||
|
int8_t result = trigger_1(target_type, room, tilepos, button_type);
|
||||||
|
if (result >= 0) pop_add_trob(room, tilepos, result);
|
||||||
|
if (get_doorlink_next(index) == 0) break;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||||||
|
uint8_t button_type, uint8_t modifier)
|
||||||
|
{
|
||||||
|
uint8_t link_timer = get_doorlink_timer(modifier);
|
||||||
|
if (link_timer != 0x1F) { /* не заклинено */
|
||||||
|
set_doorlink_timer(modifier, 5);
|
||||||
|
if (link_timer < 2)
|
||||||
|
pop_add_trob(room, tilepos, 1); /* кнопка нажимается (+ TODO sound) */
|
||||||
|
do_trigger_list(modifier, button_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* animate_button (seg007:0D3A): отсчитать таймер связи; <2 → кнопка
|
||||||
|
* отжимается (trob кончился). idx = модификатор кнопки (индекс LINKLOC). */
|
||||||
|
static void animate_button(uint8_t idx, int8_t *type)
|
||||||
|
{
|
||||||
|
if (*type >= 0) {
|
||||||
|
uint8_t timer = get_doorlink_timer(idx);
|
||||||
|
timer = (uint8_t)(timer ? timer - 1 : 0);
|
||||||
|
set_doorlink_timer(idx, timer);
|
||||||
|
if (timer < 2) *type = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* animate_door (seg007:0522): openness по типу анимации. *m — modif ворот,
|
||||||
|
* *type — 0 закрытие / 1 открытие / 2 насовсем / 3..8 быстрое закрытие. */
|
||||||
|
static const uint8_t gate_close_speeds[9] = {0, 0, 0, 20, 40, 60, 80, 100, 120};
|
||||||
|
static const int8_t door_delta[3] = {-1, 4, 4};
|
||||||
|
|
||||||
|
static void animate_door(uint8_t *m, int8_t *type)
|
||||||
|
{
|
||||||
|
int8_t anim_type = *type;
|
||||||
|
uint8_t mod = *m;
|
||||||
|
if (anim_type >= 0) {
|
||||||
|
if (anim_type >= 3) { /* быстрое закрытие */
|
||||||
|
if (anim_type < 8) { anim_type++; *type = anim_type; }
|
||||||
|
{
|
||||||
|
int new_mod = (int)mod - gate_close_speeds[anim_type];
|
||||||
|
if (new_mod < 0) { mod = 0; *type = -1; }
|
||||||
|
else mod = (uint8_t)new_mod;
|
||||||
|
}
|
||||||
|
} else if (mod != 0xFF) {
|
||||||
|
mod = (uint8_t)(mod + door_delta[anim_type]);
|
||||||
|
if (anim_type == 0) { /* закрытие */
|
||||||
|
if (mod == 0) *type = -1; /* gate_stop */
|
||||||
|
} else { /* открытие */
|
||||||
|
if (mod >= 188) {
|
||||||
|
if (anim_type < 2) { mod = 238; *type = 0; } /* открыт → потом закрывать */
|
||||||
|
else { mod = 0xFF; *type = -1; } /* насовсем */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*type = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*m = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pop_trob_room_active(uint8_t room)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < trobs_count; i++)
|
||||||
|
if (trobs[i].room == room) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_process_trobs(uint8_t cur_room)
|
||||||
|
{
|
||||||
|
uint8_t i, w = 0;
|
||||||
|
|
||||||
|
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
|
||||||
|
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
|
||||||
|
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
|
||||||
|
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
|
||||||
|
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
|
||||||
|
{
|
||||||
|
uint8_t row = 0, col = 0;
|
||||||
|
for (i = 0; i < POP_ROOMTILES; i++) {
|
||||||
|
if (spike_rest[i]) {
|
||||||
|
pop_spike_redraw(row, col);
|
||||||
|
spike_rest[i]--;
|
||||||
|
}
|
||||||
|
if (button_rest[i]) {
|
||||||
|
pop_button_redraw(row, col);
|
||||||
|
button_rest[i]--;
|
||||||
|
}
|
||||||
|
if (++col == 10) { col = 0; row++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trobs_count == 0) return;
|
||||||
|
|
||||||
|
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
|
||||||
|
* pop_level_tile() на каждый trob (тот свапал W0 каждый вызов = N свапов
|
||||||
|
* за кадр даже без анимации). Дальше цикл (с редроями) идёт с
|
||||||
|
* размапленным W0 — как раньше. */
|
||||||
|
{
|
||||||
|
uint8_t k;
|
||||||
|
pop_level_access_begin();
|
||||||
|
for (k = 0; k < trobs_count; k++)
|
||||||
|
trob_code[k] = pop_level_tile_raw(trobs[k].room, trobs[k].tilepos);
|
||||||
|
pop_level_access_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < trobs_count; i++) {
|
||||||
|
uint8_t room = trobs[i].room;
|
||||||
|
uint8_t tp = trobs[i].tilepos;
|
||||||
|
uint8_t code = trob_code[i];
|
||||||
|
uint8_t *mod = pop_trob_modif(room);
|
||||||
|
int8_t type = trobs[i].type;
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case TILE_SPIKE:
|
||||||
|
animate_spike(&mod[tp], &type);
|
||||||
|
break;
|
||||||
|
case TILE_OPENER:
|
||||||
|
case TILE_CLOSER:
|
||||||
|
animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */
|
||||||
|
break;
|
||||||
|
case TILE_GATE:
|
||||||
|
animate_door(&mod[tp], &type);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
trobs[i].type = type;
|
||||||
|
|
||||||
|
if (room == cur_room &&
|
||||||
|
(code == TILE_SPIKE || code == TILE_OPENER || code == TILE_CLOSER)) {
|
||||||
|
/* Редрой ТОЛЬКО при изменении видимого состояния (redraw_11h из
|
||||||
|
* SDLPoP). spike: draw-сигнатура = modifier; button: = «нажата»
|
||||||
|
* (таймер связи >1). Пока Кид держит кнопку — сигнатура стабильна
|
||||||
|
* → редроя нет. При изменении рисуем ТЕКУЩУЮ страницу сразу +
|
||||||
|
* <rest>=1 (вторая страница дабл-буфера следующим кадром через
|
||||||
|
* rest-цикл). tp/10,tp%10 считаем только в момент редроя (редко),
|
||||||
|
* а не каждый кадр. */
|
||||||
|
/* Сигнатура = ВИДИМЫЙ кадр, а не сырой modifier. Для пики кадр =
|
||||||
|
* (mod&0x80)?5:mod (как в draw_tile): весь ретракт-hold 0x80..0x8F
|
||||||
|
* (16 значений modifier) рисуется одним кадром 5 → сырой modifier
|
||||||
|
* дал бы 16 ложных редроёв «выдвинутой» неподвижной пики. */
|
||||||
|
uint8_t sig = (code == TILE_SPIKE)
|
||||||
|
? (uint8_t)((mod[tp] & 0x80) ? 5 : mod[tp])
|
||||||
|
: (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
||||||
|
if (sig != trob_drawn[tp]) {
|
||||||
|
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||||
|
if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; }
|
||||||
|
else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; }
|
||||||
|
trob_drawn[tp] = sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compact: удалить завершённые (type < 0). */
|
||||||
|
for (i = 0; i < trobs_count; i++)
|
||||||
|
if (trobs[i].type >= 0) trobs[w++] = trobs[i];
|
||||||
|
trobs_count = w;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* pop_trob.h — P0-каркас: персистентное per-room modifier-состояние +
|
||||||
|
* список анимируемых тайлов (trob). Порт SDLPoP seg007 (process_trobs/
|
||||||
|
* add_trob/animate_tile) + curr_room_modif.
|
||||||
|
*
|
||||||
|
* ЗАЧЕМ: тайлы-объекты (пики, ворота, кнопки) имеют состояние (modifier),
|
||||||
|
* которое живёт МЕЖДУ входами в комнату и может меняться КРОСС-КОМНАТНО
|
||||||
|
* (кнопка в одной комнате открывает ворота в другой). room_modif[room]
|
||||||
|
* — единственный источник модификаторов для отрисовки (pop_bg draw_tile
|
||||||
|
* читает его как bg), как curr_room_modif в оригинале.
|
||||||
|
*
|
||||||
|
* Список trob хранит (room, tilepos, type) активных анимаций; каждый кадр
|
||||||
|
* pop_process_trobs продвигает их (animate_spike/door/button по коду тайла),
|
||||||
|
* пишет результат в room_modif и перерисовывает изменившиеся тайлы ТЕКУЩЕЙ
|
||||||
|
* комнаты на back-странице (2-page, как loose). Невидимые комнаты
|
||||||
|
* анимируются в состоянии (без отрисовки) — при входе рисуются статически.
|
||||||
|
*/
|
||||||
|
#ifndef POP_TROB_H
|
||||||
|
#define POP_TROB_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define POP_NROOMS 24
|
||||||
|
#define POP_ROOMTILES 30
|
||||||
|
|
||||||
|
/* Сброс всего состояния (новый уровень / рестарт): забыть все комнаты и
|
||||||
|
* очистить список trob. */
|
||||||
|
void pop_trob_reset(void);
|
||||||
|
|
||||||
|
/* Указатель на persistent modifier-массив комнаты room (1..24), 30 байт.
|
||||||
|
* При ПЕРВОМ обращении лениво инициализируется из backtable уровня
|
||||||
|
* (pop_level_room_bg). Далее ЖИВЁТ (не перезагружается). room вне
|
||||||
|
* диапазона → 0. */
|
||||||
|
uint8_t *pop_trob_modif(uint8_t room);
|
||||||
|
|
||||||
|
/* Добавить/обновить trob (room, tilepos, type). Если уже есть — сменить
|
||||||
|
* type (как SDLPoP add_trob/find_trob). type<0 = будет удалён. */
|
||||||
|
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type);
|
||||||
|
|
||||||
|
/* Продвинуть все trob на кадр (animate_* по коду тайла из уровня), записать
|
||||||
|
* modif, перерисовать тайлы ТЕКУЩЕЙ (cur_room) комнаты на back-странице.
|
||||||
|
* Звать в основном цикле ПОСЛЕ set_draw_page(back), рядом с loose. */
|
||||||
|
void pop_process_trobs(uint8_t cur_room);
|
||||||
|
|
||||||
|
/* Кид встал на кнопку (opener/closer): взвести таймер связи + обойти цепочку
|
||||||
|
* LINKLOC, добавив trob целям (ворота). Порт seg007 trigger_button.
|
||||||
|
* room/tilepos — кнопка; button_type — её тайл-код; modifier — её modif
|
||||||
|
* (= индекс в LINKLOC). Звать из check_press (pop_map). */
|
||||||
|
void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||||||
|
uint8_t button_type, uint8_t modifier);
|
||||||
|
|
||||||
|
/* Есть ли активные trob в комнате room? Для перерисовки ворот соседа через
|
||||||
|
* шов (roomtest не дёргает шов, пока сосед не анимируется). */
|
||||||
|
uint8_t pop_trob_room_active(uint8_t room);
|
||||||
|
|
||||||
|
/* Смена комнаты: сбросить отложенные перерисовки «покоя» текущей комнаты
|
||||||
|
* (trob-состояние в room_modif персистентно и НЕ трогается). Звать в
|
||||||
|
* enter_room. */
|
||||||
|
void pop_trob_room_changed(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -14,18 +14,58 @@
|
|||||||
#include "pop_ctrl.h"
|
#include "pop_ctrl.h"
|
||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
#include "pop_level.h"
|
#include "pop_level.h"
|
||||||
|
#include "pop_trob.h"
|
||||||
|
|
||||||
|
__sfr __at (0xFE) io_border;
|
||||||
|
|
||||||
/* 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
|
||||||
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||||||
|
|
||||||
|
/* Тест-старт для отладки пик (Фаза S): комната 6 = тестовая (пики в col3/col4
|
||||||
|
* row2, шахта col3). Kid на полу row0 col1, идёт вправо → падает в шахту →
|
||||||
|
* пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */
|
||||||
|
#define START_ROOM 6
|
||||||
|
#define START_COL 1
|
||||||
|
#define START_ROW 0
|
||||||
|
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
||||||
|
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
||||||
|
|
||||||
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
||||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||||
static uint8_t room_fg[30], room_bg[30];
|
static uint8_t room_fg[30], room_bg[30];
|
||||||
static uint8_t lcol_fg[3], lcol_bg[3], rcol_fg[3], rcol_bg[3], below_fg[10];
|
static uint8_t lcol_fg[3], lcol_bg[3], rcol_fg[3], rcol_bg[3], below_fg[10];
|
||||||
static uint8_t cur_room; /* номер текущей комнаты (для переходов) */
|
static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
||||||
|
static uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
||||||
|
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
||||||
|
|
||||||
|
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
||||||
|
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
|
||||||
|
* же → 0. Звать каждый кадр перед kid_draw. */
|
||||||
|
static void update_kid_render_dx(void)
|
||||||
|
{
|
||||||
|
int dx = 0;
|
||||||
|
if (kid_room != cur_room) {
|
||||||
|
if (kid_room == pop_room_link(cur_room, 0)) dx = -140; /* left neighbour */
|
||||||
|
else if (kid_room == pop_room_link(cur_room, 1)) dx = 140; /* right neighbour */
|
||||||
|
}
|
||||||
|
pop_kid_set_render_dx(dx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change-driven редрой ЛЕВОГО шва (ворота соседа room8 видны в col0): тяжёлый
|
||||||
|
* draw_tile(0,0) в pop_room_redraw_seam_left зовём ТОЛЬКО когда openness гейта
|
||||||
|
* реально изменился. Пока Кид держит кнопку и гейт открыт — modifier стабилен
|
||||||
|
* → 0 редроёв (раньше шов рисовался безусловно КАЖДЫЙ кадр, пока сосед активен
|
||||||
|
* — тот же класс, что был у кнопки). seam_redraw=2 = обе страницы дабл-буфера. */
|
||||||
|
static uint8_t seam_sig; /* последняя нарисованная openness гейта в шве */
|
||||||
|
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||||
|
|
||||||
|
/* Отладочный лейбл номера комнаты — рисуем только при смене (outtextxy тяжёлая):
|
||||||
|
* sig = последняя показанная комната, redraw = остаток кадров (2 = обе страницы). */
|
||||||
|
static uint8_t room_label_sig = 0xFF;
|
||||||
|
static uint8_t room_label_redraw;
|
||||||
|
|
||||||
/* Персистентные debris-переопределения (упавший loose-кусок приземлился в
|
/* Персистентные debris-переопределения (упавший loose-кусок приземлился в
|
||||||
* комнате снизу → floor стал debris). Минимальный per-room override: живёт
|
* комнате снизу → floor стал debris). Минимальный per-room override: живёт
|
||||||
@@ -51,6 +91,7 @@ static void add_debris_override(uint8_t room, uint8_t pos)
|
|||||||
static void enter_room(uint8_t room)
|
static void enter_room(uint8_t room)
|
||||||
{
|
{
|
||||||
uint8_t pg, i;
|
uint8_t pg, i;
|
||||||
|
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);
|
||||||
for (i = 0; i < dbr_cnt; i++) /* применить debris-переопределения */
|
for (i = 0; i < dbr_cnt; i++) /* применить debris-переопределения */
|
||||||
if (dbr_room[i] == room) room_fg[dbr_pos[i]] = TILE_DEBRIS;
|
if (dbr_room[i] == room) room_fg[dbr_pos[i]] = TILE_DEBRIS;
|
||||||
@@ -59,14 +100,49 @@ static void enter_room(uint8_t room)
|
|||||||
pop_room_link(room, 2), pop_room_link(room, 3),
|
pop_room_link(room, 2), pop_room_link(room, 3),
|
||||||
lcol_fg, rcol_fg);
|
lcol_fg, rcol_fg);
|
||||||
pop_room_set_below(below_fg);
|
pop_room_set_below(below_fg);
|
||||||
|
/* Ряд 2 верхнего соседа → полоса кладки у потолка (row0). fg — статика
|
||||||
|
* уровня, mod — живой room_modif (ленивая инициализация). */
|
||||||
|
{
|
||||||
|
uint8_t up = pop_room_link(room, 2), c;
|
||||||
|
if (up) {
|
||||||
|
uint8_t *um = pop_trob_modif(up);
|
||||||
|
pop_level_access_begin();
|
||||||
|
for (c = 0; c < 10; c++) above_fg[c] = pop_level_tile_raw(up, (uint8_t)(20 + c));
|
||||||
|
pop_level_access_end();
|
||||||
|
for (c = 0; c < 10; c++) above_mod[c] = um[20 + c];
|
||||||
|
pop_room_set_above(above_fg, above_mod);
|
||||||
|
} else {
|
||||||
|
pop_room_set_above(0, 0); /* нет комнаты сверху → пол (кромка) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pop_map_set_room(room);
|
||||||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||||||
|
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||||||
|
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||||||
|
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
||||||
|
* а не по свежему backtable. Фаза P0. */
|
||||||
|
room_mod = pop_trob_modif(room);
|
||||||
|
/* Модификаторы швов — из ЖИВОГО room_modif соседей (openness ворот,
|
||||||
|
* видимых через шов, персистентен: ворота room8(0,9) видны в col0 room6).
|
||||||
|
* pop_room_load заполнил lcol_bg/rcol_bg статикой уровня — перекрываем. */
|
||||||
|
{
|
||||||
|
uint8_t lft = pop_room_link(room, 0), rgt = pop_room_link(room, 1);
|
||||||
|
if (lft) { uint8_t *m = pop_trob_modif(lft);
|
||||||
|
lcol_bg[0] = m[9]; lcol_bg[1] = m[19]; lcol_bg[2] = m[29];
|
||||||
|
seam_sig = m[9]; } /* шов рисуется в обе страницы ниже — синхр. сигнатуру */
|
||||||
|
if (rgt) { uint8_t *m = pop_trob_modif(rgt);
|
||||||
|
rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; }
|
||||||
|
}
|
||||||
|
seam_redraw = 0; /* шов свежий на обеих страницах — редрой не нужен */
|
||||||
for (pg = 0; pg < 2; pg++) {
|
for (pg = 0; pg < 2; pg++) {
|
||||||
gfx_set_draw_page(pg);
|
gfx_set_draw_page(pg);
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
bar(0, 0, 319, 255);
|
bar(0, 0, 319, 255);
|
||||||
pop_room_draw(room, room_fg, room_bg, lcol_fg, lcol_bg);
|
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
|
||||||
}
|
}
|
||||||
cur_room = room;
|
cur_room = room;
|
||||||
|
kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
|
||||||
|
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
@@ -75,6 +151,7 @@ int main(void)
|
|||||||
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
||||||
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
||||||
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
|
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
|
||||||
|
uint16_t dead_frames = 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");
|
||||||
@@ -100,14 +177,17 @@ int main(void)
|
|||||||
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
||||||
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
||||||
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
|
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
|
||||||
/* Извлечь и отрисовать стартовую комнату 1 (в обе страницы). */
|
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
||||||
enter_room(ROOM_1);
|
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
||||||
|
pop_trob_reset();
|
||||||
|
enter_room(START_ROOM);
|
||||||
gfx_set_draw_page(0);
|
gfx_set_draw_page(0);
|
||||||
|
|
||||||
/* K2b: Kid стоит на уступе (row1), управляется с клавиатуры.
|
/* K2b: Kid стоит на уступе (row1), управляется с клавиатуры.
|
||||||
* ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход.
|
* ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход.
|
||||||
* Карта коллизии (pop_map_set) уже задана в enter_room. */
|
* Карта коллизии (pop_map_set) уже задана в enter_room. */
|
||||||
kid_init(SEQ_STAND, 1, 1, 0);
|
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
|
||||||
|
pop_kid_hp_reset();
|
||||||
kid_draw();
|
kid_draw();
|
||||||
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(),
|
||||||
@@ -122,6 +202,9 @@ int main(void)
|
|||||||
while (!kbd_raw_down(KBD_ESC)) {
|
while (!kbd_raw_down(KBD_ESC)) {
|
||||||
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
||||||
* переключ). Выключение возвращает показ на страницу 0. */
|
* переключ). Выключение возвращает показ на страницу 0. */
|
||||||
|
|
||||||
|
io_border = 2; /* синий - начало */
|
||||||
|
|
||||||
uint8_t sp = kbd_raw_down(KBD_SPACE);
|
uint8_t sp = kbd_raw_down(KBD_SPACE);
|
||||||
if (sp && !sp_prev) {
|
if (sp && !sp_prev) {
|
||||||
dbuf ^= 1;
|
dbuf ^= 1;
|
||||||
@@ -138,6 +221,7 @@ int main(void)
|
|||||||
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;
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
|
io_border = 0; /* черный: ждём vsync */
|
||||||
gfx_wait_vsync();
|
gfx_wait_vsync();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -157,9 +241,11 @@ int main(void)
|
|||||||
enter_room(down); /* координаты Kid уже репроецированы pop_map */
|
enter_room(down); /* координаты Kid уже репроецированы pop_map */
|
||||||
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
|
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
|
||||||
} else { /* выпал из уровня (нет комнаты снизу) — респавн */
|
} else { /* выпал из уровня (нет комнаты снизу) — респавн */
|
||||||
enter_room(ROOM_1);
|
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
|
||||||
|
enter_room(START_ROOM);
|
||||||
gfx_set_draw_page(back);
|
gfx_set_draw_page(back);
|
||||||
kid_init(SEQ_STAND, 1, 1, 0);
|
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
|
||||||
|
pop_kid_hp_reset();
|
||||||
}
|
}
|
||||||
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
|
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
|
||||||
uint8_t nb = pop_room_link(cur_room, (uint8_t)(pop_leave_dir - 1));
|
uint8_t nb = pop_room_link(cur_room, (uint8_t)(pop_leave_dir - 1));
|
||||||
@@ -169,18 +255,59 @@ int main(void)
|
|||||||
gfx_set_draw_page(back);
|
gfx_set_draw_page(back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io_border = 4; /* зеленый */
|
||||||
|
|
||||||
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
||||||
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
||||||
|
io_border = 6; /* голубой */
|
||||||
|
pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */
|
||||||
|
/* слой фона — ДО Kid) */
|
||||||
|
io_border = 2; /* синий */
|
||||||
|
{ /* ворота соседа слева (room8) видны в col0 — */
|
||||||
|
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
||||||
|
if (lft) {
|
||||||
|
uint8_t *m = pop_trob_modif(lft);
|
||||||
|
uint8_t g = m[9], prev = seam_sig;
|
||||||
|
seam_sig = g; /* СНАЧАЛА сохранить (обход SDCC z80 sub-store */
|
||||||
|
/* бага: `if(g!=s)s=g` эмитит store РАЗНОСТИ g-s, */
|
||||||
|
/* не g — sdcc_z80_cmp_store_a_bug) */
|
||||||
|
if (g != prev) seam_redraw = 2; /* openness изменился → обе страницы дабл-буфера */
|
||||||
|
if (seam_redraw) {
|
||||||
|
lcol_bg[0] = m[9]; lcol_bg[1] = m[19]; lcol_bg[2] = m[29];
|
||||||
|
pop_room_redraw_seam_left();
|
||||||
|
seam_redraw--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io_border = 4; /* зеленый */
|
||||||
|
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
||||||
|
dead_frames++;
|
||||||
|
if (dead_frames > RESPAWN_SETTLE &&
|
||||||
|
(kbd_raw_down(KBD_UP) || dead_frames > RESPAWN_DELAY)) {
|
||||||
|
dead_frames = 0;
|
||||||
|
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
|
||||||
|
enter_room(START_ROOM);
|
||||||
|
gfx_set_draw_page(back);
|
||||||
|
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
|
||||||
|
pop_kid_hp_reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io_border = 6; /* голубой */
|
||||||
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
|
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
|
||||||
uint8_t fcol = (uint8_t)((pop_loose_fell - 1) % 10);
|
uint8_t fcol = (uint8_t)((pop_loose_fell - 1) % 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; /* синий */
|
||||||
if (down) {
|
if (down) {
|
||||||
int8_t lrow = pop_room_col_landing(down, fcol);
|
int8_t lrow = pop_room_col_landing(down, fcol);
|
||||||
if (lrow >= 0) add_debris_override(down, (uint8_t)(lrow * 10 + fcol));
|
if (lrow >= 0) add_debris_override(down, (uint8_t)(lrow * 10 + fcol));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
io_border = 4; /* зеленый */
|
||||||
|
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
||||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||||
|
io_border = 0; /* черный */
|
||||||
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);
|
||||||
@@ -189,6 +316,25 @@ int main(void)
|
|||||||
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
||||||
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
||||||
* set_visible_page переключает во время бланка (gfx.h паттерн). */
|
* set_visible_page переключает во время бланка (gfx.h паттерн). */
|
||||||
|
|
||||||
|
io_border = 0; /* черный: ждём vsync */
|
||||||
|
|
||||||
|
/* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО
|
||||||
|
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
||||||
|
* а не каждый кадр (обход sdcc_z80_cmp_store_a_bug: сначала сохранить). */
|
||||||
|
{
|
||||||
|
uint8_t r = cur_room;
|
||||||
|
if (r != room_label_sig) { room_label_sig = r; room_label_redraw = 2; }
|
||||||
|
if (room_label_redraw) {
|
||||||
|
char roomLabel[] = "Room ##";
|
||||||
|
roomLabel[5] = (char)(r / 10 + '0');
|
||||||
|
roomLabel[6] = (char)(r % 10 + '0');
|
||||||
|
setcolor(WHITE);
|
||||||
|
outtextxy(100, 0, roomLabel);
|
||||||
|
room_label_redraw--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gfx_wait_vsync();
|
gfx_wait_vsync();
|
||||||
gfx_wait_vsync();
|
gfx_wait_vsync();
|
||||||
gfx_wait_vsync();
|
gfx_wait_vsync();
|
||||||
|
|||||||
@@ -56,6 +56,21 @@ CLIMB_OVERLAY_ENV_IDS = {32, 150, 151}
|
|||||||
# попадают, поэтому добавляем явно. Без них дрожащая плита невидима.
|
# попадают, поэтому добавляем явно. Без них дрожащая плита невидима.
|
||||||
LOOSE_ANIM_ENV_IDS = {69, 70, 71, 72, 73, 74}
|
LOOSE_ANIM_ENV_IDS = {69, 70, 71, 72, 73, 74}
|
||||||
|
|
||||||
|
# Кадры ВЫДВИЖЕНИЯ пик (seg008): три части — left (128-132, своя ячейка,
|
||||||
|
# draw_tile_anim), right (134-138, правый сосед, draw_tile_anim_right), fore
|
||||||
|
# (139-143, поверх Kid, draw_tile_fore). Рисуются ТОЛЬКО динамикой
|
||||||
|
# (animate_spike); в уровнях пики статически убраны (modif=0 → кадр 0),
|
||||||
|
# поэтому в used render_room НЕ попадают. Без них выдвинутые пики невидимы.
|
||||||
|
SPIKE_ANIM_ENV_IDS = {128, 129, 130, 131, 132, # left
|
||||||
|
134, 135, 136, 137, 138, # right
|
||||||
|
139, 140, 141, 142, 143} # fore
|
||||||
|
|
||||||
|
# Кадры ОТКРЫТИЯ ворот (portcullis, DOOR_FRAM_SLICE + gate pieces 50/51,
|
||||||
|
# seg008 draw_gate): 50-59 + 67. В уровнях ворота статически ЗАКРЫТЫ
|
||||||
|
# (низкий modif → используются только нижние срезы 50-53); срезы более
|
||||||
|
# открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно.
|
||||||
|
GATE_ANIM_ENV_IDS = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 67}
|
||||||
|
|
||||||
ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31
|
ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31
|
||||||
ENV_MASK = (1 << ENV_SHIFT) - 1
|
ENV_MASK = (1 << ENV_SHIFT) - 1
|
||||||
|
|
||||||
@@ -248,7 +263,9 @@ def main():
|
|||||||
used = collect_used()
|
used = collect_used()
|
||||||
env_ids = sorted(set(id for ch, id in used if ch != R.CHTAB_WALL)
|
env_ids = sorted(set(id for ch, id in used if ch != R.CHTAB_WALL)
|
||||||
| CLIMB_OVERLAY_ENV_IDS # + пол-оверлей подъёма
|
| CLIMB_OVERLAY_ENV_IDS # + пол-оверлей подъёма
|
||||||
| LOOSE_ANIM_ENV_IDS) # + дрожащие кадры loose
|
| LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose
|
||||||
|
| SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик
|
||||||
|
| GATE_ANIM_ENV_IDS) # + кадры открытия ворот
|
||||||
wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL)
|
wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL)
|
||||||
|
|
||||||
env_bg = [i for i in env_ids if i not in FORE_ENV_IDS]
|
env_bg = [i for i in env_ids if i not in FORE_ENV_IDS]
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
Attribution-ShareAlike 3.0 Unported
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||||
|
DAMAGES RESULTING FROM ITS USE.
|
||||||
|
|
||||||
|
License
|
||||||
|
|
||||||
|
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||||
|
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||||
|
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||||
|
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||||
|
|
||||||
|
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||||
|
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||||
|
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||||
|
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||||
|
CONDITIONS.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||||
|
other pre-existing works, such as a translation, adaptation,
|
||||||
|
derivative work, arrangement of music or other alterations of a
|
||||||
|
literary or artistic work, or phonogram or performance and includes
|
||||||
|
cinematographic adaptations or any other form in which the Work may be
|
||||||
|
recast, transformed, or adapted including in any form recognizably
|
||||||
|
derived from the original, except that a work that constitutes a
|
||||||
|
Collection will not be considered an Adaptation for the purpose of
|
||||||
|
this License. For the avoidance of doubt, where the Work is a musical
|
||||||
|
work, performance or phonogram, the synchronization of the Work in
|
||||||
|
timed-relation with a moving image ("synching") will be considered an
|
||||||
|
Adaptation for the purpose of this License.
|
||||||
|
b. "Collection" means a collection of literary or artistic works, such as
|
||||||
|
encyclopedias and anthologies, or performances, phonograms or
|
||||||
|
broadcasts, or other works or subject matter other than works listed
|
||||||
|
in Section 1(f) below, which, by reason of the selection and
|
||||||
|
arrangement of their contents, constitute intellectual creations, in
|
||||||
|
which the Work is included in its entirety in unmodified form along
|
||||||
|
with one or more other contributions, each constituting separate and
|
||||||
|
independent works in themselves, which together are assembled into a
|
||||||
|
collective whole. A work that constitutes a Collection will not be
|
||||||
|
considered an Adaptation (as defined below) for the purposes of this
|
||||||
|
License.
|
||||||
|
c. "Creative Commons Compatible License" means a license that is listed
|
||||||
|
at https://creativecommons.org/compatiblelicenses that has been
|
||||||
|
approved by Creative Commons as being essentially equivalent to this
|
||||||
|
License, including, at a minimum, because that license: (i) contains
|
||||||
|
terms that have the same purpose, meaning and effect as the License
|
||||||
|
Elements of this License; and, (ii) explicitly permits the relicensing
|
||||||
|
of adaptations of works made available under that license under this
|
||||||
|
License or a Creative Commons jurisdiction license with the same
|
||||||
|
License Elements as this License.
|
||||||
|
d. "Distribute" means to make available to the public the original and
|
||||||
|
copies of the Work or Adaptation, as appropriate, through sale or
|
||||||
|
other transfer of ownership.
|
||||||
|
e. "License Elements" means the following high-level license attributes
|
||||||
|
as selected by Licensor and indicated in the title of this License:
|
||||||
|
Attribution, ShareAlike.
|
||||||
|
f. "Licensor" means the individual, individuals, entity or entities that
|
||||||
|
offer(s) the Work under the terms of this License.
|
||||||
|
g. "Original Author" means, in the case of a literary or artistic work,
|
||||||
|
the individual, individuals, entity or entities who created the Work
|
||||||
|
or if no individual or entity can be identified, the publisher; and in
|
||||||
|
addition (i) in the case of a performance the actors, singers,
|
||||||
|
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||||
|
play in, interpret or otherwise perform literary or artistic works or
|
||||||
|
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||||
|
being the person or legal entity who first fixes the sounds of a
|
||||||
|
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||||
|
organization that transmits the broadcast.
|
||||||
|
h. "Work" means the literary and/or artistic work offered under the terms
|
||||||
|
of this License including without limitation any production in the
|
||||||
|
literary, scientific and artistic domain, whatever may be the mode or
|
||||||
|
form of its expression including digital form, such as a book,
|
||||||
|
pamphlet and other writing; a lecture, address, sermon or other work
|
||||||
|
of the same nature; a dramatic or dramatico-musical work; a
|
||||||
|
choreographic work or entertainment in dumb show; a musical
|
||||||
|
composition with or without words; a cinematographic work to which are
|
||||||
|
assimilated works expressed by a process analogous to cinematography;
|
||||||
|
a work of drawing, painting, architecture, sculpture, engraving or
|
||||||
|
lithography; a photographic work to which are assimilated works
|
||||||
|
expressed by a process analogous to photography; a work of applied
|
||||||
|
art; an illustration, map, plan, sketch or three-dimensional work
|
||||||
|
relative to geography, topography, architecture or science; a
|
||||||
|
performance; a broadcast; a phonogram; a compilation of data to the
|
||||||
|
extent it is protected as a copyrightable work; or a work performed by
|
||||||
|
a variety or circus performer to the extent it is not otherwise
|
||||||
|
considered a literary or artistic work.
|
||||||
|
i. "You" means an individual or entity exercising rights under this
|
||||||
|
License who has not previously violated the terms of this License with
|
||||||
|
respect to the Work, or who has received express permission from the
|
||||||
|
Licensor to exercise rights under this License despite a previous
|
||||||
|
violation.
|
||||||
|
j. "Publicly Perform" means to perform public recitations of the Work and
|
||||||
|
to communicate to the public those public recitations, by any means or
|
||||||
|
process, including by wire or wireless means or public digital
|
||||||
|
performances; to make available to the public Works in such a way that
|
||||||
|
members of the public may access these Works from a place and at a
|
||||||
|
place individually chosen by them; to perform the Work to the public
|
||||||
|
by any means or process and the communication to the public of the
|
||||||
|
performances of the Work, including by public digital performance; to
|
||||||
|
broadcast and rebroadcast the Work by any means including signs,
|
||||||
|
sounds or images.
|
||||||
|
k. "Reproduce" means to make copies of the Work by any means including
|
||||||
|
without limitation by sound or visual recordings and the right of
|
||||||
|
fixation and reproducing fixations of the Work, including storage of a
|
||||||
|
protected performance or phonogram in digital form or other electronic
|
||||||
|
medium.
|
||||||
|
|
||||||
|
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||||
|
limit, or restrict any uses free from copyright or rights arising from
|
||||||
|
limitations or exceptions that are provided for in connection with the
|
||||||
|
copyright protection under copyright law or other applicable laws.
|
||||||
|
|
||||||
|
3. License Grant. Subject to the terms and conditions of this License,
|
||||||
|
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||||
|
perpetual (for the duration of the applicable copyright) license to
|
||||||
|
exercise the rights in the Work as stated below:
|
||||||
|
|
||||||
|
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||||
|
Collections, and to Reproduce the Work as incorporated in the
|
||||||
|
Collections;
|
||||||
|
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||||
|
including any translation in any medium, takes reasonable steps to
|
||||||
|
clearly label, demarcate or otherwise identify that changes were made
|
||||||
|
to the original Work. For example, a translation could be marked "The
|
||||||
|
original work was translated from English to Spanish," or a
|
||||||
|
modification could indicate "The original work has been modified.";
|
||||||
|
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||||
|
in Collections; and,
|
||||||
|
d. to Distribute and Publicly Perform Adaptations.
|
||||||
|
e. For the avoidance of doubt:
|
||||||
|
|
||||||
|
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||||
|
which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme cannot be waived, the Licensor
|
||||||
|
reserves the exclusive right to collect such royalties for any
|
||||||
|
exercise by You of the rights granted under this License;
|
||||||
|
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||||
|
which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme can be waived, the Licensor waives the
|
||||||
|
exclusive right to collect such royalties for any exercise by You
|
||||||
|
of the rights granted under this License; and,
|
||||||
|
iii. Voluntary License Schemes. The Licensor waives the right to
|
||||||
|
collect royalties, whether individually or, in the event that the
|
||||||
|
Licensor is a member of a collecting society that administers
|
||||||
|
voluntary licensing schemes, via that society, from any exercise
|
||||||
|
by You of the rights granted under this License.
|
||||||
|
|
||||||
|
The above rights may be exercised in all media and formats whether now
|
||||||
|
known or hereafter devised. The above rights include the right to make
|
||||||
|
such modifications as are technically necessary to exercise the rights in
|
||||||
|
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||||
|
granted by Licensor are hereby reserved.
|
||||||
|
|
||||||
|
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||||
|
subject to and limited by the following restrictions:
|
||||||
|
|
||||||
|
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||||
|
of this License. You must include a copy of, or the Uniform Resource
|
||||||
|
Identifier (URI) for, this License with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||||
|
on the Work that restrict the terms of this License or the ability of
|
||||||
|
the recipient of the Work to exercise the rights granted to that
|
||||||
|
recipient under the terms of the License. You may not sublicense the
|
||||||
|
Work. You must keep intact all notices that refer to this License and
|
||||||
|
to the disclaimer of warranties with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||||
|
Perform the Work, You may not impose any effective technological
|
||||||
|
measures on the Work that restrict the ability of a recipient of the
|
||||||
|
Work from You to exercise the rights granted to that recipient under
|
||||||
|
the terms of the License. This Section 4(a) applies to the Work as
|
||||||
|
incorporated in a Collection, but this does not require the Collection
|
||||||
|
apart from the Work itself to be made subject to the terms of this
|
||||||
|
License. If You create a Collection, upon notice from any Licensor You
|
||||||
|
must, to the extent practicable, remove from the Collection any credit
|
||||||
|
as required by Section 4(c), as requested. If You create an
|
||||||
|
Adaptation, upon notice from any Licensor You must, to the extent
|
||||||
|
practicable, remove from the Adaptation any credit as required by
|
||||||
|
Section 4(c), as requested.
|
||||||
|
b. You may Distribute or Publicly Perform an Adaptation only under the
|
||||||
|
terms of: (i) this License; (ii) a later version of this License with
|
||||||
|
the same License Elements as this License; (iii) a Creative Commons
|
||||||
|
jurisdiction license (either this or a later license version) that
|
||||||
|
contains the same License Elements as this License (e.g.,
|
||||||
|
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
|
||||||
|
License. If you license the Adaptation under one of the licenses
|
||||||
|
mentioned in (iv), you must comply with the terms of that license. If
|
||||||
|
you license the Adaptation under the terms of any of the licenses
|
||||||
|
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
|
||||||
|
comply with the terms of the Applicable License generally and the
|
||||||
|
following provisions: (I) You must include a copy of, or the URI for,
|
||||||
|
the Applicable License with every copy of each Adaptation You
|
||||||
|
Distribute or Publicly Perform; (II) You may not offer or impose any
|
||||||
|
terms on the Adaptation that restrict the terms of the Applicable
|
||||||
|
License or the ability of the recipient of the Adaptation to exercise
|
||||||
|
the rights granted to that recipient under the terms of the Applicable
|
||||||
|
License; (III) You must keep intact all notices that refer to the
|
||||||
|
Applicable License and to the disclaimer of warranties with every copy
|
||||||
|
of the Work as included in the Adaptation You Distribute or Publicly
|
||||||
|
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
|
||||||
|
You may not impose any effective technological measures on the
|
||||||
|
Adaptation that restrict the ability of a recipient of the Adaptation
|
||||||
|
from You to exercise the rights granted to that recipient under the
|
||||||
|
terms of the Applicable License. This Section 4(b) applies to the
|
||||||
|
Adaptation as incorporated in a Collection, but this does not require
|
||||||
|
the Collection apart from the Adaptation itself to be made subject to
|
||||||
|
the terms of the Applicable License.
|
||||||
|
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||||
|
Collections, You must, unless a request has been made pursuant to
|
||||||
|
Section 4(a), keep intact all copyright notices for the Work and
|
||||||
|
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||||
|
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||||
|
and/or if the Original Author and/or Licensor designate another party
|
||||||
|
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||||
|
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||||
|
terms of service or by other reasonable means, the name of such party
|
||||||
|
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||||
|
extent reasonably practicable, the URI, if any, that Licensor
|
||||||
|
specifies to be associated with the Work, unless such URI does not
|
||||||
|
refer to the copyright notice or licensing information for the Work;
|
||||||
|
and (iv) , consistent with Ssection 3(b), in the case of an
|
||||||
|
Adaptation, a credit identifying the use of the Work in the Adaptation
|
||||||
|
(e.g., "French translation of the Work by Original Author," or
|
||||||
|
"Screenplay based on original Work by Original Author"). The credit
|
||||||
|
required by this Section 4(c) may be implemented in any reasonable
|
||||||
|
manner; provided, however, that in the case of a Adaptation or
|
||||||
|
Collection, at a minimum such credit will appear, if a credit for all
|
||||||
|
contributing authors of the Adaptation or Collection appears, then as
|
||||||
|
part of these credits and in a manner at least as prominent as the
|
||||||
|
credits for the other contributing authors. For the avoidance of
|
||||||
|
doubt, You may only use the credit required by this Section for the
|
||||||
|
purpose of attribution in the manner set out above and, by exercising
|
||||||
|
Your rights under this License, You may not implicitly or explicitly
|
||||||
|
assert or imply any connection with, sponsorship or endorsement by the
|
||||||
|
Original Author, Licensor and/or Attribution Parties, as appropriate,
|
||||||
|
of You or Your use of the Work, without the separate, express prior
|
||||||
|
written permission of the Original Author, Licensor and/or Attribution
|
||||||
|
Parties.
|
||||||
|
d. Except as otherwise agreed in writing by the Licensor or as may be
|
||||||
|
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||||
|
Publicly Perform the Work either by itself or as part of any
|
||||||
|
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||||
|
take other derogatory action in relation to the Work which would be
|
||||||
|
prejudicial to the Original Author's honor or reputation. Licensor
|
||||||
|
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||||
|
of the right granted in Section 3(b) of this License (the right to
|
||||||
|
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||||
|
modification or other derogatory action prejudicial to the Original
|
||||||
|
Author's honor and reputation, the Licensor will waive or not assert,
|
||||||
|
as appropriate, this Section, to the fullest extent permitted by the
|
||||||
|
applicable national law, to enable You to reasonably exercise Your
|
||||||
|
right under Section 3(b) of this License (right to make Adaptations)
|
||||||
|
but not otherwise.
|
||||||
|
|
||||||
|
5. Representations, Warranties and Disclaimer
|
||||||
|
|
||||||
|
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||||
|
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||||
|
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||||
|
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||||
|
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||||
|
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||||
|
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||||
|
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||||
|
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||||
|
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||||
|
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
7. Termination
|
||||||
|
|
||||||
|
a. This License and the rights granted hereunder will terminate
|
||||||
|
automatically upon any breach by You of the terms of this License.
|
||||||
|
Individuals or entities who have received Adaptations or Collections
|
||||||
|
from You under this License, however, will not have their licenses
|
||||||
|
terminated provided such individuals or entities remain in full
|
||||||
|
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||||
|
survive any termination of this License.
|
||||||
|
b. Subject to the above terms and conditions, the license granted here is
|
||||||
|
perpetual (for the duration of the applicable copyright in the Work).
|
||||||
|
Notwithstanding the above, Licensor reserves the right to release the
|
||||||
|
Work under different license terms or to stop distributing the Work at
|
||||||
|
any time; provided, however that any such election will not serve to
|
||||||
|
withdraw this License (or any other license that has been, or is
|
||||||
|
required to be, granted under the terms of this License), and this
|
||||||
|
License will continue in full force and effect unless terminated as
|
||||||
|
stated above.
|
||||||
|
|
||||||
|
8. Miscellaneous
|
||||||
|
|
||||||
|
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||||
|
the Licensor offers to the recipient a license to the Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||||
|
offers to the recipient a license to the original Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
c. If any provision of this License is invalid or unenforceable under
|
||||||
|
applicable law, it shall not affect the validity or enforceability of
|
||||||
|
the remainder of the terms of this License, and without further action
|
||||||
|
by the parties to this agreement, such provision shall be reformed to
|
||||||
|
the minimum extent necessary to make such provision valid and
|
||||||
|
enforceable.
|
||||||
|
d. No term or provision of this License shall be deemed waived and no
|
||||||
|
breach consented to unless such waiver or consent shall be in writing
|
||||||
|
and signed by the party to be charged with such waiver or consent.
|
||||||
|
e. This License constitutes the entire agreement between the parties with
|
||||||
|
respect to the Work licensed here. There are no understandings,
|
||||||
|
agreements or representations with respect to the Work not specified
|
||||||
|
here. Licensor shall not be bound by any additional provisions that
|
||||||
|
may appear in any communication from You. This License may not be
|
||||||
|
modified without the mutual written agreement of the Licensor and You.
|
||||||
|
f. The rights granted under, and the subject matter referenced, in this
|
||||||
|
License were drafted utilizing the terminology of the Berne Convention
|
||||||
|
for the Protection of Literary and Artistic Works (as amended on
|
||||||
|
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||||
|
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||||
|
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||||
|
These rights and subject matter take effect in the relevant
|
||||||
|
jurisdiction in which the License terms are sought to be enforced
|
||||||
|
according to the corresponding provisions of the implementation of
|
||||||
|
those treaty provisions in the applicable national law. If the
|
||||||
|
standard suite of rights granted under applicable copyright law
|
||||||
|
includes additional rights not granted under this License, such
|
||||||
|
additional rights are deemed to be included in the License; this
|
||||||
|
License is not intended to restrict the license of any rights under
|
||||||
|
applicable law.
|
||||||
|
|
||||||
|
|
||||||
|
Creative Commons Notice
|
||||||
|
|
||||||
|
Creative Commons is not a party to this License, and makes no warranty
|
||||||
|
whatsoever in connection with the Work. Creative Commons will not be
|
||||||
|
liable to You or any party on any legal theory for any damages
|
||||||
|
whatsoever, including without limitation any general, special,
|
||||||
|
incidental or consequential damages arising in connection to this
|
||||||
|
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||||
|
Commons has expressly identified itself as the Licensor hereunder, it
|
||||||
|
shall have all rights and obligations of Licensor.
|
||||||
|
|
||||||
|
Except for the limited purpose of indicating to the public that the
|
||||||
|
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||||
|
the use by either party of the trademark "Creative Commons" or any
|
||||||
|
related trademark or logo of Creative Commons without the prior
|
||||||
|
written consent of Creative Commons. Any permitted use will be in
|
||||||
|
compliance with Creative Commons' then-current trademark usage
|
||||||
|
guidelines, as may be published on its website or otherwise made
|
||||||
|
available upon request from time to time. For the avoidance of doubt,
|
||||||
|
this trademark restriction does not form part of the License.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at https://creativecommons.org/.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 16x16 RPG characters `v3.0`
|
||||||
|
|
||||||
|
16x16px RPG character sprite sheet for up to down games.
|
||||||
|
|
||||||
|
This asset pack has been downloaded from https://route1rodent.itch.io/16x16-rpg-character-sprite-sheet
|
||||||
|
|
||||||
|
Adapted from opengameart.org 's "[NES-Style RPG Characters](https://opengameart.org/content/nes-style-rpg-characters)" and "[More NES-style RPG Characters](https://opengameart.org/content/more-nes-style-rpg-characters)", with additional content made by [@route1rodent](https://route1rodent.itch.io).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
16x16 RPG character sprite sheet (c) by @route1rodent
|
||||||
|
|
||||||
|
"16x16 RPG character sprite sheet" is licensed under a
|
||||||
|
Creative Commons Attribution-ShareAlike 3.0 Unported License (CC BY-SA 3.0).
|
||||||
|
|
||||||
|
You should have received a copy of the license along with this
|
||||||
|
work. If not, see http://creativecommons.org/licenses/by-sa/3.0/.
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Maintained by @route1rodent:
|
||||||
|
|
||||||
|
- itch.io: https://route1rodent.itch.io
|
||||||
|
- Twitter: https://twitter.com/route1rodent
|
||||||
|
- Github: https://github.com/itsjavi
|
||||||
|
- Blog: https://blog.itsjavi.com
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 8.4 KiB |