Усі сторінки форматів ↓
Мапи й рельєф

SCT — секції мап

Джерело: coreprime/kbot @ 0ab97bf · Редагувати на GitHub

Файл .sct — це багаторазовий фрагмент рельєфу мапи: дизайнери рівнів Cavedog зшивали десятки таких фрагментів в офіційному редакторі мап (TAE), щоб зібрати готові мапи .tnt. Кожна секція містить власні графіку тайлів, сітку тайлів, висоти для кожної клітинки й мініатюру мінімапи. Секції — це будівельні блоки; TNT — зібраний виріб.

Секція узбережжя Greenworld, відрендерена тайлами
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

Див. довідник CLI 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 px). Щоб секція була сумісна з TAE, обидва мають бути кратні 4 (тобто кратні 128 px).
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 індексів палітри. Розміри фіксовані незалежно від ширини й висоти секції в тайлах, і мінімапа розтягується під пропорції секції, а не вписується з полями.


Порядок читання

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 px). Хоча секція має 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 (в усіх біомах)

Див. також

  • TNT — формат зібраної мапи, що використовує секції.
  • PAL — палітра, якою забарвлюються і пікселі тайлів, і мінімапа.
  • Глосарій — tile, attribute cell, heightmap.