c71981fdf9
Банк 9 дошёл до 98,5% (247 свободных байт), и следующая заметная правка в menu/status/ui упёрлась бы в потолок. Критерий переселения тот же, что у остальной раскладки, — ЧАСТОТА ВЫЗОВА, а не размер: POP.CFG и POP.HOF работают раз за партию и упираются в диск, где один `open` стоит 51 мс, так что трамплин банк→банк на их фоне не существует. Отдельный банк заводить не пришлось: в десятом лежал один pop_pal.c на 485 байт, то есть 3% страницы. Новой страницы в образе не появилось. банк 9: 16137 (98,5%) -> 11948 (72,9%), свободно 247 -> 4436 банк 10: 485 ( 3,0%) -> 4674 (28,5%), свободно 11710 Переезд безопасен, потому что все публичные функции обоих модулей помечены __banked: трамплин выбирается по пометке в объявлении, а не по банку, и непомеченная функция пережила бы переезд только внутри своего банка. pop_settings — обычный глобал в W2, виден отовсюду. Следующий рычаг, если понадобится: pop_menu (4745 байт, 29% банка 9, работает только при открытом меню). Трогать нельзя pop_ui, pop_status, pop_music и pop_timer — они в кадровом пути. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
499 lines
34 KiB
Makefile
499 lines
34 KiB
Makefile
# SprPoP — порт Prince of Persia на Sprinter Sp2000.
|
||
#
|
||
# Приложение АВТОНОМНО: весь код, все исходные ресурсы, все конверторы лежат
|
||
# здесь. Наружу оно знает ровно один путь — корень тулчейна sprinter-cc:
|
||
#
|
||
# export SPRINTER_ROOT=/путь/к/C-Compiler
|
||
# make
|
||
#
|
||
# Без переменной берётся ../.. (раскладка applications/SprPoP внутри дерева
|
||
# компилятора), так что в родном дереве ничего настраивать не надо.
|
||
#
|
||
# Раскладка:
|
||
# src/ рукописный C
|
||
# gen/ генерируемые заголовки (в репозитории: без них src/ не
|
||
# собрать, а генератору нужны оригиналы из assets/orig/)
|
||
# assets/orig/ оригинальные данные — SDLPoP, MSDOS, записи музыки.
|
||
# НЕ в репозитории (копирайт), см. assets/orig/README.md
|
||
# assets/packed/ то, что ложится на диск игры, в раскладке диска
|
||
# tools/ конверторы ресурсов
|
||
# build/ ВЫХОД: sprpop.exe + BG/ KID/ ... + hdd/sprpop.chd
|
||
#
|
||
# Основные цели:
|
||
# make собрать sprpop.exe и разложить build/
|
||
# make hdd + образ build/hdd/sprpop.chd
|
||
# make mame-link однократно связать образ с MAME (см. ниже)
|
||
# make resources перегенерировать ресурсы из assets/orig/
|
||
# make music-mp3 музыка из другого набора записей (flac|mp3|ogg|mt32)
|
||
# make clean снести build/ (ассеты не трогает)
|
||
# make distclean clean + снести упакованные ассеты assets/packed/
|
||
|
||
SPRINTER_ROOT ?= $(abspath $(CURDIR)/../..)
|
||
PROJ_ROOT := $(SPRINTER_ROOT)
|
||
EXAMPLE := sprpop
|
||
SRC_DIR := src
|
||
BUILD_DIR := build
|
||
|
||
# huge: small-раскладка (CODE в W1, DATA в W2) + банки кода в W3.
|
||
MEMORY ?= huge
|
||
# --max-allocs-per-node: во сколько вариантов размещения регистров упирается
|
||
# SDCC на узел. Дефолт sprinter-cc (100000) на модулях такого размера — это
|
||
# МИНУТЫ на банк. 6000 — компромисс: сборка дольше секунд на двадцать, зато
|
||
# резидент W1 худеет примерно на 150 байт против дефолтных 3000 SDCC, а это
|
||
# ровно те байты, которых не хватает куче.
|
||
#
|
||
# make — обычная сборка (ALLOCS=6000)
|
||
# make ALLOCS=100000 — минимальный код, для замеров размера и релиза
|
||
#
|
||
# ВАЖНО: сравнивать занятость банков можно только при ОДНОМ ALLOCS — иначе
|
||
# сравниваются не правки, а уровни оптимизации.
|
||
ALLOCS ?= 6000
|
||
# Build info не хранится в C: ID и дата извлекаются из текущего commit.
|
||
BUILD_ID ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
|
||
BUILD_DATE ?= $(shell git log -1 --format=%cs 2>/dev/null || echo unknown)
|
||
BUILD_FLAGS := -DPOP_BUILD_ID=\"$(BUILD_ID)\" -DPOP_BUILD_DATE=\"$(BUILD_DATE)\"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Банковая раскладка
|
||
# ---------------------------------------------------------------------------
|
||
# Единственный список банковых исходников: из него строятся И ключи
|
||
# sprinter-cc, И зависимости sprpop.exe. Держать два списка нельзя — так уже
|
||
# ловили stale-бинарник (модуль компилировался, но make не замечал правок).
|
||
#
|
||
# Банк 2 = отрисовка (pop_bg + pop_cdraw в соседних банках): вызовы банк→банк
|
||
# работают, трамплин сохраняет страницу окна на СТЕКЕ. Резидента --w3 нет.
|
||
# Банк 5 = pop_ctrl.c: кандидат выбран не по размеру, а по частоте вызова —
|
||
# диспетчер управления дёргается раз в кадр на персонажа и горячих банк→банк
|
||
# переходов не создаёт (в отличие от pop_level, чей pop_level_tile зовётся из
|
||
# банка 2 на КАЖДЫЙ тайл).
|
||
# Банк 9 = оболочка (menu/settings/config/font, автомат, title, attract-demo,
|
||
# таблица рекордов) — мелкие редкие точки входа, но не рендеры заставок.
|
||
# Банк 10 = единственный владелец экранных палитр/fade; отделён от UI, потому
|
||
# что его зовут и игровой lifecycle, и все ветки катсцен.
|
||
# Банк 11 = тяжёлая кинематика заставок, чтобы анимации не вытесняли
|
||
# menu/config из банка оболочки.
|
||
BANK1_SRCS := guards.c
|
||
BANK2_SRCS := pop_bg.c
|
||
BANK3_SRCS := pop_map.c
|
||
BANK4_SRCS := pop_cdraw.c pop_kdraw.c
|
||
BANK5_SRCS := pop_ctrl.c pop_tile_cold.c
|
||
BANK6_SRCS := pop_trob.c
|
||
BANK7_SRCS := pop_room.c pop_redraw.c
|
||
BANK8_SRCS := sprpop_cold.c pop_arc.c pop_level_cold.c pop_kboot.c pop_guard_cold.c \
|
||
pop_shadow.c pop_sfx_cold.c pop_qsave.c pop_qsave_coldio.c \
|
||
pop_level_qsave.c
|
||
BANK9_SRCS := pop_menu.c pop_status.c pop_music.c pop_app.c pop_timer.c \
|
||
pop_ui.c pop_title.c pop_demo.c pop_flow.c
|
||
# Банк 10 = палитры + РЕДКОЕ С ФАЙЛАМИ. pop_config (POP.CFG) и pop_hof
|
||
# (POP.HOF) переехали сюда из банка 9, когда тот дошёл до 98,5% (247 байт
|
||
# свободно). Критерий тот же, что и у остальной раскладки, — частота, а не
|
||
# размер: обе вещи работают раз за партию и упираются в диск, где `open`
|
||
# стоит 51 мс, так что трамплин банк→банк на их фоне не существует.
|
||
# Отдельный банк для этого не нужен: в десятом лежал один pop_pal.c на
|
||
# 485 байт, то есть 3% страницы.
|
||
BANK10_SRCS := pop_pal.c pop_config.c pop_hof.c
|
||
BANK11_SRCS := pop_cutscene.c pop_intro.c
|
||
BANK_NUMS := 1 2 3 4 5 6 7 8 9 10 11
|
||
BANKED_SRCS := $(foreach n,$(BANK_NUMS),$(BANK$(n)_SRCS))
|
||
BANK_FLAGS := $(foreach n,$(BANK_NUMS),\
|
||
$(foreach f,$(BANK$(n)_SRCS),--bank $(n)=$(SRC_DIR)/$(f)))
|
||
|
||
# Модули резидента (кроме sprpop.c — его подставляет app.mk).
|
||
EXTRA_SRCS := $(addprefix $(SRC_DIR)/,pop_vflip.c pop_pace.c pop_sfx.c pop_state.c \
|
||
pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c \
|
||
pop_qsave_io.c)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Отладочные ключи
|
||
# ---------------------------------------------------------------------------
|
||
# Профилирование полосами бордюра (PROF() в sprpop.c): каждая фаза кадра красит
|
||
# бордюр в свой цвет, высота полосы на скриншоте MAME = время фазы в строках.
|
||
PROF ?= 0
|
||
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
||
# Стартовый уровень: make LEVEL=9 — начать сразу с девятого.
|
||
LEVEL ?= 1
|
||
PROF_FLAGS += -DFIRST_LEVEL=$(LEVEL)
|
||
# Стартовая КОМНАТА и позиция Кида: make ROOM=15 POS=2 — начать прямо в
|
||
# целевой комнате, минуя проход уровня. POS — тайл 0..29, то есть row*10+col.
|
||
# make ROOM= (пусто) возвращает штатный старт из данных уровня.
|
||
ROOM ?=
|
||
POS ?= 2
|
||
ifneq ($(strip $(ROOM)),)
|
||
PROF_FLAGS += -DDBG_START_ROOM=$(ROOM) -DDBG_START_POS=$(POS)
|
||
endif
|
||
|
||
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/gen -I $(CURDIR)/$(SRC_DIR) \
|
||
--max-allocs $(ALLOCS) $(BANK_FLAGS) $(PROF_FLAGS) $(BUILD_FLAGS)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Ресурсы: что уходит на диск
|
||
# ---------------------------------------------------------------------------
|
||
# assets/packed/<КАТАЛОГ>/ повторяет раскладку диска (8.3, как в DSS), поэтому
|
||
# отдельного списка «файл → каталог образа» больше нет: каталог виден из пути.
|
||
PACKED := assets/packed
|
||
ATL := $(BUILD_DIR)/atl
|
||
|
||
# Промежуточные атласы (в build/atl, в репозиторий не идут) — из них
|
||
# упаковщик архивов склеивает то, что реально ложится на диск. Порядок в
|
||
# списках = порядок индексов внутри архива, а их упаковщик печатает в gen/*.h;
|
||
# менять порядок нельзя, не перегенерировав заголовок.
|
||
BG_SETS := pop pal
|
||
BG_PAGES := 0 1 2 3 4 5 6 7 8 9
|
||
BG_ATL := $(foreach s,$(BG_SETS),\
|
||
$(foreach n,$(BG_PAGES),$(ATL)/bg/$(s)_env$(n).atl) \
|
||
$(ATL)/bg/$(s)_wall.atl $(ATL)/bg/$(s)_fore.atl) \
|
||
$(ATL)/bg/pop_pot.atl
|
||
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23) не
|
||
# рисуются. Плюс зеркальный ПО ВЕРТИКАЛИ набор (зелье инверсии, уровень 9):
|
||
# разворот готовит упаковщик, а не рантайм — страница разворачивается 0,59 с
|
||
# против 0,045 с на её чтение с диска.
|
||
KID_N := 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
||
KID_ATL := $(foreach n,$(KID_N),$(ATL)/kid/kid$(n).atl) \
|
||
$(foreach n,$(KID_N),$(ATL)/kid/kid$(n)_v.atl) \
|
||
$(ATL)/kid/sword.atl $(ATL)/kid/sword_v.atl
|
||
GUARD_ATL := $(foreach n,0 1 2 3 4,$(ATL)/guard/g$(n).atl) \
|
||
$(foreach n,0 1 2 3 4,$(ATL)/guard/g$(n)_v.atl)
|
||
# Скелет — ОТДЕЛЬНЫЙ набор (SKEL.DAT), его грузит уровень 3; кадров меньше.
|
||
# Зеркального набора у него нет: зелье инверсии — уровень 9, там обычный страж.
|
||
SKEL_ATL := $(foreach n,0 1 2 3,$(ATL)/skel/g$(n).atl)
|
||
# Джафар (VIZIER.DAT) — уровень 13; кадров столько же, сколько у стража, но
|
||
# палитра своя, из файла набора.
|
||
VIZIER_ATL := $(foreach n,0 1 2 3 4,$(ATL)/vizier/g$(n).atl)
|
||
# ТЕНЬ — набор, запечённый упаковщиком (наложение спрайта на себя со сдвигом;
|
||
# наш блит так не умеет). sk* — кадры вне боя, sf* — кадры 150..189.
|
||
SHADOW_ATL := $(foreach n,$(KID_N),$(ATL)/shadow/sk$(n).atl) \
|
||
$(foreach n,0 1 2 3,$(ATL)/shadow/sf$(n).atl)
|
||
# Звук: оригинальная оцифровка на частоте CBL (10 937,5 Гц, 8 бит моно).
|
||
# Число файлов задаёт упаковщик (POP_SND_PAGES в gen/pop_sound_tbl.h) —
|
||
# список ОБЯЗАН совпадать: недостающий файл это молча пропавшие эффекты.
|
||
SND_ATL := $(foreach n,0 1 2 3 4 5 6 7 8 9,$(ATL)/sound/s$(n).bin)
|
||
TITLE_ATL := $(foreach s,0 1 2 3,$(foreach p,0 1 2 3 4,$(ATL)/title/t$(s)_$(p).atl))
|
||
# PV/INTRO: story и стадии режутся на полосы; у настоящего PV intro есть ещё
|
||
# чистый фон b0_* и шесть <=16K actor-атласов — они остаются в EMM на время
|
||
# сцены, а не перечитывают полный 320x200 кадр с диска.
|
||
PV_ATL := $(foreach s,0 1 2 3 4 5,$(foreach p,0 1 2 3 4,$(ATL)/pv/s$(s)_$(p).atl)) \
|
||
$(foreach s,0 1 2 3 4 5 6 7,$(foreach p,0 1 2 3 4,$(ATL)/pv/p$(s)_$(p).atl)) \
|
||
$(foreach p,0 1 2 3 4,$(ATL)/pv/b0_$(p).atl) \
|
||
$(ATL)/pv/a0.atl $(ATL)/pv/a1.atl $(ATL)/pv/a2.atl \
|
||
$(ATL)/pv/j0.atl $(ATL)/pv/j1.atl $(ATL)/pv/j2.atl \
|
||
$(ATL)/pv/h0.atl $(ATL)/pv/t0.atl
|
||
|
||
# МУЗЫКА: записи DOS-версии в формате CBL. Номера — оригинала, чтобы игровой
|
||
# код просил трек тем же числом, каким SDLPoP зовёт play_sound.
|
||
# заставка 54 55 50 53 52 | катсцены 26 27 35 36 40
|
||
# игра 24 25 28 29 30 32 33 37 39 41 43 | 56 = won (стриминг с диска)
|
||
MUS_IDS := 24 25 26 27 28 29 30 32 33 35 36 37 39 40 41 43 50 52 53 54 55 56
|
||
# Набор записей, из которого делается музыка: flac (по умолчанию — лучший
|
||
# исходник), mp3, ogg, mt32 (то же в исполнении Roland MT-32). Все четыре
|
||
# лежат в assets/orig/PoP1_DOS_music/, имена треков совпадают.
|
||
# Формат входит в имя stamp'а, поэтому смена MUSIC_FMT сама по себе делает
|
||
# музыку устаревшей — принудительная пересборка не нужна.
|
||
MUSIC_FMT ?= flac
|
||
# res2000 — attract-demo; res2001..res2014 — штатная игра. res2015 (copy
|
||
# protection) намеренно не пакуется и не существует в маршруте загрузчика.
|
||
LVL_NUMS := 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14
|
||
|
||
# ЕДИНСТВЕННЫЙ список того, что ложится на диск. Из него выводятся и
|
||
# staging в build/, и аргументы упаковщика образа.
|
||
# Архивы вместо россыпи файлов не ради места: замер 2026-08-25 — `open` 51,4 мс
|
||
# против 32,6 мс на чтение 16 КБ, то есть на одних открытиях набор Кида терял
|
||
# три секунды. Четыре архива актёров, а не один: имена внутри наборов
|
||
# совпадают (g0.atl есть и у стража, и у скелета, и у Джафара), а константы
|
||
# индексов упаковщик делает из имени файла.
|
||
DISK := BG/bg.arc BG/pop_tile.pal BG/pal_tile.pal \
|
||
KID/kid.arc KID/kid.pal KID/kid.ani \
|
||
GUARD/guard.arc SKEL/skel.arc VIZIER/vizier.arc SHADOW/shadow.arc \
|
||
SND/snd.arc \
|
||
$(foreach i,$(MUS_IDS),MUS/m$(i).bin) \
|
||
FONT/font.atl \
|
||
TITLE/title.arc TITLE/title.pal \
|
||
PV/pv.arc PV/story.pal PV/pv.pal \
|
||
$(foreach n,$(LVL_NUMS),LEVELS/res20$(n).bin)
|
||
|
||
# Все шестнадцать генерируемых заголовков перечислены явно, а не wildcard'ом:
|
||
# список — это и зависимость exe, и то, что умеет чинить себя само.
|
||
GEN_ARC_H := bg_arc.h kid_arc.h guard_arc.h skel_arc.h vizier_arc.h \
|
||
shadow_arc.h title_arc.h pv_arc.h
|
||
GEN_H := $(addprefix gen/,$(GEN_ARC_H) pop_bg_atlas.h kid_atlas.h kid_data.h \
|
||
pop_guard_pal.h pop_shadow_atlas.h pop_sound_tbl.h \
|
||
pop_music_tbl.h pop_music_ticks.h pop_font.h)
|
||
|
||
PACKED_FILES := $(addprefix $(PACKED)/,$(DISK))
|
||
STAGED := $(addprefix $(BUILD_DIR)/,$(DISK))
|
||
|
||
EXTRA_DATA := $(STAGED)
|
||
HDD_PACK_ARGS := $(BUILD_DIR)/$(EXAMPLE).exe \
|
||
$(foreach f,$(DISK),$(word 1,$(subst /, ,$(f))):$(BUILD_DIR)/$(f))
|
||
# Образ живёт в build/, а не в mame/v306/IMG — приложение автономно. Связь с
|
||
# MAME — одна символьная ссылка, ставится однократно: make mame-link.
|
||
HDD_IMG := $(CURDIR)/$(BUILD_DIR)/hdd/$(EXAMPLE).chd
|
||
|
||
include $(PROJ_ROOT)/app.mk
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Сборка приложения
|
||
# ---------------------------------------------------------------------------
|
||
# sprinter-cc собирает все модули одним вызовом и depfile не выдаёт, поэтому
|
||
# зависимости перечисляем сами. wildcard по заголовкам намеренно широкий:
|
||
# цена лишней полной сборки ниже риска получить stale-бинарник.
|
||
HEADERS := $(wildcard $(SRC_DIR)/*.h)
|
||
# ОТПЕЧАТОК КЛЮЧЕЙ СБОРКИ. LEVEL/ROOM/POS/PROF/ALLOCS — это -D на входе
|
||
# компилятора, а не файлы, и make их «не видит»: после `make LEVEL=14`
|
||
# обычный `make` считал бы отладочный бинарь свежим и молча оставил его.
|
||
# Поэтому ключи пишутся в файл, и exe зависит от него; меняется строка —
|
||
# перелинковка. Файл переписывается ТОЛЬКО при изменении, иначе каждый
|
||
# make дёргал бы полную сборку заново.
|
||
BUILD_SIG := MEMORY=$(MEMORY) ALLOCS=$(ALLOCS) $(PROF_FLAGS) $(BANK_FLAGS)
|
||
BUILD_STAMP := $(BUILD_DIR)/.buildflags
|
||
# Отпечаток обновляется НА РАЗБОРЕ Makefile, а не рецептом. Так время файла
|
||
# уже верное к моменту, когда make сверяет его с exe — иначе не работает ни
|
||
# один из простых вариантов:
|
||
# * рецепт с cmp — под `make -n` не выполняется, и make врёт про работу;
|
||
# * подпись в имени файла — при ВОЗВРАТЕ к прежним ключам файл уже лежит
|
||
# и оказывается старше exe, то есть перелинковки не будет.
|
||
# Запись идёт только при отличии, поэтому обычный make ничего не трогает.
|
||
# На целях очистки пропускаем, чтобы `make clean` не создавал build/ заново.
|
||
ifeq ($(filter clean distclean,$(MAKECMDGOALS)),)
|
||
$(shell mkdir -p $(BUILD_DIR); printf '%s\n' '$(BUILD_SIG)' | cmp -s - $(BUILD_STAMP) \
|
||
|| printf '%s\n' '$(BUILD_SIG)' > $(BUILD_STAMP))
|
||
endif
|
||
|
||
# Цепочка: exe -> gen/*.h -> ресурсы -> упаковщик -> assets/orig (см. ниже).
|
||
$(EXE): Makefile $(PROJ_ROOT)/app.mk $(addprefix $(SRC_DIR)/,$(BANKED_SRCS)) \
|
||
$(HEADERS) $(GEN_H) $(BUILD_STAMP)
|
||
|
||
# Полная сборка = exe + разложенный build/.
|
||
all: stage
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# staging: assets/packed/ -> build/
|
||
# ---------------------------------------------------------------------------
|
||
# build/ обязан быть самодостаточным (его можно снести целиком), поэтому
|
||
# ресурсы именно КОПИРУЮТСЯ, а не берутся с диска из assets/.
|
||
stage: $(STAGED)
|
||
|
||
$(BUILD_DIR)/%: $(PACKED)/%
|
||
@mkdir -p $(dir $@)
|
||
@cp $< $@
|
||
# ---------------------------------------------------------------------------
|
||
# Ресурсы: цепочка зависимостей
|
||
# ---------------------------------------------------------------------------
|
||
# Направление одно и сквозное:
|
||
#
|
||
# build/sprpop.exe -> gen/*.h -> архивы и атласы -> упаковщик -> assets/orig/
|
||
#
|
||
# Код зависит от генерируемых заголовков, заголовки — от ресурсов, которые они
|
||
# описывают (индексы внутри .arc, число страниц звука, таблицы кадров), а те —
|
||
# от упаковщика и оригиналов. Поэтому собрать код против устаревших констант
|
||
# нельзя: gen/*.h делаются ТЕМ ЖЕ запуском упаковщика, что и сам ресурс, и
|
||
# объявлены его целями наравне с ним.
|
||
#
|
||
# Правила генерации живут в ветке GENERATE=1 и включаются в двух случаях:
|
||
# * явная цель `make resources*` — перегенерация с учётом таймстемпов
|
||
# (поправил упаковщик — ресурс пересоберётся);
|
||
# * какого-то ресурса или заголовка НЕТ на месте (после `make distclean`,
|
||
# либо файл снесли руками) — тогда обычный `make` сам уходит в
|
||
# рекурсивный вызов и доделывает недостающее вместе с его gen/*.h.
|
||
#
|
||
# Почему не одни таймстемпы на все случаи: assets/packed/ и gen/ лежат в
|
||
# репозитории, а git не сохраняет времена файлов — в свежем клоне упаковщик
|
||
# и его выхлоп получают одинаковую метку, и любое сравнение по времени
|
||
# превращается в лотерею «перепаковать 6 МБ ресурсов на ровном месте, причём
|
||
# на машине, где assets/orig/ может вообще не быть». Отсутствие файла —
|
||
# признак однозначный, и именно на нём построена автоматика.
|
||
TOOLS := $(CURDIR)/tools
|
||
PY := python3
|
||
|
||
ifeq ($(GENERATE),1)
|
||
# ===========================================================================
|
||
# Ветка генерации
|
||
# ===========================================================================
|
||
STAMP := $(BUILD_DIR)/.stamps
|
||
$(STAMP):
|
||
@mkdir -p $@
|
||
|
||
# Упаковщики создают сразу ГРУППУ файлов. Обычное multi-output правило
|
||
# заставляет make при `-B` запускать один и тот же упаковщик отдельно для
|
||
# каждого выходного файла. Stamp представляет ОДИН запуск генератора; все
|
||
# созданные им файлы зависят от stamp без собственных рецептов.
|
||
#
|
||
# check-orig — order-only: без оригиналов запускать упаковщик бессмысленно, и
|
||
# понятное сообщение лучше трейсбека Python. На «устарел ли stamp» он не
|
||
# влияет, только выполняется раньше.
|
||
define stamp_rule
|
||
$(STAMP)/$(1): $(2) | $(STAMP) check-orig
|
||
cd $(TOOLS) && $(PY) $(3)
|
||
@touch $$@
|
||
endef
|
||
|
||
$(eval $(call stamp_rule,bg,$(TOOLS)/pop_pack_bg.py $(TOOLS)/render_room.py $(TOOLS)/paths.py,pop_pack_bg.py dungeon && $(PY) pop_pack_bg.py palace))
|
||
$(eval $(call stamp_rule,kid,$(TOOLS)/pop_pack_kid.py $(TOOLS)/pop_pack_bg.py $(TOOLS)/paths.py,pop_pack_kid.py))
|
||
$(eval $(call stamp_rule,kid-bin,$(TOOLS)/pop_extract_kid_data.py $(TOOLS)/paths.py,pop_extract_kid_data.py))
|
||
$(eval $(call stamp_rule,guard,$(TOOLS)/pop_pack_guard.py $(TOOLS)/paths.py,pop_pack_guard.py))
|
||
$(eval $(call stamp_rule,skel,$(TOOLS)/pop_pack_guard.py $(TOOLS)/paths.py,pop_pack_guard.py SKEL))
|
||
$(eval $(call stamp_rule,vizier,$(TOOLS)/pop_pack_guard.py $(TOOLS)/paths.py,pop_pack_guard.py VIZIER))
|
||
$(eval $(call stamp_rule,shadow,$(TOOLS)/pop_pack_shadow.py $(TOOLS)/pop_pack_bg.py $(TOOLS)/pop_pack_kid.py $(TOOLS)/paths.py,pop_pack_shadow.py))
|
||
$(eval $(call stamp_rule,sound,$(TOOLS)/pop_pack_sound.py $(TOOLS)/paths.py,pop_pack_sound.py))
|
||
$(eval $(call stamp_rule,music-$(MUSIC_FMT),$(TOOLS)/pop_pack_music.py $(TOOLS)/paths.py,pop_pack_music.py --format $(MUSIC_FMT) $(MUS_IDS)))
|
||
$(eval $(call stamp_rule,font,$(TOOLS)/pop_extract_font.py $(TOOLS)/paths.py,pop_extract_font.py))
|
||
$(eval $(call stamp_rule,title,$(TOOLS)/pop_pack_title.py $(TOOLS)/paths.py,pop_pack_title.py))
|
||
$(eval $(call stamp_rule,pv,$(TOOLS)/pop_pack_intro.py $(TOOLS)/paths.py,pop_pack_intro.py))
|
||
|
||
# Файл, сделанный упаковщиком, зависит только от stamp. Если stamp свежий, а
|
||
# файла нет (снесли руками, либо build/ вычистили) — перезапускаем генератор
|
||
# принудительно. Заголовки gen/, которые упаковщик печатает попутно, стоят в
|
||
# ТЕХ ЖЕ списках: это и есть «gen зависит от ресурсов, для которых сделан».
|
||
define from_stamp
|
||
$(1): $(STAMP)/$(2)
|
||
@test -f $$@ || { $(MAKE) GENERATE=1 -B $(STAMP)/$(2); test -f $$@; }
|
||
endef
|
||
$(eval $(call from_stamp,$(BG_ATL) $(ATL)/bg/pop_tile.pal $(ATL)/bg/pal_tile.pal gen/pop_bg_atlas.h,bg))
|
||
$(eval $(call from_stamp,$(KID_ATL) $(ATL)/kid/kid.pal gen/kid_atlas.h,kid))
|
||
$(eval $(call from_stamp,$(ATL)/kid/kid.ani gen/kid_data.h,kid-bin))
|
||
$(eval $(call from_stamp,$(GUARD_ATL) gen/pop_guard_pal.h,guard))
|
||
$(eval $(call from_stamp,$(SKEL_ATL),skel))
|
||
$(eval $(call from_stamp,$(VIZIER_ATL),vizier))
|
||
$(eval $(call from_stamp,$(SHADOW_ATL) gen/pop_shadow_atlas.h,shadow))
|
||
$(eval $(call from_stamp,$(SND_ATL) gen/pop_sound_tbl.h,sound))
|
||
$(eval $(call from_stamp,$(foreach i,$(MUS_IDS),$(ATL)/music/m$(i).bin) gen/pop_music_tbl.h gen/pop_music_ticks.h,music-$(MUSIC_FMT)))
|
||
$(eval $(call from_stamp,$(ATL)/font/font.atl gen/pop_font.h,font))
|
||
$(eval $(call from_stamp,$(TITLE_ATL) $(ATL)/title/title.pal,title))
|
||
$(eval $(call from_stamp,$(PV_ATL) $(ATL)/pv/story.pal $(ATL)/pv/pv.pal,pv))
|
||
|
||
# Архивы: собираются ПРЯМО в assets/packed — это уже дисковый артефакт.
|
||
# Заголовок с индексами объявлен целью НАРАВНЕ с архивом: индексы описывают
|
||
# именно это содержимое и разъехаться с ним не должны.
|
||
ARC := $(PY) $(TOOLS)/pop_pack_arc.py
|
||
define arc_rule
|
||
$(PACKED)/$(1)/$(2).arc gen/$(3)_arc.h: $(4) $(TOOLS)/pop_pack_arc.py
|
||
@mkdir -p $(PACKED)/$(1)
|
||
$(ARC) --header gen/$(3)_arc.h --prefix $(5) $(PACKED)/$(1)/$(2).arc $(4)
|
||
endef
|
||
$(eval $(call arc_rule,BG,bg,bg,$(BG_ATL),BG))
|
||
$(eval $(call arc_rule,KID,kid,kid,$(KID_ATL),KID))
|
||
$(eval $(call arc_rule,GUARD,guard,guard,$(GUARD_ATL),GUARD))
|
||
$(eval $(call arc_rule,SKEL,skel,skel,$(SKEL_ATL),SKEL))
|
||
$(eval $(call arc_rule,VIZIER,vizier,vizier,$(VIZIER_ATL),VIZIER))
|
||
$(eval $(call arc_rule,SHADOW,shadow,shadow,$(SHADOW_ATL),SHADOW))
|
||
$(eval $(call arc_rule,TITLE,title,title,$(TITLE_ATL),TITLE))
|
||
$(eval $(call arc_rule,PV,pv,pv,$(PV_ATL),PV))
|
||
# У звука своего *_arc.h нет: содержимое адресуется таблицей pop_sound_tbl.h,
|
||
# которую печатает сам упаковщик звука.
|
||
$(PACKED)/SND/snd.arc: $(SND_ATL) $(TOOLS)/pop_pack_arc.py
|
||
@mkdir -p $(dir $@)
|
||
$(ARC) $@ $(SND_ATL)
|
||
|
||
# Файлы, которые ложатся на диск как есть, — простое копирование из staging.
|
||
define copy_rule
|
||
$(PACKED)/$(1): $(2)
|
||
@mkdir -p $$(dir $$@)
|
||
@cp $$< $$@
|
||
endef
|
||
$(eval $(call copy_rule,BG/pop_tile.pal,$(ATL)/bg/pop_tile.pal))
|
||
$(eval $(call copy_rule,BG/pal_tile.pal,$(ATL)/bg/pal_tile.pal))
|
||
$(eval $(call copy_rule,KID/kid.pal,$(ATL)/kid/kid.pal))
|
||
$(eval $(call copy_rule,KID/kid.ani,$(ATL)/kid/kid.ani))
|
||
$(eval $(call copy_rule,FONT/font.atl,$(ATL)/font/font.atl))
|
||
$(eval $(call copy_rule,TITLE/title.pal,$(ATL)/title/title.pal))
|
||
$(eval $(call copy_rule,PV/story.pal,$(ATL)/pv/story.pal))
|
||
$(eval $(call copy_rule,PV/pv.pal,$(ATL)/pv/pv.pal))
|
||
$(foreach i,$(MUS_IDS),$(eval $(call copy_rule,MUS/m$(i).bin,$(ATL)/music/m$(i).bin)))
|
||
# Уровни идут на диск ПРЯМО из оригинала: формат res20xx.bin наш загрузчик
|
||
# читает как есть, упаковщика для них нет. check-orig здесь тоже order-only —
|
||
# без оригиналов взять уровни неоткуда, и сказать об этом надо внятно.
|
||
LVL_ORIG := assets/orig/SDLPoP/data/LEVELS
|
||
define lvl_rule
|
||
$(PACKED)/LEVELS/res20$(1).bin: $(LVL_ORIG)/res20$(1).bin | check-orig
|
||
@mkdir -p $$(dir $$@)
|
||
@cp $$< $$@
|
||
endef
|
||
$(foreach n,$(LVL_NUMS),$(eval $(call lvl_rule,$(n))))
|
||
|
||
resources-bg: $(PACKED)/BG/bg.arc $(PACKED)/BG/pop_tile.pal $(PACKED)/BG/pal_tile.pal
|
||
resources-kid: $(PACKED)/KID/kid.arc $(PACKED)/KID/kid.pal $(PACKED)/KID/kid.ani
|
||
resources-actors: $(PACKED)/GUARD/guard.arc $(PACKED)/SKEL/skel.arc \
|
||
$(PACKED)/VIZIER/vizier.arc $(PACKED)/SHADOW/shadow.arc
|
||
resources-sound: $(PACKED)/SND/snd.arc
|
||
resources-music: $(foreach i,$(MUS_IDS),$(PACKED)/MUS/m$(i).bin)
|
||
resources-font: $(PACKED)/FONT/font.atl
|
||
resources-title: $(PACKED)/TITLE/title.arc $(PACKED)/TITLE/title.pal
|
||
resources-pv: $(PACKED)/PV/pv.arc $(PACKED)/PV/story.pal $(PACKED)/PV/pv.pal
|
||
resources-levels: $(foreach n,$(LVL_NUMS),$(PACKED)/LEVELS/res20$(n).bin)
|
||
resources: resources-bg resources-kid resources-actors resources-sound \
|
||
resources-music resources-font resources-title resources-pv \
|
||
resources-levels $(GEN_H)
|
||
|
||
check-orig:
|
||
@test -d $(LVL_ORIG) || { \
|
||
echo "SprPoP: нет assets/orig/ — генерировать ресурсы не из чего." >&2; \
|
||
echo " Что и откуда положить, написано в assets/orig/README.md" >&2; false; }
|
||
|
||
else
|
||
# ===========================================================================
|
||
# Обычная сборка: assets/packed/ и gen/ — готовые входы
|
||
# ===========================================================================
|
||
# Ни одного правила с таймстемпами: пока файлы на месте, make их даже не
|
||
# рассматривает. Пропал файл — доделываем ровно его, рекурсивным вызовом в
|
||
# ветку генерации (она подтянет и заголовок, и оригиналы).
|
||
$(PACKED_FILES) $(GEN_H):
|
||
@echo "SprPoP: нет $@ — генерирую ресурсы"
|
||
@$(MAKE) GENERATE=1 $@
|
||
|
||
RES_TARGETS := resources resources-bg resources-kid resources-actors \
|
||
resources-sound resources-music resources-font resources-title \
|
||
resources-pv resources-levels
|
||
$(RES_TARGETS):
|
||
@$(MAKE) GENERATE=1 $@
|
||
# Перегенерация ВСЕГО, не глядя на таймстемпы.
|
||
resources-rebuild:
|
||
@$(MAKE) GENERATE=1 -B resources
|
||
.PHONY: $(RES_TARGETS) resources-rebuild
|
||
endif
|
||
# ---------------------------------------------------------------------------
|
||
# Прочее
|
||
# ---------------------------------------------------------------------------
|
||
# Образ HDD собирается после staging — иначе на диск уедет старое содержимое.
|
||
# Рецепта здесь нет намеренно: он в app.mk, а это только зависимости.
|
||
hdd: stage | $(BUILD_DIR)/hdd
|
||
$(BUILD_DIR)/hdd:
|
||
@mkdir -p $@
|
||
|
||
# Однократная связка с MAME: run_bridge.sh жёстко открывает IMG/test_hdd.chd,
|
||
# поэтому подсовываем ему символьную ссылку на наш образ. chdman переписывает
|
||
# сам файл, ссылка остаётся живой — но MAME после пересборки образа ОБЯЗАН
|
||
# полный рестарт (новый inode).
|
||
mame-link: hdd
|
||
ln -sf $(HDD_IMG) $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd
|
||
@echo "MAME: IMG/test_hdd.chd -> $(HDD_IMG)"
|
||
|
||
# Музыка из конкретного набора записей. Каждая цель пересобирает музыку и
|
||
# сразу раскладывает её в build/, чтобы `make hdd` взял уже новую.
|
||
# make music-mp3 собрать музыку из mp3
|
||
# make music-mt32 вариант Roland MT-32
|
||
# Разовая сборка без смены умолчания: make MUSIC_FMT=ogg resources-music
|
||
MUSIC_TARGETS := music-flac music-mp3 music-ogg music-mt32
|
||
$(MUSIC_TARGETS):
|
||
@$(MAKE) MUSIC_FMT=$(patsubst music-%,%,$@) resources-music
|
||
@$(MAKE) MUSIC_FMT=$(patsubst music-%,%,$@) stage
|
||
|
||
# Перелинковка приложения без пересборки библиотек и ресурсов.
|
||
relink:
|
||
$(MAKE) -W Makefile $(EXE)
|
||
|
||
# clean (из app.mk) сносит только build/ — ассеты остаются на месте.
|
||
# distclean дополнительно убирает УПАКОВАННЫЕ ассеты; вернуть их можно
|
||
# командой `make resources`, но для неё нужны оригиналы в assets/orig/.
|
||
# Сам assets/orig/ не трогается НИКОГДА: он не производный, а исходный, и
|
||
# восстановить его командой нельзя — только положить руками.
|
||
# gen/ тоже остаётся: без него не скомпилировать src/, а вес его — байты.
|
||
distclean: clean
|
||
rm -rf $(PACKED)
|
||
@echo "assets/packed снесён; вернуть — make resources (нужен assets/orig)"
|
||
|
||
# Цели генерации ресурсов объявлены .PHONY в своей ветке (см. выше): в
|
||
# GENERATE=1 они настоящие агрегаты, в обычной сборке — переходники.
|
||
.PHONY: stage check-orig resources resources-rebuild resources-bg resources-kid \
|
||
resources-actors resources-sound resources-music resources-font \
|
||
resources-title resources-pv resources-levels $(MUSIC_TARGETS) \
|
||
relink distclean mame-link
|