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