Route accepted non-autonomous local UpdateMotion state through retail's wholesale interpreted-motion funnel, so ACE-selected melee and missile actions use the normal motion queue and action-stamp gate. Share nullable wire conversion with remotes and remove the local direct command replay. Co-Authored-By: Codex <codex@openai.com>
89 lines
3.2 KiB
C#
89 lines
3.2 KiB
C#
using AcDream.Core.Net.Messages;
|
|
using AcDream.Core.Physics;
|
|
|
|
namespace AcDream.App.Physics;
|
|
|
|
/// <summary>
|
|
/// Converts the nullable wire fields from an mt-0 UpdateMotion payload into
|
|
/// retail's wholesale <see cref="InboundInterpretedState"/> value.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Retail constructs an <c>InterpretedMotionState</c>, whose constructor
|
|
/// supplies NonCombat/Ready/1.0 defaults, then calls <c>UnPack</c> over it
|
|
/// before <c>MovementManager::move_to_interpreted_state</c> copies every
|
|
/// field. See <c>MovementManager::unpack_movement</c> at 0x00524440 and
|
|
/// <c>CMotionInterp::move_to_interpreted_state</c> at 0x005289c0.
|
|
/// </para>
|
|
/// <para>
|
|
/// Keeping this conversion outside <c>GameWindow</c> gives local and remote
|
|
/// actors one definition of absent-field semantics and one command-list
|
|
/// reconstruction path.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal static class InboundInterpretedMotionFactory
|
|
{
|
|
private const uint NonCombatStyle = 0x8000003Du;
|
|
private const uint Ready = 0x41000003u;
|
|
|
|
public static InboundInterpretedState Create(
|
|
in CreateObject.ServerMotionState wire,
|
|
uint fallbackForwardClass = 0x40000000u)
|
|
{
|
|
var result = new InboundInterpretedState
|
|
{
|
|
CurrentStyle = wire.Stance != 0
|
|
? 0x80000000u | wire.Stance
|
|
: NonCombatStyle,
|
|
ForwardCommand = ResolveForward(wire.ForwardCommand, fallbackForwardClass),
|
|
ForwardSpeed = wire.ForwardSpeed ?? 1f,
|
|
SideStepCommand = ResolveAxis(wire.SideStepCommand, 0x65000000u),
|
|
SideStepSpeed = wire.SideStepSpeed ?? 1f,
|
|
TurnCommand = ResolveAxis(wire.TurnCommand, 0x65000000u),
|
|
TurnSpeed = wire.TurnSpeed ?? 1f,
|
|
};
|
|
|
|
if (wire.Commands is { Count: > 0 } commands)
|
|
{
|
|
var actions = new List<InboundMotionAction>(commands.Count);
|
|
foreach (var item in commands)
|
|
{
|
|
uint command = MotionCommandResolver.ReconstructFullCommand(item.Command);
|
|
if (command == 0)
|
|
command = 0x10000000u | item.Command;
|
|
|
|
actions.Add(new InboundMotionAction(
|
|
command,
|
|
Stamp: item.PackedSequence & 0x7FFF,
|
|
Autonomous: (item.PackedSequence & 0x8000) != 0,
|
|
Speed: item.Speed));
|
|
}
|
|
|
|
result.Actions = actions;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static uint ResolveForward(ushort? wireCommand, uint fallbackClass)
|
|
{
|
|
if (wireCommand is not { } command || command == 0)
|
|
return Ready;
|
|
|
|
uint resolved = MotionCommandResolver.ReconstructFullCommand(command);
|
|
if (resolved != 0)
|
|
return resolved;
|
|
|
|
uint commandClass = fallbackClass & 0xFF000000u;
|
|
return (commandClass != 0 ? commandClass : 0x40000000u) | command;
|
|
}
|
|
|
|
private static uint ResolveAxis(ushort? wireCommand, uint fallbackClass)
|
|
{
|
|
if (wireCommand is not { } command || command == 0)
|
|
return 0;
|
|
|
|
uint resolved = MotionCommandResolver.ReconstructFullCommand(command);
|
|
return resolved != 0 ? resolved : fallbackClass | command;
|
|
}
|
|
}
|