using System; namespace AcDream.Core.Rendering.Wb { /// /// Flags that indicate which fields are present in TerrainEntry /// [Flags] public enum TerrainEntryFlags : byte { None = 0, Height = 1 << 0, Texture = 1 << 1, Scenery = 1 << 2, Road = 1 << 3, Encounters = 1 << 4, } /// /// Represents a terrain entry with optional height, texture, scenery, road, encounters, and flags. /// Flags are automatically synchronized with nullable fields. /// Packed format: [Height:8][Texture:5][Scenery:5][Encounters:4][Road:3][Unused:2][Flags:5] /// public struct TerrainEntry { private const int HEIGHT_SHIFT = 24; private const uint HEIGHT_MASK = 0xFF000000; private const int TEXTURE_SHIFT = 19; private const uint TEXTURE_MASK = 0x00F80000; private const int SCENERY_SHIFT = 14; private const uint SCENERY_MASK = 0x0007C000; private const int ENCOUNTERS_SHIFT = 10; private const uint ENCOUNTERS_MASK = 0x00003C00; private const int ROAD_SHIFT = 7; private const uint ROAD_MASK = 0x00000380; private const int FLAGS_SHIFT = 0; private const uint FLAGS_MASK = 0x0000001F; private uint _data; /// Gets the flags for this terrain entry, indicating which fields are set. public TerrainEntryFlags Flags { get => (TerrainEntryFlags)((_data & FLAGS_MASK) >> FLAGS_SHIFT); private set => _data = (_data & ~FLAGS_MASK) | ((uint)value << FLAGS_SHIFT); } /// Gets or sets the height of the terrain. public byte? Height { get => (_data & (uint)TerrainEntryFlags.Height) != 0 ? (byte)((_data & HEIGHT_MASK) >> HEIGHT_SHIFT) : null; set { if (value.HasValue) { _data = (_data & ~HEIGHT_MASK) | ((uint)value.Value << HEIGHT_SHIFT); Flags |= TerrainEntryFlags.Height; } else { _data &= ~HEIGHT_MASK; Flags &= ~TerrainEntryFlags.Height; } } } /// Gets or sets the texture type of the terrain. public byte? Type { get => (_data & (uint)TerrainEntryFlags.Texture) != 0 ? (byte)((_data & TEXTURE_MASK) >> TEXTURE_SHIFT) : null; set { if (value.HasValue) { if (value.Value > 31) throw new ArgumentOutOfRangeException(nameof(Type), "Texture must be 0-31"); _data = (_data & ~TEXTURE_MASK) | ((uint)value.Value << TEXTURE_SHIFT); Flags |= TerrainEntryFlags.Texture; } else { _data &= ~TEXTURE_MASK; Flags &= ~TerrainEntryFlags.Texture; } } } /// Gets or sets the scenery index of the terrain. public byte? Scenery { get => (_data & (uint)TerrainEntryFlags.Scenery) != 0 ? (byte)((_data & SCENERY_MASK) >> SCENERY_SHIFT) : null; set { if (value.HasValue) { if (value.Value > 31) throw new ArgumentOutOfRangeException(nameof(Scenery), "Scenery must be 0-31"); _data = (_data & ~SCENERY_MASK) | ((uint)value.Value << SCENERY_SHIFT); Flags |= TerrainEntryFlags.Scenery; } else { _data &= ~SCENERY_MASK; Flags &= ~TerrainEntryFlags.Scenery; } } } /// Gets or sets the road index of the terrain. public byte? Road { get => (_data & (uint)TerrainEntryFlags.Road) != 0 ? (byte)((_data & ROAD_MASK) >> ROAD_SHIFT) : null; set { if (value.HasValue) { if (value.Value > 7) throw new ArgumentOutOfRangeException(nameof(Road), "Road must be 0-7"); _data = (_data & ~ROAD_MASK) | ((uint)value.Value << ROAD_SHIFT); Flags |= TerrainEntryFlags.Road; } else { _data &= ~ROAD_MASK; Flags &= ~TerrainEntryFlags.Road; } } } /// Gets or sets the encounters index of the terrain. public byte? Encounters { get => (_data & (uint)TerrainEntryFlags.Encounters) != 0 ? (byte)((_data & ENCOUNTERS_MASK) >> ENCOUNTERS_SHIFT) : null; set { if (value.HasValue) { if (value.Value > 15) throw new ArgumentOutOfRangeException(nameof(Encounters), "Encounters must be 0-15"); _data = (_data & ~ENCOUNTERS_MASK) | ((uint)value.Value << ENCOUNTERS_SHIFT); Flags |= TerrainEntryFlags.Encounters; } else { _data &= ~ENCOUNTERS_MASK; Flags &= ~TerrainEntryFlags.Encounters; } } } /// Initializes a new instance of the struct. public TerrainEntry() { _data = 0; } /// /// Initializes a new instance of the struct with specified components. /// /// The terrain height. /// The texture type. /// The scenery index. /// The road index. /// The encounters index. public TerrainEntry(byte? height, byte? texture, byte? scenery, byte? road, byte? encounters) { _data = 0; if (texture.HasValue && texture.Value > 31) throw new ArgumentOutOfRangeException(nameof(texture), "Texture must be 0-31"); if (scenery.HasValue && scenery.Value > 31) throw new ArgumentOutOfRangeException(nameof(scenery), "Scenery must be 0-31"); if (road.HasValue && road.Value > 7) throw new ArgumentOutOfRangeException(nameof(road), "Road must be 0-7"); if (encounters.HasValue && encounters.Value > 15) throw new ArgumentOutOfRangeException(nameof(encounters), "Encounters must be 0-15"); Height = height; Type = texture; Scenery = scenery; Road = road; Encounters = encounters; } /// Creates a with only height data. /// The height. /// A new . public static TerrainEntry FromHeight(byte height) => new(height, null, null, null, null); /// Creates a with only texture data. /// The texture type. /// A new . public static TerrainEntry FromTexture(byte texture) => new(null, texture, null, null, null); /// Creates a with only scenery data. /// The scenery index. /// A new . public static TerrainEntry FromScenery(byte scenery) => new(null, null, scenery, null, null); /// Creates a with only road data. /// The road index. /// A new . public static TerrainEntry FromRoad(byte road) => new(null, null, null, road, null); /// Creates a with only encounters data. /// The encounters index. /// A new . public static TerrainEntry FromEncounters(byte encounters) => new(null, null, null, null, encounters); /// Creates a with texture and scenery data. /// The texture type. /// The scenery index. /// A new . public static TerrainEntry FromTextureScenery(byte texture, byte scenery) => new(null, texture, scenery, null, null); /// /// Returns the packed 32-bit representation /// public uint Pack() => _data; /// /// Creates a TerrainEntry from a packed 32-bit value /// public static TerrainEntry Unpack(uint packed) { return new TerrainEntry { _data = packed }; } /// /// Merges another entry into this one. Null values are ignored. /// public void Merge(TerrainEntry value) { if (value.Height.HasValue) Height = value.Height; if (value.Type.HasValue) Type = value.Type; if (value.Scenery.HasValue) Scenery = value.Scenery; if (value.Road.HasValue) Road = value.Road; if (value.Encounters.HasValue) Encounters = value.Encounters; } /// public override string ToString() { string height = Height?.ToString() ?? "null"; string texture = Type?.ToString() ?? "null"; string scenery = Scenery?.ToString() ?? "null"; string road = Road?.ToString() ?? "null"; string encounters = Encounters?.ToString() ?? "null"; return $"Height:{height}, Texture:{texture}, Scenery:{scenery}, Road:{road}, Encounters:{encounters}, Flags:{Flags}"; } } }