EBLAN_ENGINE/editor/src/level_format.h

188 lines
7.5 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 = 5; // v5 adds authored lighting and baked RGB cells
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
};
// Entity System Archetypes
enum EntityArchetype : uint8_t {
ENTITY_ARCHETYPE_PROP = 0, // Static decoration
ENTITY_ARCHETYPE_GUARD = 1, // Enemy Guard AI (patrol, chase, attack)
ENTITY_ARCHETYPE_MEDKIT = 2, // Health pickup (+25 HP)
ENTITY_ARCHETYPE_AMMO = 3, // Ammo pickup (+20 Ammo)
ENTITY_ARCHETYPE_KEY = 4, // Door Key pickup
ENTITY_ARCHETYPE_SWITCH = 5, // Interactive Switch / Trigger
ENTITY_ARCHETYPE_BARREL = 6, // Explosive Barrel (AOE Blast)
ENTITY_ARCHETYPE_COUNT = 7
};
#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: 5; reader also accepts v1/v2/v3/v4
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
uint16_t lightCount; // v5 only, authored lights (slots 1..13 in engine)
uint16_t lightingReserved; // must be zero in v5; 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)
inline uint8_t GetSpriteEntityType(const SpriteEntry& s) {
uint8_t t = static_cast<uint8_t>(s.flags & 0xFF);
if (t == 0) {
if (s.textureId == 11) return ENTITY_ARCHETYPE_BARREL;
if (s.textureId == 13) return ENTITY_ARCHETYPE_GUARD;
if (s.textureId == 14) return ENTITY_ARCHETYPE_MEDKIT;
if (s.textureId == 15) return ENTITY_ARCHETYPE_AMMO;
}
return t;
}
inline uint8_t GetSpriteHealth(const SpriteEntry& s) {
uint8_t h = static_cast<uint8_t>((s.flags >> 8) & 0xFF);
return (h == 0) ? 100 : h;
}
inline uint8_t GetSpriteSpeed(const SpriteEntry& s) {
uint8_t spd = static_cast<uint8_t>((s.flags >> 16) & 0xFF);
return (spd == 0) ? 2 : spd;
}
inline uint8_t GetSpriteAggro(const SpriteEntry& s) {
uint8_t agg = static_cast<uint8_t>((s.flags >> 24) & 0xFF);
return (agg == 0) ? 6 : agg;
}
inline void SetSpriteEntityData(SpriteEntry& s, uint8_t type, uint8_t health, uint8_t speed, uint8_t aggro) {
s.flags = static_cast<uint32_t>(type) |
(static_cast<uint32_t>(health) << 8) |
(static_cast<uint32_t>(speed) << 16) |
(static_cast<uint32_t>(aggro) << 24);
}
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");
// Lighting Modes & Types (mirrors include/engine_defs.inc)
enum LightingMode : uint32_t {
LIGHTING_MODE_NONE = 0,
LIGHTING_MODE_BAKED = 1,
LIGHTING_MODE_DYNAMIC = 2
};
enum LightType : uint32_t {
LIGHT_TYPE_STATIC = 0,
LIGHT_TYPE_POINT = 1,
LIGHT_TYPE_FLASHLIGHT = 2,
LIGHT_TYPE_BURST = 3,
LIGHT_TYPE_PULSE = 4
};
constexpr uint32_t LIGHT_FLAG_INACTIVE = 0;
constexpr uint32_t LIGHT_FLAG_ACTIVE = 1;
constexpr uint32_t LIGHT_FLAG_SHADOWS = 2;
constexpr uint32_t LIGHT_FLAG_FLICKER = 4;
constexpr int MAX_EDITOR_LIGHTS = 13; // engine slots 0 / 14 / 15 are flashlight / muzzle / explosion
#pragma pack(push, 1)
struct EditorLightSource {
int32_t x = 0; // raw world units (cell * 64 + 32)
int32_t y = 0;
int32_t radius = 240; // radius in raw world units
int32_t intensity = 220; // 0..255 base intensity
int32_t curIntensity = 220;// 0..255 current intensity
int32_t lightType = LIGHT_TYPE_POINT;
uint32_t flags = LIGHT_FLAG_ACTIVE | LIGHT_FLAG_SHADOWS | LIGHT_FLAG_FLICKER;
uint32_t color = 0x00FFB040;// 00RRGGBBh tint (warm torch)
int32_t decaySpeed = 0;
int32_t flickerPhase = 0;
};
#pragma pack(pop)
static_assert(sizeof(EditorLightSource) == 40, "EditorLightSource size mismatch with MASM LightSource");
struct LightingSettings {
uint32_t mode = LIGHTING_MODE_NONE;
uint32_t ambient = 40;
uint32_t flashlight = 1;
uint32_t flashlightRadius = 384;
uint32_t flashlightIntensity = 240;
uint32_t flashlightCone = 45;
uint32_t baked = 1;
uint32_t reserved = 0;
};
static_assert(sizeof(LightingSettings) == 32, "Lighting settings wire layout changed");