using System; using System.Collections.Generic; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; namespace AcDream.Core.Physics.Motion; /// /// #175: the motion table's DEFAULT-STATE part pose — the pose an idle /// entity's parts hold (retail: CMotionTable::SetDefaultState /// 0x005230a0 installs StyleDefaults[DefaultStyle]'s cycle; the parts /// then sit at that animation's frames — the live CPhysicsPart pose /// collision tests against). Used as the BSP shadow-shape part-pose override /// at server-entity registration (doors: the CLOSED pose). /// /// /// Cycle key arithmetic mirrors 's /// LookupCycle (CMotionTable.cs:683): (style << 16) | /// (substate & 0xFFFFFF) — the raw dat Cycles dictionary is /// keyed by the COMBINED word, not the bare style (the first cut of this /// helper looked up the bare style, always missed, and silently fell back /// to placement frames — the #175 "not fixed" report). /// /// public static class MotionTablePose { /// /// Resolve the default-state pose frames. Returns null (callers fall /// back to placement frames) when the table has no default-style /// substate, no matching cycle, or no animation. A pose covering FEWER /// parts than the Setup is returned as-is — /// falls back to the placement frame /// PER PART beyond the override's length (e.g. a door anim that poses /// only the panel parts, not the BSP-less frame header). /// /// The raw dat motion table (wire MotionTableId). /// Animation loader (production: /// id => dats.Get<Animation>(id)). public static IReadOnlyList? DefaultStatePartFrames( MotionTable mt, Func loadAnimation) { if (mt is null) return null; // SetDefaultState: StyleDefaults[DefaultStyle] → the default substate. if (!mt.StyleDefaults.TryGetValue(mt.DefaultStyle, out var defaultSubstateCmd)) return null; // LookupCycle key (CMotionTable.cs:683 — same wrap semantics). uint style = (uint)mt.DefaultStyle; uint substate = (uint)defaultSubstateCmd; int key = (int)((style << 16) | (substate & 0xFFFFFFu)); if (!mt.Cycles.TryGetValue(key, out var cycle) || cycle.Anims.Count == 0) return null; var animRef = cycle.Anims[0]; var anim = loadAnimation(animRef.AnimId); if (anim is null || anim.PartFrames.Count == 0) return null; int idx = Math.Clamp((int)animRef.LowFrame, 0, anim.PartFrames.Count - 1); var frames = anim.PartFrames[idx].Frames; return frames.Count > 0 ? frames : null; } }