using System.Numerics; namespace AcDream.Core.World; public sealed class WorldEntity { public required uint Id { get; init; } /// /// Server-assigned GUID (from CreateObject). Zero for dat-hydrated /// scenery/static entities that don't come from the server. /// Used by GpuWorldState for persistent-entity rescue on landblock unload. /// public uint ServerGuid { get; init; } public required uint SourceGfxObjOrSetupId { get; init; } /// /// World-space position. Settable so Phase 6.7 position-update events /// can reseat an existing entity without rebuilding its meshes. /// public required Vector3 Position { get; set; } /// Settable for the same reason as . public required Quaternion Rotation { get; set; } /// /// Per-part mesh references with their root-relative transforms. /// Mutable so the animation tick can replace it each frame for /// entities that play a cycle (Phase 6.4); static entities set it /// once at hydration and never touch it again. /// public required IReadOnlyList MeshRefs { get; set; } /// /// Optional per-entity palette override (server-specified base + /// subpalette overlays). When non-null, applies to every palette- /// indexed texture on this entity. Used for character skin/hair /// colors, creature recolors (e.g. stone-colored drudge statue), /// and team colors. Non-palette-indexed textures ignore this field. /// public PaletteOverride? PaletteOverride { get; init; } /// /// EnvCell ID that owns this entity (room geometry or static object inside /// the cell). Used by portal visibility to filter interior entities — only /// entities whose ParentCellId appears in the visible set are rendered. /// Null for outdoor entities (stabs, scenery, live server spawns). /// public uint? ParentCellId { get; init; } /// /// Uniform scale applied to this entity's mesh by the scenery pipeline. /// For scenery objects this is spawn.Scale (typically 0.8–1.3). For stabs /// and interior static objects this is 1.0 (no scaling). /// /// Used by the collision registration path to scale CylSphere / Sphere / /// Setup.Radius shapes so they match the visually-scaled mesh. Without /// this, scaled scenery has a collision cylinder that's smaller than the /// visible trunk, producing "partial passthrough" bugs. /// public float Scale { get; init; } = 1.0f; /// /// Server-sent part-swap overrides from AnimPartChange. Each entry /// replaces a Setup part's GfxObj with an alternate model (clothing, weapons, /// helmets). Carried on the entity so EntitySpawnAdapter can populate /// AnimatedEntityState's override map at spawn time. Empty for atlas- /// tier entities. /// public IReadOnlyList PartOverrides { get; init; } = Array.Empty(); /// /// Bitmask of hidden Setup parts. Bit i set hides part i at /// draw time. Sourced from the server's CreateObject record when /// present. Zero (no parts hidden) is the default. /// public ulong HiddenPartsMask { get; init; } // Per Phase A.5 spec §4.6 Change #2 — cache per-entity AABB so the // dispatcher's frustum cull is a memory read, not a per-frame recompute. // AabbDirty starts true so the dispatcher calls RefreshAabb on first read // (AabbMin/AabbMax are Vector3.Zero until refreshed). public Vector3 AabbMin { get; private set; } public Vector3 AabbMax { get; private set; } public bool AabbDirty { get; private set; } = true; private const float DefaultAabbRadius = 5.0f; public void RefreshAabb() { var p = Position; AabbMin = new Vector3(p.X - DefaultAabbRadius, p.Y - DefaultAabbRadius, p.Z - DefaultAabbRadius); AabbMax = new Vector3(p.X + DefaultAabbRadius, p.Y + DefaultAabbRadius, p.Z + DefaultAabbRadius); AabbDirty = false; } public void SetPosition(Vector3 pos) { Position = pos; AabbDirty = true; } } /// /// Lightweight value type for a server-sent AnimPartChange (part index /// → replacement GfxObj id). Decouples WorldEntity (Core) from the /// network-layer CreateObject.AnimPartChange type. /// public readonly record struct PartOverride(byte PartIndex, uint GfxObjId);