using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Rendering;
///
/// Builds the initial motion-table state for a server CreateObject using the
/// same description-then-enter-world lifecycle as retail.
///
///
/// Retail oracle: ACCObjectMaint::CreateObject (0x00558870) calls
/// CPhysicsObj::set_description (0x00514F40), which applies the wire
/// MovementData. SmartBox::HandleCreateObject (0x00454C80) then calls
/// CPhysicsObj::enter_world (0x00516170), reaching
/// CPartArray::HandleEnterWorld (0x00517D70) and
/// MotionTableManager::HandleEnterWorld (0x0051BDD0).
///
internal static class SpawnMotionInitializer
{
internal readonly record struct Plan(uint Style, uint Motion);
public static AnimationSequencer Create(
Setup setup,
MotionTable motionTable,
IAnimationLoader loader,
CreateObject.ServerMotionState? wireState)
{
var sequencer = new AnimationSequencer(setup, motionTable, loader);
Plan plan = ResolvePlan(motionTable, wireState);
// set_description: install the table default, then apply MovementData.
sequencer.InitializeState();
sequencer.SetCycle(plan.Style, plan.Motion);
// enter_world: discard description-time transition links so the first
// rendered pose is the server-authored persistent cycle. A corpse's
// NonCombat+Dead therefore starts fallen; a door's NonCombat+On/Off
// comes from the same wire path with no object-type special case.
sequencer.Manager.HandleEnterWorld();
return sequencer;
}
///
/// Replays description-time MovementData into the same retained PartArray
/// sequencer. This is used only when a fresher same-incarnation CreateObject
/// interrupted initial hydration before the object crossed its ready edge;
/// completed objects consume the packet through SetObjectMovement instead.
///
public static void Reinitialize(
AnimationSequencer sequencer,
MotionTable motionTable,
CreateObject.ServerMotionState? wireState)
{
ArgumentNullException.ThrowIfNull(sequencer);
ArgumentNullException.ThrowIfNull(motionTable);
Plan plan = ResolvePlan(motionTable, wireState);
sequencer.Reset();
sequencer.InitializeState();
sequencer.SetCycle(plan.Style, plan.Motion);
sequencer.Manager.HandleEnterWorld();
}
internal static Plan ResolvePlan(
MotionTable motionTable,
CreateObject.ServerMotionState? wireState)
{
uint style = wireState is { Stance: > 0 } state
? 0x80000000u | state.Stance
: (uint)motionTable.DefaultStyle;
uint motion = MotionCommand.Ready;
if (wireState?.ForwardCommand is ushort command && command > 0)
{
uint resolved = MotionCommandResolver.ReconstructFullCommand(command);
motion = resolved != 0
? resolved
: 0x40000000u | command;
}
return new Plan(style, motion);
}
}