SCT: seções de mapa
Fonte: coreprime/kbot @ 0ab97bf · Editar no GitHub
Um arquivo
.scté um pedaço reutilizável de terreno de mapa: os designers de fases da Cavedog juntavam dezenas deles no editor de mapas oficial (TAE) para montar os mapas.tntfinais. Cada seção reúne os seus próprios gráficos de blocos, a grade de blocos, as alturas por célula e um minimapa em miniatura. As seções são os blocos de montagem; os TNTs são o produto montado.
sections/greenworld/coast/b_n_w02_135.sct renderizado por kbot sct image
Minimapa embutido (à esquerda) e grade de elevação (à direita).
Tip
Experimente você mesmo.
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
Veja a referência do kbot sct da linha de comando.
Em Go. Use 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))
Estrutura no disco
┌─ 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
Os campos de ponteiro podem apontar para as partes em qualquer ordem, mas as ferramentas da Cavedog as gravam na estrutura mostrada acima, e é isso que todo leitor de terceiros pressupõe.
Cabeçalho (28 bytes)
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;
| Campo | Observações |
|---|---|
Version |
3 nas seções da versão comercial do TA. Alguns protótipos antigos usam 2: a mesma estrutura, mas com entradas de altura de 8 bytes em vez de 4. O kbot aceita as duas. |
Width, Height |
Os dois medidos em blocos (unidades de 32 px). Numa seção compatível com o TAE, os dois precisam ser múltiplos de 4 (ou seja, múltiplos de 128 px). |
NumTiles |
Número de blocos únicos. A grade de índices da parte de dados aponta para esse array. |
Pixels dos blocos
NumTiles blocos de 32 × 32 = 1024 bytes cada: índices na paleta do
TA. Blocos idênticos não são eliminados automaticamente pelo formato,
por isso uma seção mal montada pode desperdiçar espaço. O comando image
do kbot avisa quando uma seção tem blocos inalcançáveis.
Dados da seção: índices de blocos + grade de alturas
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];
A grade de alturas é quatro vezes mais densa que a grade de blocos:
cada bloco recebe 2 × 2 = 4 amostras de altura com resolução de 16
pixels, como as células de atributo do TNT. Os campos always_-1 e
always_0 são metadados de rascunho do editor. Na ida e volta, qualquer
valor pode ser regravado.
Minimapa
128 × 128 índices da paleta. As dimensões são fixas, qualquer que seja a
largura ou a altura da seção em blocos, e o minimapa é esticado para
caber na proporção da seção, em vez de ganhar faixas nas bordas.
Ordem de leitura
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)
Exemplo comentado: 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
Uma seção de 4×4 blocos é a menor que o TAE aceita (as dimensões precisam
ser múltiplas de 128 px). Embora a seção tenha 4 × 4 = 16 posições, ela
referencia 64 blocos únicos, porque cada célula de 32×32 é delimitada por
transições de borda das quais o designer quis ter variações. Isso é
normal: as seções trocam espaço de armazenamento por liberdade de
mesclagem.
Como o TNT usa as seções
O conjunto de blocos de um mapa .tnt é a união de todos os .sct que o
designer de fases "pintou" nele, com as duplicatas eliminadas depois pelo
TAE. Por isso, quando dois TNTs têm um bloco em comum, ele quase sempre
veio do mesmo SCT.
A ligação existe só na hora de montar o mapa. O motor do jogo nunca
abre uma seção: ele só vê o TNT já montado. As seções ficam no disco, nos
arquivos compactados do editor (worlds.hpi) e nos arquivos compactados
dos mundos da campanha, por exemplo sections/greenworld/coast/,
sections/lava/hills/ etc.
Armadilhas
Warning
Width × Height precisa ser múltiplo de 4 para o TAE carregar a
seção. O formato em si não exige isso (dá para gravar uma seção de 3×3
blocos, e o kbot a renderiza sem problema), mas o editor da Cavedog a
rejeita com um erro enigmático, "section misaligned".
Version == 2tem células de altura de 8 bytes, não de 4. O primeiro byte continua sendo a elevação. Os 7 bytes restantes são rascunho do editor, que podem ser zerados na gravação.- O minimapa de 128×128 é esticado, e não ganha faixas nas bordas, para caber na proporção da seção. Uma seção de 4×16 fica horrivelmente achatada no minimapa.
- Não há assinatura além de
Version. Para validar um candidato a SCT, confira seVersion ∈ {2, 3}e se os quatro campos de ponteiro caem todos dentro do arquivo. - As faixas de altura ficam inteiramente a critério do designer. O TAE limita a 0–255, mas uma seção pode ser totalmente plana (um único valor) ou usar a faixa inteira. O renderizador do TNT normaliza antes de exibir; o SCT, não.
Tamanhos típicos
| Recurso | Faixa observada em sections/**/*.sct da Cavedog |
|---|---|
| Tamanho do arquivo | 30 KB – 2 MB |
| Dimensões da seção (blocos) | 4×4 – 32×32 |
| Blocos únicos por seção | 30–600 |
| Custo do cabeçalho | sempre 28 bytes |
| Minimapa (sempre 128×128 com paleta) | 16.384 bytes |
Seções em worlds.hpi |
~5.000 (em todos os biomas) |