PoP roomtest: плита-потолок, порядок слоёв по SDLPoP, фиксы окклюзии и Y

Фича — плита-ПОТОЛОК (loose ряда 2 комнаты сверху, комн.5 (2,5) над комн.6):
- check_press (seg006:1683) портирован целиком: кадры виса 87..99 и подъёма
  135..140 «нажимают» тайл, за который Kid держится; кадр 79 — удар снизу
  (с проверкой action, как в оригинале); стояние — тайл под ним;
- ряд −1 в pop_map.get_tile = ряд 2 комнаты сверху (порт find_room_of_tile),
  состояние тряски/падения — pop_ceil_modif[10]; do_knock(-1) трясёт потолок;
- падение: mob переведён на координаты оригинала (y_loose_land/y_something),
  спавнится в ряду −1, по move_loose находит пол → loose_land: debris +
  do_knock; heal коридора — по прошлой позиции ДЛЯ КАЖДОЙ страницы;
- урон: check_loose_fall_on_kid + fell_on_your_head (seq_52/seq_22, −1 HP)
  и «брызги» draw_hurt_splash (kid image 218);
- колодец наверх: leave_room «вверх» проверяется ПЕРВЫМ (иначе y≈248 при
  подтягивании ловил check_leave_below и ронял Kid из уровня);
- персистентность: общий tile-override (щебень внизу, пустота в комнате
  сверху) вместо debris-only.

Окклюзия — приведена к оригиналу (найдено трассировкой собранного SDLPoP):
- draw_tile_right/anim_right/draw_loose кладут спрайты в add_backtable
  НАПРЯМУЮ, минуя ptr_add_table → правая грань соседа («шахматка» столба 93),
  blueline и грани loose/пик соседа ВСЕГДА под персонажем.  draw_other_overlay
  поверх Kid даёт только: 42 при левом соседе-поле, base, свои пики, bottom
  (overlay_mid_tile);
- порядок объектов: тайл Kid (set_objtile_at_char) и порядок обхода
  (ряды 2→0, колонки 0→9), внутри тайла — сортировка по y (sort_curr_objs);
  то же правило для падающей плиты (pop_loose_mob_draw_over);
- fore-слой рисуется ПОСЛЕ оверлеев (foretable после midtable), иначе тёмный
  скос floor_left_overlay ложился поверх колонны;
- fore_tile больше не рисует bottom_id (в оригинале он в backtable) — минус
  2..4 блита за кадр;
- полоса потолка поверх Kid = draw_tile_bottom(1)+draw_loose(1)+draw_tile_fore
  (arg_0=1 добавляет в foretable), остальное — под ним;
- следы оверлея восстанавливаются на ОБЕИХ страницах (pop_fore_heal).

Прочее:
- control(): при action bumped/in_freefall управление игнорируется целиком
  (seg005) — иначе прерванный присед терял dy(1)+dy(1) из medland и Kid
  навсегда оставался на 2px выше пола;
- check_bumped: стена ищется по колонке переднего края (а не curr_col) +
  гард разворота — Kid больше не проходит сквозь опущенную решётку шва;
- pop_floor_bake для смены floor→debris (bake_empty стирал верх стены ряда
  ниже — чёрный прямоугольник);
- в pop_room_redraw_seam_left восстанавливается полоса потолка над решёткой.

