055d6d7c89
Первый запуск после разгрузки встал в halt по мусору в 0xC40E. Причина: pop_level_read_file и pop_kid_data_load маппят страницу данных в W3 на время read() (файл читается по 0xC000), а модули банка исполняются ИЗ ЭТОГО ЖЕ окна — после sprinter_page_w3() следующая инструкция приходит уже из чужой страницы. Обе функции вернулись в резидент; в банке остался код, который ходит через W0 (gfx_w0_map) или окон не трогает вовсе. Правило записано в _pop_level.h и _pop_kid.h: банковый модуль НЕ маппит W3 (через W0 — можно). Поэтому же в резиденте живёт весь libbgi. Куча 6095 Б (было 902 до разгрузки). Проверено в MAME: игра стартует, комната рисуется, Кид управляем. tests-host 6/6.
281 lines
15 KiB
C
281 lines
15 KiB
C
/*
|
|
* pop_level_cold.c — ХОЛОДНАЯ половина уровня (БАНК 8). Контракт и деление
|
|
* половин — в _pop_level.h.
|
|
*
|
|
* Здесь всё, что отрабатывает раз на уровень (чтение файла res200N.bin,
|
|
* рестарт тайлов/стражей, потабличные различия) или раз на комнату (разбор
|
|
* комнаты в рабочие массивы, посадочный ряд колонки, страж комнаты). Цена
|
|
* выноса — один трамплин на вызов; в кадре этих вызовов ноль.
|
|
*
|
|
* Формат — порт blueprnt DAT 1.0 (Table 6 POP-DAT).
|
|
*/
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sprinter.h>
|
|
#include <sprinter_mem.h>
|
|
#include <sprite.h> /* gfx_w0_map / gfx_w0_unmap */
|
|
#include "pop_level.h"
|
|
#include "_pop_level.h"
|
|
|
|
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
|
|
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
|
|
* _gfx_w0_isr (восстановит окно). Символ внутренний для libbgi, но
|
|
* стабильный — линкер тянет его из bgi256.lib (графика уже подключена). */
|
|
extern void _gfx_w0_isr(void);
|
|
|
|
/* ЧТЕНИЕ ФАЙЛА уровня живёт в РЕЗИДЕНТЕ (pop_level.c, pop_level_read_file):
|
|
* оно маппит страницу в W3 на время read(), а этот модуль сам лежит в W3 —
|
|
* переключив окно, он вырезал бы из-под себя собственный код (проверено:
|
|
* первый же вызов улетал в halt по мусору). Правило записано в _pop_level.h. */
|
|
|
|
void pop_level_free(void) __banked
|
|
{
|
|
if (pop_lvl_ok) { mem_free_block(pop_lvl_blk); pop_lvl_ok = 0; }
|
|
}
|
|
|
|
/* ---- Потабличные различия уровней ------------------------------------ */
|
|
|
|
/* Таблицы из SDLPoP data.h:840..848, индекс = номер уровня (0 = демо).
|
|
* Держим целиком по 16 байт: короче и дешевле любых «оптимизаций», а
|
|
* расхождение с оригиналом сразу видно глазами. */
|
|
static const uint8_t tbl_entry_pose[16] =
|
|
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 };
|
|
static const uint8_t tbl_guard_hp[16] =
|
|
{ 4, 3, 3, 3, 3, 4, 5, 4, 4, 5, 5, 5, 4, 6, 0, 0 };
|
|
static const int8_t tbl_guard_type[16] =
|
|
{ 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 4, 3, -1, -1 };
|
|
/* tbl_level_type (SDLPoP data.h:840): 0 — подземелье, 1 — дворец. По нему
|
|
* расходятся не только атласы, но и ветки отрисовки (seg008), кладка стены
|
|
* (wall_pattern) и модификатор пустой клетки от упавшей плиты
|
|
* (remove_loose, seg007:0EB8). */
|
|
static const uint8_t tbl_level_type[16] =
|
|
{ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0 };
|
|
|
|
uint8_t pop_level_entry_pose(uint8_t n) __banked { return n < 16 ? tbl_entry_pose[n] : 0; }
|
|
uint8_t pop_level_guard_hp(uint8_t n) __banked { return n < 16 ? tbl_guard_hp[n] : 3; }
|
|
int8_t pop_level_guard_type(uint8_t n) __banked { return n < 16 ? tbl_guard_type[n] : -1; }
|
|
uint8_t pop_level_tileset(uint8_t n) __banked { return n < 16 ? tbl_level_type[n] : 0; }
|
|
uint8_t pop_level_type(void) __banked { return tbl_level_type[pop_current_level & 15]; }
|
|
|
|
int pop_level_load_num(uint8_t n) __banked
|
|
{
|
|
/* Имена патчим на месте, а не через sprintf: printf-семейство тянет в
|
|
* резидент W1 форматтер, которого здесь больше негде применить. */
|
|
static char pri[] = "LEVELS\\res2000.bin"; /* цифры — [12],[13] */
|
|
static char alt[] = "a:\\res2000.bin"; /* цифры — [8],[9] */
|
|
uint8_t saved_blk = pop_lvl_blk, saved_page = pop_lvl_page, saved_ok = pop_lvl_ok;
|
|
|
|
if (n > 15) return -1;
|
|
pri[12] = alt[8] = (char)('0' + n / 10);
|
|
pri[13] = alt[9] = (char)('0' + n % 10);
|
|
|
|
/* Старую страницу отпускаем ТОЛЬКО после успешной загрузки новой:
|
|
* pop_level_read_file сам аллоцирует страницу, и если файла нет — остаёмся
|
|
* на текущем уровне вместо падения в пустой уровень. */
|
|
pop_lvl_ok = 0; /* чтобы level_load_path не мешался */
|
|
if (pop_level_read_file(pri) != 0 && pop_level_read_file(alt) != 0) {
|
|
pop_lvl_blk = saved_blk; pop_lvl_page = saved_page; pop_lvl_ok = saved_ok;
|
|
return -1;
|
|
}
|
|
if (saved_ok) mem_free_block(saved_blk);
|
|
pop_current_level = n;
|
|
return 0;
|
|
}
|
|
|
|
int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
|
|
uint8_t *lcol_fg, uint8_t *lcol_bg,
|
|
uint8_t *rcol_fg, uint8_t *rcol_bg, uint8_t *below_fg) __banked
|
|
{
|
|
const uint8_t *rfg, *rbg, *lnk;
|
|
uint8_t left, right, down, i;
|
|
|
|
if (!pop_lvl_ok || room < 1 || room > 24) return -1;
|
|
|
|
gfx_w0_map(pop_lvl_page);
|
|
rfg = room_fg_ptr(room);
|
|
rbg = room_bg_ptr(room);
|
|
lnk = (const uint8_t *)(LVL_DATA_OFF + BP_LINKS) + (uint16_t)(room - 1) * 4;
|
|
|
|
for (i = 0; i < ROOM_TILES; i++) {
|
|
fg[i] = (uint8_t)(rfg[i] & 0x1F); /* тип тайла (верх. биты — модиф., пока не нужны) */
|
|
bg[i] = rbg[i]; /* backtable как есть */
|
|
}
|
|
|
|
left = lnk[0]; /* L */
|
|
right = lnk[1]; /* R */
|
|
down = lnk[3]; /* D */
|
|
|
|
/* leftcol: правый столбец (col9) комнаты СЛЕВА, строки 0..2 (кромка col0),
|
|
* а в [3..5] — ПРЕДпоследний (col8). Вторая колонка нужна коллизии: Kid,
|
|
* стоящий В шве (curr_col = −1), смотрит вперёд на колонку −2, и без неё
|
|
* там оказывалась мнимая стена (get_tile → TILE_WALL), которая выпихивала
|
|
* его сквозь решётку. Оригинал резолвит любую колонку через
|
|
* find_room_of_tile (seg006:005D). */
|
|
if (left >= 1 && left <= 24) {
|
|
const uint8_t *lf = room_fg_ptr(left);
|
|
const uint8_t *lb = room_bg_ptr(left);
|
|
for (i = 0; i < 3; i++) {
|
|
lcol_fg[i] = (uint8_t)(lf[i * 10 + 9] & 0x1F);
|
|
lcol_fg[3 + i] = (uint8_t)(lf[i * 10 + 8] & 0x1F);
|
|
lcol_bg[i] = lb[i * 10 + 9];
|
|
}
|
|
} else {
|
|
for (i = 0; i < 3; i++) { lcol_fg[i] = 20; lcol_fg[3 + i] = 20; lcol_bg[i] = 0; }
|
|
}
|
|
|
|
/* rightcol: левый столбец (col0) комнаты СПРАВА, строки 0..2 (кромка col9:
|
|
* коллизия на правом шве + переход вправо) */
|
|
if (right >= 1 && right <= 24) {
|
|
const uint8_t *rf = room_fg_ptr(right);
|
|
const uint8_t *rb = room_bg_ptr(right);
|
|
for (i = 0; i < 3; i++) {
|
|
rcol_fg[i] = (uint8_t)(rf[i * 10 + 0] & 0x1F);
|
|
rcol_fg[3 + i] = (uint8_t)(rf[i * 10 + 1] & 0x1F); /* колонка 11 (см. выше) */
|
|
rcol_bg[i] = rb[i * 10 + 0];
|
|
}
|
|
} else {
|
|
for (i = 0; i < 3; i++) { rcol_fg[i] = 20; rcol_fg[3 + i] = 20; rcol_bg[i] = 0; }
|
|
}
|
|
|
|
/* belowrow: верхний ряд (row0) комнаты снизу — ОДИННАДЦАТЬ байт.
|
|
* [0..9] — колонки 0..9 комнаты снизу; [10] — тайл (0,9) комнаты
|
|
* ПО ДИАГОНАЛИ СНИЗУ-СЛЕВА (room_BL у оригинала). Одиннадцатый нужен
|
|
* тайлу (2,0): его «сосед снизу-слева» — это колонка −1 ряда 3, то есть
|
|
* чужая комната, и load_rowbelow (seg008:368) резолвит её именно так,
|
|
* с дефолтом «стена» только когда такой комнаты НЕТ. Раньше мы клали
|
|
* туда стену БЕЗУСЛОВНО, и в пустом углу рисовался клин кладки
|
|
* (BUG-SEAM-WEDGE-1, ур. 3 комн. 18). */
|
|
if (down >= 1 && down <= 24) {
|
|
const uint8_t *df = room_fg_ptr(down);
|
|
uint8_t dl = ((const uint8_t *)(LVL_DATA_OFF + BP_LINKS))[(down - 1) * 4];
|
|
for (i = 0; i < 10; i++) below_fg[i] = (uint8_t)(df[i] & 0x1F);
|
|
below_fg[10] = (dl >= 1 && dl <= 24)
|
|
? (uint8_t)(room_fg_ptr(dl)[9] & 0x1F)
|
|
: 20; /* кромка уровня */
|
|
} else {
|
|
/* Комнаты снизу НЕТ. Оригинал (load_rowbelow, seg008:368) в этом
|
|
* случае подставляет РАЗНЫЕ кромки: колонкам 1..9 — `tiles_0_empty`,
|
|
* и только левому краю (колонка 0, тайл из room_BL) — `tiles_20_wall`.
|
|
* У нас тут стояла стена на всё, и её грань (topright стены) лезла
|
|
* жёлтыми треугольниками под пустые тайлы нижнего ряда — видно в
|
|
* шахте падения, уровень 6 комната 3 (BUG-BELOWROW-WALL). */
|
|
for (i = 0; i < 10; i++) below_fg[i] = 0; /* tiles_0_empty */
|
|
below_fg[10] = 20; /* левая кромка уровня */
|
|
}
|
|
|
|
gfx_w0_unmap();
|
|
return 0;
|
|
}
|
|
|
|
int8_t pop_room_col_landing(uint8_t room, uint8_t col) __banked
|
|
{
|
|
const uint8_t *rf;
|
|
int8_t row, res = -1;
|
|
if (!pop_lvl_ok || room < 1 || room > 24 || col > 9) return -1;
|
|
gfx_w0_map(pop_lvl_page);
|
|
rf = room_fg_ptr(room);
|
|
for (row = 0; row < 3; row++) { /* кусок пролетает пустые ряды */
|
|
uint8_t t = (uint8_t)(rf[row * 10 + col] & 0x1F);
|
|
if (t == 0) continue; /* empty — дальше вниз */
|
|
/* Набор площадок — дословно switch из loose_land (seg007:11E8):
|
|
* пол, пика, обе кнопки, зелье и оба факела. На всём остальном
|
|
* (стена, ворота, колонна) кусок просто исчезает. Раньше здесь
|
|
* стоял только пол, и плита, падавшая на КНОПКУ в комнате снизу,
|
|
* пропадала бесследно: ни щебня, ни нажатия (уровень 4, плита
|
|
* 16(1,1) -> кнопка 17(0,1), открывающая ворота 23(0,9)). */
|
|
if (t == 1 || t == 2 || t == 6 || t == 10 ||
|
|
t == 15 || t == 19 || t == 30) res = row;
|
|
break; /* первый не-пустой тайл — стоп */
|
|
}
|
|
gfx_w0_unmap();
|
|
return res;
|
|
}
|
|
|
|
/* Рестарт уровня (порт load_level из цикла play_level, seg003:57): вернуть
|
|
* foretable в исходное состояние из эталонной копии. */
|
|
void pop_level_reset_tiles(void) __banked
|
|
{
|
|
uint16_t i;
|
|
uint8_t *dst;
|
|
const uint8_t *src;
|
|
if (!pop_lvl_ok) return;
|
|
gfx_w0_map(pop_lvl_page);
|
|
dst = (uint8_t *)(LVL_DATA_OFF + BP_FG);
|
|
src = (const uint8_t *)LVL_PRISTINE_OFF;
|
|
for (i = 0; i < 24u * ROOM_TILES; i++) dst[i] = src[i];
|
|
gfx_w0_unmap();
|
|
}
|
|
|
|
/* Рестарт уровня, часть вторая (тот же load_level + pos_guards, seg003:83):
|
|
* стражи — такая же часть данных уровня, как тайлы, и перезагрузка файла
|
|
* возвращает их всех. Убитый до смерти Кида страж после respawn снова жив
|
|
* и стоит на исходном тайле; забывается и запомненная поза трупа
|
|
* (guards_seq_hi = 0 -> «поднять стандартной стойкой»). */
|
|
void pop_level_reset_guards(void) __banked
|
|
{
|
|
if (!pop_lvl_ok) return;
|
|
pop_gstate_init();
|
|
}
|
|
|
|
void pop_gstate_init(void) __banked
|
|
{
|
|
const uint8_t *p = (const uint8_t *)LVL_DATA_OFF;
|
|
uint8_t r;
|
|
gfx_w0_map(pop_lvl_page);
|
|
for (r = 0; r < 24; r++) {
|
|
uint8_t *g = pop_gstate + (uint16_t)r * 6;
|
|
g[GS_TILE] = p[BP_GTILE + r];
|
|
g[GS_DIR] = p[BP_GDIR + r];
|
|
g[GS_X] = p[BP_GX + r];
|
|
g[GS_SKILL] = p[BP_GSKILL + r];
|
|
/* guards_seq_lo/hi в ФАЙЛЕ не используются — там 0xFF во всех
|
|
* комнатах (оригинал чистит их в reset_level_unused_fields).
|
|
* Осмысленное значение туда кладёт только leave_guard, поэтому
|
|
* стартуем нулями: 0 = «поднимать стандартной стойкой».
|
|
* Без этого в интерпретатор уходил curr_seq = 0xFFFF и он вис. */
|
|
g[GS_SEQL] = 0;
|
|
g[GS_SEQH] = 0;
|
|
}
|
|
gfx_w0_unmap();
|
|
}
|
|
|
|
/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат
|
|
* 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из
|
|
* колонки (pos_guards), поэтому guards_x из файла не читаем. */
|
|
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
|
|
uint8_t *color, uint8_t *skill) __banked
|
|
{
|
|
const uint8_t *g;
|
|
uint8_t t;
|
|
if (!pop_lvl_ok || room < 1 || room > 24) return 0;
|
|
/* Тайл/направление/мастерство — из ЖИВОЙ копии (её правит
|
|
* pop_guard_state_save); цвет статичен, его читаем со страницы. */
|
|
g = pop_gstate + (uint16_t)(room - 1) * 6;
|
|
t = g[GS_TILE];
|
|
if (t < 30) {
|
|
*tile = t;
|
|
*dir = (int8_t)g[GS_DIR];
|
|
*skill = g[GS_SKILL];
|
|
gfx_w0_map(pop_lvl_page);
|
|
*color = (uint8_t)(((const uint8_t *)LVL_DATA_OFF)[BP_GCOLOR + room - 1] & 0x0F);
|
|
gfx_w0_unmap();
|
|
}
|
|
return (uint8_t)(t < 30);
|
|
}
|
|
|
|
/* ---- Стартовая позиция уровня ---------------------------------------- */
|
|
|
|
static uint8_t start_byte(uint16_t off)
|
|
{
|
|
uint8_t v;
|
|
if (!pop_lvl_ok) return 0;
|
|
gfx_w0_map(pop_lvl_page);
|
|
v = ((const uint8_t *)(LVL_DATA_OFF))[off];
|
|
gfx_w0_unmap();
|
|
return v;
|
|
}
|
|
|
|
uint8_t pop_level_start_room(void) __banked { return start_byte(BP_START + 0); }
|
|
uint8_t pop_level_start_pos(void) __banked { return start_byte(BP_START + 1); }
|
|
int8_t pop_level_start_dir(void) __banked { return (int8_t)start_byte(BP_START + 2); }
|