diff --git a/source/ClientDebug/ClientDebug.csproj.user b/source/ClientDebug/ClientDebug.csproj.user
index 205a84948..8bd749317 100644
--- a/source/ClientDebug/ClientDebug.csproj.user
+++ b/source/ClientDebug/ClientDebug.csproj.user
@@ -11,4 +11,4 @@
..\mb2\bin\Win64_Shipping_Client\Bannerlord.exe
..\..\..\..\mb2\bin\Win64_Shipping_Client\
-
\ No newline at end of file
+
diff --git a/source/MissionTestMod/app.config b/source/MissionTestMod/app.config
index 5e0edae7a..41ab6110c 100644
--- a/source/MissionTestMod/app.config
+++ b/source/MissionTestMod/app.config
@@ -14,14 +14,14 @@
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/source/MissionTests/AgentMovementDeltaSerializationTest.cs b/source/MissionTests/AgentMovementDeltaSerializationTest.cs
new file mode 100644
index 000000000..93c5914c8
--- /dev/null
+++ b/source/MissionTests/AgentMovementDeltaSerializationTest.cs
@@ -0,0 +1,39 @@
+using Common.Serialization;
+using Missions.Services.Agents.Messages;
+using Missions.Services.Agents.Packets;
+using Missions.Services.Network.Surrogates;
+using ProtoBuf.Meta;
+using System;
+using TaleWorlds.Library;
+using Xunit;
+
+namespace IntroductionServerTests
+{
+ public class AgentMovementDeltaSerializationTest
+ {
+ [Fact]
+ public void Serialize_Test()
+ {
+ try
+ {
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ }
+ catch
+ {
+ // nop
+ }
+
+ var delta = new AgentMovement(Guid.NewGuid());
+
+ var bytes = ProtoBufSerializer.Serialize(delta);
+
+ Assert.NotNull(bytes);
+ Assert.NotEmpty(bytes);
+
+ var desDelta = (AgentMovement)ProtoBufSerializer.Deserialize(bytes);
+
+ Assert.Equal(delta, desDelta);
+ }
+ }
+}
diff --git a/source/MissionTests/MissionTests.csproj b/source/MissionTests/MissionTests.csproj
index 18d07daa1..60a8ad1f4 100644
--- a/source/MissionTests/MissionTests.csproj
+++ b/source/MissionTests/MissionTests.csproj
@@ -122,6 +122,7 @@
+
diff --git a/source/MissionTests/NetworkAgentRegistryTests.cs b/source/MissionTests/NetworkAgentRegistryTests.cs
index 3ab58b451..299169e58 100644
--- a/source/MissionTests/NetworkAgentRegistryTests.cs
+++ b/source/MissionTests/NetworkAgentRegistryTests.cs
@@ -1,11 +1,8 @@
-using Common.Messaging;
-using Missions.Services.Network;
+using Missions.Services.Network;
using System;
using LiteNetLib;
using System.Collections.Generic;
using System.Runtime.Serialization;
-using System.Text;
-using TaleWorlds.CampaignSystem;
using TaleWorlds.MountAndBlade;
using Xunit;
diff --git a/source/MissionTests/NetworkMessageSerializationTest.cs b/source/MissionTests/NetworkMessageSerializationTest.cs
index 6ddfc3a18..565db9b3d 100644
--- a/source/MissionTests/NetworkMessageSerializationTest.cs
+++ b/source/MissionTests/NetworkMessageSerializationTest.cs
@@ -35,10 +35,17 @@ public NetworkMessageSerializationTest()
[Fact]
public void Serialize_Test()
{
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
+ try
+ {
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ }
+ catch
+ {
+ // nop
+ }
var character = (CharacterObject)FormatterServices.GetUninitializedObject(typeof(CharacterObject));
@@ -64,11 +71,17 @@ public void Serialize_Test()
[Fact]
public void Serialize2_Test()
{
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
- RuntimeTypeModel.Default.SetSurrogate();
+ try
+ {
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ RuntimeTypeModel.Default.SetSurrogate();
+ }
+ catch
+ {
+ // nop
+ }
var attackerGuid = Guid.NewGuid();
diff --git a/source/MissionTests/app.config b/source/MissionTests/app.config
index c166e67c6..d7d512025 100644
--- a/source/MissionTests/app.config
+++ b/source/MissionTests/app.config
@@ -15,16 +15,16 @@
-
-
+
+
-
-
+
+
diff --git a/source/Missions/MissionModule.cs b/source/Missions/MissionModule.cs
index 6a0d22a62..07b5639d4 100644
--- a/source/Missions/MissionModule.cs
+++ b/source/Missions/MissionModule.cs
@@ -5,6 +5,8 @@
using GameInterface;
using IntroServer.Config;
using Missions.Services;
+using Missions.Services.Agents;
+using Missions.Services.Agents.Packets;
using Missions.Services.Agents.Handlers;
using Missions.Services.Arena;
using Missions.Services.BoardGames;
@@ -13,11 +15,13 @@
using Missions.Services.Missiles.Handlers;
using Missions.Services.Network;
using Missions.Services.Taverns;
+using System;
namespace Missions
{
public class MissionModule : Module
{
+
protected override void Load(ContainerBuilder builder)
{
// TODO find how to make this not disgusting
@@ -52,11 +56,14 @@ protected override void Load(ContainerBuilder builder)
.SingleInstance()
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
-
builder.RegisterInstance(NetworkAgentRegistry.Instance)
.As()
.SingleInstance();
+ builder.RegisterInstance(new AgentPublisherConfig())
+ .As()
+ .SingleInstance();
+
// Interface classes
builder.RegisterType().As().AsSelf().InstancePerLifetimeScope();
@@ -73,6 +80,10 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().AsSelf().InstancePerLifetimeScope();
+ builder.RegisterType().AsSelf().InstancePerLifetimeScope();
+ builder.RegisterType().AsSelf().InstancePerLifetimeScope();
+
base.Load(builder);
}
}
diff --git a/source/Missions/Missions.csproj b/source/Missions/Missions.csproj
index 1201a5326..33d8cbad0 100644
--- a/source/Missions/Missions.csproj
+++ b/source/Missions/Missions.csproj
@@ -45,11 +45,23 @@
..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll
+
+ ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+
+
+ ..\packages\Microsoft.Extensions.Logging.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll
+
+
+ ..\packages\Microsoft.Extensions.Options.7.0.0\lib\net462\Microsoft.Extensions.Options.dll
+
+
+ ..\packages\Microsoft.Extensions.Primitives.7.0.0\lib\net462\Microsoft.Extensions.Primitives.dll
+
- ..\packages\protobuf-net.3.2.16\lib\net462\protobuf-net.dll
+ ..\packages\protobuf-net.3.1.26\lib\net462\protobuf-net.dll
- ..\packages\protobuf-net.Core.3.2.16\lib\net462\protobuf-net.Core.dll
+ ..\packages\protobuf-net.Core.3.1.26\lib\net462\protobuf-net.Core.dll
..\..\mb2\Modules\SandBox\bin\Win64_Shipping_Client\SandBox.dll
@@ -70,9 +82,10 @@
..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
-
- ..\packages\System.Collections.Immutable.7.0.0\lib\net462\System.Collections.Immutable.dll
+
+ ..\packages\System.Collections.Immutable.1.7.1\lib\net461\System.Collections.Immutable.dll
+
@@ -92,6 +105,9 @@
..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
+
+ ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll
+
@@ -162,8 +178,16 @@
+
+
+
+
+
+
+
+
-
+
@@ -223,7 +247,6 @@
-
diff --git a/source/Missions/Services/Agents/AgentPublisher.cs b/source/Missions/Services/Agents/AgentPublisher.cs
new file mode 100644
index 000000000..a173decda
--- /dev/null
+++ b/source/Missions/Services/Agents/AgentPublisher.cs
@@ -0,0 +1,123 @@
+using Common.Logging;
+using Common.Messaging;
+using Missions.Services.Agents.Messages;
+using Missions.Services.Agents.Packets;
+using Missions.Services.Network;
+using Serilog;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents
+{
+ ///
+ /// Manages player controlled .
+ ///
+ public class AgentPublisher
+ {
+ private Guid _agentId;
+
+ private AgentMovement _agentMovement;
+
+ private static readonly ILogger _logger = LogManager.GetLogger();
+
+ private IMessageBroker _messageBroker;
+
+ private readonly IAgentPublisherConfig _agentPublisherConfig;
+
+ private readonly INetworkAgentRegistry _networkAgentRegistry;
+
+ private Timer _pollAndUpdateAgentMovementTimer;
+
+ ///
+ /// Constructor
+ ///
+ public AgentPublisher(IMessageBroker messageBroker, IAgentPublisherConfig agentPublisherConfig, INetworkAgentRegistry networkAgentRegistry)
+ {
+ _agentMovement = new AgentMovement(_agentId);
+ _messageBroker = messageBroker;
+ _agentPublisherConfig = agentPublisherConfig;
+
+ _pollAndUpdateAgentMovementTimer = new Timer(PollAndUpdateAgentMovement, null, 0, _agentPublisherConfig.PacketUpdateRate);
+
+ _networkAgentRegistry = networkAgentRegistry;
+ }
+
+ ~AgentPublisher()
+ {
+ _pollAndUpdateAgentMovementTimer?.Dispose();
+ }
+
+ private void PollAndUpdateAgentMovement(object obj)
+ {
+ // TODO: also add all player agents
+ // TODO: ensure the used dictionary is safe for concurrent operations
+ var agents = _networkAgentRegistry.AgentToId.Keys;
+
+ foreach (var agent in agents)
+ {
+ var movementChanges = new List();
+
+ CheckAndUpdateLookDirection(movementChanges, agent);
+ CheckAndUpdateInputVector(movementChanges, agent);
+ CheckAndUpdateAgentActionData(movementChanges, agent);
+ CheckAndUpdateAgentMountData(movementChanges, agent);
+
+ // sending all changes down the broker
+ // the handlers will handle them however they see fit
+ foreach (var movement in movementChanges)
+ {
+ _messageBroker.Publish(this, movement);
+ }
+ }
+ }
+
+ private void CheckAndUpdateLookDirection(IList movementChanges, Agent agent)
+ {
+ var lookDirection = new LookDirectionChanged(agent);
+
+ if (lookDirection.LookDirection != agent.LookDirection)
+ {
+ _agentMovement.CalculateMovement(lookDirection);
+ movementChanges.Add(lookDirection);
+ }
+ }
+
+ private void CheckAndUpdateInputVector(IList movementChanges, Agent agent)
+ {
+ var inputVector = new MovementInputVectorChanged(agent);
+
+ if (inputVector.InputVector != _agentMovement.InputDirection)
+ {
+ _agentMovement.CalculateMovement(inputVector);
+ movementChanges.Add(inputVector);
+ }
+ }
+
+ private void CheckAndUpdateAgentActionData(IList movementChanges, Agent agent)
+ {
+ var actionData = new ActionDataChanged(agent);
+
+ if (!actionData.Equals(_agentMovement.ActionData))
+ {
+ _agentMovement.CalculateMovement(actionData);
+ movementChanges.Add(actionData);
+ }
+ }
+
+ private void CheckAndUpdateAgentMountData(IList movementChanges, Agent agent)
+ {
+ if (agent.HasMount)
+ {
+ var mountData = new MountDataChanged(agent);
+
+ if (!mountData.Equals(mountData))
+ {
+ _agentMovement.CalculateMovement(mountData);
+ movementChanges.Add(mountData);
+ }
+ }
+ }
+ }
+}
diff --git a/source/Missions/Services/Agents/AgentPublisherConfig.cs b/source/Missions/Services/Agents/AgentPublisherConfig.cs
new file mode 100644
index 000000000..b56eeb070
--- /dev/null
+++ b/source/Missions/Services/Agents/AgentPublisherConfig.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Missions.Services.Agents
+{
+ ///
+ /// Contains configuration for .
+ ///
+ public interface IAgentPublisherConfig
+ {
+ ///
+ /// The rate at which packets are getting updated.
+ ///
+ int PacketUpdateRate { get; }
+
+ ///
+ /// The amount of packets to be sent in .
+ ///
+ int Packets { get; }
+
+ ///
+ /// The between packents.
+ ///
+ TimeSpan TimeBetweenPackets { get; }
+ }
+
+ ///
+ public class AgentPublisherConfig : IAgentPublisherConfig
+ {
+ ///
+ public int PacketUpdateRate { get => (int)Math.Round(TimeBetweenPackets.TotalMilliseconds / Packets); }
+
+ // TODO: maybe read this from a config file in the future
+
+ ///
+ public int Packets { get => 30; }
+
+ ///
+ public TimeSpan TimeBetweenPackets { get => TimeSpan.FromSeconds(1); }
+ }
+}
diff --git a/source/Missions/Services/Agents/Extensions/AgentExtensions.cs b/source/Missions/Services/Agents/Extensions/AgentExtensions.cs
index d655f67a8..8f4ecfe5f 100644
--- a/source/Missions/Services/Agents/Extensions/AgentExtensions.cs
+++ b/source/Missions/Services/Agents/Extensions/AgentExtensions.cs
@@ -1,5 +1,5 @@
-using TaleWorlds.MountAndBlade;
-using Missions.Services.Network;
+using Missions.Services.Network;
+using TaleWorlds.MountAndBlade;
namespace Missions.Services.Agents.Extensions
{
diff --git a/source/Missions/Services/Agents/Handlers/AgentMovementHandler.cs b/source/Missions/Services/Agents/Handlers/AgentMovementHandler.cs
index 51ca159bb..d25d70f28 100644
--- a/source/Missions/Services/Agents/Handlers/AgentMovementHandler.cs
+++ b/source/Missions/Services/Agents/Handlers/AgentMovementHandler.cs
@@ -16,6 +16,7 @@
namespace Missions.Services.Agents.Handlers
{
+ // TODO: this needs to be adjusted or replaced with code of MovementHandler
public interface IAgentMovementHandler : IPacketHandler, IDisposable
{
}
@@ -85,8 +86,8 @@ private async void PollAgents()
{
if (agent.Mission != null)
{
- MovementPacket packet = new MovementPacket(guid, agent);
- client.SendAll(packet);
+ //MovementPacket packet = new MovementPacket(guid, agent);
+ //client.SendAll(packet);
}
}
}
@@ -99,8 +100,8 @@ public void HandlePacket(NetPeer peer, IPacket packet)
{
if (agentRegistry.OtherAgents.TryGetValue(peer, out AgentGroupController agentGroupController))
{
- MovementPacket movement = (MovementPacket)packet;
- agentGroupController.ApplyMovement(movement);
+ //MovementPacket movement = (MovementPacket)packet;
+ //agentGroupController.ApplyMovement(movement);
}
}
diff --git a/source/Missions/Services/Agents/Messages/ActionDataChanged.cs b/source/Missions/Services/Agents/Messages/ActionDataChanged.cs
new file mode 100644
index 000000000..250a4a4d8
--- /dev/null
+++ b/source/Missions/Services/Agents/Messages/ActionDataChanged.cs
@@ -0,0 +1,31 @@
+using Missions.Services.Agents.Packets;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Messages
+{
+ ///
+ /// An used to propagate movement related changes of of an .
+ ///
+ public readonly struct ActionDataChanged : IMovementEvent
+ {
+ ///
+ /// The changed propagated by this .
+ ///
+ public AgentActionData AgentActionData { get; }
+
+ ///
+ public Agent Agent { get; }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ /// optional
+ public ActionDataChanged(Agent agent, AgentActionData actionData = null)
+ {
+ Agent = agent;
+ AgentActionData = actionData ?? new AgentActionData(agent);
+ }
+
+ }
+}
diff --git a/source/Missions/Services/Agents/Messages/IMovementEvent.cs b/source/Missions/Services/Agents/Messages/IMovementEvent.cs
new file mode 100644
index 000000000..9ae2bac7c
--- /dev/null
+++ b/source/Missions/Services/Agents/Messages/IMovementEvent.cs
@@ -0,0 +1,17 @@
+using Common.Messaging;
+using System;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Messages
+{
+ ///
+ /// An used to propagate movement related changes of an .
+ ///
+ interface IMovementEvent : IEvent
+ {
+ ///
+ /// The this is for.
+ ///
+ Agent Agent { get; }
+ }
+}
diff --git a/source/Missions/Services/Agents/Messages/LookDirectionChanged.cs b/source/Missions/Services/Agents/Messages/LookDirectionChanged.cs
new file mode 100644
index 000000000..c6777f765
--- /dev/null
+++ b/source/Missions/Services/Agents/Messages/LookDirectionChanged.cs
@@ -0,0 +1,30 @@
+using System;
+using TaleWorlds.Library;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Messages
+{
+ ///
+ /// An propagating the 's change in look direction.
+ ///
+ public readonly struct LookDirectionChanged : IMovementEvent
+ {
+ ///
+ /// The changed vector representing the look direction.
+ ///
+ public Vec3 LookDirection { get; }
+
+ ///
+ public Agent Agent { get; }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public LookDirectionChanged(Agent agent)
+ {
+ Agent = agent;
+ LookDirection = agent.LookDirection;
+ }
+ }
+}
diff --git a/source/Missions/Services/Agents/Messages/MountDataChanged.cs b/source/Missions/Services/Agents/Messages/MountDataChanged.cs
new file mode 100644
index 000000000..28c66eec8
--- /dev/null
+++ b/source/Missions/Services/Agents/Messages/MountDataChanged.cs
@@ -0,0 +1,31 @@
+using Missions.Services.Agents.Packets;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Messages
+{
+ ///
+ /// An used to propagate movement related changes of of an .
+ ///
+ public readonly struct MountDataChanged : IMovementEvent
+ {
+ ///
+ /// The changed propagated by this .
+ ///
+ public AgentMountData AgentMountData { get; }
+
+ ///
+ public Agent Agent { get; }
+
+
+ ///
+ /// Constructor
+ ///
+ ///
+ /// optional
+ public MountDataChanged(Agent agent, AgentMountData agentMountData = null)
+ {
+ Agent = agent;
+ AgentMountData = agentMountData ?? new AgentMountData(agent);
+ }
+ }
+}
diff --git a/source/Missions/Services/Agents/Messages/MovementInputVectorChanged.cs b/source/Missions/Services/Agents/Messages/MovementInputVectorChanged.cs
new file mode 100644
index 000000000..5eb27f341
--- /dev/null
+++ b/source/Missions/Services/Agents/Messages/MovementInputVectorChanged.cs
@@ -0,0 +1,29 @@
+using TaleWorlds.Library;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Messages
+{
+ ///
+ /// An propagating that the 's input vector has changed.
+ ///
+ public readonly struct MovementInputVectorChanged : IMovementEvent
+ {
+ ///
+ /// The changed input vector.
+ ///
+ public Vec2 InputVector { get; }
+
+ ///
+ public Agent Agent { get; }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public MovementInputVectorChanged(Agent agent)
+ {
+ Agent = agent;
+ InputVector = agent.MovementInputVector;
+ }
+ }
+}
diff --git a/source/Missions/Services/Agents/Packets/AgentData.cs b/source/Missions/Services/Agents/Packets/AgentData.cs
deleted file mode 100644
index 54d8b1a17..000000000
--- a/source/Missions/Services/Agents/Packets/AgentData.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using ProtoBuf;
-using TaleWorlds.Library;
-using TaleWorlds.MountAndBlade;
-
-namespace Missions.Services.Agents.Packets
-{
- [ProtoContract(SkipConstructor = true)]
- public struct AgentData
- {
- public AgentData(Agent agent)
- {
- Position = agent.Position;
- MovementDirection = agent.GetMovementDirection();
- LookDirection = agent.LookDirection;
- InputVector = agent.MovementInputVector;
-
- AgentEquipment = new AgentEquipmentData(agent);
-
- if (agent.Health > 0f)
- {
- ActionData = new AgentActionData(agent);
- }
- else
- {
- ActionData = null;
- }
-
- if (agent.HasMount)
- {
- MountData = new AgentMountData(agent.MountAgent);
- }
- else
- {
- MountData = null;
- }
- }
-
- public void Apply(Agent agent)
- {
- // if the player is dead, dont sync anything
- if (agent.Health <= 0)
- {
- return;
- }
-
- Vec3 pos = Position;
-
- // if the distance between the local agent and the info passed from the server is greater than 1 unit, teleport the agent
- if (agent.GetPathDistanceToPoint(ref pos) > 1f)
- {
- agent.TeleportToPosition(pos);
- }
-
- agent.SetMovementDirection(MovementDirection);
-
- // apply the agent's look direction
- agent.LookDirection = LookDirection;
-
- // apply the agent's movement input vector...Is this necessary?
- agent.MovementInputVector = InputVector;
-
- // Update equipment
- AgentEquipment.Apply(agent);
-
- // Update actions
- ActionData?.Apply(agent);
-
- // Update mount
- if (agent.HasMount)
- {
- MountData?.ApplyMount(agent.MountAgent);
- }
- }
-
- [ProtoMember(1)]
- public Vec3 Position { get; }
- [ProtoMember(2)]
- public Vec2 InputVector { get; }
- [ProtoMember(3)]
- public Vec3 LookDirection { get; }
- [ProtoMember(4)]
- public Vec2 MovementDirection { get; }
- [ProtoMember(5)]
- public AgentEquipmentData AgentEquipment { get; }
- [ProtoMember(6)]
- public AgentActionData ActionData { get; }
- [ProtoMember(7)]
- public AgentMountData MountData { get; }
- }
-}
diff --git a/source/Missions/Services/Agents/Packets/AgentEquipmentData.cs b/source/Missions/Services/Agents/Packets/AgentEquipmentData.cs
index 80ef53dc9..2384599ff 100644
--- a/source/Missions/Services/Agents/Packets/AgentEquipmentData.cs
+++ b/source/Missions/Services/Agents/Packets/AgentEquipmentData.cs
@@ -4,15 +4,26 @@
namespace Missions.Services.Agents.Packets
{
+ ///
+ /// Struct representing the index of the main-hand and the off-hand.
+ ///
[ProtoContract(SkipConstructor = true)]
public readonly struct AgentEquipmentData
{
+ ///
+ /// Constructor
+ ///
+ ///
public AgentEquipmentData(Agent agent)
{
MainHandIndex = (int)agent.GetWieldedItemIndex(Agent.HandIndex.MainHand);
OffHandIndex = (int)agent.GetWieldedItemIndex(Agent.HandIndex.OffHand);
}
+ ///
+ /// Applies this to the given
+ ///
+ ///
public void Apply(Agent agent)
{
// Check if there is a change on the right hand
@@ -35,6 +46,29 @@ public void Apply(Agent agent)
[ProtoMember(2)]
public int OffHandIndex { get; }
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj) || obj.GetType() != GetType()) return false;
+
+ return this == (AgentEquipmentData)obj;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return MainHandIndex.GetHashCode() ^ OffHandIndex.GetHashCode();
+ }
+ // Remarks: Needs to be overriden or else the default-keyword does not work.
+ public static bool operator ==(AgentEquipmentData a, AgentEquipmentData b)
+ {
+ return a.MainHandIndex == b.MainHandIndex && a.OffHandIndex == b.OffHandIndex;
+ }
+
+ public static bool operator !=(AgentEquipmentData a, AgentEquipmentData b)
+ {
+ return !(a == b);
+ }
}
}
diff --git a/source/Missions/Services/Agents/Packets/AgentGroupController.cs b/source/Missions/Services/Agents/Packets/AgentGroupController.cs
index 80883a468..f6a263209 100644
--- a/source/Missions/Services/Agents/Packets/AgentGroupController.cs
+++ b/source/Missions/Services/Agents/Packets/AgentGroupController.cs
@@ -1,4 +1,5 @@
using Common.Logging;
+using Missions.Services.Agents.Messages;
using LiteNetLib;
using Serilog;
using System;
@@ -50,7 +51,7 @@ public Agent RemoveAgent(Guid agentId)
return null;
}
- public void ApplyMovement(MovementPacket movement)
+ public void ApplyMovement(AgentMovement movement)
{
if (m_ControlledAgents.TryGetValue(movement.AgentId, out Agent agent))
{
diff --git a/source/Missions/Services/Agents/Packets/AgentMovement.cs b/source/Missions/Services/Agents/Packets/AgentMovement.cs
new file mode 100644
index 000000000..e7650569c
--- /dev/null
+++ b/source/Missions/Services/Agents/Packets/AgentMovement.cs
@@ -0,0 +1,273 @@
+using Common.PacketHandlers;
+using LiteNetLib;
+using Missions.Services.Agents.Messages;
+using ProtoBuf;
+using System;
+using TaleWorlds.Library;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Packets
+{
+ ///
+ /// Represents the delta of an 's movement between the time it was created and the time it was last updated.
+ ///
+ [ProtoContract(SkipConstructor = true)]
+ public struct AgentMovement : IPacket
+ {
+ ///
+ /// The representing the direction an is looking at.
+ ///
+ [ProtoMember(1)]
+ public Vec3 LookDirection;
+
+ ///
+ /// The representing the direction the has input for.
+ ///
+ [ProtoMember(2)]
+ public Vec2 InputDirection;
+
+ ///
+ /// The 's .
+ ///
+ [ProtoMember(3)]
+ public AgentActionData ActionData;
+
+ ///
+ /// The 's .
+ ///
+ [ProtoMember(4)]
+ public AgentMountData MountData;
+
+ ///
+ /// The representing the 's current position.
+ ///
+ [ProtoMember(5)]
+ public Vec3 CurrentPosition;
+
+ ///
+ /// The representing the 's current movement direction.
+ ///
+ [ProtoMember(6)]
+ public Vec2 AgentMovementDirection;
+
+ ///
+ /// The of the .
+ ///
+ [ProtoMember(7)]
+ public AgentEquipmentData AgentEquipment;
+
+ ///
+ /// Guid of the associated .
+ ///
+ [ProtoMember(8)]
+ public Guid AgentId { get; }
+
+ ///
+ /// The .
+ ///
+ public PacketType PacketType => PacketType.Movement;
+
+ ///
+ /// The .
+ ///
+ public DeliveryMethod DeliveryMethod => DeliveryMethod.Unreliable;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public AgentMovement(
+ Vec3 agentCurrentPosition,
+ Vec2 agentMovementDirection,
+ AgentEquipmentData agentEquipmentData,
+ Agent agent,
+ Guid guid)
+ {
+ AgentId = guid;
+ CurrentPosition = agentCurrentPosition;
+ AgentMovementDirection = agentMovementDirection;
+ AgentEquipment = agentEquipmentData;
+ InputDirection = default;
+ LookDirection = default;
+
+ if (agent.Health > 0f)
+ {
+ ActionData = new AgentActionData(agent);
+ }
+ else
+ {
+ ActionData = default;
+ }
+
+ if (agent.HasMount)
+ {
+ MountData = new AgentMountData(agent);
+ }
+ else
+ {
+ MountData = default;
+ }
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public AgentMovement(Guid guid)
+ {
+ AgentId = guid;
+ LookDirection = default;
+ InputDirection = default;
+ CurrentPosition = default;
+ AgentMovementDirection = default;
+ AgentEquipment = default;
+ ActionData = default;
+ MountData = default;
+ }
+
+ ///
+ /// Re-calculate this .
+ ///
+ ///
+ public void CalculateMovement(LookDirectionChanged change)
+ {
+ LookDirection = change.LookDirection;
+ }
+
+ ///
+ /// Re-calculate this .
+ ///
+ ///
+ public void CalculateMovement(MovementInputVectorChanged change)
+ {
+ InputDirection = change.InputVector;
+ }
+
+ ///
+ /// Re-calculate this .
+ ///
+ ///
+ public void CalculateMovement(ActionDataChanged change)
+ {
+ ActionData = change.AgentActionData;
+ }
+
+ ///
+ /// Re-calculate this .
+ ///
+ ///
+ public void CalculateMovement(MountDataChanged change)
+ {
+ MountData = change.AgentMountData;
+ }
+
+ ///
+ /// Applies the movement of this to the given .
+ /// returned by this method.
+ ///
+ ///
+ public void Apply(Agent agent)
+ {
+ if (agent.Health <= 0)
+ {
+ return;
+ }
+
+ ApplyCurrentPosition(agent);
+ ApplyMovementDirection(agent);
+ ApplyLookDirection(agent);
+ ApplyMovementInputVector(agent);
+ ApplyAgentEquipment(agent);
+
+ ActionData?.Apply(agent);
+
+ if (agent.HasMount)
+ {
+ MountData?.ApplyMount(agent);
+ }
+ }
+
+ private void ApplyCurrentPosition(Agent agent)
+ {
+ if (CurrentPosition != default && agent.GetPathDistanceToPoint(ref CurrentPosition) > 1f)
+ {
+ agent.TeleportToPosition(CurrentPosition);
+ }
+ }
+
+ private void ApplyMovementDirection(Agent agent)
+ {
+ if (AgentMovementDirection != default)
+ {
+ agent.SetMovementDirection(AgentMovementDirection);
+ }
+ }
+
+ private void ApplyLookDirection(Agent agent)
+ {
+ if (LookDirection != default)
+ {
+ agent.LookDirection = LookDirection;
+ }
+ }
+
+ private void ApplyMovementInputVector(Agent agent)
+ {
+ if (InputDirection != default)
+ {
+ agent.MovementInputVector = InputDirection;
+ }
+ }
+
+ private void ApplyAgentEquipment(Agent agent)
+ {
+ if (AgentEquipment != default)
+ {
+ AgentEquipment.Apply(agent);
+ }
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj) || obj.GetType() != GetType()) return false;
+
+ return Equals((AgentMovement)obj);
+ }
+
+ ///
+ /// Compares with this instance of .
+ ///
+ ///
+ ///
+ public bool Equals(AgentMovement other)
+ {
+ return other.AgentId == AgentId
+ && other.LookDirection == LookDirection
+ && other.InputDirection == InputDirection
+ && other.ActionData != null ? other.ActionData.Equals(ActionData) : ActionData == null
+ && other.MountData != null ? other.MountData.Equals(MountData) : MountData == null
+ && other.CurrentPosition == CurrentPosition
+ && other.AgentMovementDirection == AgentMovementDirection
+ && other.AgentEquipment.Equals(AgentEquipment);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return AgentId.GetHashCode()
+ ^ LookDirection.GetHashCode()
+ ^ InputDirection.GetHashCode()
+ ^ ActionData?.GetHashCode() ?? 37
+ ^ MountData?.GetHashCode() ?? 37
+ ^ CurrentPosition.GetHashCode()
+ ^ AgentMovementDirection.GetHashCode()
+ ^ AgentEquipment.GetHashCode();
+ }
+ }
+}
diff --git a/source/Missions/Services/Agents/Packets/MovementHandler.cs b/source/Missions/Services/Agents/Packets/MovementHandler.cs
new file mode 100644
index 000000000..f05686f9c
--- /dev/null
+++ b/source/Missions/Services/Agents/Packets/MovementHandler.cs
@@ -0,0 +1,208 @@
+using Common;
+using Common.Logging;
+using Common.Messaging;
+using Common.Network;
+using Common.PacketHandlers;
+using LiteNetLib;
+using Missions.Services.Agents.Messages;
+using Missions.Services.Network;
+using Missions.Services.Network.Messages;
+using Serilog;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using TaleWorlds.MountAndBlade;
+
+namespace Missions.Services.Agents.Packets
+{
+ public class MovementHandler : IPacketHandler, IDisposable
+ {
+ private static readonly ILogger Logger = LogManager.GetLogger();
+
+ private readonly IPacketManager _packetManager;
+ private readonly INetwork _client;
+ private readonly IMessageBroker _messageBroker;
+ private readonly INetworkAgentRegistry _agentRegistry;
+ private readonly IAgentPublisherConfig _agentPublisherConfig;
+
+ private readonly AgentPublisher _agentPublisher;
+
+ private ConcurrentDictionary _agentMovementDeltas = new ConcurrentDictionary();
+
+ private Timer _senderTimer;
+
+ public MovementHandler(LiteNetP2PClient client, IMessageBroker messageBroker, INetworkAgentRegistry agentRegistry, IPacketManager packetManager, AgentPublisher agentPublisher, IAgentPublisherConfig agentPublisherConfig)
+ {
+ _packetManager = packetManager;
+ _client = client;
+ _messageBroker = messageBroker;
+ _agentRegistry = agentRegistry;
+
+ _messageBroker.Subscribe(Handle_PeerDisconnect);
+ _messageBroker.Subscribe(Handle_MovementEvent);
+
+ _agentPublisher = agentPublisher;
+ _agentPublisherConfig = agentPublisherConfig;
+
+ _packetManager.RegisterPacketHandler(this);
+
+ // start the SendMessage every PACKET_UPDATE_RATE milliseconds
+ _senderTimer = new Timer(SendMessage, null, 0, _agentPublisherConfig.PacketUpdateRate);
+ }
+
+ ~MovementHandler()
+ {
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ _packetManager.RemovePacketHandler(this);
+ _messageBroker.Unsubscribe(Handle_PeerDisconnect);
+ _messageBroker.Unsubscribe(Handle_MovementEvent);
+ _senderTimer?.Dispose();
+ }
+
+ public PacketType PacketType => PacketType.Movement;
+
+ private void Handle_MovementEvent(MessagePayload payload)
+ {
+ var payloadType = payload.What.GetType();
+
+ // TODO: get rid of this horrible mess somehow
+ if (payloadType == typeof(MovementInputVectorChanged))
+ {
+ Handle_MovementInputVectorChanged((MovementInputVectorChanged)payload.What);
+ }
+ else if (payloadType == typeof(ActionDataChanged))
+ {
+ Handle_ActionDataChanged((ActionDataChanged)payload.What);
+ }
+ else if (payloadType == typeof(LookDirectionChanged))
+ {
+ Handle_LookDirectionChanged((LookDirectionChanged)payload.What);
+ }
+ else if (payloadType == typeof(MountDataChanged))
+ {
+ Handle_MountDataChanged((MountDataChanged)payload.What);
+ }
+ }
+
+ private void Handle_ActionDataChanged(ActionDataChanged payload)
+ {
+ var delta = GetDelta(payload);
+
+ delta.CalculateMovement(payload);
+ }
+
+ private void Handle_LookDirectionChanged(LookDirectionChanged payload)
+ {
+ var delta = GetDelta(payload);
+
+ delta.CalculateMovement(payload);
+ }
+
+ private void Handle_MountDataChanged(MountDataChanged payload)
+ {
+ var delta = GetDelta(payload);
+
+ delta.CalculateMovement(payload);
+ }
+
+ private void Handle_MovementInputVectorChanged(MovementInputVectorChanged payload)
+ {
+ var delta = GetDelta(payload);
+
+ delta.CalculateMovement(payload);
+ }
+
+ private AgentMovement GetDelta(IMovementEvent payload)
+ {
+ if (!_agentRegistry.TryGetAgentId(payload.Agent, out var payloadGuid))
+ {
+ Logger.Error("No {agent} found", nameof(Agent));
+ }
+
+ if (_agentMovementDeltas.TryGetValue(payloadGuid, out var delta))
+ {
+ return delta;
+ }
+
+ var agent = payload.Agent;
+ delta = new AgentMovement(
+ agent.Position,
+ agent.GetMovementDirection(),
+ new AgentEquipmentData(agent),
+ agent,
+ payloadGuid);
+
+ _agentMovementDeltas.TryAdd(payloadGuid, delta);
+
+ return delta;
+ }
+
+ private IEnumerable PopAllDeltas()
+ {
+ var copiedDeltas = new List();
+
+ foreach (var kv in _agentMovementDeltas)
+ {
+ copiedDeltas.Add(kv.Value);
+ }
+
+ _agentMovementDeltas.Clear();
+
+ return copiedDeltas;
+ }
+
+ private void SendMessage(object state)
+ {
+ foreach (var delta in PopAllDeltas())
+ {
+ _client.SendAll(delta);
+ }
+ }
+
+ public void HandlePacket(NetPeer peer, IPacket packet)
+ {
+ if (_agentRegistry.TryGetGroupController(peer, out AgentGroupController agentGroupController))
+ {
+ var movement = (AgentMovement)packet;
+ agentGroupController.ApplyMovement(movement);
+ }
+ }
+
+ public void Handle_PeerDisconnect(MessagePayload payload)
+ {
+ if (Mission.Current == null) return;
+
+ NetPeer peer = payload.What.NetPeer;
+
+ Logger.Debug("Handling disconnect for {peer}", peer);
+
+ if (_agentRegistry.TryGetGroupController(peer, out AgentGroupController controller))
+ {
+ foreach (var kv in controller.ControlledAgents)
+ {
+ var agent = kv.Value;
+ var guid = kv.Key;
+
+ GameLoopRunner.RunOnMainThread(() =>
+ {
+ if (agent.Health > 0)
+ {
+ agent.MakeDead(false, ActionIndexValueCache.act_none);
+ agent.FadeOut(false, true);
+ }
+ });
+
+ _agentMovementDeltas.TryRemove(guid, out var _);
+ }
+
+ _agentRegistry.RemovePeer(peer);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/Missions/Services/Agents/Packets/MovementPacket.cs b/source/Missions/Services/Agents/Packets/MovementPacket.cs
deleted file mode 100644
index d5453b39f..000000000
--- a/source/Missions/Services/Agents/Packets/MovementPacket.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Common.PacketHandlers;
-using LiteNetLib;
-using ProtoBuf;
-using System;
-using TaleWorlds.MountAndBlade;
-
-namespace Missions.Services.Agents.Packets
-{
- [ProtoContract]
- public readonly struct MovementPacket : IPacket
- {
- public DeliveryMethod DeliveryMethod => DeliveryMethod.Unreliable;
-
- public PacketType PacketType => PacketType.Movement;
-
- [ProtoMember(1)]
- public AgentData Agent { get; }
- [ProtoMember(2)]
- public Guid AgentId { get; }
-
- public MovementPacket(Guid agentGuid, Agent agent)
- {
- AgentId = agentGuid;
- Agent = new AgentData(agent);
- }
-
- public void Apply(Agent agent)
- {
- Agent.Apply(agent);
- }
- }
-}
diff --git a/source/Missions/Services/Arena/ArenaSpawnManager.cs b/source/Missions/Services/Arena/ArenaSpawnManager.cs
new file mode 100644
index 000000000..9469967d4
--- /dev/null
+++ b/source/Missions/Services/Arena/ArenaSpawnManager.cs
@@ -0,0 +1,63 @@
+using Mono.Cecil.Cil;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using TaleWorlds.Core;
+using TaleWorlds.Engine;
+using TaleWorlds.Library;
+
+namespace Missions.Services.Arena
+{
+ ///
+ /// Manages the spawns of players inside the arena.
+ ///
+ interface IArenaSpawnManager
+ {
+ ///
+ /// Gets a random spawn location from a scene.
+ ///
+ ///
+ MatrixFrame GetRandomSpawnLocation();
+ }
+
+ ///
+ internal sealed class ArenaSpawnManager : IArenaSpawnManager
+ {
+ private readonly Scene _scene;
+
+ private IReadOnlyList _sceneSpawnPoints = new List();
+
+ private IList _selectedSpawnPoints = new List();
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public ArenaSpawnManager(Scene scene)
+ {
+ _scene = scene;
+ }
+
+ ///
+ public MatrixFrame GetRandomSpawnLocation()
+ {
+ if (_sceneSpawnPoints.IsEmpty())
+ {
+ // TODO: get rid of this hard reference here
+ _sceneSpawnPoints = ArenaTestGameManager.GetAllSpawnPoints(_scene);
+ }
+
+ if (_sceneSpawnPoints.Count == _selectedSpawnPoints.Count) return MatrixFrame.Identity;
+
+ string spawnPoint = string.Empty;
+
+ do
+ {
+ spawnPoint = _sceneSpawnPoints.GetRandomElement();
+ } while (_selectedSpawnPoints.Contains(spawnPoint));
+
+ _selectedSpawnPoints.Add(spawnPoint);
+ return _scene.FindEntitiesWithTag(spawnPoint).First().GetGlobalFrame();
+ }
+ }
+}
diff --git a/source/Missions/Services/Arena/CoopArenaController.cs b/source/Missions/Services/Arena/CoopArenaController.cs
index 6f894a319..5169a3d58 100644
--- a/source/Missions/Services/Arena/CoopArenaController.cs
+++ b/source/Missions/Services/Arena/CoopArenaController.cs
@@ -1,4 +1,4 @@
-using Common;
+using Common;
using Common.Logging;
using Common.Messaging;
using Common.Network;
@@ -45,7 +45,7 @@ public class CoopArenaController : MissionBehavior, IDisposable
public CoopArenaController(
INetworkMessageBroker networkMessageBroker,
- INetworkAgentRegistry agentRegistry,
+ INetworkAgentRegistry agentRegistry,
IRandomEquipmentGenerator equipmentGenerator,
IBinaryPackageFactory packageFactory,
IMissileHandler missileHandler,
@@ -121,7 +121,7 @@ private void Handle_ServerDisconnected(MessagePayload obj)
"Back to Menu",
"",
new Action(() => { MBGameManager.EndGame(); }),
- null),
+ null),
true);
}
@@ -138,9 +138,9 @@ private void SendJoinInfo(NetPeer peer)
if (agent == Agent.Main) continue;
AiAgentData aiAgentData = new AiAgentData(
- agentId,
- agent.Position,
- agent.Character.StringId,
+ agentId,
+ agent.Position,
+ agent.Character.StringId,
agent.Health);
@@ -154,11 +154,11 @@ private void SendJoinInfo(NetPeer peer)
float health = Agent.Main?.Health ?? 0;
NetworkMissionJoinInfo request = new NetworkMissionJoinInfo(
- characterObject,
- isPlayerAlive,
- playerId,
- position,
- health,
+ characterObject,
+ isPlayerAlive,
+ playerId,
+ position,
+ health,
aiAgentDatas.ToArray());
networkMessageBroker.PublishNetworkEvent(peer, request);
@@ -253,7 +253,7 @@ private void AddPlayerToArena()
Agent.Main.SetTeam(Mission.Current.PlayerTeam, false);
- for(int i = 0; i < 10; i++)
+ for (int i = 0; i < 10; i++)
{
Agent ai = SpawnAgent(randomElement.origin, gameCharacters[rand.Next(gameCharacters.Length - 1)], false);
agentRegistry.RegisterControlledAgent(Guid.NewGuid(), ai);
@@ -303,9 +303,9 @@ public Agent SpawnAgent(Vec3 startingPos, CharacterObject character, bool isEnem
agentBuildData.NoHorses(true);
agentBuildData.Equipment(equipment ?? (character.IsHero ? character.HeroObject.BattleEquipment : character.Equipment));
agentBuildData.TroopOrigin(new SimpleAgentOrigin(character, -1, null, default));
-
- if(isEnemy)
+
+ if (isEnemy)
{
agentBuildData.Controller(Agent.ControllerType.None);
}
diff --git a/source/Missions/packages.config b/source/Missions/packages.config
index 107720ff3..0fb322d83 100644
--- a/source/Missions/packages.config
+++ b/source/Missions/packages.config
@@ -4,8 +4,12 @@
-
-
+
+
+
+
+
+
@@ -14,4 +18,5 @@
+
\ No newline at end of file