using System.Collections.Generic; namespace AcDream.Core.Physics.Motion; /// /// One entry of a chain — retail's /// MotionList node (12 bytes: motion + speed_mod + next; /// acclient.h, r2-motiontable-decomp.md §14). One struct, two independent /// chain disciplines: the modifier chain is a PUSH-FRONT STACK, the action /// chain a TAIL-APPEND FIFO. /// public sealed class MotionEntry { public uint Motion; public float SpeedMod = 1f; public MotionEntry(uint motion, float speedMod) { Motion = motion; SpeedMod = speedMod; } } /// /// R2-Q1 — verbatim port of retail's MotionState (acclient.h:31081; /// r2-motiontable-decomp.md §13): the motion-table-facing state block — /// current style / substate / substate_mod plus the modifier stack and the /// action FIFO that CMotionTable::GetObjectSequence, /// StopSequenceMotion, re_modify, and /// MotionTableManager::AnimationDone read and write. /// public sealed class MotionState { /// Current style/stance command word (0 = unset; ctor 0x00525fd0). public uint Style; /// Current base substate command word (0 = unset). public uint Substate; /// Speed modifier of the base substate (default 1.0). public float SubstateMod = 1f; private readonly LinkedList _modifiers = new(); // modifier_head — push-front stack private readonly LinkedList _actions = new(); // action_head/tail — FIFO public MotionState() { } /// /// Deep copy (retail copy ctor 0x00526790; Q0-pins A4-#5): both chains /// CLONED — re_modify's snapshot is a termination bound, never /// shared state. /// public MotionState(MotionState other) { Style = other.Style; Substate = other.Substate; SubstateMod = other.SubstateMod; foreach (var m in other._modifiers) _modifiers.AddLast(new MotionEntry(m.Motion, m.SpeedMod)); foreach (var a in other._actions) _actions.AddLast(new MotionEntry(a.Motion, a.SpeedMod)); } /// Modifier chain in retail order (newest first — push-front). public IEnumerable Modifiers => _modifiers; /// Action FIFO in retail order (oldest first). public IEnumerable Actions => _actions; /// add_modifier_no_check (0x00525ff0): unconditional /// push-front. public void AddModifierNoCheck(uint motion, float speedMod) => _modifiers.AddFirst(new MotionEntry(motion, speedMod)); /// /// add_modifier (0x00526340): refuse when the motion is already /// a modifier (caller must stop-then-re-add to refresh) or already IS /// the current base substate. /// public bool AddModifier(uint motion, float speedMod) { for (var n = _modifiers.First; n is not null; n = n.Next) if (n.Value.Motion == motion) return false; if (Substate == motion) return false; AddModifierNoCheck(motion, speedMod); return true; } /// remove_modifier (0x00526040) — by node identity /// (retail's node+predecessor pair collapses in a managed list). public void RemoveModifier(MotionEntry entry) { _modifiers.Remove(entry); } /// clear_modifiers (0x00526070). public void ClearModifiers() => _modifiers.Clear(); /// add_action (0x005260a0): tail append. public void AddAction(uint motion, float speedMod) => _actions.AddLast(new MotionEntry(motion, speedMod)); /// remove_action_head (0x00526120): pop the FIFO head, /// returning its motion id (0 when empty). public uint RemoveActionHead() { var head = _actions.First; if (head is null) return 0; _actions.RemoveFirst(); return head.Value.Motion; } /// clear_actions (0x005260f0). public void ClearActions() => _actions.Clear(); }