fix(animation): preserve wire motion on every spawn path
Replace the door-specific static-animation seed with one retail description-then-enter-world initializer shared by normal and reactive spawns. This preserves authoritative Dead and On/Off states, prevents replacement corpses from returning to Ready, and pins the lifecycle with Core and App tests. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
9b97102c67
commit
8be933fc94
9 changed files with 335 additions and 64 deletions
63
src/AcDream.App/Rendering/SpawnMotionInitializer.cs
Normal file
63
src/AcDream.App/Rendering/SpawnMotionInitializer.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Builds the initial motion-table state for a server CreateObject using the
|
||||
/// same description-then-enter-world lifecycle as retail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail oracle: <c>ACCObjectMaint::CreateObject</c> (0x00558870) calls
|
||||
/// <c>CPhysicsObj::set_description</c> (0x00514F40), which applies the wire
|
||||
/// MovementData. <c>SmartBox::HandleCreateObject</c> (0x00454C80) then calls
|
||||
/// <c>CPhysicsObj::enter_world</c> (0x00516170), reaching
|
||||
/// <c>CPartArray::HandleEnterWorld</c> (0x00517D70) and
|
||||
/// <c>MotionTableManager::HandleEnterWorld</c> (0x0051BDD0).
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue