/* * 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 #include #include #include #include #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 + POP_YOFF, img); /* +YOFF: центрирование */ 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 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 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; } 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); } /* ---- 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); 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) — статичные части --- */ 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) --- */ if (code == 11) env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])], x, dby); /* --- draw_tile_base (seg008.c:611) --- */ { 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_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); } /* Запечь ПУСТОТУ на месте упавшего 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) */ 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); 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 (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 = &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 (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 = tile_code(row, col - 1); if (left == 0 || (code != 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, rT, rB, r, c; if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ xr = xl + wh; cL = col_from_x(xl); if (cL < 0) cL = 0; else if (cL > 9) cL = 9; cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9; rB = y_to_row(obj_y); if (rB < 0) rB = 0; else if (rB > 2) rB = 2; rT = y_to_row(obj_y - (int)h + 1); 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); 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); }