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>
This commit is contained in:
@@ -6,8 +6,8 @@ EXAMPLE := roomtest
|
||||
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
|
||||
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
|
||||
MEMORY ?= small
|
||||
EXTRA_FLAGS ?= --gfx 256
|
||||
EXTRA_SRCS := pop_bg.c pop_kid.c pop_ctrl.c pop_map.c pop_level.c
|
||||
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c --w3 pop_map.c --w3 pop_bg.c
|
||||
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c
|
||||
|
||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||
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
|
||||
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 <unistd.h>
|
||||
#include "pop_bg.h"
|
||||
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
||||
|
||||
/* ---- Атласы (прямая адресация) ------------------------------------- */
|
||||
static atlas_t env[5];
|
||||
@@ -44,20 +45,36 @@ void pop_bg_free(void)
|
||||
atlas_free(&fore_at);
|
||||
}
|
||||
|
||||
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра с screen_y <
|
||||
* g_clip_top не рисуются (частичным блитом со сдвигом источника). Нужно
|
||||
* полосе кладки у потолка (row<0): высокие кадры не должны лезть в верхний
|
||||
* борт (за пределы игрового поля). 0 — без клипа. */
|
||||
static int g_clip_top;
|
||||
|
||||
/* Блит ленты 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)
|
||||
{
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
int top;
|
||||
if (idx >= a->count)
|
||||
return;
|
||||
img = (const uint8_t *)atlas_image(a, idx);
|
||||
gfx_w0_map(a->page);
|
||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
if (w && h)
|
||||
gfx_blit(x, ybottom - (int)h + 1 + POP_YOFF, img); /* +YOFF: центрирование */
|
||||
top = ybottom - (int)h + 1 + POP_YOFF; /* +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();
|
||||
}
|
||||
|
||||
@@ -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 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_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 int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
|
||||
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) ------------------- */
|
||||
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_above, *g_abovemod; /* row2 fg/mod комнаты СВЕРХУ (полоса кладки у потолка) */
|
||||
static uint8_t g_room;
|
||||
|
||||
/* Задать ряд 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; }
|
||||
|
||||
/* Задать ряд 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)
|
||||
{
|
||||
if (col < 0) {
|
||||
if (g_lfg && row >= 0 && row < 3) return g_lfg[row];
|
||||
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;
|
||||
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];
|
||||
return 0;
|
||||
}
|
||||
if (row == -1 && g_abovemod && col < 10) return g_abovemod[col];
|
||||
if (row < 0 || row > 2 || col > 9) return 0;
|
||||
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;
|
||||
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); /* сброс */
|
||||
mid_div = prandom(1);
|
||||
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 lcode = tile_code(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 x = xh * 8;
|
||||
int dby = 63 * row + 65;
|
||||
@@ -367,15 +407,19 @@ static void draw_tile(int row, int col)
|
||||
}
|
||||
}
|
||||
|
||||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части --- */
|
||||
if (lcode == 4) { /* gate: решётка */
|
||||
draw_gate_back(lmod, xh, dby, dmy);
|
||||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||
env_b(LOOSE_FRAM_RIGHT[get_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1);
|
||||
} else if (lcode == 2) { /* spike: статич. кадр */
|
||||
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
|
||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
||||
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||||
* отрицательного индекса в pop_loose_modif. --- */
|
||||
if (row >= 0) {
|
||||
if (lcode == 4) { /* gate: решётка */
|
||||
draw_gate_back(lmod, xh, dby, dmy);
|
||||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||
env_b(LOOSE_FRAM_RIGHT[get_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1);
|
||||
} else if (lcode == 2) { /* spike: статич. кадр */
|
||||
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
|
||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||||
@@ -387,12 +431,16 @@ static void draw_tile(int row, int col)
|
||||
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)
|
||||
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;
|
||||
if (code == 11) /* loose: левая грань (анимир.) */
|
||||
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);
|
||||
}
|
||||
|
||||
/* --- 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) --- */
|
||||
if (code == 20) {
|
||||
wmod = wall_modifier(row, col);
|
||||
@@ -431,6 +490,75 @@ void pop_loose_shake_draw(int row, int col)
|
||||
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 (текущая страница). Порт
|
||||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||||
@@ -571,6 +699,14 @@ void pop_room_draw(uint8_t room_num,
|
||||
for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */
|
||||
for (col = 0; col < 10; 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);
|
||||
}
|
||||
|
||||
@@ -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) /* передний слой одного тайла */
|
||||
{
|
||||
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 dmy = 63 * row + 62;
|
||||
int dby = dmy + 3; /* = 63*row+65 (draw_tile_bottom) */
|
||||
@@ -611,6 +753,12 @@ static void fore_tile(int row, int col) /* передний слой одно
|
||||
} else {
|
||||
if (t->bottom_id) /* передняя кромка пола (draw_tile_bottom) */
|
||||
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) /* передние объекты: колонны/ворота/щебень */
|
||||
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 xl = obj_x / 2 + 58; /* char_x_left (58-база) */
|
||||
int xr;
|
||||
int8_t cL, cR, rT, rB, r, c;
|
||||
int8_t cL, cR, cLraw, rT, rB, r, c;
|
||||
if (dir >= 0) 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;
|
||||
/* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(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 (c = cL; c <= cR; 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 */
|
||||
uint8_t fidx = (uint8_t)(frame - 137);
|
||||
for (r = rT; r <= rB; r++)
|
||||
|
||||
@@ -46,6 +46,11 @@ void pop_room_draw(uint8_t room_num,
|
||||
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
|
||||
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 ПОВЕРХ него —
|
||||
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
||||
* спрайта, 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_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): спавн при падении тайла
|
||||
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
|
||||
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
#include "pop_kid.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) --------------- */
|
||||
#define CONTROL_RELEASED 0
|
||||
#define CONTROL_IGNORE 1
|
||||
@@ -314,9 +320,14 @@ static void read_input(void)
|
||||
uint8_t r = kbd_raw_down(KBD_RIGHT);
|
||||
uint8_t u = kbd_raw_down(KBD_UP);
|
||||
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_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;
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,10 @@ static uint8_t kid_lvalid[2];
|
||||
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
|
||||
static int kid_fp_x, kid_fp_y;
|
||||
static uint16_t kid_fp_w, kid_fp_h;
|
||||
/* Рендер-смещение по ЛОГИЧЕСКОЙ X (straddle у шва, порт xpos_in_drawn_room):
|
||||
* ∓140 когда комната Kid ≠ отрисованной; 0 обычно. */
|
||||
static int kid_render_dx;
|
||||
void pop_kid_set_render_dx(int dx) { kid_render_dx = dx; }
|
||||
int kid_fp_obj_x(void) { return kid_fp_x; }
|
||||
int kid_fp_obj_y(void) { return kid_fp_y; }
|
||||
uint16_t kid_fp_width(void) { return kid_fp_w; }
|
||||
@@ -209,7 +213,10 @@ void kid_draw(void)
|
||||
|
||||
if (fr.image == 255)
|
||||
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_y = (int)Kid.y + fr.dy;
|
||||
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КБ). */
|
||||
void kid_set_seq(uint8_t seq_id);
|
||||
|
||||
/* Рендер-смещение Kid по ЛОГИЧЕСКОЙ X (порт xpos_in_drawn_room, seg004):
|
||||
* когда комната Kid ≠ отрисованной (straddle у шва), спрайт рисуется со
|
||||
* сдвигом ∓140 (левый сосед −140 / правый +140), при этом Kid.x/коллизия
|
||||
* остаются в кадре комнаты Kid. 0 — комнаты совпадают (обычный случай).
|
||||
* Влияет на kid_draw и метрики футпринта (fore) — heal берёт запомненный
|
||||
* прямоугольник, поэтому согласован автоматически. */
|
||||
void pop_kid_set_render_dx(int dx);
|
||||
|
||||
/* Нарисовать текущий кадр Kid (банк GFX_BANK_SPRITE — видео-ОЗУ, без
|
||||
* теневой копии; запоминает прямоугольник для kid_heal). */
|
||||
void kid_draw(void);
|
||||
|
||||
@@ -20,12 +20,16 @@ extern void _gfx_w0_isr(void);
|
||||
/* Смещения блоков blueprnt относительно начала данных (Table 6). */
|
||||
#define BP_FG 0u
|
||||
#define BP_BG 720u
|
||||
#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */
|
||||
#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */
|
||||
#define BP_LINKS 1952u
|
||||
#define BP_START 2112u
|
||||
#define ROOM_TILES 30u
|
||||
#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -47,11 +51,17 @@ int pop_level_load(const char *path)
|
||||
/* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */
|
||||
n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF);
|
||||
if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */
|
||||
uint16_t i;
|
||||
stub = (uint16_t)&_gfx_w0_isr;
|
||||
pg[0x38] = 0xC3; /* JP _gfx_w0_isr */
|
||||
pg[0x39] = (uint8_t)(stub & 0xFF);
|
||||
pg[0x3A] = (uint8_t)(stub >> 8);
|
||||
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);
|
||||
close(fd);
|
||||
@@ -154,6 +164,49 @@ int8_t pop_room_col_landing(uint8_t room, uint8_t col)
|
||||
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 v;
|
||||
|
||||
@@ -41,6 +41,29 @@ int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
|
||||
* 0, если стороны нет. Для переходов между комнатами (L2/L3). */
|
||||
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: первый
|
||||
* НЕ-пустой тайл сверху вниз; возвращает его ряд, если это floor (→ debris),
|
||||
* иначе -1 (пролетел/крушение о стену). */
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "pop_map.h"
|
||||
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
|
||||
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */
|
||||
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
|
||||
|
||||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||||
#define TILE_EMPTY 0
|
||||
@@ -31,6 +32,9 @@
|
||||
#define TILE_WALL 20
|
||||
#define TILE_LOOSE 11 /* проваливающийся пол */
|
||||
#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_RUNJUMP 1
|
||||
#define ACT_HANGCLIMB 2
|
||||
@@ -38,6 +42,7 @@
|
||||
#define ACT_FREEFALL 4
|
||||
#define ACT_BUMPED 5
|
||||
#define ACT_HANGSTRAIGHT 6
|
||||
#define ACT_TURN 7
|
||||
|
||||
#define SEQ_7_FALL 7 /* stepfall (stand/run/step/crouch) */
|
||||
#define SEQ_17_SOFT_LAND 17
|
||||
@@ -58,6 +63,13 @@
|
||||
#define SEQ_23_RELEASE_LEDGE_FALL 23 /* отпустить уступ и падать */
|
||||
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
|
||||
#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_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_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_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_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
||||
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_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,
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
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()); }
|
||||
|
||||
/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
|
||||
* Передний край персонажа char_x_*_coll = char_dx_forward(cur_frame.dx)
|
||||
* (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для
|
||||
* THIN-кадров. dist_from_wall_forward — расстояние переднего края до грани
|
||||
* стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */
|
||||
* Передний край персонажа = char_x_right (взгляд вправо) / char_x_left
|
||||
* (влево) из set_char_collision (seg006:0723): obj_x/2 + 58, где obj_x =
|
||||
* 2*char_dx_forward(cur_frame.dx) - 116 и load_frame_to_obj (seg008:1728)
|
||||
* добавляет +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)
|
||||
{
|
||||
int cx = char_dx_forward(kid_cur_dx());
|
||||
int cx = char_x_forward_edge();
|
||||
if (kid_cur_flags() & FRAME_THIN)
|
||||
cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */
|
||||
return cx;
|
||||
@@ -270,10 +338,36 @@ static void in_wall(void)
|
||||
/* ---- Падение ------------------------------------------------------- */
|
||||
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)
|
||||
{
|
||||
uint8_t seq;
|
||||
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)
|
||||
Kid.x = (uint8_t)char_dx_forward(-3);
|
||||
@@ -618,7 +712,7 @@ static void check_action(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'ы:
|
||||
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
|
||||
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
|
||||
@@ -635,8 +729,19 @@ static void check_bumped(void)
|
||||
* underflow X и уход в стену. fell_out ловит do_fall. */
|
||||
if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
|
||||
t = get_tile_at_char(); tcol = Kid.curr_col;
|
||||
if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; }
|
||||
if (!wall_type(t)) return;
|
||||
wt = wall_type(t);
|
||||
/* 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);
|
||||
if (d >= 0) return; /* ещё не зашёл за грань */
|
||||
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);
|
||||
return;
|
||||
}
|
||||
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR */
|
||||
if (action != ACT_BUMPED && action >= ACT_HANGCLIMB) return;
|
||||
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR.
|
||||
* 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 (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): сотрясение от
|
||||
@@ -729,29 +843,32 @@ void pop_loose_reset(void)
|
||||
void pop_loose_tick(void)
|
||||
{
|
||||
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++) {
|
||||
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
|
||||
pop_loose_bake_empty(pos / 10, pos % 10);
|
||||
pop_loose_bake_empty(row, col);
|
||||
loose_bake[pos]--;
|
||||
continue;
|
||||
}
|
||||
if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||||
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||||
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
||||
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
||||
* страницы дабл-буфера — иначе на одной остаётся застывший
|
||||
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
||||
pop_loose_shake_draw(pos / 10, pos % 10);
|
||||
pop_loose_shake_draw(row, col);
|
||||
loose_rest[pos]--;
|
||||
continue;
|
||||
}
|
||||
if (pop_loose_modif[pos] == 0) continue;
|
||||
{
|
||||
} else if (pop_loose_modif[pos] != 0) {
|
||||
uint8_t m = ++pop_loose_modif[pos];
|
||||
if (m & 0x80) { /* тряска (do_knock) */
|
||||
if (m >= 0x84) { /* конец тряски: сброс + перерисовать покой */
|
||||
pop_loose_modif[pos] = 0;
|
||||
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */
|
||||
continue;
|
||||
} else {
|
||||
pop_loose_shake_draw(row, col); /* дрожащий кадр */
|
||||
}
|
||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||
@@ -761,14 +878,15 @@ void pop_loose_tick(void)
|
||||
* (стена-вниз от тайла снизу + чёрный фон). */
|
||||
g_fg[pos] = TILE_EMPTY;
|
||||
pop_loose_modif[pos] = 0;
|
||||
pop_loose_bake_empty(pos / 10, pos % 10); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||||
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||||
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); /* сигнал приложению: кусок улетел */
|
||||
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(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||
}
|
||||
@@ -799,9 +917,27 @@ static void check_leave_below(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 (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;
|
||||
} else if (g_link_l && fx <= 57) {
|
||||
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)
|
||||
{
|
||||
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
|
||||
if (grab_timer) grab_timer--;
|
||||
fall_accel();
|
||||
fall_speed();
|
||||
@@ -824,7 +1057,19 @@ void pop_phys_tick(void)
|
||||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||||
check_action();
|
||||
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 в ряду */
|
||||
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
|
||||
check_leave(); /* seg002: уход вбок (право) → переход в соседнюю комнату */
|
||||
/* 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(); /* 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,
|
||||
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 для
|
||||
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
|
||||
* Приложение переключает комнату по соответствующей связи и снимает флаг. */
|
||||
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] — состояние на
|
||||
* тайл (0 покой / 0x80.. тряска / 1..11 отсчёт падения); читает pop_bg при
|
||||
* отрисовке. 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_map.h"
|
||||
#include "pop_level.h"
|
||||
#include "pop_trob.h"
|
||||
|
||||
__sfr __at (0xFE) io_border;
|
||||
|
||||
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
|
||||
#define SEQ_STAND 2
|
||||
#define ROOM_1 1
|
||||
#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) + срезы
|
||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||
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 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-кусок приземлился в
|
||||
* комнате снизу → 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)
|
||||
{
|
||||
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);
|
||||
for (i = 0; i < dbr_cnt; i++) /* применить 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),
|
||||
lcol_fg, rcol_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_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++) {
|
||||
gfx_set_draw_page(pg);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
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;
|
||||
kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
|
||||
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
|
||||
}
|
||||
|
||||
int main(void)
|
||||
@@ -75,6 +151,7 @@ int main(void)
|
||||
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
||||
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
||||
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) */
|
||||
puts("pop_bg_load failed");
|
||||
@@ -100,14 +177,17 @@ int main(void)
|
||||
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
||||
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
||||
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
|
||||
/* Извлечь и отрисовать стартовую комнату 1 (в обе страницы). */
|
||||
enter_room(ROOM_1);
|
||||
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
||||
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
||||
pop_trob_reset();
|
||||
enter_room(START_ROOM);
|
||||
gfx_set_draw_page(0);
|
||||
|
||||
/* K2b: Kid стоит на уступе (row1), управляется с клавиатуры.
|
||||
* ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход.
|
||||
* Карта коллизии (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();
|
||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||
kid_fp_width(), kid_fp_height(),
|
||||
@@ -122,6 +202,9 @@ int main(void)
|
||||
while (!kbd_raw_down(KBD_ESC)) {
|
||||
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
||||
* переключ). Выключение возвращает показ на страницу 0. */
|
||||
|
||||
io_border = 2; /* синий - начало */
|
||||
|
||||
uint8_t sp = kbd_raw_down(KBD_SPACE);
|
||||
if (sp && !sp_prev) {
|
||||
dbuf ^= 1;
|
||||
@@ -138,6 +221,7 @@ int main(void)
|
||||
if (kbd_raw_down(0x16)) frozen = 1;
|
||||
if (kbd_raw_down(0x1E)) frozen = 0;
|
||||
if (frozen) {
|
||||
io_border = 0; /* черный: ждём vsync */
|
||||
gfx_wait_vsync();
|
||||
continue;
|
||||
}
|
||||
@@ -157,9 +241,11 @@ int main(void)
|
||||
enter_room(down); /* координаты Kid уже репроецированы pop_map */
|
||||
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
|
||||
} else { /* выпал из уровня (нет комнаты снизу) — респавн */
|
||||
enter_room(ROOM_1);
|
||||
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
|
||||
enter_room(START_ROOM);
|
||||
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: уход вбок/вверх (координаты уже репроецированы) */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
io_border = 4; /* зеленый */
|
||||
|
||||
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
||||
/* — ДО 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 в комнате снизу */
|
||||
uint8_t fcol = (uint8_t)((pop_loose_fell - 1) % 10);
|
||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||
pop_loose_fell = 0;
|
||||
io_border = 2; /* синий */
|
||||
if (down) {
|
||||
int8_t lrow = pop_room_col_landing(down, 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(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||
io_border = 0; /* черный */
|
||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||
kid_fp_width(), kid_fp_height(),
|
||||
Kid.direction, Kid.frame, Kid.action);
|
||||
@@ -189,6 +316,25 @@ int main(void)
|
||||
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
||||
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
||||
* 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();
|
||||
|
||||
Reference in New Issue
Block a user