process TODO.md step 10
This commit is contained in:
@@ -50,6 +50,36 @@ public static class ClientGameplayInputFlow
|
||||
TargetId = targetId ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public static void SendShootInput(
|
||||
MessageManager messageManager,
|
||||
string playerId,
|
||||
long tick,
|
||||
Vector3 aimDirection,
|
||||
string targetId = "")
|
||||
{
|
||||
if (messageManager == null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(messageManager));
|
||||
}
|
||||
|
||||
SendShootInput(messageManager, CreateShootInput(playerId, tick, aimDirection, targetId));
|
||||
}
|
||||
|
||||
public static void SendShootInput(MessageManager messageManager, ShootInput message)
|
||||
{
|
||||
if (messageManager == null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(messageManager));
|
||||
}
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
throw new System.ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
messageManager.SendMessage(message, MessageType.ShootInput);
|
||||
}
|
||||
}
|
||||
|
||||
public class MovementComponent : MonoBehaviour
|
||||
|
||||
@@ -215,12 +215,13 @@ public class NetworkManager : MonoBehaviour
|
||||
|
||||
public void SendShootInput(string playerId, Vector3 direction, long tick = 0, string targetId = "")
|
||||
{
|
||||
SendShootInput(ClientGameplayInputFlow.CreateShootInput(playerId, tick, direction, targetId));
|
||||
ClientGameplayInputFlow.SendShootInput(_networkRuntime.MessageManager, playerId, tick, direction, targetId);
|
||||
Debug.Log($"PlayerShootSeq: {_sequence++}");
|
||||
}
|
||||
|
||||
public void SendShootInput(ShootInput message)
|
||||
{
|
||||
_networkRuntime.MessageManager.SendMessage(message, MessageType.ShootInput);
|
||||
ClientGameplayInputFlow.SendShootInput(_networkRuntime.MessageManager, message);
|
||||
Debug.Log($"PlayerShootSeq: {_sequence++}");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a4fcc49ae3441f6bc9f251af0f1f067
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Network.Defines;
|
||||
using Network.NetworkApplication;
|
||||
using Network.NetworkHost;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
namespace Tests.EditMode.Network
|
||||
{
|
||||
public class GameplayFlowRoundTripTests
|
||||
{
|
||||
private static readonly IPEndPoint ClientPeer = new(IPAddress.Loopback, 9401);
|
||||
private static readonly IPEndPoint RemotePeer = new(IPAddress.Loopback, 9402);
|
||||
private static readonly IPEndPoint ServerSender = new(IPAddress.Loopback, 9000);
|
||||
|
||||
[Test]
|
||||
public void FakeTransportRoundTrip_MoveInputAndShootInput_ProduceAuthoritativePlayerStateAndCombatEvent()
|
||||
{
|
||||
var clientReliableTransport = new GameplayFlowFakeTransport();
|
||||
var clientSyncTransport = new GameplayFlowFakeTransport();
|
||||
var clientRuntime = new SharedNetworkRuntime(
|
||||
clientReliableTransport,
|
||||
new MainThreadNetworkDispatcher(),
|
||||
syncTransport: clientSyncTransport);
|
||||
var clientHarness = new ClientGameplayTestHarness("player-a");
|
||||
clientHarness.Register(clientRuntime.MessageManager);
|
||||
|
||||
var serverTransports = new Dictionary<int, GameplayFlowFakeTransport>();
|
||||
var configuration = new ServerRuntimeConfiguration(9000)
|
||||
{
|
||||
SyncPort = 9001,
|
||||
Dispatcher = new MainThreadNetworkDispatcher(),
|
||||
TransportFactory = port => CreateTransport(serverTransports, port),
|
||||
AuthoritativeMovement = new ServerAuthoritativeMovementConfiguration
|
||||
{
|
||||
MoveSpeed = 10f,
|
||||
BroadcastInterval = TimeSpan.FromMilliseconds(50),
|
||||
DefaultHp = 100
|
||||
},
|
||||
AuthoritativeCombat = new ServerAuthoritativeCombatConfiguration
|
||||
{
|
||||
DamagePerShot = 30
|
||||
}
|
||||
};
|
||||
|
||||
clientRuntime.StartAsync().GetAwaiter().GetResult();
|
||||
using var serverRuntime = ServerRuntimeEntryPoint.StartAsync(configuration).GetAwaiter().GetResult();
|
||||
|
||||
serverTransports[9001].EmitReceive(
|
||||
GameplayFlowTestSupport.BuildEnvelope(
|
||||
MessageType.MoveInput,
|
||||
new MoveInput
|
||||
{
|
||||
PlayerId = "player-b",
|
||||
Tick = 1,
|
||||
MoveX = 0f,
|
||||
MoveY = 0f
|
||||
}),
|
||||
RemotePeer);
|
||||
serverRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
serverTransports[9001].ClearOutgoing();
|
||||
serverTransports[9000].ClearOutgoing();
|
||||
|
||||
clientRuntime.MessageManager.SendMessage(
|
||||
new MoveInput
|
||||
{
|
||||
PlayerId = "player-a",
|
||||
Tick = 1,
|
||||
MoveX = 1f,
|
||||
MoveY = 0f
|
||||
},
|
||||
MessageType.MoveInput);
|
||||
ClientGameplayInputFlow.SendShootInput(
|
||||
clientRuntime.MessageManager,
|
||||
"player-a",
|
||||
2,
|
||||
Vector3.right,
|
||||
"player-b");
|
||||
|
||||
TransferSentMessages(clientSyncTransport, serverTransports[9001], ClientPeer);
|
||||
TransferSentMessages(clientReliableTransport, serverTransports[9000], ClientPeer);
|
||||
|
||||
serverRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
serverRuntime.UpdateAuthoritativeMovement(TimeSpan.FromMilliseconds(50));
|
||||
|
||||
TransferBroadcastMessages(serverTransports[9000], clientReliableTransport, ServerSender);
|
||||
clientRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
TransferBroadcastMessages(serverTransports[9001], clientSyncTransport, ServerSender);
|
||||
clientRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
|
||||
Assert.That(serverRuntime.TryGetAuthoritativeMovementState(ClientPeer, out var localServerState), Is.True);
|
||||
Assert.That(localServerState.PlayerId, Is.EqualTo("player-a"));
|
||||
Assert.That(localServerState.PositionX, Is.EqualTo(0.5f).Within(0.0001f));
|
||||
Assert.That(serverRuntime.TryGetAuthoritativeCombatState(RemotePeer, out var remoteCombatState), Is.True);
|
||||
Assert.That(remoteCombatState.PlayerId, Is.EqualTo("player-b"));
|
||||
Assert.That(remoteCombatState.Hp, Is.EqualTo(70));
|
||||
|
||||
Assert.That(clientHarness.TryGetState("player-a", out var localClientState), Is.True);
|
||||
Assert.That(localClientState.Tick, Is.EqualTo(1));
|
||||
Assert.That(localClientState.Position.x, Is.EqualTo(0.5f).Within(0.0001f));
|
||||
Assert.That(clientHarness.TryGetState("player-b", out var remoteClientState), Is.True);
|
||||
Assert.That(remoteClientState.Hp, Is.EqualTo(70));
|
||||
Assert.That(clientHarness.TryGetCombatPresentation("player-b", out var remoteCombatPresentation), Is.True);
|
||||
Assert.That(remoteCombatPresentation.LastEventType, Is.EqualTo(CombatEventType.DamageApplied));
|
||||
Assert.That(remoteCombatPresentation.LastDamage, Is.EqualTo(30));
|
||||
Assert.That(remoteCombatPresentation.IsDead, Is.False);
|
||||
}
|
||||
|
||||
private static GameplayFlowFakeTransport CreateTransport(IDictionary<int, GameplayFlowFakeTransport> serverTransports, int port)
|
||||
{
|
||||
var transport = new GameplayFlowFakeTransport();
|
||||
serverTransports.Add(port, transport);
|
||||
return transport;
|
||||
}
|
||||
|
||||
private static void TransferSentMessages(GameplayFlowFakeTransport source, GameplayFlowFakeTransport destination, IPEndPoint sender)
|
||||
{
|
||||
foreach (var payload in source.SentMessages)
|
||||
{
|
||||
destination.EmitReceive(payload, sender);
|
||||
}
|
||||
|
||||
source.ClearOutgoing();
|
||||
}
|
||||
|
||||
private static void TransferBroadcastMessages(GameplayFlowFakeTransport source, GameplayFlowFakeTransport destination, IPEndPoint sender)
|
||||
{
|
||||
foreach (var payload in source.BroadcastMessages)
|
||||
{
|
||||
destination.EmitReceive(payload, sender);
|
||||
}
|
||||
|
||||
source.ClearOutgoing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbe6dfdb34f64d4e895f57f7191b9407
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Protobuf;
|
||||
using Network.Defines;
|
||||
using Network.NetworkApplication;
|
||||
using Network.NetworkTransport;
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
namespace Tests.EditMode.Network
|
||||
{
|
||||
internal sealed class ClientGameplayTestHarness
|
||||
{
|
||||
private readonly string localPlayerId;
|
||||
private readonly Dictionary<string, ClientAuthoritativePlayerState> states = new();
|
||||
private readonly Dictionary<string, RemotePlayerSnapshotInterpolator> remoteInterpolators = new();
|
||||
|
||||
public ClientGameplayTestHarness(string localPlayerId)
|
||||
{
|
||||
this.localPlayerId = localPlayerId ?? string.Empty;
|
||||
}
|
||||
|
||||
public void Register(MessageManager messageManager)
|
||||
{
|
||||
messageManager.RegisterHandler(MessageType.PlayerState, (payload, sender) =>
|
||||
{
|
||||
HandlePlayerState(PlayerState.Parser.ParseFrom(payload), receivedAtSeconds: 0f);
|
||||
});
|
||||
messageManager.RegisterHandler(MessageType.CombatEvent, (payload, sender) =>
|
||||
{
|
||||
HandleCombatEvent(CombatEvent.Parser.ParseFrom(payload));
|
||||
});
|
||||
}
|
||||
|
||||
public bool HandlePlayerState(PlayerState state, float receivedAtSeconds)
|
||||
{
|
||||
var owner = GetOrCreateOwner(state.PlayerId);
|
||||
var accepted = owner.TryAccept(state, out var snapshot);
|
||||
if (!accepted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsLocalPlayer(state.PlayerId))
|
||||
{
|
||||
GetOrCreateInterpolator(state.PlayerId).TryAddSnapshot(snapshot, receivedAtSeconds);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HandleCombatEvent(CombatEvent combatEvent)
|
||||
{
|
||||
if (!ClientCombatEventRouting.TryGetAffectedPlayerId(combatEvent, out var playerId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var owner = GetOrCreateOwner(playerId);
|
||||
return owner.TryApplyCombatEvent(combatEvent, playerId, out _, out _);
|
||||
}
|
||||
|
||||
public bool TryGetState(string playerId, out ClientAuthoritativePlayerStateSnapshot snapshot)
|
||||
{
|
||||
if (states.TryGetValue(playerId, out var owner) && owner.Current != null)
|
||||
{
|
||||
snapshot = owner.Current;
|
||||
return true;
|
||||
}
|
||||
|
||||
snapshot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetCombatPresentation(string playerId, out ClientCombatPresentationSnapshot snapshot)
|
||||
{
|
||||
if (states.TryGetValue(playerId, out var owner))
|
||||
{
|
||||
snapshot = owner.CombatPresentation;
|
||||
return true;
|
||||
}
|
||||
|
||||
snapshot = ClientCombatPresentationSnapshot.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetBufferedSnapshotCount(string playerId)
|
||||
{
|
||||
return remoteInterpolators.TryGetValue(playerId, out var interpolator)
|
||||
? interpolator.BufferedSnapshotCount
|
||||
: 0;
|
||||
}
|
||||
|
||||
public long GetLatestBufferedTick(string playerId)
|
||||
{
|
||||
return remoteInterpolators.TryGetValue(playerId, out var interpolator)
|
||||
? interpolator.LatestBufferedTick
|
||||
: -1;
|
||||
}
|
||||
|
||||
public RemotePlayerInterpolationSample SampleRemote(string playerId, float nowSeconds)
|
||||
{
|
||||
return GetOrCreateInterpolator(playerId).Sample(nowSeconds);
|
||||
}
|
||||
|
||||
private bool IsLocalPlayer(string playerId)
|
||||
{
|
||||
return string.Equals(playerId, localPlayerId, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private ClientAuthoritativePlayerState GetOrCreateOwner(string playerId)
|
||||
{
|
||||
if (!states.TryGetValue(playerId, out var owner))
|
||||
{
|
||||
owner = new ClientAuthoritativePlayerState();
|
||||
states.Add(playerId, owner);
|
||||
}
|
||||
|
||||
return owner;
|
||||
}
|
||||
|
||||
private RemotePlayerSnapshotInterpolator GetOrCreateInterpolator(string playerId)
|
||||
{
|
||||
if (!remoteInterpolators.TryGetValue(playerId, out var interpolator))
|
||||
{
|
||||
interpolator = new RemotePlayerSnapshotInterpolator();
|
||||
remoteInterpolators.Add(playerId, interpolator);
|
||||
}
|
||||
|
||||
return interpolator;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GameplayFlowFakeTransport : ITransport
|
||||
{
|
||||
private readonly List<byte[]> sentMessages = new();
|
||||
private readonly List<byte[]> targetMessages = new();
|
||||
private readonly List<(byte[] Data, IPEndPoint Target)> targetedSends = new();
|
||||
private readonly List<byte[]> broadcastMessages = new();
|
||||
|
||||
public event Action<byte[], IPEndPoint> OnReceive;
|
||||
|
||||
public IReadOnlyList<byte[]> SentMessages => sentMessages;
|
||||
|
||||
public IReadOnlyList<byte[]> TargetMessages => targetMessages;
|
||||
|
||||
public IReadOnlyList<(byte[] Data, IPEndPoint Target)> TargetedSends => targetedSends;
|
||||
|
||||
public IReadOnlyList<byte[]> BroadcastMessages => broadcastMessages;
|
||||
|
||||
public Task StartAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
}
|
||||
|
||||
public void Send(byte[] data)
|
||||
{
|
||||
sentMessages.Add(Copy(data));
|
||||
}
|
||||
|
||||
public void SendTo(byte[] data, IPEndPoint target)
|
||||
{
|
||||
var copy = Copy(data);
|
||||
targetMessages.Add(copy);
|
||||
targetedSends.Add((copy, target));
|
||||
}
|
||||
|
||||
public void SendToAll(byte[] data)
|
||||
{
|
||||
broadcastMessages.Add(Copy(data));
|
||||
}
|
||||
|
||||
public void EmitReceive(byte[] data, IPEndPoint sender)
|
||||
{
|
||||
OnReceive?.Invoke(Copy(data), sender);
|
||||
}
|
||||
|
||||
public void ClearOutgoing()
|
||||
{
|
||||
sentMessages.Clear();
|
||||
targetMessages.Clear();
|
||||
targetedSends.Clear();
|
||||
broadcastMessages.Clear();
|
||||
}
|
||||
|
||||
private static byte[] Copy(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var copy = new byte[data.Length];
|
||||
Array.Copy(data, copy, data.Length);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class GameplayFlowTestSupport
|
||||
{
|
||||
public static byte[] BuildEnvelope(MessageType type, IMessage payload)
|
||||
{
|
||||
return new Envelope
|
||||
{
|
||||
Type = (int)type,
|
||||
Payload = payload.ToByteString()
|
||||
}.ToByteArray();
|
||||
}
|
||||
|
||||
public static PlayerState CreatePlayerState(string playerId, long tick, Vector3 position, int hp = 100, float rotation = 0f, Vector3? velocity = null)
|
||||
{
|
||||
var resolvedVelocity = velocity ?? Vector3.zero;
|
||||
return new PlayerState
|
||||
{
|
||||
PlayerId = playerId,
|
||||
Tick = tick,
|
||||
Position = new global::Network.Defines.Vector3 { X = position.x, Y = position.y, Z = position.z },
|
||||
Velocity = new global::Network.Defines.Vector3 { X = resolvedVelocity.x, Y = resolvedVelocity.y, Z = resolvedVelocity.z },
|
||||
Rotation = rotation,
|
||||
Hp = hp
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3db9ca1d45945ed9f5fd1b14922ca21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user