cd8d566d82
Порт PoP на Sprinter. Текущий PoC — applications/PoP/roomtest/: комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре и коллизией с картой. - roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип, seqtbl-анимация), pop_ctrl (порт control() PoP на held-state kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены, падение/приземление, отскок seq_47, вертикальный прыжок K4.1). MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge). - toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract — распаковка res-графики MSDOS в атласы + композиция комнат. - toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD), png_strip.py / room_compose.py (ассет-пайплайн). - docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT). - bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип). .gitignore: build-артефакты applications/*/*/*; исключены внешние референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
395 lines
16 KiB
C
395 lines
16 KiB
C
/*
|
|
* 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"
|
|
|
|
/* ---- Атласы (прямая адресация) ------------------------------------- */
|
|
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);
|
|
}
|
|
|
|
/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x,
|
|
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0). */
|
|
static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
|
{
|
|
const uint8_t *img;
|
|
uint16_t w, h;
|
|
if (idx >= a->count)
|
|
return;
|
|
img = (const uint8_t *)atlas_image(a, idx);
|
|
gfx_w0_map(a->page);
|
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
|
if (w && h)
|
|
gfx_blit(x, ybottom - (int)h + 1, img);
|
|
gfx_w0_unmap();
|
|
}
|
|
|
|
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 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 uint8_t g_room;
|
|
|
|
static uint8_t tile_code(int row, int col)
|
|
{
|
|
if (col < 0) {
|
|
if (g_lfg && row >= 0 && row < 3) return g_lfg[row];
|
|
return 20; /* уровневая кромка = стена */
|
|
}
|
|
if (row < 0) return 1; /* верхняя кромка = пол */
|
|
if (row > 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 < 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;
|
|
|
|
rnd_seed = (unsigned long)(g_room + TBL_LINE[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);
|
|
}
|
|
|
|
/* ---- 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);
|
|
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) */
|
|
if (row + 1 > 2) rbl_code = 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) — статичные части --- */
|
|
if (lcode == 4) { /* gate: решётка */
|
|
draw_gate_back(lmod, xh, dby, dmy);
|
|
} else if (lcode == 11) { /* loose: правый край */
|
|
env_b(42, 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) — статич. кадр --- */
|
|
if (code == 11)
|
|
env_b(43, x, dby); /* loose_fram_bottom[0] */
|
|
|
|
/* --- draw_tile_base (seg008.c:611) --- */
|
|
{
|
|
uint8_t base_id = t->base_id;
|
|
if (code == 11) base_id = 41; /* loose_fram_left[0] */
|
|
if (base_id)
|
|
env_b(base_id, x, t->base_y + dmy);
|
|
}
|
|
|
|
/* --- 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);
|
|
}
|
|
|
|
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);
|
|
gfx_set_bank(GFX_BANK_NORMAL);
|
|
}
|