- Stage 1: Eliminate fish-eye distortion with precomputed tangential ray offset table and fractional DDA depth - Stage 2: Full asset management in EblanStudio (stb_image/stb_image_write, 128x128 BGRA texture packaging, WAV sound manager) - Stage 3: Multi-level room streaming, LevelTransition/CELL_PORTAL trigger system, 16-frame screen fade to black - Stage 4: 60Hz cutscene engine in pure x86 ASM (v4 binary track, waypoint camera motion, shortest-arc pan, CP1251 Russian subtitles, letterbox overlay, audio) - Stage 5: EblanStudio cutscene timeline (F6) and windowless Live ASM Viewport over shared-memory IPC directly rendered into DX11 - Tooling & Tests: comprehensive regression test suite (94 editor + 107 engine tests), IPC tests, and cutscene demo
96 lines
4 KiB
C++
96 lines
4 KiB
C++
#pragma once
|
|
#include <cstdint>
|
|
|
|
// ==============================================================================
|
|
// EBLAN ENGINE - Binary Level Format Specification (assets/map.dat)
|
|
// ==============================================================================
|
|
|
|
constexpr uint32_t MAP_MAGIC = 0x4E4C4245; // 'EBLN'
|
|
constexpr uint16_t MAP_FORMAT_VERSION = 4; // v4 adds a cutscene track and CP1251 string pool
|
|
constexpr int MAP_GRID_W = 32;
|
|
constexpr int MAP_GRID_H = 32;
|
|
constexpr int MAP_TOTAL_CELLS = MAP_GRID_W * MAP_GRID_H; // 1024 bytes
|
|
constexpr int MAX_LEVEL_SPRITES = 32;
|
|
|
|
// Cell Types
|
|
enum CellType : uint8_t {
|
|
CELL_EMPTY = 0,
|
|
CELL_BRICK = 1,
|
|
CELL_WOOD = 2,
|
|
CELL_STONE = 3,
|
|
CELL_METAL = 4,
|
|
CELL_DOOR = 5,
|
|
CELL_DOOR_OPEN = 6,
|
|
CELL_PILLAR = 7,
|
|
CELL_GLASS = 8,
|
|
CELL_MARBLE = 9,
|
|
CELL_PORTAL = 10,
|
|
CELL_COUNT = 11
|
|
};
|
|
|
|
constexpr int MAX_LEVEL_TRANSITIONS = 16;
|
|
constexpr int MAX_CUTSCENE_EVENTS = 128;
|
|
constexpr int MAX_CUTSCENE_STRINGS = 32;
|
|
constexpr int CUTSCENE_STRING_BYTES = 96;
|
|
constexpr int CUTSCENE_MAX_DURATION = 3600;
|
|
constexpr uint16_t CUTSCENE_AUTOPLAY = 1;
|
|
|
|
enum CutsceneOpCode : uint8_t {
|
|
OP_END, OP_WAIT, OP_MOVE_CAM_TO, OP_PAN_CAM_TO, OP_SHOW_SUBTITLE,
|
|
OP_HIDE_SUBTITLE, OP_PLAY_SOUND, OP_FADE_SCREEN, OP_CHANGE_SUBLEVEL
|
|
};
|
|
|
|
#pragma pack(push, 1)
|
|
struct LevelTransition {
|
|
uint8_t triggerCellX; // Trigger cell coordinate X (0..31)
|
|
uint8_t triggerCellY; // Trigger cell coordinate Y (0..31)
|
|
char targetMap[32]; // Target sublevel filename (e.g. "sublevel_02.dat")
|
|
uint16_t targetSpawnX; // Spawn cell X in destination map (0..31)
|
|
uint16_t targetSpawnY; // Spawn cell Y in destination map (0..31)
|
|
uint16_t targetAngle; // Player orientation (0..511)
|
|
uint8_t fadeEffect; // 1 = smooth fade to black, 0 = instant cut
|
|
uint8_t reserved; // Padding (total 42 bytes)
|
|
};
|
|
|
|
struct MapHeader {
|
|
uint32_t magic; // 'EBLN'
|
|
uint16_t version; // Current writer: 4; reader also accepts v1/v2/v3
|
|
uint16_t mapWidth; // 32
|
|
uint16_t mapHeight; // 32
|
|
uint16_t playerStartX; // Cell coordinate (0..31)
|
|
uint16_t playerStartY; // Cell coordinate (0..31)
|
|
uint16_t playerStartAngle; // 0..511 (0=East, 128=South, 256=West, 384=North)
|
|
uint16_t spriteCount; // Number of active sprites
|
|
uint32_t ceilingColor; // 00RRGGBBh
|
|
uint32_t floorColor; // 00RRGGBBh
|
|
uint16_t transitionCount; // Number of sublevel transitions
|
|
uint16_t cutsceneCount; // v4 only; old versions ignore the remaining header fields
|
|
uint16_t cutsceneStringCount;
|
|
uint16_t cutsceneFlags; // bit 0 = start automatically after entering this map
|
|
uint8_t reserved[4]; // Total header remains 38 bytes
|
|
};
|
|
|
|
struct CutsceneEvent {
|
|
uint16_t tickTimestamp; // Absolute 60 Hz timeline timestamp, 0..65535
|
|
uint8_t opCode;
|
|
uint8_t param8; // PLAY_SOUND bit 0 = loop; other operations require 0
|
|
int32_t paramA; // X, angle, text/sound index, fade level or transition index
|
|
int32_t paramB; // Y, duration (PAN/FADE/SUBTITLE) or unused
|
|
int32_t dataId; // MOVE duration; never a serialized memory pointer
|
|
};
|
|
|
|
// Engine world units: 1 cell = 64 units (raw integers, NOT fixed point).
|
|
struct SpriteEntry {
|
|
int32_t x; // Raw world units (cell * 64 + 32)
|
|
int32_t y; // Raw world units
|
|
uint32_t textureId; // Texture slot 1..15 (0 = solid marker color)
|
|
int32_t scale; // 16.16 fixed point (65536 = 1.0)
|
|
uint32_t dist; // Runtime calculation (can be 0)
|
|
uint32_t flags; // Flags / type
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
static_assert(sizeof(MapHeader) == 38, "EBLN header layout changed");
|
|
static_assert(sizeof(SpriteEntry) == 24, "EBLN sprite layout changed");
|
|
static_assert(sizeof(LevelTransition) == 42, "EBLN transition layout changed");
|
|
static_assert(sizeof(CutsceneEvent) == 16, "EBLN cutscene event layout changed");
|