#pragma once #include // ============================================================================== // 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");