RUDPClient/Assets/Tests/EditMode/Network/ClientGameplayFlowTests.cs

162 lines
7.5 KiB
C#

using System.Net;
using Network.Defines;
using Network.NetworkApplication;
using NUnit.Framework;
using UnityEngine;
using Vector3 = UnityEngine.Vector3;
namespace Tests.EditMode.Network
{
public class ClientGameplayFlowTests
{
private static readonly IPEndPoint Sender = new(IPAddress.Loopback, 9300);
[Test]
public void ClientGameplayInputFlow_SendShootInput_UsesDedicatedGameplayPathAndReliableLane()
{
var reliableTransport = new GameplayFlowFakeTransport();
var syncTransport = new GameplayFlowFakeTransport();
var manager = new MessageManager(
reliableTransport,
new MainThreadNetworkDispatcher(),
new DefaultMessageDeliveryPolicyResolver(),
syncTransport);
ClientGameplayInputFlow.SendShootInput(
manager,
"player-1",
17,
new Vector3(3f, 0f, 4f),
"enemy-1");
Assert.That(reliableTransport.SentMessages.Count, Is.EqualTo(1));
Assert.That(syncTransport.SentMessages.Count, Is.EqualTo(0));
var envelope = Envelope.Parser.ParseFrom(reliableTransport.SentMessages[0]);
var shootInput = ShootInput.Parser.ParseFrom(envelope.Payload);
Assert.That((MessageType)envelope.Type, Is.EqualTo(MessageType.ShootInput));
Assert.That(shootInput.PlayerId, Is.EqualTo("player-1"));
Assert.That(shootInput.Tick, Is.EqualTo(17));
Assert.That(shootInput.DirX, Is.EqualTo(0.6f).Within(0.0001f));
Assert.That(shootInput.DirY, Is.EqualTo(0.8f).Within(0.0001f));
Assert.That(shootInput.TargetId, Is.EqualTo("enemy-1"));
}
[Test]
public void SharedNetworkRuntime_CombatEventReceivePath_AppliesAuthoritativeDamageAndDeath()
{
var transport = new GameplayFlowFakeTransport();
var runtime = new SharedNetworkRuntime(transport, new MainThreadNetworkDispatcher());
var harness = new ClientGameplayTestHarness("player-1");
harness.Register(runtime.MessageManager);
transport.EmitReceive(
GameplayFlowTestSupport.BuildEnvelope(
MessageType.PlayerState,
GameplayFlowTestSupport.CreatePlayerState("player-1", 10, Vector3.zero, hp: 100)),
Sender);
transport.EmitReceive(
GameplayFlowTestSupport.BuildEnvelope(
MessageType.CombatEvent,
new CombatEvent
{
Tick = 11,
EventType = CombatEventType.DamageApplied,
AttackerId = "enemy-1",
TargetId = "player-1",
Damage = 35,
HitPosition = new global::Network.Defines.Vector3 { X = 2f, Y = 0f, Z = 1f }
}),
Sender);
transport.EmitReceive(
GameplayFlowTestSupport.BuildEnvelope(
MessageType.CombatEvent,
new CombatEvent
{
Tick = 12,
EventType = CombatEventType.Death,
AttackerId = "enemy-1",
TargetId = "player-1"
}),
Sender);
runtime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(harness.TryGetState("player-1", out var snapshot), Is.True);
Assert.That(snapshot.Hp, Is.EqualTo(0));
Assert.That(harness.TryGetCombatPresentation("player-1", out var combatPresentation), Is.True);
Assert.That(combatPresentation.HasLastEvent, Is.True);
Assert.That(combatPresentation.LastEventType, Is.EqualTo(CombatEventType.Death));
Assert.That(combatPresentation.IsDead, Is.True);
}
[Test]
public void SharedNetworkRuntime_CombatEventReceivePath_RoutesShootRejectedToAttackerDiagnostics()
{
var transport = new GameplayFlowFakeTransport();
var runtime = new SharedNetworkRuntime(transport, new MainThreadNetworkDispatcher());
var harness = new ClientGameplayTestHarness("player-1");
harness.Register(runtime.MessageManager);
transport.EmitReceive(
GameplayFlowTestSupport.BuildEnvelope(
MessageType.PlayerState,
GameplayFlowTestSupport.CreatePlayerState("player-1", 20, Vector3.zero, hp: 90)),
Sender);
transport.EmitReceive(
GameplayFlowTestSupport.BuildEnvelope(
MessageType.CombatEvent,
new CombatEvent
{
Tick = 21,
EventType = CombatEventType.ShootRejected,
AttackerId = "player-1",
TargetId = "enemy-2"
}),
Sender);
runtime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(harness.TryGetState("player-1", out var snapshot), Is.True);
Assert.That(snapshot.Hp, Is.EqualTo(90));
Assert.That(harness.TryGetCombatPresentation("player-1", out var combatPresentation), Is.True);
Assert.That(combatPresentation.LastEventType, Is.EqualTo(CombatEventType.ShootRejected));
Assert.That(combatPresentation.LastDamage, Is.EqualTo(0));
Assert.That(combatPresentation.IsDead, Is.False);
}
[Test]
public void ClientGameplayHarness_RemotePlayerStateFlow_RejectsStaleSnapshots_AndUsesInterpolationOrLatestClamp()
{
var harness = new ClientGameplayTestHarness("local-player");
var firstAccepted = harness.HandlePlayerState(
GameplayFlowTestSupport.CreatePlayerState("remote-player", 10, new Vector3(0f, 0f, 0f), rotation: 0f),
receivedAtSeconds: 0f);
var secondAccepted = harness.HandlePlayerState(
GameplayFlowTestSupport.CreatePlayerState("remote-player", 11, new Vector3(10f, 0f, 0f), rotation: 90f),
receivedAtSeconds: 0.05f);
var staleAccepted = harness.HandlePlayerState(
GameplayFlowTestSupport.CreatePlayerState("remote-player", 9, new Vector3(99f, 0f, 0f), rotation: 180f),
receivedAtSeconds: 0.06f);
var interpolated = harness.SampleRemote("remote-player", 0.125f);
var clamped = harness.SampleRemote("remote-player", 0.35f);
Assert.That(firstAccepted, Is.True);
Assert.That(secondAccepted, Is.True);
Assert.That(staleAccepted, Is.False);
Assert.That(harness.GetBufferedSnapshotCount("remote-player"), Is.EqualTo(1));
Assert.That(harness.GetLatestBufferedTick("remote-player"), Is.EqualTo(11));
Assert.That(interpolated.HasValue, Is.True);
Assert.That(interpolated.UsedInterpolation, Is.True);
Assert.That(interpolated.Position.x, Is.EqualTo(5f).Within(0.001f));
Assert.That(interpolated.Rotation.eulerAngles.y, Is.EqualTo(45f).Within(0.01f));
Assert.That(clamped.HasValue, Is.True);
Assert.That(clamped.UsedInterpolation, Is.False);
Assert.That(clamped.LatestSnapshot.Tick, Is.EqualTo(11));
Assert.That(clamped.Position, Is.EqualTo(new Vector3(10f, 0f, 0f)));
}
}
}