6a824dd7ab
BUG-GUARD-IX-1 — «зависание» в бою со стражем. Не зависание: главный цикл крутился, а отладочная локаль frozen сама становилась ненулевой. Корень — у main затирался IX (0xBFFA -> 0xBF00), и все его локали адресовали живой стековый мусор. Затирал check_chomped_guard: coll_row() пишет по flags + scan_off, длину берёт из win_lo/win_hi, а scan_off выставляла только coll_scan_prepare() из пути Кида. Ряд стража писался по смещению Кида длиной стража и при Киде у правого края комнаты вылезал за flags[13] — прямо в сохранённый IX. Фикс: coll_scan_prepare() в начале get_row_collision_data(); заодно чинится расчёт (scan_left0 задаёт x колонок, чомпер-коллизия стража считалась по координатам Кида). На уровне 1 не проявлялось: бой идёт левее середины, запись оставалась внутри массива — данные были неверны молча. BUG-GATE-SEAM-ROW1 — решётка в шве не анимировалась. Плита комнаты 1 открывает ворота комнаты 8 в (1,9), видимые через левый шов, а change-driven редрой смотрел только m[9] (ряд 0) и перерисовывал жёстко draw_tile(0,0). На уровне 1 та же связка работала лишь потому, что решётка соседа стояла в (0,9). Фикс: сигнатура по всем трём рядам, маска изменившихся рядов в seam_rows (переживает оба кадра дабл-буфера), pop_room_redraw_seam_left(rows) перерисовывает только помеченные. Плюс ряд выше (changed | changed>>1): верх решётки (draw_tile_anim_topright, seg008:0568) рисует тайл над-справа от ворот, без этого чёрный треугольник над ними оставался статичным. Проверено пользователем в MAME (бой в комнате 18; анимация решётки в стартовой комнате), детекторы IX висели без починки и не сработали; make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1051 lines
65 KiB
C
1051 lines
65 KiB
C
/*
|
||
* pop_room.c — ХОЛОДНАЯ половина слоя фона PoP: полная отрисовка комнаты
|
||
* (порт seg008.c draw_tile / render_room.py), точечные перерисовки тайлов
|
||
* при событиях (пики, ворота, кнопки, дверь уровня, дрожащие плиты) и
|
||
* падающие куски loose (mob, seg007 move_loose). Всё это работает раз на
|
||
* комнату или раз на событие, поэтому живёт в ОТДЕЛЬНОМ банке и платит
|
||
* трамплин за вызовы горячей половины — см. _pop_bg.h, там же граница реза.
|
||
*
|
||
* Только DUNGEON-стиль (Фаза 1).
|
||
*/
|
||
/* Имена файлов атласов обоих тайлсетов нужны только здесь; макрос обязан
|
||
* стоять ДО первого включения pop_bg_atlas.h (его тянет pop_tile.h). */
|
||
#define POP_BG_ATLAS_NAMES
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include "pop_bg.h"
|
||
#include "_pop_bg.h" /* контракт с горячей половиной (pop_bg.c, банк 2) */
|
||
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||
#include "pop_state.h"
|
||
#include "pop_geom.h"
|
||
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
||
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */
|
||
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
|
||
/* ---- Тайлсет: подземелье / дворец ----------------------------------- *
|
||
* Порт tbl_envir_ki[tbl_level_type[level]] (seg000:1108): оригинал под один
|
||
* и тот же набор id грузит РАЗНЫЙ .DAT — VDUNGEON или VPALACE. У нас так
|
||
* же: раскладка id -> (страница, idx) общая (упаковщик печёт оба набора по
|
||
* одному объединению id), меняются только файлы атласов и 32 записи
|
||
* палитры — env 0x50..0x5F и wall 0x60..0x6F. Кид, страж, меч и зелья
|
||
* живут в других слотах, поэтому общая kid.pal не трогается. */
|
||
static uint8_t bg_set = 0xFF; /* какой набор сейчас загружен */
|
||
|
||
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон, KID —
|
||
* персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска зарастает
|
||
* десятками .atl. Путь склеиваем на месте: держать по строке на каждый из
|
||
* двадцати атласов — лишние полкилобайта в банке. */
|
||
static char bg_path_buf[20];
|
||
|
||
static const char *bg_path(const char *name)
|
||
{
|
||
char *d = bg_path_buf;
|
||
*d++ = 'B'; *d++ = 'G'; *d++ = '\\';
|
||
while (*name) *d++ = *name++;
|
||
*d = 0;
|
||
return bg_path_buf;
|
||
}
|
||
|
||
/* 32 записи (env, затем wall) в ОБЕ палитры: у страниц дабл-буфера они
|
||
* раздельные. Буфер — на стеке: gfx_pal_load отдаёт указатель BIOS ($A4),
|
||
* а тот читает только #4000-#BFFF, то есть банковую rodata бы не увидел
|
||
* (те же грабли, что в pop_guard_set_palette). */
|
||
static int bg_load_tile_pal(const char *name)
|
||
{
|
||
uint8_t buf[64];
|
||
int fd = open(bg_path(name), O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
if (read(fd, buf, 64) != 64) { close(fd); return -1; }
|
||
gfx_pal_load(0, POP_PAL_ENV, 16, buf);
|
||
gfx_pal_load(1, POP_PAL_ENV, 16, buf);
|
||
if (read(fd, buf, 64) != 64) { close(fd); return -1; }
|
||
gfx_pal_load(0, POP_PAL_WALL, 16, buf);
|
||
gfx_pal_load(1, POP_PAL_WALL, 16, buf);
|
||
close(fd);
|
||
return 0;
|
||
}
|
||
|
||
/* Загрузить набор set (0 подземелье, 1 дворец). Повторный вызов с тем же
|
||
* набором ничего не делает; со сменой — сначала выгружает старый, иначе
|
||
* EMM-страницы утекут (их по 12 на набор). */
|
||
int pop_bg_load(uint8_t set) __banked
|
||
{
|
||
int i;
|
||
if (set >= POP_TILESETS) return -1;
|
||
if (set == bg_set) return 0;
|
||
if (bg_set != 0xFF) { /* смена набора — освободить старый */
|
||
for (i = 0; i < POP_ENV_PAGES; i++) atlas_free(&pop_env[i]);
|
||
atlas_free(&pop_wall_at);
|
||
atlas_free(&pop_fore_at);
|
||
}
|
||
for (i = 0; i < POP_ENV_PAGES; i++)
|
||
if (atlas_load(&pop_env[i], bg_path(pop_env_atl[set][i])) != 0) return -1;
|
||
if (atlas_load(&pop_wall_at, bg_path(pop_wall_atl[set])) != 0) return -1;
|
||
if (atlas_load(&pop_fore_at, bg_path(pop_fore_atl[set])) != 0) return -1;
|
||
if (bg_set == 0xFF && /* chtab_1 от набора не зависит */
|
||
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
|
||
bg_set = set;
|
||
pop_palace = (uint8_t)(set == 1);
|
||
/* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от
|
||
* прежнего набора и картинка хотя бы не разъезжается. */
|
||
return bg_load_tile_pal(pop_tile_pal[set]);
|
||
}
|
||
|
||
/* Пере-применить палитру ТЕКУЩЕГО набора. Нужна отдельно от pop_bg_load
|
||
* из-за порядка старта: атласы грузятся ДО initgraph (тот снимает
|
||
* DSS-страницу W0), а общая игровая палитра kid.pal читается ПОСЛЕ него и
|
||
* затирает слоты 0x50..0x6F — она собрана из VDUNGEON
|
||
* (pop_pack_kid.py build_palette). Звать сразу за gfx_pal_fload. */
|
||
int pop_bg_pal_apply(void) __banked
|
||
{
|
||
if (bg_set == 0xFF) return -1;
|
||
return bg_load_tile_pal(pop_tile_pal[bg_set]);
|
||
}
|
||
|
||
void pop_bg_free(void) __banked
|
||
{
|
||
int i;
|
||
if (bg_set == 0xFF) return;
|
||
for (i = 0; i < POP_ENV_PAGES; i++)
|
||
atlas_free(&pop_env[i]);
|
||
atlas_free(&pop_wall_at);
|
||
atlas_free(&pop_fore_at);
|
||
atlas_free(&pop_pot_at);
|
||
bg_set = 0xFF;
|
||
}
|
||
|
||
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
|
||
|
||
/* ---- Чомпер (seg008.c:0B2B draw_tile_anim, ветка tiles_18_chomper) ---- *
|
||
* Кадр берётся НЕ прямо из modif: chomper_fram1[min(modif & 0x7F, 6)] даёт
|
||
* номер позы 0..4, и уже он индексирует три набора кусков — нижнюю челюсть
|
||
* (backtable), верхнюю (backtable, со своим подъёмом) и передний слой
|
||
* (foretable, поверх персонажа). Кровь — тот же номер позы + 114 (back) /
|
||
* +119 (fore), рисуется mono-силуэтом цветом 12; в атласе она уже такой
|
||
* (pop_pack_bg.py, 2026-08-07).
|
||
* Старший бит modif (0x80) = «в этом чомпере кого-то перемололо».
|
||
*
|
||
* Номер позы (pop_chomp_pose) и передние куски живут в РЕЗИДЕНТЕ
|
||
* (pop_tile.c): их читает ещё и fore-проход из горячей половины, а
|
||
* const-таблицы банка из чужого банка не видны. */
|
||
static const uint8_t CHOMP_FRAM_BOT[5] = {101, 102, 103, 104, 105};
|
||
static const uint8_t CHOMP_FRAM_TOP[5] = {0, 0, 111, 112, 113};
|
||
static const uint8_t CHOMP_FRAM_Y[5] = {0, 0, 0x25, 0x2F, 0x32};
|
||
|
||
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};
|
||
|
||
/* ---- Зелье (tiles_10_potion, seg008 draw_tile_anim/draw_tile_fore) ----
|
||
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
||
* склянка (id 13) и pot_size=1, остальные — маленькая (id 12). Спрайты — из
|
||
* chtab_1 (атлас pop_pot), поэтому НЕ из env: раньше мы рисовали chtab_6 id 12
|
||
* (res212), которого в VDUNGEON нет — оттуда и «мусорный объект».
|
||
* Позиции оригинала: склянка xh = fore_x + draw_xh, xl = 6, ybottom = fore_y +
|
||
* draw_main_y; пузырёк/маска xh = draw_xh + 3, xl = 1,
|
||
* ybottom = draw_main_y − (pot_size << 2) − 14. */
|
||
#define POT_MASK_ID 23
|
||
static const uint8_t POTION_FRAM_BUBB[8] = {0, 16, 17, 18, 19, 20, 21, 22};
|
||
|
||
static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3); }
|
||
|
||
|
||
|
||
/* Дворец? — по ЗАГРУЖЕННОМУ набору (pop_palace ставит pop_bg_load), а не по
|
||
* номеру уровня: так ветки отрисовки и картинки заведомо согласованы даже в
|
||
* момент смены. */
|
||
#define PALACE (pop_palace)
|
||
|
||
/* Декоративная панель над воротами — ТОЛЬКО в паласе (в подземелье над
|
||
* воротами не рисуется ничего). Индекс — модификатор тайла doortop.
|
||
* seg008:407 doortop_fram_top / seg008:453 doortop_fram_bot. */
|
||
static const uint8_t DOORTOP_FRAM_TOP[4] = {0, 81, 83, 0};
|
||
static const uint8_t DOORTOP_FRAM_BOT[4] = {78, 80, 82, 0};
|
||
|
||
/* Верх ворот: его рисует тайл НАД воротами (draw_tile_anim_topright,
|
||
* seg008:0568). Кадр — по открытости соседа снизу-слева; 68 — маска,
|
||
* mono-блит чёрным «продавливает» фон под створкой.
|
||
* Без этого над воротами оставался чёрный клин — виден у шва во дворце,
|
||
* где сверху идёт декоративная панель doortop. */
|
||
static const uint8_t DOOR_FRAM_TOP[8] = {60, 61, 62, 63, 64, 65, 66, 67};
|
||
#define GATE_TOP_MASK 68
|
||
|
||
/* topright тайла снизу-слева (seg008.c:410). */
|
||
static void draw_topright(uint8_t tt, uint8_t tmod, int x, int dby)
|
||
{
|
||
if (tt == 7 || tt == 12) { /* верх ворот */
|
||
if (!PALACE) return; /* подземелье: ничего (seg008:413) */
|
||
if (DOORTOP_FRAM_TOP[tmod & 3])
|
||
pop_env_b(DOORTOP_FRAM_TOP[tmod & 3], x, dby);
|
||
return;
|
||
}
|
||
if (tt == 20) { pop_wall_b(2, x, dby); return; }
|
||
if (pop_tile_table[tt].topright_id)
|
||
pop_env_b(pop_tile_table[tt].topright_id, x, dby);
|
||
}
|
||
|
||
|
||
/* ---- 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. */
|
||
static const uint8_t LOOSE_FRAM_RIGHT[12] = {42,71,42,72,72,42,42,42,72,72,72,0};
|
||
|
||
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
||
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
|
||
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
|
||
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
|
||
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Пример: упала плита
|
||
* (0,6); её запечка перерисовывает и соседа, а правый угол СОСЕДНЕЙ плиты
|
||
* (0,5), который рисуется в ячейке (0,6), был в этот момент в тряске.
|
||
* Фон обязан быть состоянием ПОКОЯ — анимация каждый кадр кладётся поверх
|
||
* (банк SPRITE, только видео-ОЗУ). */
|
||
/* draw_leveldoor (seg008:1D29) — дверь уровня. Рисуется при обработке
|
||
* ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8px (xh+1):
|
||
* 99 низ лестницы (всегда),
|
||
* 144 марш лестницы за створкой (когда створка приподнята, modif != 0),
|
||
* 33 слайс створки — повторяется вниз с шагом 4px, пока не дойдёт до
|
||
* y = ybottom − modif: modif 0 = закрыто (створка на всю высоту),
|
||
* 43 = открыто (остаётся только верхняя кромка),
|
||
* 34 верх коробки двери.
|
||
* Куски непрозрачные (blitters_0_no_transp) — как в оригинале. */
|
||
static void draw_leveldoor(uint8_t modif, int xh, int dmy)
|
||
{
|
||
int x = (xh + 1) * 8;
|
||
int ybottom = dmy - 13;
|
||
int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */
|
||
int y = ybottom - (int)modif;
|
||
/* leveldoor_right (seg008:1D29): правая кромка проёма — от xh ТЕКУЩЕГО
|
||
* тайла, то есть ПРАВОЙ половины двери (сюда мы попадаем из
|
||
* draw_tile_anim_right, разбирая левого соседа). +8 у palace-уровней
|
||
* (tbl_level_type) — на уровне 1 не применяется. */
|
||
pop_leveldoor_right = xh * 8 + 48;
|
||
pop_env_b(99, x, ybottom);
|
||
if (modif)
|
||
pop_env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */
|
||
for (;;) {
|
||
pop_env_b(33, x, ylo);
|
||
if (y > ylo) ylo += 4;
|
||
else break;
|
||
}
|
||
/* ИМЕННО после цикла: в оригинале clip_char читает уже доведённое
|
||
* значение (низ поднятой створки), а не стартовое. */
|
||
pop_leveldoor_ybottom = ylo;
|
||
pop_env_b(34, x, dmy - 64);
|
||
}
|
||
|
||
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
||
static void draw_tile(int row, int col)
|
||
{
|
||
uint8_t code = pop_tile_code(row, col);
|
||
uint8_t lcode = pop_tile_code(row, col - 1);
|
||
uint8_t lmod = pop_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(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||
else if (code == 0x06) { if ((pop_doorlink2(pop_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 = POP_COL_XH[col];
|
||
int x = xh * 8;
|
||
int dby = 63 * row + 65;
|
||
int dmy = dby - 3;
|
||
const piece *t = &pop_tile_table[code];
|
||
const piece *lt = &pop_tile_table[lcode];
|
||
uint8_t rbl_code, rbl_mod;
|
||
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;
|
||
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
|
||
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
|
||
* «стена» там стоит, только если такой комнаты нет — как в
|
||
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
|
||
if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
|
||
else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 1;
|
||
/* Модификатора ряда снизу у нас нет (pop_t_below — только fg), а он
|
||
* нужен паласной панели над воротами. Ставим 0 = «панели нет»:
|
||
* случай узкий (ворота в комнате СНИЗУ, по диагонали от пустого
|
||
* тайла нижнего ряда), но расхождение с оригиналом честное. */
|
||
rbl_mod = 0;
|
||
} else {
|
||
rbl_code = pop_tile_code(row + 1, col - 1);
|
||
rbl_mod = pop_tile_mod(row + 1, col - 1);
|
||
}
|
||
|
||
/* --- draw_tile_anim_topright (seg008:0568): верх ВОРОТ снизу-слева.
|
||
* Условие дословно: свой тайл пуст / верх большой колонны / верх двери,
|
||
* а снизу-слева — ворота. --- */
|
||
if ((code == 0 || code == 9 || code == 12) && rbl_code == 4) {
|
||
uint8_t om = (rbl_mod > 188) ? 188 : rbl_mod;
|
||
pop_env_b(GATE_TOP_MASK, x, dby); /* маска (mono, чёрным) */
|
||
pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], x, dby);
|
||
}
|
||
|
||
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
|
||
if (code == 0 || code == 9 || code == 12 || code == 26) {
|
||
draw_topright(rbl_code, rbl_mod, 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) {
|
||
/* Полоска на стене (seg008:510) — только палас и только если у
|
||
* стены не взведён старший бит модификатора. */
|
||
if (PALACE && (lmod & 0x80) == 0)
|
||
pop_env_b(84, x + 3 * 8, dmy - 27);
|
||
pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */
|
||
} else if (lcode == 0) {
|
||
if (lmod <= 3 && BLUELINE_FRAM1[lmod])
|
||
pop_env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy);
|
||
} else if (lcode == 1) {
|
||
uint8_t num;
|
||
pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */
|
||
num = (lmod <= 3) ? lmod : 0;
|
||
/* seg008:501 — `if (num == !!level_type) return;`: в подземелье
|
||
* пропускается num==0, в паласе num==1. */
|
||
if (num != (PALACE ? 1 : 0))
|
||
pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */
|
||
} else if (lcode == 7 || lcode == 12) {
|
||
/* Низ декоративной панели над воротами (seg008:506) — только
|
||
* палас; в подземелье у doortop right_id нулевой и ветка пустая. */
|
||
if (PALACE && DOORTOP_FRAM_BOT[lmod & 3])
|
||
pop_env_b(DOORTOP_FRAM_BOT[lmod & 3], x, lt->right_y + dmy);
|
||
} else {
|
||
if (lt->right_id)
|
||
pop_env_b(lt->right_id, x, lt->right_y + dmy);
|
||
/* Орнаментная лента соседа (seg008:486) — только палас. Она и
|
||
* даёт непрерывную полосу под окнами: stripe_id = 145 у пола,
|
||
* кнопок, зелья, loose, чомпера и меча. */
|
||
if (PALACE && lt->stripe_id)
|
||
pop_env_b(lt->stripe_id, x, dmy - 27);
|
||
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
|
||
if (lcode == 19 || lcode == 30)
|
||
pop_env_b(146, x, dby - 28);
|
||
}
|
||
}
|
||
|
||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
||
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||
* отрицательного индекса в pop_loose_modif. --- */
|
||
if (row >= 0) {
|
||
if (lcode == 4) { /* gate: решётка */
|
||
pop_gate_back_b(lmod, xh, dby, dmy);
|
||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||
pop_env_b(LOOSE_FRAM_RIGHT[pop_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])
|
||
pop_env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||
} else if (lcode == 16) { /* дверь уровня */
|
||
draw_leveldoor(lmod, xh, dmy);
|
||
}
|
||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
|
||
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
|
||
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
|
||
}
|
||
|
||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||
if (code == 20) {
|
||
wmod = pop_wall_modifier(row, col);
|
||
if (!PALACE) /* во дворце тело стены даёт */
|
||
pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); /* wall_pattern */
|
||
/* заливками (seg008:576) */
|
||
pop_wall_pattern_b(row, col, 0);
|
||
} else if (t->bottom_id) {
|
||
pop_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)
|
||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_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 = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
|
||
else if (code == 0x0F && lcode == 0 && !PALACE) /* opener, слева пусто */
|
||
base_id = 148; /* левая половина без пола — только */
|
||
/* подземелье (seg008:628) */
|
||
if (base_id)
|
||
pop_env_b(base_id, x, t->base_y + dmy);
|
||
}
|
||
|
||
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
|
||
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
|
||
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
||
* пустая. */
|
||
if (row >= 0 && code == 2) {
|
||
uint8_t sm = pop_tile_mod(row, col);
|
||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||
}
|
||
/* Чомпер (тот же draw_tile_anim, case tiles_18_chomper): нижняя челюсть
|
||
* на draw_main_y, поверх неё кровь (если кого-то перемололо), затем
|
||
* верхняя челюсть со своим подъёмом. Всё это BACKtable — то есть ПОД
|
||
* персонажем; передний слой рисует fore-проход. */
|
||
if (row >= 0 && code == 0x12) {
|
||
uint8_t cm = pop_tile_mod(row, col);
|
||
uint8_t pose = pop_chomp_pose(cm);
|
||
pop_env_b(CHOMP_FRAM_BOT[pose], x, dmy);
|
||
if (cm & 0x80)
|
||
pop_env_b((uint8_t)(pose + 114), x + 8, dmy - 6);
|
||
if (CHOMP_FRAM_TOP[pose])
|
||
pop_env_b(CHOMP_FRAM_TOP[pose], x, dmy - CHOMP_FRAM_Y[pose]);
|
||
}
|
||
/* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
|
||
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
|
||
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
|
||
* в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */
|
||
if (row >= 0 && code == 22)
|
||
pop_pot_b((uint8_t)((pop_tile_mod(row, col) == 1) + 10), x, dmy - 3);
|
||
|
||
/* --- draw_tile_fore (seg008.c:690) --- */
|
||
if (code == 20) {
|
||
wmod = pop_wall_modifier(row, col);
|
||
if (!PALACE) /* seg008:711 */
|
||
pop_wall_b(POP_WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
|
||
pop_wall_pattern_b(row, col, 1);
|
||
}
|
||
if (code == 0x12) { /* чомпер: передние зубья + кровь.
|
||
* Отдельной веткой, как в оригинале:
|
||
* у записи 0x12 fore_id == 0, и через
|
||
* таблицу этот слой не идёт. */
|
||
uint8_t cm = pop_tile_mod(row, col);
|
||
uint8_t pose = pop_chomp_pose(cm);
|
||
/* Передние куски чомпера и его кровь лежат в pop_fore.atl
|
||
* (FORE_ENV_IDS в pop_pack_bg.py), а НЕ в env-страницах: блит
|
||
* обязан быть pop_fore_b, иначе id уходит в чужую страницу. */
|
||
pop_fore_b(POP_CHOMP_FRAM_FOR[pose], x, dmy);
|
||
if (cm & 0x80)
|
||
pop_fore_b((uint8_t)(pose + 119), x + 8, dmy - 6);
|
||
}
|
||
if (code == 10) { /* зелье: chtab_1, не env fore_id.
|
||
* Пузырёк — динамика (pop_potion_draw). */
|
||
pop_potion_flask(row, col, pop_tile_mod(row, col));
|
||
} else if (t->fore_id) {
|
||
pop_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) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
||
* перерисовка обоих тайлов с дрожащим кадром (draw_tile берёт кадр из
|
||
* pop_loose_modif). Порт redraw_tile_height (loose + правый сосед).
|
||
* NB: печёный кадр-0 может слегка просвечивать по кромкам — чистое
|
||
* решение (bake тайла как empty + рисовать loose всегда динамически)
|
||
* отложено; wipe-в-чёрный делал плиту невидимой — не годится. */
|
||
pop_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 = pop_t_bg[row*10+col] = живой modif пики). pop_t_bg должен указывать
|
||
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
||
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
||
void pop_spike_redraw(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
||
* redraw ДВУХ тайлов — своего и правого соседа (остриё рисуется в его
|
||
* ячейке). Именно ПОЛНЫЙ: рисовать только два спрайта остриёв нельзя —
|
||
* порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и
|
||
* fore соседа ложатся ПОВЕРХ) тогда теряется, и остриё лезет на колонну
|
||
* справа. Заодно draw_tile сам восстанавливает вклад соседних пик:
|
||
* в ячейке col — правое остриё пики col-1 (ветка lcode==2).
|
||
* Шипы выдвигаются выше линии пола (dmy-7), поэтому heal с запасом вверх
|
||
* (63*row+34 .. +74); ширина 64 = обе ячейки. */
|
||
pop_heal_off(x, 63 * row + 34, 64, 40);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10)
|
||
draw_tile(row, col + 1);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Чомпер: перерисовка кадра анимации (порт redraw_20h/draw_trob). Челюсти
|
||
* поднимаются высоко над линией пола (верхний кусок уходит на 0x32 = 50 px
|
||
* вверх от dmy), поэтому heal берём на всю высоту тайла. Правый сосед НЕ
|
||
* нужен: в отличие от пик, все куски чомпера рисуются в СВОЕЙ ячейке. */
|
||
void pop_chomp_redraw(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
pop_heal_off(x, 63 * row + 2, 32, 64);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
draw_tile(row, col);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
|
||
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
|
||
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]).
|
||
* pop_t_lbg (lcol_bg) должен указывать на живой modif (roomtest обновляет из
|
||
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
||
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
||
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
||
void pop_room_redraw_seam_left(uint8_t rows) __banked
|
||
{
|
||
int8_t r;
|
||
/* rows — битовая маска рядов 0..2: перерисовываем ТОЛЬКО те, где openness
|
||
* реально изменился. Раньше ряд был жёстко нулевым, потому что на уровне 1
|
||
* решётка соседа стояла в (0,9); на уровне 4 она в (1,9), и открытые ворота
|
||
* оставались нарисованными закрытыми (BUG-GATE-SEAM-ROW1). */
|
||
for (r = 0; r < 3; r++) {
|
||
int yt = POP_YOFF + 63 * r;
|
||
if (!(rows & (1 << r))) continue;
|
||
pop_cd_touch(0, yt + 3, 32, 62); /* bar мимо pop_blit_b (DRAW-COST) */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
/* Полосу кладки НАД рядом НЕ трогаем: её низ на room-space 63*r+2, а
|
||
* бары ворот начинаются с 63*r+3 (gate_top_y = dby−62). Раньше bar шёл
|
||
* от POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать
|
||
* ещё и draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации. */
|
||
bar(0, yt + 3, 31, yt + 64); /* col0 этого ряда (бары ворот) */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
draw_tile(r, 0); /* фон col0 + бары ворот (живой modif) */
|
||
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 её сохранял). */
|
||
|
||
void pop_ceil_shake_draw(int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
pop_heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
pop_t_clip_top = POP_YOFF;
|
||
draw_tile(-1, col);
|
||
if (col + 1 < 10) draw_tile(-1, col + 1);
|
||
pop_t_clip_top = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
void pop_ceil_bake_empty(int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* bar мимо pop_blit_b */
|
||
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);
|
||
pop_t_clip_top = POP_YOFF;
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
||
* нижней грани и свисают ВПРАВО, поэтому в полосу над тайлом col лезут:
|
||
* • ряд −1, колонки col−1..col+1 — сама полоса кладки у потолка;
|
||
* • ряд 0, колонки col−1..col+1 — их верхушки заходят в те же 8 px.
|
||
* Раньше перерисовывались только (−1,col) и (−1,col+1), и в полосе
|
||
* оставался чёрный прямоугольник ~13×3 px (замер: экранные y 47..52 при
|
||
* полосе 44..52), который heal возвращал каждый кадр. Признак, что дело
|
||
* именно в запечке: после выхода из комнаты и возврата бара нет. */
|
||
{
|
||
int c0 = (col > 0) ? col - 1 : 0;
|
||
int c1 = (col + 1 < 9) ? col + 1 : 9;
|
||
int c;
|
||
for (c = c0; c <= c1; c++) draw_tile(-1, c);
|
||
for (c = c0; c <= c1; c++) draw_tile(0, c);
|
||
}
|
||
pop_t_bake_rest = 0;
|
||
pop_t_clip_top = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* ---- Анимация факела и зелья (динамика, каждый кадр) ---------------- *
|
||
* Оба кадра рисуются ПОВЕРХ статического фона и стираются heal'ом своей
|
||
* области (фон запечён БЕЗ них). Факел: пламя chtab_1 id modifier+1 в
|
||
* ячейке ПРАВОГО соседа (seg008:560: xh = draw_xh + 1, ybottom =
|
||
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
||
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
||
* (pot_size<<2) − 14 (seg008:668). */
|
||
void pop_torch_draw(int row, int col, uint8_t modif) __banked
|
||
{
|
||
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = POP_COL_XH[col+1] + 1
|
||
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
||
* 146), которое тоже лежит в ячейке соседа. */
|
||
int x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
||
int yb = 63 * row + 62 - 40;
|
||
if (modif >= 9) return;
|
||
/* heal НЕ нужен: оригинал рисует пламя blitters_0_no_transp (seg008
|
||
* draw_tile_anim_right), и все 9 кадров лежат на общем канвасе 16x18 —
|
||
* непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры
|
||
* упакованы с opaque=True (pop_pack_bg POT_FLAME_IDS). */
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
pop_pot_b((uint8_t)(modif + 1), x, yb);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
void pop_potion_draw(int row, int col, uint8_t modif) __banked
|
||
{
|
||
uint8_t type = potion_type(modif);
|
||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||
int x = (POP_COL_XH[col] + 3) * 8 + 1;
|
||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
||
if (type == 0) return;
|
||
pop_heal_off(x - 1, yb - 10, 8, 12);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
pop_pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
|
||
pop_pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
||
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) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
pop_cd_touch(x, POP_YOFF + 63 * row + 26, 60, 39); /* bar мимо pop_blit_b */
|
||
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);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10) draw_tile(row, col + 1);
|
||
pop_t_bake_rest = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
|
||
|
||
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
||
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
||
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
||
* Куски двери непрозрачные — wipe не нужен. */
|
||
void pop_leveldoor_redraw(int row, int col) __banked
|
||
{
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
draw_tile(row, col);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Ворота ВНУТРИ комнаты: перерисовать бары по живому modif (порт draw_trob,
|
||
* seg007:01E6 — им заканчивается animate_door). (row,col) — сами ворота;
|
||
* решётку рисует ячейка ПРАВОГО соседа (draw_tile, ветка lcode==4), поэтому
|
||
* wipe и redraw идут по ней. Логика ровно как у левого шва
|
||
* (pop_room_redraw_seam_left), только колонка произвольная.
|
||
*
|
||
* Bar-в-чёрный обязателен: бары висят в видео-ОЗУ и ОЗУ-копии, и при
|
||
* ПОДЪЁМЕ решётки heal вернул бы запечённые нижние бары. Полоса — ровно
|
||
* зона решётки: от gate_top_y (dby−62 = 63*row+3) до низа (63*row+64);
|
||
* выше начинается ряд −1, ниже — стена ряда ниже.
|
||
*
|
||
* Ворота в col9: их бары рисуются уже в СОСЕДНЕЙ комнате, в отрисованной их
|
||
* не видно — оригинал такой trob тоже пропускает
|
||
* (get_trob_right_pos_in_drawn_room → 30). Для взгляда из комнаты справа
|
||
* работает pop_room_redraw_seam_left. */
|
||
void pop_gate_redraw(int row, int col) __banked
|
||
{
|
||
int x, yt;
|
||
if (col + 1 > 9 || row < 0 || row > 2) return;
|
||
x = POP_COL_XH[col + 1] * 8;
|
||
yt = POP_YOFF + 63 * row;
|
||
pop_cd_touch(x, yt + 3, 32, 62); /* bar мимо pop_blit_b (DRAW-COST) */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
bar(x, yt + 3, x + 31, yt + 64);
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
draw_tile(row, col + 1);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
}
|
||
|
||
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||
* рисовал loose-правый-край pop_env_b(42); теперь пусто → чистая грань).
|
||
* pop_t_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
||
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
||
void pop_loose_bake_empty(int row, int col) __banked
|
||
{
|
||
int x = POP_COL_XH[col] * 8;
|
||
int xr = POP_COL_XH[col + 1] * 8;
|
||
int yb = 63 * row + 46;
|
||
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
||
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
||
* heal вернул бы старый пол из ОЗУ-копии. Ширина 40 — кусок пола
|
||
* заходит на x+32..+40. */
|
||
/* НИЗ строго 63*row+65 = низ собственной графики плиты (bottom_id 43/73/74
|
||
* стоит ровно на dby; base 41/69/70 — до dmy=62; правая грань 42/66/67 —
|
||
* до dby-1). Ниже начинается СТЕНА ряда ниже (высокий спрайт), которую
|
||
* здесь не перерисовываем: заливка на 9 строк глубже оставляла ЧЁРНЫЙ БОКС
|
||
* под соседним столбом, причём в ОЗУ-копии — то есть heal возвращал его
|
||
* снова, и на второй странице дабл-буфера его не было → мерцание.
|
||
* Тот же урок уже учтён в pop_floor_bake. */
|
||
yb += POP_YOFF;
|
||
/* bar идёт мимо pop_blit_b — пометить область самим (DRAW-COST). */
|
||
pop_cd_touch(x, yb, 72, POP_YOFF + 63 * row + 66 - yb);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
bar(x, yb, x + 39, POP_YOFF + 63 * row + 65); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
||
if (col + 1 < 10)
|
||
bar(xr, yb, xr + 31, POP_YOFF + 63 * row + 65); /* сосед: РОВНО 32 — не залезть в col+2 (его не перерисовываем) */
|
||
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
||
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
||
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
||
* ОЗУ-копию → heal сохранит. */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
draw_tile(row, col);
|
||
if (col + 1 < 10)
|
||
draw_tile(row, col + 1);
|
||
pop_t_bake_rest = 0;
|
||
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. */
|
||
/* Падающие куски — ТАБЛИЦА (порт mobs[14]/mobs_count, seg007:965). Одного
|
||
* куска не хватает: в комнате 7 плиты (0,5) и (0,6) соседние, Kid роняет обе
|
||
* подряд, и второй спавн затирал состояние первого — его пиксели оставались
|
||
* висеть в воздухе (на каждой странице на своей y → мерцание), а щебень
|
||
* появлялся только под одной плитой. 4 слота: больше одновременно падающих
|
||
* плит в одной комнате практически не бывает (в оригинале 14). */
|
||
#define MOB_MAX 4
|
||
typedef struct {
|
||
uint8_t active; /* 0 нет; 1 падает */
|
||
uint8_t clean; /* кадров дочистки после конца полёта (2 = обе стр.) */
|
||
uint8_t defer; /* кусок ниже Kid → рисуем ПОСЛЕ него (y-порядок) */
|
||
int x; /* левый край (POP_COL_XH*8) */
|
||
int y; /* = curmob.y оригинала (низ куска, dby), растёт вниз */
|
||
int prev_y[2]; /* последняя нарисованная y на КАЖДОЙ странице (heal) */
|
||
int8_t speed;
|
||
int8_t row, col; /* тайл-позиция куска (для окклюзии соседа) */
|
||
uint8_t room; /* curmob.room: кусок падает В СВОЕЙ комнате, не в текущей */
|
||
} mob_t;
|
||
static mob_t mobs[MOB_MAX];
|
||
|
||
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
||
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
||
/* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
|
||
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
|
||
|
||
/* 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) __banked
|
||
{
|
||
uint8_t i;
|
||
mob_t *m = 0;
|
||
/* свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
||
* который ещё не стёрт со второй страницы) */
|
||
for (i = 0; i < MOB_MAX; i++)
|
||
if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; }
|
||
if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */
|
||
m->x = POP_COL_XH[col] * 8;
|
||
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
|
||
m->row = (int8_t)row;
|
||
m->col = (int8_t)col;
|
||
m->room = pop_t_room; /* curmob.room (add_mob, seg007:1010) */
|
||
m->speed = 0;
|
||
m->active = 1;
|
||
m->clean = 0;
|
||
m->defer = 0;
|
||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||
}
|
||
|
||
/* Габарит куска по 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 не
|
||
* должно утечь в новую комнату). */
|
||
static void mob_render(mob_t *m, uint8_t pg);
|
||
|
||
void pop_loose_mob_reset(void) __banked
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < MOB_MAX; i++) {
|
||
mobs[i].active = 0; mobs[i].clean = 0; mobs[i].defer = 0;
|
||
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
||
}
|
||
}
|
||
|
||
/* Смена комнаты. Куски НЕ останавливаем: в оригинале mobs[] чистится только
|
||
* в start_level (seg003:88), а do_mobs (seg007:1063) крутит их все независимо
|
||
* от drawn_room — move_loose работает по curmob.room, и плита, за которой Кид
|
||
* не досмотрел, всё равно долетает и кладёт щебень (BUG-LOOSE-2: в комнате 12
|
||
* плита 0,1 не успевала упасть до ухода в комнату 15). Сбросить надо только
|
||
* heal-историю: новая комната перерисована целиком, старых пикселей нет. */
|
||
void pop_loose_mob_room_changed(void) __banked
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < MOB_MAX; i++) {
|
||
mobs[i].defer = 0;
|
||
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
||
/* дочистка хвоста относилась к покинутой комнате — она уже не нужна */
|
||
if (!mobs[i].active) mobs[i].clean = 0;
|
||
}
|
||
}
|
||
|
||
/* Тайл под куском. В своей комнате — обычный tile_code (он знает про кромки
|
||
* и соседей), в чужой — прямо из данных уровня. */
|
||
static uint8_t mob_tile_at(mob_t *m)
|
||
{
|
||
if (m->room == pop_t_room) return (uint8_t)(pop_tile_code(m->row, m->col) & 0x1F);
|
||
if (m->row < 0 || m->row > 2 || m->col < 0 || m->col > 9) return 1; /* кромка = пол */
|
||
return (uint8_t)(pop_level_tile(m->room, (uint8_t)(m->row * 10 + m->col)) & 0x1F);
|
||
}
|
||
|
||
static void mob_tick_one(mob_t *m, uint8_t pg)
|
||
{
|
||
uint8_t here;
|
||
if (!m->active && !m->clean) return;
|
||
here = (uint8_t)(m->room == pop_t_room);
|
||
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
|
||
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
|
||
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
|
||
* прошлой позиции. */
|
||
if (here && m->prev_y[pg] != MOB_Y_NONE) {
|
||
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32);
|
||
m->prev_y[pg] = MOB_Y_NONE;
|
||
}
|
||
if (!m->active) { m->clean--; return; }
|
||
/* гравитация (move_loose): ускорение до 29 */
|
||
if (m->speed < 29) m->speed += 3;
|
||
m->y += m->speed;
|
||
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
|
||
* смотрим тайл этого ряда. Пусто/loose — валится дальше (порт
|
||
* loose_fall «плита ломает плиту» не нужен: в ряду ниже её нет);
|
||
* пол — приземление (loose_land: debris + do_knock). */
|
||
if (m->row < 3 && m->y < 226 && MOB_Y_BOUND[m->row + 1] <= m->y) {
|
||
uint8_t t = mob_tile_at(m);
|
||
if (t == 0 || t == 11) {
|
||
m->row++; /* mob_down_a_row */
|
||
} else if (!here) {
|
||
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, сигнальная
|
||
* пара pop_loose_landed/pop_debris_at обслуживает только текущую
|
||
* комнату — щебень кладём прямо в данные уровня (loose_land пишет
|
||
* в curr_room_tiles комнаты curmob.room, а не отрисованной). */
|
||
pop_level_set_tile(m->room, (uint8_t)(m->row * 10 + m->col), 0x0E);
|
||
m->active = 0; m->clean = 0;
|
||
return;
|
||
} else if (pop_loose_landed) {
|
||
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
|
||
* подержим кусок на границе и приземлим следующим кадром */
|
||
m->y = MOB_Y_BOUND[m->row + 1];
|
||
} else {
|
||
m->y = MOB_Y_BOUND[m->row + 1];
|
||
pop_loose_landed = (uint8_t)(m->row * 10 + m->col + 1);
|
||
m->active = 0; m->clean = 2; /* кусок исчез — на его месте debris */
|
||
return;
|
||
}
|
||
}
|
||
if (m->y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
|
||
m->active = 0; m->clean = (uint8_t)(here ? 2 : 0);
|
||
return;
|
||
}
|
||
if (!here) 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)(((m->y + 60) / 63) % 4 - 1); /* y_to_row_mod4 */
|
||
uint8_t over;
|
||
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */
|
||
else if (m->col != pop_bg_obj_col) over = (m->col > pop_bg_obj_col);
|
||
else over = (m->y > pop_cd[POP_CH_KID].fpy);
|
||
if (over) { m->defer = 1; return; }
|
||
}
|
||
mob_render(m, pg);
|
||
}
|
||
|
||
void pop_loose_mob_tick(void) __banked
|
||
{
|
||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
||
for (i = 0; i < MOB_MAX; i++) {
|
||
/* Летящий кусок и его дочистка ходят по чужим пикселям СВОИМ heal'ом,
|
||
* мимо pop_blit_b — пока он в кадре, персонажей пропускать нельзя
|
||
* (DRAW-COST). Взводим ДО тика: кусок может исчезнуть прямо в нём, а
|
||
* стереть себя ему всё равно надо будет. */
|
||
if (mobs[i].active || mobs[i].clean)
|
||
pop_cd_touch(MOB_X0(mobs[i].x), mobs[i].y - 27 + POP_YOFF, MOB_W, 64);
|
||
mob_tick_one(&mobs[i], pg);
|
||
}
|
||
}
|
||
|
||
static void mob_render(mob_t *m, 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);
|
||
pop_env_b(43, m->x, m->y);
|
||
pop_env_b(41, m->x, m->y - 3);
|
||
pop_env_b(42, m->x + 32, m->y - 1);
|
||
m->prev_y[pg] = m->y;
|
||
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
|
||
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
|
||
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
|
||
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
|
||
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
|
||
* до heal'а следующего кадра — виден как хвост во время падения). */
|
||
if (m->col + 1 < 10) {
|
||
/* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из
|
||
* m->row: последний — логический счётчик «сквозь какой ряд летим», и
|
||
* mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда —
|
||
* сразу в 3, которого нет). Тогда пол соседа не перерисовывался
|
||
* вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна
|
||
* через пол + перекрыта передняя грань пола.
|
||
* Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на
|
||
* границе рядов. Порт draw_mob (seg007:13E5). */
|
||
int8_t r = (int8_t)(((m->y + 60) / 63) % 4 - 1);
|
||
int8_t rt = (int8_t)(((m->y - 18 + 60) / 63) % 4 - 1);
|
||
if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */
|
||
draw_tile(r, m->col + 1);
|
||
pop_t_clip_top = 0;
|
||
if (rt != r) {
|
||
if (rt < 0) pop_t_clip_top = POP_YOFF;
|
||
draw_tile(rt, m->col + 1);
|
||
pop_t_clip_top = 0;
|
||
}
|
||
}
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
|
||
pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30);
|
||
}
|
||
|
||
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
|
||
* после отрисовки персонажей, до fore-слоя. */
|
||
void pop_loose_mob_draw_over(void) __banked
|
||
{
|
||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
||
for (i = 0; i < MOB_MAX; i++)
|
||
if (mobs[i].defer) { mobs[i].defer = 0; mob_render(&mobs[i], pg); }
|
||
}
|
||
|
||
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
|
||
* y (низ куска). 0 — куска нет. */
|
||
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
|
||
{
|
||
uint8_t i;
|
||
/* check_loose_fall_on_kid (seg007:1192) начинается с Char.room ==
|
||
* curmob.room: кусок, падающий в покинутой комнате, Киду по голове не
|
||
* бьёт. */
|
||
for (i = 0; i < MOB_MAX; i++)
|
||
if (mobs[i].active && mobs[i].room == pop_t_room) {
|
||
*col = mobs[i].col; *y = mobs[i].y; return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Спрайт вышел за поле? → взвести флаг на 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) __banked
|
||
{
|
||
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) __banked
|
||
{
|
||
if (!border_dirty) return;
|
||
border_dirty--;
|
||
/* ОСТАЁТСЯ клипающим осознанно (CLIP-1, 2026-08-01): полоса шириной 320
|
||
* не лезет в 8-битный параметр noclip-ядра, а разбивать её на два куска
|
||
* по 160 нет смысла — гейт border_dirty пускает сюда только в кадрах
|
||
* падения/выхода за поле, и выигрыш (~2×5.6 К тактов подготовки) там
|
||
* тонет в цене самих 320×28 пикселей. Не «оптимизировать» повторно. */
|
||
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) __banked
|
||
{
|
||
int row, col;
|
||
pop_t_room = room_num;
|
||
pop_wall_pattern_reset_b(); /* кэш кладки привязан к номеру комнаты */
|
||
pop_t_fg = fg; pop_t_bg = bg;
|
||
pop_t_lfg = lcol_fg; pop_t_lbg = lcol_bg;
|
||
|
||
/* прозрачный блит: 0xFF не пишется (чёрный фон/нижние слои проступают) */
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||
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) рисует ТОЛЬКО срез у потолка, без зачернения борта. */
|
||
pop_t_clip_top = POP_YOFF;
|
||
for (col = 0; col < 10; col++)
|
||
draw_tile(-1, col);
|
||
pop_t_clip_top = 0;
|
||
pop_t_bake_rest = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
} |