fix(anim): Phase L.1c animate server-controlled chase

This commit is contained in:
Erik 2026-04-28 19:38:52 +02:00
parent b96b680a20
commit 7656fe0970
6 changed files with 224 additions and 13 deletions

View file

@ -536,6 +536,7 @@ public sealed class GameWindow : IDisposable
string? Name,
AcDream.Core.Items.ItemType ItemType);
private static bool IsPlayerGuid(uint guid) => (guid & 0xFF000000u) == 0x50000000u;
private const double ServerControlledVelocityStaleSeconds = 0.60;
private int _liveSpawnReceived; // diagnostics
private int _liveSpawnHydrated;
private int _liveDropReasonNoPos;
@ -2037,8 +2038,22 @@ public sealed class GameWindow : IDisposable
if (mtable is not null)
{
sequencer = new AcDream.Core.Physics.AnimationSequencer(setup, mtable, _animLoader);
uint seqStyle = stanceOverride is > 0 ? (uint)stanceOverride.Value : (uint)mtable.DefaultStyle;
uint seqMotion = commandOverride is > 0 ? (uint)commandOverride.Value : 0x41000003u;
uint seqStyle = stanceOverride is > 0
? (0x80000000u | (uint)stanceOverride.Value)
: (uint)mtable.DefaultStyle;
uint seqMotion;
if (commandOverride is > 0)
{
uint resolved = AcDream.Core.Physics.MotionCommandResolver
.ReconstructFullCommand(commandOverride.Value);
seqMotion = resolved != 0
? resolved
: (0x40000000u | (uint)commandOverride.Value);
}
else
{
seqMotion = AcDream.Core.Physics.MotionCommand.Ready;
}
sequencer.SetCycle(seqStyle, seqMotion);
}
}
@ -2217,7 +2232,7 @@ public sealed class GameWindow : IDisposable
uint seqStyle = ae.Sequencer?.CurrentStyle ?? 0;
uint seqMotion = ae.Sequencer?.CurrentMotion ?? 0;
Console.WriteLine(
$"UM guid=0x{update.Guid:X8} stance=0x{stance:X4} cmd={cmdStr} spd={spd:F2} " +
$"UM guid=0x{update.Guid:X8} mt=0x{update.MotionState.MovementType:X2} stance=0x{stance:X4} cmd={cmdStr} spd={spd:F2} " +
$"| seq now style=0x{seqStyle:X8} motion=0x{seqMotion:X8}");
}
@ -2259,9 +2274,18 @@ public sealed class GameWindow : IDisposable
// command.Value == 0 → explicit 0 (rare) → Ready
// otherwise → resolve class byte and use full cmd
uint fullMotion;
if (!command.HasValue || command.Value == 0)
if ((!command.HasValue || command.Value == 0)
&& update.MotionState.IsServerControlledMoveTo)
{
// MoveTo packets preserve the current cycle until velocity
// chooses the visible walk/run/ready state.
uint current = ae.Sequencer.CurrentMotion;
fullMotion = (current & 0xFF000000u) != 0
? current
: AcDream.Core.Physics.MotionCommand.Ready;
}
else if (!command.HasValue || command.Value == 0)
{
// Stop — return to the style's default substate (Ready).
fullMotion = 0x41000003u;
}
else
@ -2619,6 +2643,34 @@ public sealed class GameWindow : IDisposable
}
}
private static bool IsRemoteLocomotion(uint motion)
{
uint low = motion & 0xFFu;
return low is 0x05 or 0x06 or 0x07 or 0x0F or 0x10;
}
private void ApplyServerControlledVelocityCycle(
uint serverGuid,
AnimatedEntity ae,
RemoteMotion rm,
System.Numerics.Vector3 velocity)
{
if (IsPlayerGuid(serverGuid)) return;
if (rm.Airborne) return;
if (ae.Sequencer is null) return;
var plan = AcDream.Core.Physics.ServerControlledLocomotion
.PlanFromVelocity(velocity);
uint currentMotion = ae.Sequencer.CurrentMotion;
if (!plan.IsMoving && !IsRemoteLocomotion(currentMotion))
return;
uint style = ae.Sequencer.CurrentStyle != 0
? ae.Sequencer.CurrentStyle
: 0x8000003Du;
ae.Sequencer.SetCycle(style, plan.Motion, plan.SpeedMod);
}
private void OnLivePositionUpdated(AcDream.Core.Net.WorldSession.EntityPositionUpdate update)
{
// Phase A.1: track the most recently updated entity's landblock so the
@ -2789,6 +2841,17 @@ public sealed class GameWindow : IDisposable
rmState.Body.Velocity = rmState.ServerVelocity;
}
if (!IsPlayerGuid(update.Guid)
&& rmState.HasServerVelocity
&& _animatedEntities.TryGetValue(entity.Id, out var aeForVelocity))
{
ApplyServerControlledVelocityCycle(
update.Guid,
aeForVelocity,
rmState,
rmState.ServerVelocity);
}
entity.Position = rmState.Body.Position;
entity.Rotation = rmState.Body.Orientation;
}
@ -4937,9 +5000,28 @@ public sealed class GameWindow : IDisposable
| AcDream.Core.Physics.TransientStateFlags.OnWalkable
| AcDream.Core.Physics.TransientStateFlags.Active;
if (!IsPlayerGuid(serverGuid) && rm.HasServerVelocity)
rm.Body.Velocity = rm.ServerVelocity;
{
double velocityAge = nowSec - rm.LastServerPosTime;
if (velocityAge > ServerControlledVelocityStaleSeconds)
{
rm.ServerVelocity = System.Numerics.Vector3.Zero;
rm.HasServerVelocity = false;
rm.Body.Velocity = System.Numerics.Vector3.Zero;
ApplyServerControlledVelocityCycle(
serverGuid,
ae,
rm,
System.Numerics.Vector3.Zero);
}
else
{
rm.Body.Velocity = rm.ServerVelocity;
}
}
else
{
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
}
}
else
{