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);