Files
Sprinter-SDCC/applications/PoP/roomtest/pop_bg.c
T
snark13 63a3b60524 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>
2026-07-27 17:35:25 +03:00

1167 lines
64 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_bg.c — статический фон комнаты PoP композицией слоёв (порт
* applications/PoP/toolchain/render_room.py -> seg008.c draw_tile/
* wall_pattern). Подробности алгоритма — в render_room.py и
* memory/pop_background_strategy.
*
* Только DUNGEON-стиль (Фаза 1). Анимация (пламя факелов, шипы, дверь-
* плита, сама решётка ворот) НЕ рисуется — отдельными sprite_t поверх
* (Фаза 2). Решётка ворот из ЛЕВОЙ комнаты рисуется статичным кадром.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include <fcntl.h>
#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];
static atlas_t wall_at;
static atlas_t fore_at;
static const char *const ENV_ATL[5] = {
"pop_env0.atl", "pop_env1.atl", "pop_env2.atl",
"pop_env3.atl", "pop_env4.atl"
};
int pop_bg_load(void)
{
int i;
for (i = 0; i < 5; i++)
if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1;
if (atlas_load(&wall_at, "pop_wall.atl") != 0) return -1;
if (atlas_load(&fore_at, "pop_fore.atl") != 0) return -1;
return 0;
}
void pop_bg_free(void)
{
int i;
for (i = 0; i < 5; i++)
atlas_free(&env[i]);
atlas_free(&wall_at);
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).
* При 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));
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();
}
/* 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); }
/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */
typedef struct {
uint8_t base_id, floor_left; int8_t base_y;
uint8_t right_id, floor_right; int8_t right_y;
uint8_t stripe_id, topright_id, bottom_id, fore_id, fore_x; int8_t fore_y;
} piece;
static const piece tile_table[31] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 00 empty */
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 01 floor */
{127, 1, 0, 133, 1, 2, 145, 0, 43, 0, 0, 0}, /* 02 spike */
{ 92, 1, 0, 93, 1, 2, 0, 94, 43, 95, 1, 0}, /* 03 pillar */
{ 46, 1, 0, 47, 1, 2, 0, 48, 43, 49, 3, 0}, /* 04 gate */
{ 41, 1, 1, 35, 1, 3, 145, 0, 36, 0, 0, 0}, /* 05 stuck floor */
{ 41, 1, 0, 42, 1, 2, 145, 0, 96, 0, 0, 0}, /* 06 close button */
{ 46, 1, 0, 0, 0, 2, 0, 0, 43, 49, 3, 0}, /* 07 door top w/floor */
{ 86, 1, 0, 87, 1, 2, 0, 0, 43, 88, 1, 0}, /* 08 big pillar bottom */
{ 0, 0, 0, 89, 0, 3, 0, 90, 0, 91, 1, 3}, /* 09 big pillar top */
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 12, 2, -3}, /* 0A potion */
{ 0, 1, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0}, /* 0B loose floor */
{ 0, 0, 0, 0, 0, 2, 0, 0, 85, 49, 3, 0}, /* 0C door top */
{ 75, 1, 0, 42, 1, 2, 0, 0, 43, 77, 0, 0}, /* 0D mirror */
{ 97, 1, 0, 98, 1, 2, 145, 0, 43,100, 0, 0}, /* 0E debris */
{147, 1, 0, 42, 1, 1, 145, 0,149, 0, 0, 0}, /* 0F open button */
{ 41, 1, 0, 37, 0, 0, 0, 38, 43, 0, 0, 0}, /* 10 leveldoor left */
{ 0, 0, 0, 39, 1, 2, 0, 40, 43, 0, 0, 0}, /* 11 leveldoor right */
{ 0, 0, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 12 chomper */
{ 41, 1, 0, 42, 1, 2, 0, 0, 43, 0, 0, 0}, /* 13 torch */
{ 0, 0, 0, 1, 1, 2, 0, 2, 0, 0, 0, 0}, /* 14 wall */
{ 30, 1, 0, 31, 1, 2, 0, 0, 43, 0, 0, 0}, /* 15 skeleton */
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 16 sword */
{ 41, 1, 0, 10, 0, 0, 0, 11, 43, 0, 0, 0}, /* 17 balcony left */
{ 0, 0, 0, 12, 1, 2, 0, 13, 43, 0, 0, 0}, /* 18 balcony right */
{ 92, 1, 0, 42, 1, 2, 145, 0, 43, 95, 1, 0}, /* 19 lattice pillar */
{ 1, 0, 0, 0, 0, 0, 0, 0, 2, 9, 0,-53}, /* 1A lattice down */
{ 3, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1B lattice small */
{ 4, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1C lattice left */
{ 5, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1D lattice right */
{ 97, 1, 0, 98, 1, 2, 0, 0, 43,100, 0, 0}, /* 1E debris w/torch */
};
/* ---- Константные таблицы (render_room.py) -------------------------- */
static const uint8_t COL_XH[10] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36};
static const uint8_t TBL_LINE[3] = {0, 10, 20};
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};
static const uint8_t LPOS[5] = {58, 41, 37, 20, 16};
static const uint8_t RPOS[4] = {52, 42, 31, 21};
/* Wall (chtab_7) id: делители/блок/марки. */
#define RES_WALL_RNDBLOCK 13
#define RES_WALL_DIVIDER1 11
#define RES_WALL_MARK_TL 14
#define RES_WALL_MARK_BL 15
#define RES_WALL_MARK_TR 16
#define RES_WALL_MARK_BR 17
/* ---- Текущая комната (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/
* floorright, когда тайл нижнего ряда стал видимым (empty после падения
* loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты
* снизу (кромка уровня = стена). */
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) { /* верхняя полоса: ряд 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];
}
static uint8_t tile_mod(int row, int col)
{
if (col < 0) {
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];
}
/* seg008.c:1255 (без FAKE_TILES): сосед — стена, если его тип == 20.
* На кромке col0 сосед по умолчанию = стена (как render_room). */
static uint8_t wall_modifier(int row, int col)
{
uint8_t lw = (col > 0) ? (tile_code(row, col - 1) == 20) : 1;
uint8_t rw = (col < 9) ? (tile_code(row, col + 1) == 20) : 1;
if (lw && rw) return 3; /* WWW */
if (lw) return 2; /* WWS */
if (rw) return 1; /* SWW */
return 0; /* SWS */
}
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */
static unsigned long rnd_seed;
static uint16_t prandom(uint16_t maxv)
{
rnd_seed = rnd_seed * 214013UL + 2531011UL;
return (uint16_t)((uint16_t)(rnd_seed >> 16) % (uint16_t)(maxv + 1));
}
/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */
static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
{
uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BL : RES_WALL_MARK_TL;
int lv2 = 0;
int xh = COL_XH[col];
int dby = 63 * row + 65;
if (dv > 3) lv2 = arg1 + 6;
else if (dv > 1) lv2 = arg2 + 6;
wall_b(image_id, (xh + ((dv == 2 || dv == 3) ? 1 : 0)) * 8 + lv2, dby - LPOS[dv]);
}
static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
{
uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BR : RES_WALL_MARK_TR;
int a = (dv < 2) ? 24 : arg1 - 3;
int xh = COL_XH[col];
int dby = 63 * row + 65;
wall_b(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a, dby - RPOS[dv]);
}
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
static void wall_pattern(int row, int col, int which_part)
{
int xh = COL_XH[col];
int dby = 63 * row + 65;
unsigned long saved = rnd_seed;
uint16_t mid_div, mid_off, bot_div, bot_off;
uint8_t bg;
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col);
prandom(1); /* сброс */
mid_div = prandom(1);
mid_off = prandom(4);
bot_div = prandom(1);
bot_off = prandom(4);
bg = wall_modifier(row, col);
if (bg == 3) { /* WWW */
if (which_part) {
if (prandom(4) == 0)
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
}
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby);
if (which_part) {
if (prandom(4) == 0)
draw_right_mark(prandom(3), mid_off, row, col);
if (prandom(4) == 0)
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col);
}
} else if (bg == 0) { /* SWS */
if (which_part) {
if (prandom(6) == 0)
draw_left_mark(prandom(1), mid_off - mid_div, bot_off - bot_div, row, col);
}
} else if (bg == 1) { /* SWW */
if (which_part) {
if (prandom(4) == 0)
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
if (prandom(4) == 0)
draw_right_mark(prandom(3), mid_off, row, col);
if (prandom(4) == 0)
draw_left_mark(prandom(3), mid_off - mid_div, bot_off - bot_div, row, col);
}
} else { /* WWS (bg==2) */
if (which_part)
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21);
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby);
if (which_part) {
if (prandom(4) == 0)
draw_right_mark((uint16_t)(prandom(1) + 2), mid_off, row, col);
if (prandom(4) == 0)
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col);
}
}
rnd_seed = saved;
}
/* topright тайла снизу-слева (seg008.c:410). */
static void draw_topright(uint8_t tt, int x, int dby)
{
if (tt == 7 || tt == 12) return;
if (tt == 20) { wall_b(2, x, dby); return; }
if (tile_table[tt].topright_id)
env_b(tile_table[tt].topright_id, x, dby);
}
/* Решётка ворот (portcullis) из ЛЕВОЙ комнаты (seg008.c:1110). */
static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
{
int x = xh * 8;
int gate_top_y = dby - 62;
int openness = (((modl > 188) ? 188 : modl) >> 2) + 1;
int gate_bot_y = dmy - openness;
int yb, gate_frame;
if (gate_bot_y + 12 < dmy)
env_b(50, x, gate_bot_y);
else
env_b(51, x, gate_bot_y - 2);
yb = gate_bot_y - 12;
if (yb < 192) {
while (yb >= 0 && yb > 7 && (yb - 7) > gate_top_y) {
env_b(52, x, yb);
yb -= 8;
}
}
gate_frame = yb - gate_top_y + 1;
if (gate_frame > 0 && gate_frame < 9)
env_b(DOOR_FRAM_SLICE[gate_frame], x, yb);
}
/* ---- Loose floors: кадры дрожащей плиты (seg008 loose_fram_*) ------- *
* Состояние pop_loose_modif[] живёт в pop_map. draw_tile рисует грани
* loose АНИМИРОВАННО по modif (как SDLPoP: сам тайл — лево/низ, ПРАВЫЙ
* СОСЕД — правую грань через 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};
static const uint8_t LOOSE_FRAM_BOTTOM[12] = {43,73,43,74,74,43,43,43,74,74,74,0};
/* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору. */
static uint8_t get_loose_frame(uint8_t m)
{
if (m & 0x80) { m &= 0x7F; if (m > 10) return 1; }
return m;
}
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
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;
int dmy = dby - 3;
const piece *t = &tile_table[code];
const piece *lt = &tile_table[lcode];
uint8_t rbl_code;
uint8_t wmod;
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[col] =
* room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */
if (row + 1 > 2) {
int lc = col - 1;
if (lc < 0) rbl_code = 20; /* кромка уровня = стена */
else rbl_code = g_below ? (uint8_t)(g_below[lc] & 0x1F) : 1;
} else rbl_code = tile_code(row + 1, col - 1);
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
if (code == 0 || code == 9 || code == 12 || code == 26) {
draw_topright(rbl_code, x, dby);
/* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
* floor_right: lt->floor_right. */
}
/* --- draw_tile_right (seg008.c:456) --- */
if (code != 20) {
if (lcode == 20) {
wall_b(1, x, tile_table[20].right_y + dmy); /* грань стены */
} else if (lcode == 0) {
if (lmod <= 3 && BLUELINE_FRAM1[lmod])
env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy);
} else if (lcode == 1) {
uint8_t num;
env_b(42, x, tile_table[1].right_y + dmy); /* правый треуг. пола */
num = (lmod <= 3) ? lmod : 0;
if (num != 0) /* dungeon: level_type=0 */
env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */
} else {
if (lt->right_id)
env_b(lt->right_id, x, lt->right_y + dmy);
/* stripe — только палас, dungeon пропускает */
}
}
/* --- 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) --- */
if (code == 20) {
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_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
* верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
* её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */
if (code == 11)
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) — только нижние/правые грани. --- */
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])];
if (base_id)
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);
wall_b(WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
wall_pattern(row, col, 1);
}
if (t->fore_id)
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
}
/* ---- Loose floors — динамическая перерисовка (seg008 draw_loose) ---- *
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
* внутри draw_tile). */
void pop_loose_shake_draw(int row, int col)
{
int x = COL_XH[col] * 8;
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
* перерисовка обоих тайлов с дрожащим кадром (draw_tile берёт кадр из
* pop_loose_modif). Порт redraw_tile_height (loose + правый сосед).
* NB: печёный кадр-0 может слегка просвечивать по кромкам — чистое
* решение (bake тайла как empty + рисовать loose всегда динамически)
* отложено; wipe-в-чёрный делал плиту невидимой — не годится. */
heal_off(x, 63 * row + 46, 64, 24);
gfx_set_bank(GFX_BANK_SPRITE);
draw_tile(row, col); /* loose: лево+низ (анимир.) */
if (col + 1 < 10)
draw_tile(row, col + 1); /* сосед: правая грань loose (анимир.) + его пол */
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) */
/* 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);
}
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
void pop_floor_bake(int row, int col)
{
int x = COL_XH[col] * 8;
int yb = POP_YOFF + 63 * row;
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
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);
}
void pop_button_redraw(int row, int col) { pop_floor_bake(row, col); }
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
* рисовал loose-правый-край env_b(42); теперь пусто → чистая грань).
* g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
void pop_loose_bake_empty(int row, int col)
{
int x = COL_XH[col] * 8;
int xr = COL_XH[col + 1] * 8;
int yb = 63 * row + 46;
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
* heal вернул бы старый пол из ОЗУ-копии. Ширина 40 — кусок пола
* заходит на x+32..+40. */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, yb + POP_YOFF, x + 39, yb + 28 + POP_YOFF); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
if (col + 1 < 10)
bar(xr, yb + POP_YOFF, xr + 31, yb + 28 + POP_YOFF); /* сосед: РОВНО 32 — не залезть в col+2 (его не перерисовываем) */
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
* снизу) + правый сосед (его левая грань: был loose-край env_b(42),
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
* ОЗУ-копию → heal сохранит. */
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 (mob, seg007 move_loose) ------------------ *
* Упрощ. одиночный кусок (в комнате 1 — один loose). Модель fixed-
* corridor: коридор падения (колонка тайла) heal'ится каждый кадр (стирает
* прошлый кусок, возвращает печёный фон — empty+«стена вниз»), поверх —
* кадр 0 loose (41/42/43) на текущей y. Гравитация: speed 0→+3/кадр
* (cap 29), y+=speed. Двоебуфер: коридор фиксирован → heal чистит любой
* прошлый кадр ЭТОЙ страницы. Ниже поля кусок прячет clip_borders. */
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; /* = 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 = 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)
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
#define MOB_X0(mx) ((mx) - 4)
#define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */
/* Остановить падающий кусок (при смене комнаты — состояние per-tile не
* должно утечь в новую комнату). */
/* Тайл ОБЪЕКТА 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)
{
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;
/* 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,
* заходит в тайл СПРАВА) на obj_y-1. */
gfx_set_bank(GFX_BANK_SPRITE);
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; пока плита у уровня пола,
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
* до heal'а следующего кадра — виден как хвост во время падения). */
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 - 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 кадра (обе страницы дабл-
* буфера). Узкий per-спрайт heal НЕ используем: accel-выравнивание блока
* оставляет 1–2px слайверы правой грани → на одной странице мерцает.
* Вместо этого борта чистит pop_room_clip_borders (полоса, но ТОЛЬКО в
* кадрах падения — гейт по флагу). */
static uint8_t border_dirty;
void pop_clip_sprite(int x, int w, int sy_top, int h)
{
int sy_bot = sy_top + h;
(void)x; (void)w;
if (sy_bot > POP_CLIP_BOTTOM || sy_top < POP_YOFF)
border_dirty = 2; /* 2 кадра = обе страницы */
}
/* Клип бортов (см. pop_bg.h): работает ТОЛЬКО пока border_dirty (кто-то
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
void pop_room_clip_borders(void)
{
if (!border_dirty) return;
border_dirty--;
gfx_heal(0, 0, 320, POP_YOFF); /* верхний борт */
gfx_heal(0, POP_CLIP_BOTTOM, 320, 256 - POP_CLIP_BOTTOM);/* статус-полоса + нижний борт */
}
void pop_room_draw(uint8_t room_num,
const uint8_t *fg, const uint8_t *bg,
const uint8_t *lcol_fg, const uint8_t *lcol_bg)
{
int row, col;
g_room = room_num;
g_fg = fg; g_bg = bg;
g_lfg = lcol_fg; g_lbg = lcol_bg;
/* прозрачный блит: 0xFF не пишется (чёрный фон/нижние слои проступают) */
gfx_set_bank(GFX_BANK_TRANSPARENT);
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);
}
/* ---- fore-over-Kid (порт seg003 redraw_at_char + seg008 set_char_collision) --
* Передний слой (fore_id = foretable-кусок) тайлов ФУТПРИНТА спрайта Kid
* — рядов top..bottom × колонок left..right (<= 2×2 = 4 тайла) — рисуется
* ПОВЕРХ Kid: то, что по изометрии перед ним (передние грани колонн/ворот/
* большой колонны/щебня). Стены и факелы имеют fore_id=0 → остаются сзади.
* Рисуем в GFX_BANK_SPRITE (видео-ОЗУ, 0xFF прозрачен); kid_heal восстановит
* из теневого фона (fore в нём уже запечён pop_room_draw). obj_x — ЛОГИЧЕСКАЯ
* X кадра Kid (до ×8/7), obj_y — низ спрайта, w/h — размер, dir — направление. */
static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка (58/14) */
{
int x = xpos - 58;
int xh = x / 14;
if (x % 14 < 0) xh--; /* округление вниз для отрицательных */
return (int8_t)xh;
}
static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=63) */
{
return (int8_t)((y + 60) / 63 % 4 - 1);
}
static void fore_only_tile(int row, int col)
{
uint8_t code = tile_code(row, col);
const piece *t;
/* Нажатая кнопка → пол/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;
if (code == 20) {
wall_b(WALL_FRAM_MAIN[wall_modifier(row, col)], x, dmy);
wall_pattern(row, col, 1);
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
* подтягивается на floor-подобный тайл назначения (floor/pillar/stuck/torch),
* а тайл СЛЕВА пуст (т.е. кромка), передний край пола перекрывает нижнюю
* часть Kid — иначе видна часть, что должна быть за полом. Кадр подъёма
* 137..144 индексирует floor_left_overlay. */
static const uint8_t FLOOR_LEFT_OVERLAY[8] = {32, 151, 151, 150, 150, 151, 32, 32};
static void climb_overlay_tile(int row, int col, uint8_t fidx)
{
uint8_t code = tile_code(row, col);
int x, dmy, dby;
const piece *t;
if (code != 1 && code != 3 && code != 5 && code != 19) return; /* не floor-тайл */
if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
x = COL_XH[col] * 8;
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);
uint8_t left;
/* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для
* него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань
* пустого соседа). В оригинале она уходит в y-сортируемый midtable и
* ложится ПОЗАДИ Kid; у нас draw_tile кладёт её прямо поверх → сетка на
* висящем/падающем 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) { 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,
int8_t dir, uint8_t frame, uint8_t action)
{
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, rTraw;
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh;
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 в полосе
* «ряд 3») трактуется как ряд 3, НЕ ряд 0. Верхний ряд считается от
* char_top_y с ресетом в 0, если весь спрайт ниже комнаты (>=192). */
{
int top_y = obj_y - (int)h + 1;
if (top_y >= 192) top_y = 0;
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 (передняя грань пола, за которой нога). */
if (rB < 0) rB = 0; else if (rB > 2) rB = 2;
if (rT < 0) rT = 0; else if (rT > 2) rT = 2;
/* Голова/руки почти всегда торчат в ряд НАД опорным (Kid выше тайла).
* y_to_row(верх спрайта) из-за +60-сдвига может схлопнуться в опорный
* ряд (rT==rB) — тогда ряд над ним не попадёт в перебор и его окклюдер
* (нижняя грань стены / кромка пола) не перерисуется поверх 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 восстановит */
/* Шовная окклюзия ворот (порт 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++)
for (c = cL; c <= cR; c++)
climb_overlay_tile(r, c, fidx);
} else {
/* redraw_at_char2 (seg003:449) — путь set_redraw2/draw_other_overlay:
* тело пола на кромке перекрывает Kid в позах захвата (78-79), виса
* (action 2/6), полёта (3/4) и старта падения (bumped 102-106).
* Верхний ряд — всегда; опорный — кроме action 2 (hang_climb). */
uint8_t do_ov = 0;
if (frame >= 78 && frame < 80) do_ov = 1; /* захват */
else if (action == 2 || action == 3 || action == 4 || action == 6) do_ov = 1;
else if (action == 5 && frame >= 102 && frame <= 106) do_ov = 1;
else if (frame == 135 || frame == 136) do_ov = 1; /* начало подъёма (SEQ_UP */
/* на 141 — до него Kid ещё */
/* в ряду 1 ПОД полом [0,3]) */
if (do_ov) {
for (c = cR; c >= cL; c--) {
if (action != 2) other_overlay_tile(rB, c); /* опорный ряд */
if (rT != rB) other_overlay_tile(rT, c); /* верхний ряд */
}
}
}
/* 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);
}