SCT — секции карт
Источник: coreprime/kbot @ 0ab97bf · Редактировать на GitHub
Файл
.sct— это многоразовый фрагмент рельефа карты: дизайнеры уровней Cavedog сшивали десятки таких фрагментов в официальном редакторе карт (TAE), чтобы собрать готовые карты.tnt. Каждая секция содержит собственную графику тайлов, сетку тайлов, высоты ячеек и миникарту-эскиз. Секции — строительные блоки, а TNT — собранный продукт.
sections/greenworld/coast/b_n_w02_135.sct, отрисованная командой kbot sct image
Встроенная миникарта (слева) и сетка высот (справа).
Tip
Попробуйте сами.
kbot sct info sections/greenworld/coast/b_n_w02_135.sct
kbot sct describe sections/greenworld/coast/b_n_w02_135.sct
kbot sct image sections/greenworld/coast/b_n_w02_135.sct -t out.png
kbot sct minimap sections/greenworld/coast/b_n_w02_135.sct -t mini.png
kbot sct heightmap sections/greenworld/coast/b_n_w02_135.sct -t height.png
См. справочник по kbot sct для командной строки.
Из Go. Используйте пакет formats/sct:
import "github.com/coreprime/kbot/formats/sct"
f, _ := os.Open("sections/greenworld/coast/b_n_w02_135.sct")
defer f.Close()
s, _ := sct.LoadFromReader(f)
fmt.Printf("%dx%d tiles, %d unique, %d heights\n",
s.Header.Width, s.Header.Height, s.Header.NumTiles, len(s.HeightMap))
Структура на диске
┌─ Header (28 B) ──┐ version, pointers, dimensions
├─ Tile pixels ────┤ NumTiles × 1024 bytes (32×32 palette indices)
├─ Section data ───┤ Width × Height × int16 tile indices
├─ Height grid ────┤ (Width × 2) × (Height × 2) × {4 or 8} bytes
└─ Minimap ────────┘ 128 × 128 palette indices
Поля-указатели могут ссылаться на части файла в любом порядке, но инструменты Cavedog записывают их в порядке, показанном выше, и именно на это рассчитывают все сторонние читатели.
Заголовок (28 байт)
typedef struct {
uint32 Version; // 2 or 3 (TA's TAE always emits 3)
uint32 PtrMinimap; // → 128×128 minimap
uint32 NumTiles; // unique 32×32 tile count
uint32 PtrTiles; // → tile pixel block
uint32 Width; // section width in tiles
uint32 Height; // section height in tiles
uint32 PtrData; // → tile-index grid + heights
} SCTHeader;
| Поле | Примечания |
|---|---|
Version |
3 для оригинальных секций TA. Некоторые ранние прототипы используют 2 — та же структура, но записи высот занимают 8 байт вместо 4. kbot принимает обе версии. |
Width, Height |
Оба заданы в тайлах (единицах по 32 пикселя). Для секции, совместимой с TAE, оба должны быть кратны 4 (то есть кратны 128 пикселям). |
NumTiles |
Число уникальных тайлов. Сетка индексов в секции данных ссылается на этот массив. |
Пиксели тайлов
NumTiles блоков по 32 × 32 = 1024 байта — индексы в палитре TA.
Одинаковые тайлы сам формат автоматически не дедуплицирует, так что
неаккуратно собранная секция может тратить место впустую; команда
image в kbot предупреждает, если в секции есть недостижимые тайлы.
Данные секции — индексы тайлов + сетка высот
int16 tileMap[Height][Width]; // Index into the tile pixel array
// (signed; negative values are unused
// in retail content but valid)
struct HeightCell_V3 { uint8 h; int16 always_minus_one; uint8 always_zero; };
struct HeightCell_V2 { uint8 h; int16 always_minus_one; uint8 always_zero;
uint32 reserved; };
HeightCell heights[Height * 2][Width * 2];
Сетка высот в четыре раза плотнее сетки тайлов: на каждый тайл
приходится 2 × 2 = 4 отсчёта высоты с разрешением 16 пикселей — так
же, как устроены ячейки атрибутов в TNT. Поля always_-1 и always_0 —
служебные метаданные редактора; при round-trip можно записывать обратно
любое значение.
Миникарта
128 × 128 индексов палитры. Размеры фиксированы независимо от ширины и
высоты секции в тайлах, и миникарта растягивается под пропорции
секции, а не вписывается с полями (letterbox).
Порядок чтения
header = read_struct(SCTHeader)
assert header.Version in (2, 3)
seek(header.PtrTiles)
tiles = [read(1024) for _ in range(header.NumTiles)]
seek(header.PtrData)
tile_map = read_int16_array(header.Width * header.Height)
entry_size = 4 if header.Version == 3 else 8
attrs_w, attrs_h = header.Width * 2, header.Height * 2
heights = [read(entry_size)[0] for _ in range(attrs_w * attrs_h)]
seek(header.PtrMinimap)
minimap = read(128 * 128)
Разбор примера — b_n_w02_135.sct
$ kbot sct describe sections/greenworld/coast/b_n_w02_135.sct
Version: 3
Section: 4 × 4 tiles (128 × 128 px)
Tiles: 64 unique
Heights: 8 × 8 cells (height range 50–62)
Minimap: 128 × 128
Секция 4×4 тайла — наименьшая, которую примет TAE (размеры должны быть
кратны 128 пикселям). Хотя в секции 4 × 4 = 16 позиций, она ссылается
на 64 уникальных тайла, потому что каждая ячейка 32×32 сама ограничена
краевыми переходами, для которых дизайнер хотел иметь варианты. Это
нормально: секции жертвуют объёмом ради свободы при смешивании.
Как TNT использует секции
Пул тайлов карты .tnt — это объединение всех .sct, которые дизайнер
уровня «нарисовал» на ней, с последующей дедупликацией в TAE. Поэтому
если у двух TNT нашёлся общий тайл, он почти всегда взят из одной и той
же SCT.
Эта связь существует только во время сборки карты. Движок во время
игры никогда не открывает секции, он видит только уже собранный TNT.
Секции лежат на диске в архивах редактора (worlds.hpi) и в архивах
миров кампании, например sections/greenworld/coast/,
sections/lava/hills/ и т. д.
Подводные камни
Warning
Чтобы TAE загрузил секцию, Width × Height должно быть кратно 4.
Сам формат этого не требует — можно записать секцию 3×3 тайла, и kbot
спокойно её отрисует, — но редактор Cavedog отвергнет её с невнятной
ошибкой «section misaligned».
- При
Version == 2ячейки высот занимают 8 байт, а не 4. Первый байт — по-прежнему высота, остальные 7 байт — служебные данные редактора, которые при записи можно обнулить. - Миникарта 128×128 растягивается, а не вписывается с полями, под пропорции секции. Секция 4×16 на миникарте будет выглядеть ужасно сплюснутой.
- Сигнатуры нет, кроме
Version. Чтобы проверить, похож ли файл на SCT, убедитесь, чтоVersion ∈ {2, 3}и что все четыре поля-указателя указывают внутрь файла. - Диапазон высот целиком на усмотрение дизайнера. TAE ограничивает значения диапазоном 0–255, но секция может быть совершенно плоской (одно значение) или занимать весь диапазон. Отрисовщик TNT нормализует высоты перед показом, SCT — нет.
Типичные размеры
| Ресурс | Диапазон в sections/**/*.sct от Cavedog |
|---|---|
| Размер файла | 30 КБ – 2 МБ |
| Размеры секции (в тайлах) | 4×4 – 32×32 |
| Уникальных тайлов в секции | 30–600 |
| Заголовок | всегда 28 байт |
| Миникарта (всегда 128×128 с палитрой) | 16384 байта |
Секций в worlds.hpi |
~5000 (во всех биомах) |