Files
Sprinter-SDCC/applications/PoP/roomtest/pop_bg.c
T
snark13 86d7615841 PoP: roomtest — объекты/обломки/переходы комнат + арт
Порт Prince of Persia (applications/PoP/roomtest): развитие уровня,
объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка;
планы (room_model/size_optimization), bug_list, pop_trob.
Арт third_party/16x16-RPG-characters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 19:38:23 +03:00

884 lines
49 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 — состояние нажатия кнопки */
/* ---- Атласы (прямая адресация) ------------------------------------- */
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); }
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];
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 верхнего соседа рисуем статически
* (кадр покоя 43): анимацию/разбитость соседа мы не тянем. --- */
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);
/* --- 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) */
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
void pop_button_redraw(int row, int col)
{
int x = COL_XH[col] * 8;
int yb = POP_YOFF + 63 * row;
/* Wipe СВОЕЙ ячейки + правой грани кнопки (заходит в 0,3) + перерисовка
* пола. НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда 1
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, yb + 26, x + 59, yb + 64);
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(row, col);
if (col + 1 < 10) draw_tile(row, col + 1);
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
* рисовал 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 падает; 2 = очистка второй страницы */
static int mob_x; /* левый край (COL_XH*8) */
static int mob_y; /* main-y (dmy покоя), растёт вниз */
static int8_t mob_speed;
static int8_t mob_row, mob_col; /* тайл-позиция куска (для окклюзии соседа) */
void pop_loose_mob_spawn(int row, int col)
{
mob_x = COL_XH[col] * 8;
mob_y = 63 * row + 62; /* dmy покоя — откуда отрывается кусок */
mob_row = (int8_t)row;
mob_col = (int8_t)col;
mob_speed = 0;
mob_active = 1;
}
/* Габарит куска по 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 не
* должно утечь в новую комнату). */
void pop_loose_mob_reset(void) { mob_active = 0; }
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; } /* очистили вторую стр. — всё */
/* гравитация (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 кадр очистки */
/* Падающий кусок (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). */
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);
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
* её правый край ПРЯЧЕТСЯ за передней гранью пола 2,7. Перерисовываем
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
* до heal'а следующего кадра — виден как хвост во время падения). */
if (mob_col + 1 < 10)
draw_tile(mob_row, mob_col + 1);
gfx_set_bank(GFX_BANK_NORMAL);
/* клип СВОЕГО выхода за низ поля (в статус-полосу) — по ВСЕЙ ширине куска */
pop_clip_sprite(MOB_X0(mob_x), MOB_W, mob_y - 23 + POP_YOFF, 30);
}
/* Спрайт вышел за поле? → взвести флаг на 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_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 → «остаток нажатой кнопки» на грани. */
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) */
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);
}
}
/* Пол-оверлей при подъёме (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];
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_other_overlay (seg008:1492): для поз виса/прыжка/падения/захвата на
* КРОМКЕ (сосед слева пуст, либо тайл через один слева пуст) — перерисовать
* ВЕСЬ тайл поверх Kid. Тело пола перекрывает висящего/прыгающего/падающего
* ПОД ним. Срабатывает только в этих позах (см. redraw_at_char2 ниже) —
* поэтому при стоянии/залезании ложной окклюзии нет. */
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;
left = tile_code(row, col - 1);
if (left == 0 || (col > 0 && tile_code(row, col - 2) == 0))
draw_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;
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 (ниже комнаты) */
}
/* 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;
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, стоя за закрытыми воротами
* (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); /* верхний ряд */
}
}
}
gfx_set_bank(GFX_BANK_NORMAL);
}