diff --git a/applications/PoP/poc/res/bg/pal_env0.atl b/applications/PoP/poc/res/bg/pal_env0.atl new file mode 100644 index 0000000..5d57118 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env0.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env1.atl b/applications/PoP/poc/res/bg/pal_env1.atl new file mode 100644 index 0000000..634d398 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env1.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env2.atl b/applications/PoP/poc/res/bg/pal_env2.atl new file mode 100644 index 0000000..ed26d18 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env2.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env3.atl b/applications/PoP/poc/res/bg/pal_env3.atl new file mode 100644 index 0000000..11e3b62 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env3.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env4.atl b/applications/PoP/poc/res/bg/pal_env4.atl new file mode 100644 index 0000000..c4e24fb Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env4.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env5.atl b/applications/PoP/poc/res/bg/pal_env5.atl new file mode 100644 index 0000000..cf17816 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env5.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env6.atl b/applications/PoP/poc/res/bg/pal_env6.atl new file mode 100644 index 0000000..8b415b4 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env6.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env7.atl b/applications/PoP/poc/res/bg/pal_env7.atl new file mode 100644 index 0000000..260d801 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env7.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env8.atl b/applications/PoP/poc/res/bg/pal_env8.atl new file mode 100644 index 0000000..ebfd0fa Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env8.atl differ diff --git a/applications/PoP/poc/res/bg/pal_env9.atl b/applications/PoP/poc/res/bg/pal_env9.atl new file mode 100644 index 0000000..671c49f Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_env9.atl differ diff --git a/applications/PoP/poc/res/bg/pal_fore.atl b/applications/PoP/poc/res/bg/pal_fore.atl new file mode 100644 index 0000000..1d039fe Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_fore.atl differ diff --git a/applications/PoP/poc/res/bg/pal_tile.pal b/applications/PoP/poc/res/bg/pal_tile.pal new file mode 100644 index 0000000..df429b2 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_tile.pal differ diff --git a/applications/PoP/poc/res/bg/pal_wall.atl b/applications/PoP/poc/res/bg/pal_wall.atl new file mode 100644 index 0000000..1c82e43 Binary files /dev/null and b/applications/PoP/poc/res/bg/pal_wall.atl differ diff --git a/applications/PoP/poc/res/bg/pop_bg_atlas.h b/applications/PoP/poc/res/bg/pop_bg_atlas.h index 10e44d8..e62944c 100644 --- a/applications/PoP/poc/res/bg/pop_bg_atlas.h +++ b/applications/PoP/poc/res/bg/pop_bg_atlas.h @@ -2,32 +2,40 @@ * Сгенерировано toolchain/pop_pack_bg.py — НЕ править вручную. * * Прямая адресация (ноль remap-таблиц в W2): - * ENV фон id N -> atlas env_bg[N>>5], idx N&31 + * ENV фон id N -> atlas env_bg[N>>4], idx N&15 * WALL id N -> atlas wall, idx N * FORE id N -> atlas fore, idx N + * + * ДВА ТАЙЛСЕТА (порт tbl_envir_ki[tbl_level_type], seg000:1108): + * 0 = подземелье (pop_*), 1 = дворец (pal_*). Набор id и раскладка + * у них ОДНИ И ТЕ ЖЕ — меняются только файлы и 32 записи палитры + * (env 0x50..0x5F, wall 0x60..0x6F), см. *tile.pal. */ #ifndef POP_BG_ATLAS_H #define POP_BG_ATLAS_H -#define POP_ENV_SHIFT 5 -#define POP_ENV_MASK 31 -#define POP_ENV_PAGES 5 +#define POP_ENV_SHIFT 4 +#define POP_ENV_MASK 15 +#define POP_ENV_PAGES 10 +#define POP_TILESETS 2 -/* Палитра: env-слоты, wall-слоты (сприйт-пиксель i -> база+i). */ +/* Палитра: env-слоты, wall-слоты (спрайт-пиксель i -> база+i). */ #define POP_PAL_ENV 0x50 #define POP_PAL_WALL 0x60 -/* Имена файлов атласов (грузятся atlas_load). */ -static const char *const pop_env_atl[POP_ENV_PAGES] = { - "pop_env0.atl", - "pop_env1.atl", - "pop_env2.atl", - "pop_env3.atl", - "pop_env4.atl", +/* Имена файлов атласов по тайлсету (грузятся atlas_load). Таблицы + * видны только тому, кто попросил POP_BG_ATLAS_NAMES: иначе копия + * строк уедет в каждый включивший заголовок модуль. */ +#ifdef POP_BG_ATLAS_NAMES +static const char *const pop_env_atl[POP_TILESETS][POP_ENV_PAGES] = { + { "pop_env0.atl", "pop_env1.atl", "pop_env2.atl", "pop_env3.atl", "pop_env4.atl", "pop_env5.atl", "pop_env6.atl", "pop_env7.atl", "pop_env8.atl", "pop_env9.atl" }, + { "pal_env0.atl", "pal_env1.atl", "pal_env2.atl", "pal_env3.atl", "pal_env4.atl", "pal_env5.atl", "pal_env6.atl", "pal_env7.atl", "pal_env8.atl", "pal_env9.atl" }, }; -#define POP_WALL_ATL "pop_wall.atl" -#define POP_FORE_ATL "pop_fore.atl" -#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья */ +static const char *const pop_wall_atl[POP_TILESETS] = { "pop_wall.atl", "pal_wall.atl" }; +static const char *const pop_fore_atl[POP_TILESETS] = { "pop_fore.atl", "pal_fore.atl" }; +static const char *const pop_tile_pal[POP_TILESETS] = { "pop_tile.pal", "pal_tile.pal" }; +#endif /* POP_BG_ATLAS_NAMES */ +#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */ #define POP_PAL_POT 0x40 #define POP_BG_PAL "pop_bg.pal" diff --git a/applications/PoP/poc/res/bg/pop_env0.atl b/applications/PoP/poc/res/bg/pop_env0.atl index 91f08be..5d57118 100644 Binary files a/applications/PoP/poc/res/bg/pop_env0.atl and b/applications/PoP/poc/res/bg/pop_env0.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env1.atl b/applications/PoP/poc/res/bg/pop_env1.atl index ce90322..634d398 100644 Binary files a/applications/PoP/poc/res/bg/pop_env1.atl and b/applications/PoP/poc/res/bg/pop_env1.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env2.atl b/applications/PoP/poc/res/bg/pop_env2.atl index 9773976..5848405 100644 Binary files a/applications/PoP/poc/res/bg/pop_env2.atl and b/applications/PoP/poc/res/bg/pop_env2.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env3.atl b/applications/PoP/poc/res/bg/pop_env3.atl index 5a30bf8..754aabb 100644 Binary files a/applications/PoP/poc/res/bg/pop_env3.atl and b/applications/PoP/poc/res/bg/pop_env3.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env4.atl b/applications/PoP/poc/res/bg/pop_env4.atl index d426803..18bdd3e 100644 Binary files a/applications/PoP/poc/res/bg/pop_env4.atl and b/applications/PoP/poc/res/bg/pop_env4.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env5.atl b/applications/PoP/poc/res/bg/pop_env5.atl new file mode 100644 index 0000000..c55cb87 Binary files /dev/null and b/applications/PoP/poc/res/bg/pop_env5.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env6.atl b/applications/PoP/poc/res/bg/pop_env6.atl new file mode 100644 index 0000000..ead77d9 Binary files /dev/null and b/applications/PoP/poc/res/bg/pop_env6.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env7.atl b/applications/PoP/poc/res/bg/pop_env7.atl new file mode 100644 index 0000000..c8ac665 Binary files /dev/null and b/applications/PoP/poc/res/bg/pop_env7.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env8.atl b/applications/PoP/poc/res/bg/pop_env8.atl new file mode 100644 index 0000000..6e34d75 Binary files /dev/null and b/applications/PoP/poc/res/bg/pop_env8.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env9.atl b/applications/PoP/poc/res/bg/pop_env9.atl new file mode 100644 index 0000000..3b57c9b Binary files /dev/null and b/applications/PoP/poc/res/bg/pop_env9.atl differ diff --git a/applications/PoP/poc/res/bg/pop_tile.pal b/applications/PoP/poc/res/bg/pop_tile.pal new file mode 100644 index 0000000..e62c573 Binary files /dev/null and b/applications/PoP/poc/res/bg/pop_tile.pal differ diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index 050fc42..eb86225 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -35,7 +35,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W # ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том # же ALLOCS — иначе сравниваются не правки, а уровни оптимизации. ALLOCS ?= 3000 -EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS) +EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS) # Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по # умолчанию, пока идёт работа с производительностью; make PROF=0 выключает. # Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте @@ -52,9 +52,16 @@ LVL_DIR := $(CURDIR)/../SDLPoP/data/LEVELS # диске. res2000 — демо-уровень, в скоуп не входит. LVL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 LVL_DATA := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin) -BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.atl \ - $(BG_DIR)/pop_env3.atl $(BG_DIR)/pop_env4.atl \ - $(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl \ +# ДВА тайлсета: pop_* — подземелье (уровни 1-3, 7-9, 12, 13, 15), +# pal_* — дворец (4-6, 10, 11, 14). Раскладка id у них общая, различаются +# файлы и палитра тайлсета (*tile.pal, 32 записи). chtab_1 (pop_pot.atl: +# пламя, меч-предмет, зелья) от набора не зависит — один на оба. +BG_SETS := pop pal +BG_PAGES := 0 1 2 3 4 5 6 7 8 9 +BG_DATA := $(foreach s,$(BG_SETS),\ + $(foreach n,$(BG_PAGES),$(BG_DIR)/$(s)_env$(n).atl) \ + $(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl \ + $(BG_DIR)/$(s)_tile.pal) \ $(BG_DIR)/pop_pot.atl # ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23) # не рисуются (отрисовка пропускает незагруженную страницу). Палитра — @@ -93,7 +100,7 @@ hdd: $(EXAMPLE).exe TC := $(PROJ_ROOT)/applications/PoP/toolchain $(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py - cd $(TC) && python3 pop_pack_bg.py + cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace $(GUARD_DATA): $(TC)/pop_pack_guard.py cd $(TC) && python3 pop_pack_guard.py diff --git a/applications/PoP/roomtest/pop_bg.h b/applications/PoP/roomtest/pop_bg.h index e7b4d40..c193ea0 100644 --- a/applications/PoP/roomtest/pop_bg.h +++ b/applications/PoP/roomtest/pop_bg.h @@ -34,7 +34,7 @@ /* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load * трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ * после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */ -int pop_bg_load(void) __banked; +int pop_bg_load(uint8_t set) __banked; /* 0 подземелье, 1 дворец */ /* Освободить EMM-страницы атласов. */ void pop_bg_free(void) __banked; diff --git a/applications/PoP/roomtest/pop_level.c b/applications/PoP/roomtest/pop_level.c index 23fdca6..39788bc 100644 --- a/applications/PoP/roomtest/pop_level.c +++ b/applications/PoP/roomtest/pop_level.c @@ -450,6 +450,19 @@ uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, return (uint8_t)(t < 30); } +/* tbl_level_type (SDLPoP data.h:840). Держим ЗДЕСЬ, а не в слое фона: + * тип уровня нужен не только атласам — по нему расходятся ветки отрисовки + * (seg008), кладка стены (wall_pattern) и модификатор пустой клетки от + * упавшей плиты (remove_loose, seg007:0EB8). */ +static const uint8_t LEVEL_TYPE[16] = { + 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0 +}; + +uint8_t pop_level_type(void) +{ + return LEVEL_TYPE[pop_current_level & 15]; +} + uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); } uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); } int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); } diff --git a/applications/PoP/roomtest/pop_level.h b/applications/PoP/roomtest/pop_level.h index be1c112..3656adf 100644 --- a/applications/PoP/roomtest/pop_level.h +++ b/applications/PoP/roomtest/pop_level.h @@ -135,6 +135,13 @@ uint8_t pop_guard_state_x(uint8_t room); uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, uint8_t *color, uint8_t *skill); +/* tbl_level_type (SDLPoP data.h:840) — какой тайлсет у уровня: + * 0 подземелье, 1 дворец. Дворцовые: 4, 5, 6, 10, 11, 14. Индекс — + * НОМЕР уровня; элемент 0 — демо-уровень. Оригинал по нему выбирает и + * .DAT окружения (tbl_envir_ki, seg000:1108), и десяток веток отрисовки + * в seg008, и графику стража (GUARD1/GUARD2). */ +uint8_t pop_level_type(void); + uint8_t pop_level_start_room(void); uint8_t pop_level_start_pos(void); int8_t pop_level_start_dir(void); diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index 59de686..f6cbc5b 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -8,6 +8,9 @@ * * Только DUNGEON-стиль (Фаза 1). */ +/* Имена файлов атласов обоих тайлсетов нужны только здесь; макрос обязан + * стоять ДО первого включения pop_bg_atlas.h (его тянет pop_tile.h). */ +#define POP_BG_ATLAS_NAMES #include #include #include @@ -22,34 +25,87 @@ #include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */ #include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */ #include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */ +#include +#include -static const char *const ENV_ATL[5] = { - /* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон, - * KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска - * зарастает десятками .atl. */ - "BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl", - "BG\\pop_env3.atl", "BG\\pop_env4.atl" -}; +/* ---- Тайлсет: подземелье / дворец ----------------------------------- * + * Порт 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; /* какой набор сейчас загружен */ -int pop_bg_load(void) __banked +/* Ресурсы разложены по каталогам образа (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; - for (i = 0; i < 5; i++) - if (atlas_load(&pop_env[i], ENV_ATL[i]) != 0) return -1; - if (atlas_load(&pop_wall_at, "BG\\pop_wall.atl") != 0) return -1; - if (atlas_load(&pop_fore_at, "BG\\pop_fore.atl") != 0) return -1; - if (atlas_load(&pop_pot_at, "BG\\pop_pot.atl") != 0) return -1; - return 0; + 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; + /* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от + * прежнего набора и картинка хотя бы не разъезжается. */ + return bg_load_tile_pal(pop_tile_pal[set]); } void pop_bg_free(void) __banked { int i; - for (i = 0; i < 5; 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}; diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index e040dbb..f4103e2 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -17,7 +17,7 @@ #include "_pop_draw.h" /* pop_heal_fast */ /* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */ -atlas_t pop_env[5], pop_wall_at, pop_fore_at, pop_pot_at; +atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at; const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg; const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod; @@ -262,7 +262,7 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) gfx_w0_unmap(); } -void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> 5], (uint8_t)(id & 31), x, yb); } +void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> POP_ENV_SHIFT], (uint8_t)(id & POP_ENV_MASK), x, yb); } void pop_wall_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_wall_at, id, x, yb); } void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); } void pop_pot_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_pot_at, id, x, yb); } diff --git a/applications/PoP/roomtest/pop_tile.h b/applications/PoP/roomtest/pop_tile.h index ca2304b..9af1d67 100644 --- a/applications/PoP/roomtest/pop_tile.h +++ b/applications/PoP/roomtest/pop_tile.h @@ -31,9 +31,13 @@ #include "pop_geom.h" /* pop_rnd_t — сид кладки комнаты */ /* ---- Атласы фона (прямая адресация id) ------------------------------ * - * env[id>>5] idx id&31 — chtab_6 environment; wall — chtab_7; fore — - * передний слой; pot — chtab_1 (зелья, меч-предмет). */ -extern atlas_t pop_env[5], pop_wall_at, pop_fore_at, pop_pot_at; + * env[id>>POP_ENV_SHIFT] idx id&POP_ENV_MASK — chtab_6 environment; + * wall — chtab_7; fore — передний слой; pot — chtab_1 (зелья, меч). + * Раскладка и число страниц — из pop_bg_atlas.h (генерит упаковщик). + * Тайлсетов ДВА (подземелье/дворец), но раскладка id у них общая: + * подменяются только файлы и 32 записи палитры (см. pop_bg_set_tileset). */ +#include "pop_bg_atlas.h" +extern atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at; /* ---- Текущая отрисованная комната ----------------------------------- * * fg — foretable-коды, bg — модификаторы; lfg/lbg — правый столбец ЛЕВОГО diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 375e3b3..476ad6f 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -479,7 +479,11 @@ int main(void) uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0; uint8_t xdec_prev = 0, xinc_prev = 0; - if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */ + /* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph + * (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан, + * поэтому тип берём напрямую от номера. */ + pop_current_level = FIRST_LEVEL; + if (pop_bg_load(pop_level_type()) != 0) { puts("pop_bg_load failed"); return 1; } @@ -757,6 +761,14 @@ int main(void) * пределы, и персонаж просто не виден. */ pop_guard_free(); pop_guard_load(); + /* Тайлсет: подземелье <-> дворец (порт tbl_envir_ki, + * seg000:1108). Внутри — no-op, если тип не менялся; + * при смене выгружает старые атласы и правит 32 записи + * палитры. Только на границе уровня, не в кадре. */ + if (pop_bg_load(pop_level_type()) != 0) { + puts("bg set load failed"); + return 1; + } /* Всё состояние прошлого уровня сносит сам pop_start_level: * pop_trob_reset обнуляет room_seen (модификаторы комнат * перечитаются из НОВОЙ страницы), тайлы/стражи приезжают diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py index e5cff0e..a37b138 100644 --- a/applications/PoP/toolchain/pop_pack_bg.py +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -129,7 +129,37 @@ CHOMPER_BLOOD_COLOR = VGA16_PAL_BASE + 12 # тот же mono-цвет 12 (seg0 # рисуется chtab_6 id 146). В статический render_room не попадает. TORCH_ENV_IDS = {146} -ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31 +# ---- Два тайлсета: подземелье и дворец -------------------------------- +# Оригинал грузит РАЗНЫЙ .DAT под один и тот же набор id: +# tbl_envir_ki[tbl_level_type[level]] -> VDUNGEON / VPALACE (seg000:1108), +# то есть id спрайта от набора НЕ зависит — меняется только картинка и +# палитра (env 0x50..0x5F, wall 0x60..0x6F). Поэтому оба набора пакуются +# по ОДНОМУ И ТОМУ ЖЕ объединению id: раскладка id -> (страница, idx) +# остаётся одна, и коду достаточно подменить ИМЕНА файлов. +# +# Порядок каталогов — каскад: сначала «свой» набор, потом чужой как +# фолбэк (в распакованном data/ несколько id есть только в одном из них: +# res230/231/348 только в VDUNGEON, два десятка — только в VPALACE). +SETS = { + "dungeon": ("pop_", lambda: (R.VDUNGEON_DIR, R.VPALACE_DIR)), + "palace": ("pal_", lambda: (R.VPALACE_DIR, R.VDUNGEON_DIR)), +} +SRC_DIRS = None # ставит main() по выбранному набору + +# Спрайты, которые рисуются ТОЛЬКО на паласных уровнях (seg008): +# 78, 80, 82 doortop_fram_bot (низ верха двери, draw_tile_right) +# 81, 83 doortop_fram_top (draw_tile_topright) +# 84 полоска на стене (draw_tile_right, tiles_20_wall) +# 145 stripe_id соседнего тайла (draw_tile_right) +# render_room про них не знает (он рисует по подземельной ветке), поэтому +# добавляем явно — иначе при переключении набора в этих местах будет дыра. +PALACE_ENV_IDS = {78, 80, 81, 82, 83, 84, 145} + +ENV_SHIFT = 4 # env фон: страница = id>>SHIFT, idx = id&MASK. + # Было 5: паласный набор добавляет куски верха + # двери (78..84), и страница 2 переваливала за + # 16 КБ. При 4 обе раскладки укладываются, цена + # — 10 страниц EMM на набор вместо 5. ENV_MASK = (1 << ENV_SHIFT) - 1 # ---- .atl формат (== libbgi, но каталог не ограничен 19) -------------- @@ -194,7 +224,8 @@ NO_TRANSP_ENV_IDS = {36, 43, 73, 74, 96, 149} | LEVELDOOR_ENV_IDS def load_indexed(chtab, id, opaque=False, mono=None): - """PNG (mode P, 16 цветов) -> (w, h, bytes 8bpp). Каскад VDUNGEON->VPALACE. + """PNG (mode P, 16 цветов) -> (w, h, bytes 8bpp). Каскад по SRC_DIRS + (порядок задаёт выбранный тайлсет, см. SETS). Пиксель 0 -> 0xFF (прозрач); i -> base+i (env 0x50 / wall 0x60). opaque=True — пиксель 0 остаётся ЦВЕТОМ (base+0 = чёрный), а не ключом @@ -203,7 +234,7 @@ def load_indexed(chtab, id, opaque=False, mono=None): pal_base = 0x60 if chtab == R.CHTAB_WALL else 0x50 fname = f"res{base_res + id}.png" path = None - for d in (R.VDUNGEON_DIR, R.VPALACE_DIR): + for d in SRC_DIRS: p = os.path.join(d, fname) if os.path.exists(p): path = p @@ -306,11 +337,29 @@ def scale6to8(v): return (v * 255) // 63 +def read_pal16(res, d): + b = open(os.path.join(d, res), "rb").read() + return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)] + + +def build_tileset_palette(out_path): + """Палитра ТОЛЬКО тайлсета: 32 записи B,G,R,0 — env (16) затем wall (16). + Ею подменяются слоты 0x50..0x6F при смене типа уровня; полная игровая + палитра (kid.pal) при этом не трогается, потому что Кид, страж, меч и + зелья живут в других слотах (levels_plan §3). Порядок каталогов — + SRC_DIRS, то есть файл соответствует выбранному набору.""" + pal = bytearray(32 * 4) + src = read_pal16("res200.pal", SRC_DIRS[0]) + read_pal16("res360.pal", SRC_DIRS[0]) + for i, (r, g, b) in enumerate(src): + pal[i * 4:i * 4 + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0)) + with open(out_path, "wb") as f: + f.write(pal) + + def build_palette(out_path): """256-записная палитра B,G,R,0: env@0x50..0x5F, wall@0x60..0x6F.""" def read_pal(res, d): - b = open(os.path.join(d, res), "rb").read() - return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)] + return read_pal16(res, d) env = read_pal("res200.pal", R.VDUNGEON_DIR) wall = read_pal("res360.pal", R.VDUNGEON_DIR) @@ -334,7 +383,10 @@ def build_palette(out_path): # ---- 5. C-заголовок -------------------------------------------------- -def write_header(out_path, env_pages, wall_name, fore_name): +def write_header(out_path, env_pages): + """Заголовок ОДИН на оба тайлсета: раскладка id -> (страница, idx) у них + общая (пакуются по одному объединению id), различаются только имена + файлов и палитра. Поэтому здесь два массива имён и один индекс.""" lines = [ "/* pop_bg_atlas.h — раскладка атласов статического фона PoP.", " * Сгенерировано toolchain/pop_pack_bg.py — НЕ править вручную.", @@ -343,6 +395,11 @@ def write_header(out_path, env_pages, wall_name, fore_name): " * ENV фон id N -> atlas env_bg[N>>%d], idx N&%d" % (ENV_SHIFT, ENV_MASK), " * WALL id N -> atlas wall, idx N", " * FORE id N -> atlas fore, idx N", + " *", + " * ДВА ТАЙЛСЕТА (порт tbl_envir_ki[tbl_level_type], seg000:1108):", + " * 0 = подземелье (pop_*), 1 = дворец (pal_*). Набор id и раскладка", + " * у них ОДНИ И ТЕ ЖЕ — меняются только файлы и 32 записи палитры", + " * (env 0x50..0x5F, wall 0x60..0x6F), см. *tile.pal.", " */", "#ifndef POP_BG_ATLAS_H", "#define POP_BG_ATLAS_H", @@ -350,22 +407,32 @@ def write_header(out_path, env_pages, wall_name, fore_name): "#define POP_ENV_SHIFT %d" % ENV_SHIFT, "#define POP_ENV_MASK %d" % ENV_MASK, "#define POP_ENV_PAGES %d" % len(env_pages), + "#define POP_TILESETS 2", "", - "/* Палитра: env-слоты, wall-слоты (сприйт-пиксель i -> база+i). */", + "/* Палитра: env-слоты, wall-слоты (спрайт-пиксель i -> база+i). */", "#define POP_PAL_ENV 0x50", "#define POP_PAL_WALL 0x60", "", - "/* Имена файлов атласов (грузятся atlas_load). */", - "static const char *const pop_env_atl[POP_ENV_PAGES] = {", + "/* Имена файлов атласов по тайлсету (грузятся atlas_load). Таблицы", + " * видны только тому, кто попросил POP_BG_ATLAS_NAMES: иначе копия", + " * строк уедет в каждый включивший заголовок модуль. */", + "#ifdef POP_BG_ATLAS_NAMES", + "static const char *const pop_env_atl[POP_TILESETS][POP_ENV_PAGES] = {", ] - for pg in range(len(env_pages)): - nm = env_pages[pg] - lines.append(' %s,' % ('"%s"' % nm if nm else "0 /* пусто */")) + for prefix, _ in SETS.values(): + row = ", ".join(('"%s%s"' % (prefix, nm.split("_", 1)[1]) if nm else "0") + for nm in env_pages) + lines.append(" { %s }," % row) lines += [ "};", - '#define POP_WALL_ATL "%s"' % wall_name, - '#define POP_FORE_ATL "%s"' % fore_name, - '#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья */', + 'static const char *const pop_wall_atl[POP_TILESETS] = { %s };' + % ", ".join('"%swall.atl"' % p for p, _ in SETS.values()), + 'static const char *const pop_fore_atl[POP_TILESETS] = { %s };' + % ", ".join('"%sfore.atl"' % p for p, _ in SETS.values()), + 'static const char *const pop_tile_pal[POP_TILESETS] = { %s };' + % ", ".join('"%stile.pal"' % p for p, _ in SETS.values()), + "#endif /* POP_BG_ATLAS_NAMES */", + '#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */', "#define POP_PAL_POT 0x%02X" % POT_PAL_BASE, '#define POP_BG_PAL "pop_bg.pal"', "", @@ -377,6 +444,14 @@ def write_header(out_path, env_pages, wall_name, fore_name): def main(): + global SRC_DIRS + set_name = sys.argv[1] if len(sys.argv) > 1 else "dungeon" + if set_name not in SETS: + die(f"набор '{set_name}': ожидался один из {', '.join(SETS)}") + prefix, dirs = SETS[set_name] + SRC_DIRS = dirs() + print(f"набор: {set_name} (источник {os.path.basename(SRC_DIRS[0])}, " + f"фолбэк {os.path.basename(SRC_DIRS[1])}), префикс {prefix}") os.makedirs(OUT_DIR, exist_ok=True) used = collect_used() env_ids = sorted(set(id for ch, id in used if ch != R.CHTAB_WALL) @@ -392,7 +467,8 @@ def main(): | CHOMPER_BLOOD_IDS # кровь (mono) | CHOMPER_BLOOD_FORE_IDS | OPENER_NOFLOOR_ENV_IDS # + opener без пола слева - | LEVELDOOR_ENV_IDS) # + дверь уровня (створка/лестница) + | LEVELDOOR_ENV_IDS # + дверь уровня (створка/лестница) + | PALACE_ENV_IDS) # + куски, что рисует только палас wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL) env_bg = [i for i in env_ids if i not in FORE_ENV_IDS] @@ -410,7 +486,7 @@ def main(): total_bytes = 0 for pg in range(max_pg + 1): if pg in pages: - nm = f"pop_env{pg}.atl" + nm = f"{prefix}env{pg}.atl" cnt, sz = pack_atlas(os.path.join(OUT_DIR, nm), pages[pg]) total_bytes += sz env_page_names.append(nm) @@ -421,18 +497,18 @@ def main(): # WALL -> idx=id wall_entries = {id: load_indexed(R.CHTAB_WALL, id) for id in wall_ids} - cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_wall.atl"), wall_entries) + cnt, sz = pack_atlas(os.path.join(OUT_DIR, prefix + "wall.atl"), wall_entries) total_bytes += sz - print(f" pop_wall.atl: {len(wall_ids)} спрайтов, каталог {cnt}, {sz} Б") + print(f" {prefix}wall.atl: {len(wall_ids)} спрайтов, каталог {cnt}, {sz} Б") # FORE -> idx=id fore_entries = {id: load_indexed(R.CHTAB_ENV, id, False, CHOMPER_BLOOD_COLOR if id in CHOMPER_BLOOD_FORE_IDS else None) for id in env_fore} - cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_fore.atl"), fore_entries) + cnt, sz = pack_atlas(os.path.join(OUT_DIR, prefix + "fore.atl"), fore_entries) total_bytes += sz - print(f" pop_fore.atl: {len(env_fore)} спрайтов, каталог {cnt}, {sz} Б") + print(f" {prefix}fore.atl: {len(env_fore)} спрайтов, каталог {cnt}, {sz} Б") # POTION (chtab_1) -> idx=id: склянки + кадры пузырька + маска # chtab_1 целиком (res151..res173 = id 1..23): пламя факелов, меч-предмет, @@ -452,13 +528,15 @@ def main(): pot_entries[id] = load_potion(id, opaque=True) else: pot_entries[id] = load_potion(id) + # chtab_1 (пламя/меч/зелья) от тайлсета НЕ зависит — пакуем один раз. cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries) total_bytes += sz print(f" pop_pot.atl (chtab_1: факелы+зелья): {len(pot_entries)} спрайтов, каталог {cnt}, {sz} Б") + build_tileset_palette(os.path.join(OUT_DIR, prefix + "tile.pal")) + print(f" {prefix}tile.pal: 32 записи (env 0x50..0x5F + wall 0x60..0x6F)") build_palette(os.path.join(OUT_DIR, "pop_bg.pal")) - write_header(os.path.join(OUT_DIR, "pop_bg_atlas.h"), - env_page_names, "pop_wall.atl", "pop_fore.atl") + write_header(os.path.join(OUT_DIR, "pop_bg_atlas.h"), env_page_names) n_pages = sum(1 for n in env_page_names if n) + 2 print(f"ИТОГО: {len(env_bg)} env-фон + {len(wall_ids)} wall + "