188 lines
7.5 KiB
C++
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");
|