Проверено в MAME (bridge); эталон — собранный SDLPoP из applications/PoP/SDLPoP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 17:35:25 +03:00
parent 86d7615841
commit 63a3b60524
10 changed files with 731 additions and 103 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ EXAMPLE := roomtest
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
MEMORY ?= small
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c --w3 pop_map.c --w3 pop_bg.c
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c --w3 pop_map.c pop_bg.c
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c
BG_DIR := $(CURDIR)/../poc/res/bg
+338 -55
View File
@@ -15,6 +15,7 @@
#include <unistd.h>
#include "pop_bg.h"
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */
/* ---- Атласы (прямая адресация) ------------------------------------- */
static atlas_t env[5];
@@ -81,6 +82,36 @@ static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
/* heal в room-space (со сдвигом POP_YOFF) — все heal слоя фона/loose. */
static void heal_off(int x, int y, int w, int h) { gfx_heal(x, y + POP_YOFF, w, h); }
/* Область, куда в этом кадре рисовался ОВЕРЛЕЙ (не футпринт fore, а
* draw_other_overlay/floor_overlay/полоса потолка). Оверлей включается
* только в позах виса/полёта/подъёма и рисует НЕ по прямоугольнику Kid,
* поэтому kid_heal его следы не убирает: на второй странице дабл-буфера
* остаётся кадр «с оверлеем», и он мерцает через кадр. Копим bbox и
* восстанавливаем фон на ОБЕИХ страницах (pop_fore_heal). */
static int ov_x0, ov_y0, ov_x1, ov_y1;
static uint8_t ov_pending; /* биты страниц, где надо восстановить фон */
static void ov_mark(int x, int y, int w, int h)
{
if (!ov_pending) { ov_x0 = x; ov_y0 = y; ov_x1 = x + w; ov_y1 = y + h; }
else {
if (x < ov_x0) ov_x0 = x;
if (y < ov_y0) ov_y0 = y;
if (x + w > ov_x1) ov_x1 = x + w;
if (y + h > ov_y1) ov_y1 = y + h;
}
ov_pending = 3; /* обе страницы */
}
void pop_fore_heal(void)
{
uint8_t pg = gfx_get_draw_page() & 1;
if (!(ov_pending & (1 << pg))) return;
heal_off(ov_x0, ov_y0, ov_x1 - ov_x0, ov_y1 - ov_y0);
ov_pending = (uint8_t)(ov_pending & ~(1 << pg));
}
static void env_b(uint8_t id, int x, int yb) { blit_b(&env[id >> 5], (uint8_t)(id & 31), x, yb); }
static void wall_b(uint8_t id, int x, int yb) { blit_b(&wall_at, id, x, yb); }
static void fore_b(uint8_t id, int x, int yb) { blit_b(&fore_at, id, x, yb); }
@@ -335,6 +366,7 @@ static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
* СОСЕД — правую грань через draw_tile_anim_right). При modif==0 (покой/
* статич. запекание) get_loose_frame=0 → базовые кадры 41/42/43. */
extern uint8_t pop_loose_modif[30];
extern uint8_t pop_ceil_modif[10]; /* плита-потолок: loose ряда 2 комнаты сверху */
static const uint8_t LOOSE_FRAM_LEFT[12] = {41,69,41,70,70,41,41,41,70,70,70,0};
static const uint8_t LOOSE_FRAM_RIGHT[12] = {42,71,42,72,72,42,42,42,72,72,72,0};
@@ -432,11 +464,11 @@ static void draw_tile(int row, int col)
}
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
* верхней полосе (row<0) loose ряда 2 верхнего соседа рисуем статически
* (кадр покоя 43): анимацию/разбитость соседа мы не тянем. --- */
* верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
* её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */
if (code == 11)
env_b(row >= 0 ? LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])]
: LOOSE_FRAM_BOTTOM[0], x, dby);
env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(row >= 0 ? pop_loose_modif[row * 10 + col]
: pop_ceil_modif[col])], x, dby);
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
@@ -536,6 +568,46 @@ void pop_room_redraw_seam_left(void)
bar(0, POP_YOFF, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
/* Bar стёр и полосу кладки У ПОТОЛКА (ряд −1 = ряд 2 комнаты сверху,
* draw_tile_aboveroom): без её перерисовки потолок над решёткой пропадал
* после первой же анимации ворот. Клип — как в pop_room_draw. */
g_clip_top = POP_YOFF;
draw_tile(-1, 0);
g_clip_top = 0;
gfx_set_bank(GFX_BANK_NORMAL);
}
/* ---- Плита-потолок (loose ряда 2 комнаты СВЕРХУ) ------------------- *
* В полосе кладки у потолка (наш ряд −1) виден только НИЗ плиты
* (draw_tile_aboveroom: bottom+loose, всё выше отрезано клипом POP_YOFF).
* Дрожащий кадр берётся из pop_ceil_modif[col] (см. draw_loose выше).
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
#define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */
void pop_ceil_shake_draw(int col)
{
int x = COL_XH[col] * 8;
heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
gfx_set_bank(GFX_BANK_SPRITE);
g_clip_top = POP_YOFF;
draw_tile(-1, col);
if (col + 1 < 10) draw_tile(-1, col + 1);
g_clip_top = 0;
gfx_set_bank(GFX_BANK_NORMAL);
}
void pop_ceil_bake_empty(int col)
{
int x = COL_XH[col] * 8;
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, POP_YOFF, x + 63, POP_YOFF + CEIL_BAND_H); /* стереть плиту в ОЗУ-копии */
gfx_set_bank(GFX_BANK_TRANSPARENT);
g_clip_top = POP_YOFF;
draw_tile(-1, col); /* тайл уже EMPTY → колодец наверх */
if (col + 1 < 10) draw_tile(-1, col + 1);
g_clip_top = 0;
gfx_set_bank(GFX_BANK_NORMAL);
}
@@ -543,12 +615,12 @@ void pop_room_redraw_seam_left(void)
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
void pop_button_redraw(int row, int col)
void pop_floor_bake(int row, int col)
{
int x = COL_XH[col] * 8;
int yb = POP_YOFF + 63 * row;
/* Wipe СВОЕЙ ячейки + правой грани кнопки (заходит в 0,3) + перерисовка
* пола. НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда 1
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
@@ -559,6 +631,8 @@ void pop_button_redraw(int row, int col)
gfx_set_bank(GFX_BANK_NORMAL);
}
void pop_button_redraw(int row, int col) { pop_floor_bake(row, col); }
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
@@ -597,20 +671,35 @@ void pop_loose_bake_empty(int row, int col)
* кадр 0 loose (41/42/43) на текущей y. Гравитация: speed 0→+3/кадр
* (cap 29), y+=speed. Двоебуфер: коридор фиксирован → heal чистит любой
* прошлый кадр ЭТОЙ страницы. Ниже поля кусок прячет clip_borders. */
static uint8_t mob_active; /* 0 нет; 1 падает; 2 = очистка второй страницы */
static uint8_t mob_active; /* 0 нет; 1 падает */
static uint8_t mob_clean; /* кадров дочистки после конца полёта (2 = обе стр.) */
static uint8_t mob_defer; /* кусок ниже Kid → рисуем ПОСЛЕ него (y-порядок) */
static int mob_x; /* левый край (COL_XH*8) */
static int mob_y; /* main-y (dmy покоя), растёт вниз */
static int mob_y; /* = curmob.y оригинала (низ куска, dby), растёт вниз */
static int mob_prev_y[2];/* последняя нарисованная y на КАЖДОЙ странице (heal) */
static int8_t mob_speed;
static int8_t mob_row, mob_col; /* тайл-позиция куска (для окклюзии соседа) */
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
* который он лёг → приложение/pop_map ставит debris + do_knock. */
uint8_t pop_loose_landed;
/* y_loose_land / y_something (seg007): [row+1], row = 1..3. Ряд 1 —
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
* tilepos, у нас — свой ряд полосы у потолка. */
static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
#define MOB_Y_NONE 0x7FFF
void pop_loose_mob_spawn(int row, int col)
{
mob_x = COL_XH[col] * 8;
mob_y = 63 * row + 62; /* dmy покоя — откуда отрывается кусок */
mob_y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
mob_row = (int8_t)row;
mob_col = (int8_t)col;
mob_speed = 0;
mob_active = 1;
mob_clean = 0;
}
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
@@ -623,39 +712,114 @@ void pop_loose_mob_spawn(int row, int col)
/* Остановить падающий кусок (при смене комнаты — состояние per-tile не
* должно утечь в новую комнату). */
void pop_loose_mob_reset(void) { mob_active = 0; }
/* Тайл ОБЪЕКТА Kid (set_objtile_at_char, seg006:1833) — в момент обработки
* этого тайла спрайт Kid попадает в midtable. Задаётся в pop_fore_over_kid,
* читается other_overlay_tile (порядок «кто поверх кого»). */
static int8_t obj_col, obj_row;
static void mob_render(uint8_t pg);
void pop_loose_mob_reset(void)
{
mob_active = 0; mob_clean = 0; mob_defer = 0;
mob_prev_y[0] = mob_prev_y[1] = MOB_Y_NONE;
}
void pop_loose_mob_tick(void)
{
if (!mob_active) return;
/* стереть прошлый кусок этой страницы + вернуть печёный фон коридора
* (фикс rect [160,192] по ВСЕЙ ширине куска; overflow прошлого кадра
* стёрт его же clip'ом той же ширины). */
heal_off(MOB_X0(mob_x), 160, MOB_W, 32);
if (mob_active == 2) { mob_active = 0; return; } /* очистили вторую стр. — всё */
uint8_t pg = gfx_get_draw_page() & 1;
if (!mob_active && !mob_clean) return;
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
* прошлой позиции. */
if (mob_prev_y[pg] != MOB_Y_NONE) {
heal_off(MOB_X0(mob_x), mob_prev_y[pg] - 27, MOB_W, 32);
mob_prev_y[pg] = MOB_Y_NONE;
}
if (!mob_active) { mob_clean--; return; }
/* гравитация (move_loose): ускорение до 29 */
if (mob_speed < 29) mob_speed += 3;
mob_y += mob_speed;
if (mob_y - 24 >= POP_PLAYFIELD_H) { mob_active = 2; return; } /* весь ниже поля → +1 кадр очистки */
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
* смотрим тайл этого ряда. Пусто/loose — валится дальше (порт
* loose_fall «плита ломает плиту» не нужен: в ряду ниже её нет);
* пол — приземление (loose_land: debris + do_knock). */
if (mob_row < 3 && mob_y < 226 && MOB_Y_BOUND[mob_row + 1] <= mob_y) {
uint8_t t = (uint8_t)(tile_code(mob_row, mob_col) & 0x1F);
if (t == 0 || t == 11) {
mob_row++; /* mob_down_a_row */
} else {
mob_y = MOB_Y_BOUND[mob_row + 1];
pop_loose_landed = (uint8_t)(mob_row * 10 + mob_col + 1);
mob_active = 0; mob_clean = 2; /* кусок исчез — на его месте debris */
return;
}
}
if (mob_y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
mob_active = 0; mob_clean = 2;
return;
}
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
* ниже — тот позже, т.е. поверх). Тайл падающего куска — draw_mob
* (seg007:13E5): row = y_to_row_mod4(y), col = xh>>2. Значит кусок
* поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при
* совпадении тайлов — если он ниже по y. */
{
int8_t mrow = (int8_t)(((mob_y + 60) / 63) % 4 - 1); /* y_to_row_mod4 */
uint8_t over;
if (mrow != obj_row) over = (mrow < obj_row); /* ряды идут 2,1,0 */
else if (mob_col != obj_col) over = (mob_col > obj_col);
else over = (mob_y > kid_fp_obj_y());
if (over) { mob_defer = 1; return; }
}
mob_render(pg);
}
static void mob_render(uint8_t pg)
{
/* Падающий кусок (SDLPoP draw_mobs/add_midtable, case 0x80): ТРИ куска —
* left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1,
* заходит в тайл СПРАВА 2,7) на obj_y-1. У нас bottom=dby(mob_y+3) →
* left=mob_y, right=mob_y+2 в mob_x+32 (col+1). */
* заходит в тайл СПРАВА) на obj_y-1. */
gfx_set_bank(GFX_BANK_SPRITE);
env_b(43, mob_x, mob_y + 3);
env_b(41, mob_x, mob_y);
env_b(42, mob_x + 32, mob_y + 2);
env_b(43, mob_x, mob_y);
env_b(41, mob_x, mob_y - 3);
env_b(42, mob_x + 32, mob_y - 1);
mob_prev_y[pg] = mob_y;
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
* её правый край ПРЯЧЕТСЯ за передней гранью пола 2,7. Перерисовываем
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
* до heal'а следующего кадра — виден как хвост во время падения). */
if (mob_col + 1 < 10)
if (mob_col + 1 < 10) {
if (mob_row < 0) g_clip_top = POP_YOFF; /* полоса у потолка — только срез */
draw_tile(mob_row, mob_col + 1);
g_clip_top = 0;
}
gfx_set_bank(GFX_BANK_NORMAL);
/* клип СВОЕГО выхода за низ поля (в статус-полосу) — по ВСЕЙ ширине куска */
pop_clip_sprite(MOB_X0(mob_x), MOB_W, mob_y - 23 + POP_YOFF, 30);
/* клип СВОЕГО выхода за полеерхний борт / статус-полоса) — по ВСЕЙ ширине */
pop_clip_sprite(MOB_X0(mob_x), MOB_W, mob_y - 26 + POP_YOFF, 30);
}
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
* после kid_draw, до fore-слоя. */
void pop_loose_mob_draw_over(void)
{
if (!mob_defer) return;
mob_defer = 0;
mob_render(gfx_get_draw_page() & 1);
}
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
* y (низ куска). 0 — куска нет. */
uint8_t pop_loose_mob_pos(int *col, int *y)
{
if (!mob_active) return 0;
*col = mob_col; *y = mob_y;
return 1;
}
/* Спрайт вышел за поле? → взвести флаг на 2 кадра (обе страницы дабл-
@@ -731,37 +895,63 @@ static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=
return (int8_t)((y + 60) / 63 % 4 - 1);
}
static void fore_tile(int row, int col) /* передний слой одного тайла */
static void fore_only_tile(int row, int col)
{
uint8_t code = tile_code(row, col);
const piece *t;
/* Нажатая кнопка → пол/stuck (как get_tile_to_draw), ИНАЧЕ fore рисует
* переднюю грань КНОПКИ (bottom_id 149) поверх Kid, перекрывая уже
* нарисованную грань пола 43 → «остаток нажатой кнопки» на грани. */
/* Нажатая кнопка → пол/stuck (get_tile_to_draw), иначе поверх Kid лезет
* передняя грань КНОПКИ. */
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) */
if (code == 20) { /* стена: ОБЕ грани перекрывают Kid */
uint8_t wmod = wall_modifier(row, col);
wall_b(WALL_FRAM_BOTTOM[wmod], x, dby); /* нижняя грань-решётка (draw_tile_bottom): */
wall_pattern(row, col, 0); /* перекрывает голову/руки в НИЖНЕЙ части ячейки */
wall_b(WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань (draw_tile_fore) */
if (code == 20) {
wall_b(WALL_FRAM_MAIN[wall_modifier(row, col)], x, dmy);
wall_pattern(row, col, 1);
} 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);
return;
}
if (code == 2) { /* пики: fore-кадр */
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);
}
/* Полоса кладки У ПОТОЛКА поверх Kid — порт redraw_needed_above (seg008:0221).
* Там draw_tile_bottom(1) и draw_loose(1): аргумент 1 = «добавить ещё и в
* FOREtable», т.е. нижняя грань потолка и кадр плиты-потолка рисуются ПОВЕРХ
* персонажа (иначе руки прыгающего Kid лезут на кромку потолка). Всё
* остальное из этой функции (floorright, anim_topright, right) идёт в
* backtable — под персонажем. */
static void ceil_over_kid_tile(int col)
{
uint8_t code = tile_code(-1, col);
const piece *t = &tile_table[code];
int x = COL_XH[col] * 8;
int dby = 63 * (-1) + 65; /* = 2 */
if (code == 20) { /* draw_tile_bottom: стена */
wall_b(WALL_FRAM_BOTTOM[wall_modifier(-1, col)], x, dby);
wall_pattern(-1, col, 0);
} else if (t->bottom_id) {
env_b(t->bottom_id, x, dby);
}
if (code == 11) /* draw_loose(1): плита-потолок */
env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(pop_ceil_modif[col])], x, dby);
fore_only_tile(-1, col); /* draw_tile_fore */
}
static void fore_tile(int row, int col) /* передний слой одного тайла */
{
/* draw_tile_fore (seg008:690) — ТОЛЬКО add_foretable-часть. Раньше здесь
* рисовалась ещё и передняя кромка пола (bottom_id) «чтобы перекрывала
* Kid», но в оригинале bottom идёт через draw_tile_bottom в backtable
* (под персонажем), а поверх него попадает лишь при draw_other_overlay.
* Из-за лишнего bottom у основания колонны проскакивала «тень». */
fore_only_tile(row, col);
}
/* Пол-оверлей при подъёме (draw_floor_overlay, seg008:1457): когда Kid
@@ -782,15 +972,70 @@ static void climb_overlay_tile(int row, int col, uint8_t fidx)
dmy = 63 * row + 62;
dby = dmy + 3;
t = &tile_table[code];
ov_mark(x, 63 * row + 2, 64, 64);
env_b(FLOOR_LEFT_OVERLAY[fidx], x, dmy + (code == 5 ? 1 : 0));
if (t->bottom_id) env_b(t->bottom_id, x, dby); /* draw_tile_bottom */
}
/* draw_tile2 (seg008:1F48) — но ТОЛЬКО та его часть, что реально может лечь
* ПОВЕРХ персонажа. Ключ (подтверждено трассой оригинала): draw_tile_right,
* draw_tile_anim_right и draw_loose добавляют спрайты через add_backtable
* НАПРЯМУЮ, игнорируя ptr_add_table — значит правая грань левого соседа
* («шахматка» столба 93, blueline, грани пик/loose соседа, кадр loose)
* ВСЕГДА рисуется ПОД персонажем. Через ptr_add_table (→ midtable при
* draw_other_overlay) идут только: «floor B» 42 при левом соседе-поле,
* draw_tile_base, draw_tile_anim (свои пики) и draw_tile_bottom. */
static void overlay_mid_tile(int row, int col)
{
uint8_t code = tile_code(row, col);
uint8_t lcode = tile_code(row, col - 1);
const piece *t;
int x = COL_XH[col] * 8;
int dby = 63 * row + 65;
int dmy = dby - 3;
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(tile_mod(row, col - 1)) & 0x1F) > 1) lcode = 1; }
else if (lcode == 0x06) { if ((pop_doorlink2(tile_mod(row, col - 1)) & 0x1F) > 1) lcode = 5; }
t = &tile_table[code];
ov_mark(x, 63 * row + 2, 64, 64); /* тайл + правый сосед (грани) */
if (code != 20 && lcode == 1) /* draw_tile_right: только 42 */
env_b(42, x, tile_table[1].right_y + dmy);
if (t->base_id) /* draw_tile_base (loose base = 0) */
env_b(t->base_id, x, t->base_y + dmy);
if (code == 2) { /* draw_tile_anim: свои пики */
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);
}
if (code == 20) { /* draw_tile_bottom */
uint8_t wmod = wall_modifier(row, col);
wall_b(WALL_FRAM_BOTTOM[wmod], x, dby);
wall_pattern(row, col, 0);
} else if (t->bottom_id) {
env_b(t->bottom_id, x, dby);
}
}
/* draw_tile_fore (seg008:690) — ТОЛЬКО передний слой (add_foretable): он
* рисуется после всего и поверх персонажа. Нужен для полосы кладки у
* потолка: в redraw_needed_above всё остальное идёт в backtable (под Kid). */
/* draw_other_overlay (seg008:1492): для поз виса/прыжка/падения/захвата на
* КРОМКЕ (сосед слева пуст, либо тайл через один слева пуст) — перерисовать
* ВЕСЬ тайл поверх Kid. Тело пола перекрывает висящего/прыгающего/падающего
* ПОД ним. Срабатывает только в этих позах (см. redraw_at_char2 ниже) —
* поэтому при стоянии/залезании ложной окклюзии нет. */
* поэтому при стоянии/залезании ложной окклюзии нет.
*
* ПОРЯДОК (redraw_needed_tiles, seg008:1B06 + redraw_needed, seg008:0211):
* тайлы обходятся ряды 2,1,0, внутри ряда колонки 0..9; в тайле сначала
* оверлей (redraw_frames2 -> midtable), потом ОБЪЕКТЫ этого тайла. Kid — тоже
* запись midtable, вставляемая при обработке СВОЕГО тайла (obj_row/obj_col).
* Значит оверлей ложится ПОВЕРХ Kid только у тайлов, идущих в обходе ПОЗЖЕ
* его собственного; всё, что раньше, — под ним. У нас списков нет (рисуем
* сразу), поэтому порядок эмулируется явной проверкой. */
static void other_overlay_tile(int row, int col)
{
uint8_t code = tile_code(row, col);
@@ -802,9 +1047,19 @@ static void other_overlay_tile(int row, int col)
* висящем/падающем Kid. Фон запечён один раз в pop_room_draw — над
* спрайтом его перерисовывать не нужно. */
if (code == 0) return;
/* Тайл идёт в обходе РАНЬШЕ тайла Kid → его оверлей лёг бы ПОД спрайт. */
if (row > obj_row || (row == obj_row && col <= obj_col)) return;
left = tile_code(row, col - 1);
if (left == 0 || (col > 0 && tile_code(row, col - 2) == 0))
draw_tile(row, col);
if (left == 0) { overlay_mid_tile(row, col); return; } /* ветка 1: сосед слева пуст */
if (col > 0 && tile_code(row, col - 2) == 0) {
/* Ветка 2 (тайл ЧЕРЕЗ ОДИН слева пуст): в оригинале она ставит
* tile_object_redraw[tilepos]=0xFF, т.е. объекты тайлов tilepos−1 и
* tilepos рисуются СРАЗУ ПОСЛЕ оверлея — Kid возвращается наверх.
* Без этого правая грань соседнего столба (кадр 93) остаётся поверх
* прыгающего Kid (баг «тень на дальнем столбе поверх Кида»). */
if (row == obj_row && (col == obj_col + 1)) return;
overlay_mid_tile(row, col);
}
}
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
@@ -813,7 +1068,7 @@ 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, cLraw, rT, rB, r, c;
int8_t cL, cR, cLraw, rT, rB, r, c, rTraw;
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh;
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
@@ -829,6 +1084,7 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
rT = y_to_row(top_y);
rB = y_to_row(obj_y);
if (rB == -1) rB = 3; /* обёртка → ряд 3 (ниже комнаты) */
rTraw = rT; /* −1 = верх спрайта в полосе у потолка */
}
/* single-room roomtest: ряд 3 (комната снизу) в этом виде не рисуется →
* окклюдер спереди = ряд 2 (передняя грань пола, за которой нога). */
@@ -840,10 +1096,17 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
* (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим
* включение ряда rB-1. */
if (rB > 0 && rT >= rB) rT = rB - 1;
/* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт
* Kid попадает в midtable — база порядка окклюзии в other_overlay_tile.
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
* подъём — колонка на одну ЛЕВЕЕ curr_col. */
obj_row = Kid.curr_row;
obj_col = Kid.curr_col;
if (action == 1) { obj_row = rB; obj_col = cL; }
if ((frame >= 135 && frame < 149) ||
action == 2 || action == 3 || action == 4 || action == 6)
obj_col--;
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, heal восстановит */
for (r = rT; r <= rB; r++)
for (c = cL; c <= cR; c++)
fore_tile(r, c);
/* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за
* ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) —
* перерисовать её бары ПОВЕРХ Kid. Иначе Kid, стоя за закрытыми воротами
@@ -879,5 +1142,25 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
}
}
}
/* draw_tile_fore (seg008:690) — foretable, рисуется ПОСЛЕ midtable, т.е.
* ПОСЛЕ оверлеев выше: передняя грань колонны перекрывает скос пола
* (floor_left_overlay), а не наоборот. Раньше fore шёл первым, и тёмный
* скос ложился поверх колонны — «тень у её основания». */
for (r = rT; r <= rB; r++)
for (c = cL; c <= cR; c++)
fore_tile(r, c);
/* redraw_needed_above (seg008:0221): полоса кладки У ПОТОЛКА (ряд −1 =
* ряд 2 комнаты сверху). В оригинале она рисуется в самом конце обхода
* тайлов — ПОСЛЕ объектов, т.е. поверх персонажа (redraw_frames_above,
* куда set_redraw* уводит отрицательные tilepos). Без этого руки Kid,
* прыгающего в ряду 0, лезли поверх потолка. */
if (rTraw < 0) {
g_clip_top = POP_YOFF;
for (c = cL; c <= cR; c++) {
ov_mark(COL_XH[c] * 8, 0, 64, CEIL_BAND_H);
ceil_over_kid_tile(c);
}
g_clip_top = 0;
}
gfx_set_bank(GFX_BANK_NORMAL);
}
+28 -3
View File
@@ -58,6 +58,12 @@ void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir, uint8_t frame, uint8_t action);
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
* прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице
* дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */
void pop_fore_heal(void);
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
* pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick.
* shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту
@@ -65,6 +71,16 @@ 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);
/* Плита-ПОТОЛОК (loose ряда 2 комнаты СВЕРХУ, наш ряд −1): дрожащий кадр в
* полосе у потолка / «запечь» пустоту (колодец наверх) после провала.
* Состояние — pop_ceil_modif[10] в pop_map. */
void pop_ceil_shake_draw(int col);
void pop_ceil_bake_empty(int col);
/* Кусок приземлился (loose_land): 0 = нет, иначе tilepos+1 тайла, на который
* он лёг. Ставит pop_loose_mob_tick, разбирает pop_loose_tick (debris+knock). */
extern uint8_t pop_loose_landed;
/* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
void pop_spike_redraw(int row, int col);
@@ -74,9 +90,11 @@ void pop_spike_redraw(int row, int col);
* на 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);
/* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице
* (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте:
* кнопка нажата/отжата, floor → debris от упавшей плиты. */
void pop_floor_bake(int row, int col);
void pop_button_redraw(int row, int col); /* = pop_floor_bake (кнопка) */
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
@@ -85,6 +103,13 @@ void pop_loose_mob_spawn(int row, int col);
void pop_loose_mob_tick(void);
void pop_loose_mob_reset(void); /* остановить кусок (смена комнаты) */
/* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs,
* seg008:1561). Звать после kid_draw, перед fore-слоем. */
void pop_loose_mob_draw_over(void);
/* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */
uint8_t pop_loose_mob_pos(int *col, int *y);
/* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом
* sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше
* верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра.
+10
View File
@@ -297,6 +297,16 @@ static void control_jumpup(void)
static void control(void)
{
uint8_t f = Kid.frame;
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
* пару пикселей меньше, чем должен»). */
if (Kid.action == 5 || Kid.action == 4) { /* bumped / in_freefall */
release_arrows();
return;
}
if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
control_standing();
else if (f == FRAME_48_TURN)
+52
View File
@@ -194,6 +194,58 @@ int kid_fp_obj_y(void) { return kid_fp_y; }
uint16_t kid_fp_width(void) { return kid_fp_w; }
uint16_t kid_fp_height(void) { return kid_fp_h; }
/* draw_hurt_splash (seg006:2003): «брызги» урона поверх персонажа — спрайт
* chtab_2_kid id 218 (res619.png; в наших атласах это image 218, стр. 27).
* Позиция от кадра Kid: obj_y = 15, obj_x += 5 ВПЕРЁД (obj_dx_forward).
* Кадры смерти/падения (185, 106..110) и пик (177) в оригинале смещают
* иначе — тут только общий случай + «на пиках» (порт по мере надобности).
* Рисовать ПОСЛЕ kid_draw (splash кладётся в objtable позже Kid). */
#define KID_SPLASH_IMAGE 218
void kid_draw_splash(void)
{
int obj_x = kid_fp_x, obj_y = kid_fp_y - 15;
uint8_t page = KID_SPLASH_IMAGE >> 3, idx = KID_SPLASH_IMAGE & 7;
const uint8_t *img;
uint16_t w, h;
if (page >= kid_npages) return;
if (Kid.frame == 177) /* напоролся на пики */
obj_y = kid_fp_y, obj_x -= (Kid.direction < 0) ? -5 : 5;
else
obj_x += (Kid.direction < 0) ? -5 : 5;
obj_x = obj_x * 8 / 7; /* лог. X -> экран (calc_screen_x_coord) */
img = (const uint8_t *)atlas_image(&kidp[page], idx);
gfx_w0_map(kidp[page].page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
if (w && h) {
uint8_t flip = (Kid.direction >= 0);
int bx = obj_x;
int top = obj_y - (int)h + 1;
if (flip) bx -= (int)w;
gfx_set_bank(GFX_BANK_SPRITE);
gfx_blit_cols(bx, top + POP_YOFF, img, flip);
gfx_set_bank(GFX_BANK_NORMAL);
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
/* Прямоугольник splash расширяет область heal ЭТОЙ страницы: иначе
* брызги останутся на экране после кадра. */
{
uint8_t dp = gfx_get_draw_page();
if (kid_lvalid[dp]) {
int x0 = kid_lx[dp] < bx ? kid_lx[dp] : bx;
int y0 = kid_ly[dp] < top ? kid_ly[dp] : top;
int x1 = kid_lx[dp] + (int)kid_lw[dp];
int y1 = kid_ly[dp] + (int)kid_lh[dp];
if (bx + (int)w > x1) x1 = bx + (int)w;
if (top + (int)h > y1) y1 = top + (int)h;
kid_lx[dp] = x0; kid_ly[dp] = y0;
kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0);
}
}
}
gfx_w0_unmap();
}
/* Восстановить фон из теневой копии на месте ПРЕДЫДУЩЕГО кадра Kid. */
void kid_heal(void)
{
+4
View File
@@ -68,6 +68,10 @@ void pop_kid_set_render_dx(int dx);
* теневой копии; запоминает прямоугольник для kid_heal). */
void kid_draw(void);
/* draw_hurt_splash (seg006:2003): «брызги» урона (image 218) поверх Kid —
* звать СРАЗУ после kid_draw в кадре, когда HP убавилось (pop_kid_hurt). */
void kid_draw_splash(void);
/* Восстановить фон (из теневой копии) на месте ПРЕДЫДУЩЕГО кадра Kid —
* вместо перерисовки всего экрана. Звать ПЕРЕД kid_tick/kid_draw. */
void kid_heal(void);
+188 -18
View File
@@ -101,6 +101,7 @@ uint8_t pop_leave_dir; /* уход вбок/вверх: 0=нет; 1=le
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 pop_kid_hurt; /* 1 = в этом кадре убавилось HP → «брызги» */
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) */
@@ -114,6 +115,23 @@ static uint8_t grab_timer; /* после зацепа (12): блокиру
static uint8_t g_link_l, g_link_r, g_link_u, g_link_d;
static uint8_t g_lcol[3], g_rcol[3];
/* ---- Плита-ПОТОЛОК: loose ряда 2 комнаты СВЕРХУ ------------------- *
* В оригинале это тот же loose, просто get_tile ушёл в комнату сверху
* (find_room_of_tile) и tilepos получился отрицательным (set_redraw* уводит
* такие в redraw_frames_above). У нас ряд 2 верхнего соседа живёт отдельной
* копией (above_fg в приложении, наш «ряд −1»), поэтому и состояние
* отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. —
* тряска, 1..11 — отсчёт до падения). */
uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */
static uint8_t ceil_rest[10]; /* «перерисовать ПОКОЙ» (2 = обе страницы) */
static uint8_t ceil_bake[10]; /* «запечь колодец» на второй странице */
static uint8_t land_bake, land_bake_pos; /* щебень от приземления — вторая страница */
static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */
uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */
uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */
void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; }
void pop_map_set(uint8_t *fg) { g_fg = fg; }
void pop_map_set_room(uint8_t room) { g_room = room; }
@@ -138,7 +156,12 @@ void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
static uint8_t get_tile(int8_t col, int8_t row)
{
if (row < 0 || row > 2)
/* Ряд −1 — ряд 2 комнаты СВЕРХУ (порт find_room_of_tile, seg006:005D:
* отрицательный row уходит по links.up). Нужен для зацепа за край
* провалившейся плиты-потолка (колодец наверх) и выбора seq прыжка. */
if (row == -1 && g_above && g_link_u && col >= 0 && col <= 9)
g_curr_tile = (uint8_t)(g_above[col] & 0x1F);
else if (row < 0 || row > 2)
g_curr_tile = TILE_WALL;
else if (col == -1 && g_link_l) /* левый шов: тайл col9 комнаты слева */
g_curr_tile = g_lcol[row] & 0x1F;
@@ -728,17 +751,22 @@ static void check_bumped(void)
* dist_from_wall_forward/x_bump[] — мусор с большим отриц. сдвигом →
* 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;
/* seg004 check_collisions: на кадрах разворота коллизии НЕ считаются
* (bump_col_* сбрасываются и функция выходит) — bump'а в развороте нет. */
if (Kid.action == ACT_TURN) return;
/* Стена ищется по колонке, в которую упирается ПЕРЕДНИЙ КРАЙ персонажа
* (порт seg004: bump_col_left/right_of_wall — колонка, где край пересёк
* грань; НЕ curr_col!). Через curr_col было ДВА расхождения: (1) весовая
* точка кадра заезжает за грань раньше края, curr_col становился −1 на
* шве, ворота оказывались «под персонажем» и гард односторонней стены их
* снимал — Kid уходил в соседнюю комнату СКВОЗЬ ОПУЩЕННУЮ РЕШЁТКУ;
* (2) тот же гард был нужен только из-за отсутствия проверки разворота
* (см. выше). Колонка переднего края даёт и то, и другое даром: стоя
* СПРАВА от ворот лицом вправо, край в соседней колонке — ворота не
* бампят; заехав краем за их грань лицом влево — бампят. */
tcol = get_tile_div_mod_m7(char_front_coll());
t = get_tile(tcol, Kid.curr_row);
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 проходит (не бамп) */
@@ -761,6 +789,8 @@ uint8_t pop_loose_modif[30]; /* публично: читает pop_bg п
static uint8_t loose_bake[30]; /* счётчик «запечь пусто» (2 = обе страницы) */
static uint8_t loose_rest[30]; /* счётчик «перерисовать ПОКОЙ» после тряски (2 = обе стр.) */
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
/* make_loose_fall (seg007:0EF6): взвести отсчёт падения, если ещё не идёт. */
static void make_loose_fall(int pos, uint8_t modifier)
{
@@ -776,16 +806,37 @@ static void make_loose_fall(int pos, uint8_t modifier)
static void check_press(void)
{
uint8_t frame = Kid.frame, action = Kid.action;
if (frame == 79) { /* прыжок вверх — тайл сверху */
if (get_tile_above_char() == TILE_LOOSE)
uint8_t above_press = 0; /* «нажимается» тайл СВЕРХУ */
/* Кадры виса (87..99) и начала подъёма (135..140): нажимается тайл, за
* КОТОРЫЙ ПЕРСОНАЖ ДЕРЖИТСЯ, т.е. верхний (seg006:1688). Отсюда падение
* плиты, за край которой Kid зацепился, — а не только той, по которой он
* ударил снизу прыжком (кадр 79). */
if ((frame >= 87 && frame < 100) || (frame >= 135 && frame < 141)) {
above_press = 1;
} else if (action == ACT_BUMPED || action == ACT_TURN || action < ACT_HANGCLIMB) {
if (frame == 79) above_press = 1; /* прыжок вверх — удар снизу */
} else {
return; /* seg006:1704 — прочее не жмёт */
}
if (above_press) {
if (Kid.curr_row == 0) {
/* Ряд 0: «сверху» — ряд 2 комнаты СВЕРХУ (в оригинале get_tile с
* row=1 уходит по links.up, find_room_of_tile seg006:005D).
* Плита-ПОТОЛОК; кнопок на потолке в этом тесте нет. */
int8_t c = Kid.curr_col;
if (g_above && g_link_u && c >= 0 && c <= 9 &&
(g_above[c] & 0x1F) == TILE_LOOSE && !(g_above[c] & 0x20) &&
(int8_t)pop_ceil_modif[c] <= 0)
pop_ceil_modif[c] = 1; /* make_loose_fall(1) */
} else if (get_tile_above_char() == TILE_LOOSE) {
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
}
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;
/* Стояние: кадр обязан требовать пол; нажимается тайл ПОД персонажем.
* ACT_TURN обязателен (seg006:1691): при развороте на кнопке иначе
* trigger_button не зовётся кадр-два → таймер связи падает <2 → кнопка
* ложно отжимается и жмётся снова. */
if (!(kid_cur_flags() & FRAME_NEEDS_FLOOR)) return;
{
uint8_t t = get_tile_at_char();
@@ -804,6 +855,13 @@ static void check_press(void)
static void do_knock(int tile_row)
{
int col, pos;
if (tile_row == -1) { /* ряд 2 комнаты сверху — плиты-потолки */
if (!g_above || !g_link_u) return;
for (col = 0; col < 10; col++)
if ((g_above[col] & 0x1F) == TILE_LOOSE && pop_ceil_modif[col] == 0)
pop_ceil_modif[col] = 0x80;
return;
}
if (tile_row < 0 || tile_row > 2) return;
for (col = 0; col < 10; col++) {
pos = tile_row * 10 + col;
@@ -835,9 +893,61 @@ void pop_loose_reset(void)
loose_bake[i] = 0;
loose_rest[i] = 0;
}
for (i = 0; i < 10; i++) {
pop_ceil_modif[i] = 0;
ceil_rest[i] = 0;
ceil_bake[i] = 0;
}
pop_ceil_fell = 0;
pop_debris_at = 0;
pop_loose_landed = 0;
land_bake = 0;
pop_loose_mob_reset();
}
/* fell_on_your_head (seg007:1205): плита прилетела Киду по голове. В беге
* (кадры 5..14) не бьёт (кроме уровня 13 — его тут нет); в висе/подъёме тоже
* (action >= hang_climb, кроме turn). Иначе −1 HP: смерть → seq_22 (crushed),
* живой → seq_52 (плита придавила: Kid приседает), с отступом на 2 назад,
* если сзади пусто. Кадр 109 (присед) последовательность не меняет. */
#define SEQ_22_CRUSHED 22
#define SEQ_52_LOOSE_FELL_ON_KID 52
#define FRAME_109_CROUCH 109
static void fell_on_your_head(void)
{
uint8_t frame = Kid.frame, action = Kid.action;
if ((frame < 5 || frame >= 15) &&
(action < ACT_HANGCLIMB || action == ACT_TURN)) {
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */
if (take_hp(1)) {
kid_set_seq(SEQ_22_CRUSHED);
play_seq();
determine_col();
pop_kid_dead = 1;
} else if (frame != FRAME_109_CROUCH) {
if (get_tile_behind_char() == TILE_EMPTY)
Kid.x = (uint8_t)char_dx_forward(-2);
kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID);
play_seq();
determine_col();
}
}
}
/* check_loose_fall_on_kid (seg007:1192): падающий кусок в той же колонке и
* его низ уже в полосе 30px над ногами Kid → удар. */
static void check_loose_fall_on_kid(void)
{
int mcol, my;
if (pop_kid_dead) return;
if (!pop_loose_mob_pos(&mcol, &my)) return;
if (Kid.curr_col != (int8_t)mcol) return;
if (my < (int)Kid.y && (int)Kid.y - 30 < my)
fell_on_your_head();
}
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
void pop_loose_tick(void)
@@ -888,7 +998,53 @@ void pop_loose_tick(void)
}
if (++col == 10) { col = 0; row++; } /* row/col без деления */
}
/* Плиты-ПОТОЛКИ (ряд 2 комнаты сверху): тот же animate_loose, но кадры
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
for (col = 0; col < 10; col++) {
if (ceil_rest[col]) {
pop_ceil_shake_draw(col); /* modif уже 0 → кадр покоя */
ceil_rest[col]--;
} else if (pop_ceil_modif[col] != 0) {
uint8_t m = ++pop_ceil_modif[col];
if (m & 0x80) { /* тряска от сотрясения (do_knock) */
if (m >= 0x84) { pop_ceil_modif[col] = 0; ceil_rest[col] = 2; }
else pop_ceil_shake_draw(col);
} else if (m >= 11) { /* loose_floor_delay — плита рушится */
pop_ceil_modif[col] = 0;
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
pop_ceil_bake_empty(col); /* колодец наверх на ТЕКУЩЕЙ странице */
ceil_bake[col] = 1; /* + вторая страница следующим кадром */
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
} else {
pop_ceil_shake_draw(col); /* отсчёт 1..10 — дрожащий кадр */
}
} else if (ceil_bake[col]) {
pop_ceil_bake_empty(col);
ceil_bake[col]--;
}
}
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
check_loose_fall_on_kid(); /* seg007:1192 — плита падает Киду на голову */
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
uint8_t pos = (uint8_t)(pop_loose_landed - 1);
uint8_t r = 0, c = pos;
pop_loose_landed = 0;
while (c >= 10) { c -= 10; r++; }
g_fg[pos] = TILE_DEBRIS;
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не bake_empty: та стирает и верх стены
* ряда ниже — оставался чёрный прямоугольник под тайлом). */
pop_floor_bake(r, c);
land_bake_pos = pos; land_bake = 1; /* вторая страница дабл-буфера */
do_knock(r); /* удар плиты трясёт loose этого ряда */
pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */
} else if (land_bake) {
uint8_t r = 0, c = land_bake_pos;
while (c >= 10) { c -= 10; r++; }
pop_floor_bake(r, c);
land_bake = 0;
}
}
/* y_to_row_mod4 (seg006): ряд по y с оборотом mod4 (TILE_SIZEY=63). */
@@ -901,6 +1057,20 @@ static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
* решит: переход (links.down) или выпал из уровня (нет комнаты снизу). */
static void check_leave_below(void)
{
/* leave_room (seg002:0504) проверяет ВВЕРХ ПЕРВЫМ: персонаж не в отскоке/
* полёте и его y (как signed) в полосе −16..10 у верха комнаты — он вылез
* в комнату сверху (через колодец от упавшей плиты). Порядок важен: при
* подтягивании y ≈ 248, и проверка «ниже комнаты» (y>=211) иначе
* перехватывает подъём и роняет Kid из уровня.
* goto_other_room(up): y += 189, curr_row = y_to_row_mod4. */
if (g_link_u && Kid.action != ACT_BUMPED && Kid.action != ACT_FREEFALL &&
Kid.action != ACT_MIDAIR &&
(int8_t)Kid.y < 10 && (int8_t)Kid.y > -16) {
Kid.y = (uint8_t)(Kid.y + 189);
Kid.curr_row = y_to_row((int16_t)Kid.y);
pop_leave_dir = 3;
return;
}
if (Kid.y >= 211) {
Kid.y = (uint8_t)(Kid.y - 189);
Kid.curr_row = y_to_row((int16_t)Kid.y);
+17
View File
@@ -60,6 +60,23 @@ void pop_loose_reset(void); /* сброс loose-состояния при см
* Приложение: определить debris-приземление в комнате снизу. Снять (=0). */
extern uint8_t pop_loose_fell;
/* Плита-ПОТОЛОК: loose ряда 2 комнаты СВЕРХУ (в оригинале — тот же loose с
* отрицательным tilepos, seg006 check_press frame 79 / seg007 animate_loose).
* pop_map_set_above даёт МУТАБЕЛЬНУЮ копию ряда 2 верхнего соседа (ту же, что
* ушла в pop_room_set_above): провалившаяся плита становится в ней EMPTY —
* сверху открывается колодец. pop_ceil_modif[col] — состояние (как
* pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности
* между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты,
* pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */
void pop_map_set_above(uint8_t *above_row2_fg);
extern uint8_t pop_ceil_modif[10];
extern uint8_t pop_ceil_fell;
extern uint8_t pop_debris_at;
/* Взводится в кадре, когда Kid получил урон (hitp_delta<0 оригинала) —
* приложение рисует «брызги» kid_draw_splash() поверх Kid и снимает флаг. */
extern uint8_t pop_kid_hurt;
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
* приземления/удара. Зовётся из pop_phys_tick. */
void pop_check_knock(void);
+60 -26
View File
@@ -27,7 +27,7 @@ __sfr __at (0xFE) io_border;
* row2, шахта col3). Kid на полу row0 col1, идёт вправо → падает в шахту →
* пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */
#define START_ROOM 6
#define START_COL 1
#define START_COL 4
#define START_ROW 0
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
@@ -67,34 +67,47 @@ static uint8_t seam_redraw; /* оставшиеся кадры редроя (
static uint8_t room_label_sig = 0xFF;
static uint8_t room_label_redraw;
/* Персистентные debris-переопределения (упавший loose-кусок приземлился в
* комнате снизу → floor стал debris). Минимальный per-room override: живёт
* между переходами, применяется в enter_room поверх загруженных тайлов.
* (Полноценное per-room modifier-состояние — в docs/gates_spikes_plan.md, P0.) */
/* Персистентные переопределения тайлов уровня: упавший loose-кусок оставил
* щебень (floor debris), а на месте самой плиты — пусто (в т.ч. в комнате
* СВЕРХУ: провалившийся потолок = колодец). Минимальный per-room override:
* живёт между переходами, применяется в enter_room поверх загруженных тайлов
* (и для ряда 2 верхнего соседа). (Полноценное per-room modifier-состояние —
* в docs/gates_spikes_plan.md, P0.) */
#define TILE_DEBRIS 0x0E
#define DEBRIS_OVR_MAX 4
static uint8_t dbr_room[DEBRIS_OVR_MAX], dbr_pos[DEBRIS_OVR_MAX], dbr_cnt;
#define TILE_EMPTY 0x00
#define OVR_MAX 8
static uint8_t ovr_room[OVR_MAX], ovr_pos[OVR_MAX], ovr_tile[OVR_MAX], ovr_cnt;
static void add_debris_override(uint8_t room, uint8_t pos)
static void add_tile_override(uint8_t room, uint8_t pos, uint8_t tile)
{
uint8_t i;
for (i = 0; i < dbr_cnt; i++)
if (dbr_room[i] == room && dbr_pos[i] == pos) return; /* уже есть */
if (dbr_cnt < DEBRIS_OVR_MAX) {
dbr_room[dbr_cnt] = room; dbr_pos[dbr_cnt] = pos; dbr_cnt++;
for (i = 0; i < ovr_cnt; i++)
if (ovr_room[i] == room && ovr_pos[i] == pos) { ovr_tile[i] = tile; return; }
if (ovr_cnt < OVR_MAX) {
ovr_room[ovr_cnt] = room; ovr_pos[ovr_cnt] = pos;
ovr_tile[ovr_cnt] = tile; ovr_cnt++;
}
}
/* Применить переопределения комнаты room к массиву тайлов: base = tilepos
* первого элемента (0 для полной комнаты, 20 для ряда 2 верхнего соседа). */
static void apply_overrides(uint8_t room, uint8_t *tiles, uint8_t base, uint8_t n)
{
uint8_t i;
for (i = 0; i < ovr_cnt; i++)
if (ovr_room[i] == room && ovr_pos[i] >= base && ovr_pos[i] < base + n)
tiles[ovr_pos[i] - base] = ovr_tile[i];
}
/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ
* страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет
* активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */
static void enter_room(uint8_t room)
{
uint8_t pg, i;
uint8_t pg;
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;
apply_overrides(room, room_fg, 0, 30); /* щебень/пустота от упавших плит */
pop_map_set(room_fg);
pop_map_set_edges(pop_room_link(room, 0), pop_room_link(room, 1),
pop_room_link(room, 2), pop_room_link(room, 3),
@@ -109,10 +122,13 @@ static void enter_room(uint8_t room)
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();
apply_overrides(up, above_fg, 20, 10); /* провалившийся потолок = колодец */
for (c = 0; c < 10; c++) above_mod[c] = um[20 + c];
pop_room_set_above(above_fg, above_mod);
pop_map_set_above(above_fg); /* плита-потолок: пробой снизу + провал */
} else {
pop_room_set_above(0, 0); /* нет комнаты сверху → пол (кромка) */
pop_map_set_above(0);
}
}
pop_map_set_room(room);
@@ -232,6 +248,7 @@ int main(void)
pop_ctrl_tick(); /* ввод -> control(): смена seq */
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
kid_tick(); /* play_seq: следующий кадр */
pop_phys_tick(); /* K3: падение/приземление/стена */
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
@@ -295,18 +312,35 @@ int main(void)
}
io_border = 6; /* голубой */
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
uint8_t fcol = (uint8_t)((pop_loose_fell - 1) % 10);
uint8_t fpos = (uint8_t)(pop_loose_fell - 1);
uint8_t fcol = (uint8_t)(fpos % 10);
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
pop_loose_fell = 0;
io_border = 2; /* синий */
add_tile_override(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */
if (down) {
int8_t lrow = pop_room_col_landing(down, fcol);
if (lrow >= 0) add_debris_override(down, (uint8_t)(lrow * 10 + fcol));
if (lrow >= 0) add_tile_override(down, (uint8_t)(lrow * 10 + fcol), TILE_DEBRIS);
}
}
if (pop_ceil_fell) { /* провалилась плита-ПОТОЛОК (ряд 2 комнаты сверху) */
uint8_t ccol = (uint8_t)(pop_ceil_fell - 1);
uint8_t up = pop_room_link(cur_room, 2);
pop_ceil_fell = 0;
if (up) add_tile_override(up, (uint8_t)(20 + ccol), TILE_EMPTY); /* колодец */
}
if (pop_debris_at) { /* кусок лёг в ЭТОЙ комнате → щебень */
add_tile_override(cur_room, (uint8_t)(pop_debris_at - 1), TILE_DEBRIS);
pop_debris_at = 0;
}
io_border = 4; /* зеленый */
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
kid_draw_splash();
pop_kid_hurt = 0;
}
io_border = 0; /* черный */
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
kid_fp_width(), kid_fp_height(),
@@ -324,15 +358,15 @@ int main(void)
* а не каждый кадр (обход 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--;
}
// 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();