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