diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs index 2a456683..61046f7b 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs @@ -34,10 +34,10 @@ public partial class Projectile // TODO: Make physical, beams, and guided projec { p.Definition.LiveMethods.OnEndOfLife?.Invoke(p.Id); }; - public long LastUpdate { get; private set; } + public long LastUpdate { get; set; } public float DistanceTravelled { get; private set; } = 0; - public float Age { get; private set; } = 0; + public float Age { get; set; } = 0; public bool QueuedDispose { get; private set; } = false; private float _health = 0; diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs index fac4f5fd..6218969b 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs @@ -155,7 +155,7 @@ public Projectile AddProjectile(int projectileDefinitionId, Vector3D position, V } } - private Projectile AddProjectile(Projectile projectile) + internal Projectile AddProjectile(Projectile projectile) { if (projectile == null || projectile.DefinitionId == -1) return null; // Ensure that invalid projectiles don't get added diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager_Networking.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager_Networking.cs deleted file mode 100644 index 1e16babd..00000000 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager_Networking.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Heart_Module.Data.Scripts.HeartModule.ErrorHandler; -using Heart_Module.Data.Scripts.HeartModule.Projectiles.StandardClasses; -using Sandbox.ModAPI; -using System.Collections.Generic; -using System.Linq; -using VRage.Game.ModAPI; -using VRage.Utils; -using VRageMath; - -namespace Heart_Module.Data.Scripts.HeartModule.Projectiles -{ - partial class ProjectileManager - { - const int MaxProjectilesSynced = 100; // This value should result in ~100kB/s up per player. - const int NetworkInterval = 2; - - public Dictionary> SyncStream = new Dictionary>(); - - public void QueueSync(Projectile projectile, int DetailLevel = 1) - { - // Sync to everyone if player is undefined - foreach (var player in HeartData.I.Players) // Ensure that all players are being synced - QueueSync(projectile, player, DetailLevel); - } - - public void QueueSync(Projectile projectile, IMyPlayer Player, int DetailLevel = 1) - { - if (!SyncStream.ContainsKey(Player.SteamUserId)) // Avoid throwing an error if the player hasn't been added yet - return; - - if (DetailLevel == 2 && SyncStream[Player.SteamUserId].Count > MaxProjectilesSynced) // Don't sync projectile closing if network load is too high - return; - - if (projectile.Position != null && Vector3D.DistanceSquared(projectile.Position, Player.GetPosition()) > HeartData.I.SyncRangeSq) // Don't sync if the player is out of sync range - return; - - n_SerializableProjectile sP = projectile.AsSerializable(DetailLevel); - if (DetailLevel == 0) - SyncStream[Player.SteamUserId].AddFirst(sP); // Queue new projectiles first - else - SyncStream[Player.SteamUserId].AddLast(sP); // Queue other projectile updates last - } - - int tick = 0; - public void UpdateSync() - { - if (MyAPIGateway.Session.IsServer && MyAPIGateway.Multiplayer.MultiplayerActive && tick % NetworkInterval == 0) - { - foreach (var player in HeartData.I.Players) // Ensure that all players are being synced - { - if (!SyncStream.ContainsKey(player.SteamUserId)) - { - SyncStream.Add(player.SteamUserId, new LinkedList()); - MyLog.Default.WriteLineAndConsole($"Heart Module: Added player {player.SteamUserId}"); - } - } - - foreach (ulong syncedPlayerSteamId in SyncStream.Keys.ToList()) - { - bool remove = true; - foreach (var player in HeartData.I.Players) - { - if (syncedPlayerSteamId == player.SteamUserId) - { - SyncPlayerProjectiles(player); // Sync individual players to lower network load - remove = false; - } - } - if (remove) // Remove disconnected players from sync list - { - SyncStream.Remove(syncedPlayerSteamId); - MyLog.Default.WriteLineAndConsole($"Heart Module: Removed player {syncedPlayerSteamId}"); - } - } - } - - tick++; - } - - private void SyncPlayerProjectiles(IMyPlayer player) - { - if (!SyncStream.ContainsKey(player.SteamUserId)) // Avoid breaking if the player somehow hasn't been added - { - SoftHandle.RaiseSyncException("Player " + player.DisplayName + " is missing projectile sync queue!"); - return; - } - - LinkedList queue = SyncStream[player.SteamUserId]; - - SyncExistingProjectiles(player, queue); - - List toSync = new List(); - for (int i = 0; i < MaxProjectilesSynced && queue.Count > 0; i++) - { - n_SerializableProjectile projectile = queue.First(); - queue.RemoveFirst(); - toSync.Add(projectile); - } - if (toSync.Count > 0) - HeartData.I.Net.SendToPlayer(new n_ProjectileArray(toSync), player.SteamUserId); - - if (queue.Count > MaxProjectilesSynced * 2) // Emergency queue freeing - queue.Clear(); - } - - private void SyncExistingProjectiles(IMyPlayer player, LinkedList queue) - { - int numSyncs = 0; - foreach (var projectile in ActiveProjectiles.Values) - { - if (numSyncs > (MaxProjectilesSynced - queue.Count)) // Only sync if there's free network load. - break; - if (Vector3D.DistanceSquared(projectile.Position, player.GetPosition()) < HeartData.I.SyncRangeSq) - { - QueueSync(projectile); - numSyncs++; - } - } - } - } -} diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/ProjectileNetwork.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/ProjectileNetwork.cs index d0bf98a9..e705314b 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/ProjectileNetwork.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/ProjectileNetwork.cs @@ -1,11 +1,16 @@ -using Heart_Module.Data.Scripts.HeartModule.Projectiles.StandardClasses; +using Heart_Module.Data.Scripts.HeartModule.ErrorHandler; +using Heart_Module.Data.Scripts.HeartModule.Projectiles.StandardClasses; +using Heart_Module.Data.Scripts.HeartModule.Weapons; using Sandbox.ModAPI; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using VRage.Game.ModAPI; +using VRage.Utils; +using VRageMath; using YourName.ModName.Data.Scripts.HeartModule.Weapons.Setup.Adding; namespace Heart_Module.Data.Scripts.HeartModule.Projectiles.ProjectileNetworking @@ -15,29 +20,52 @@ public class ProjectileNetwork const int ProjectilesPerPacket = 50; const int TicksPerPacket = 4; - private Dictionary> SyncStream_PP = new Dictionary>(); - private Dictionary> SyncStream_FireEvent = new Dictionary>(); + private Dictionary> SyncStream_PP = new Dictionary>(); + private Dictionary> SyncStream_FireEvent = new Dictionary>(); public void QueueSync_PP(Projectile projectile) { - QueueSync_PP(null, projectile); + foreach (var player in HeartData.I.Players) + QueueSync_PP(player, projectile); } - public void QueueSync_PP(IMyPlayer player, Projectile projectile) + public void QueueSync_PP(IMyPlayer player, Projectile projectile, int detailLevel = 0) { + if (!SyncStream_PP.ContainsKey(player.SteamUserId)) // Avoid throwing an error if the player hasn't been added yet + return; + SyncStream_PP[player.SteamUserId].Add(projectile.Definition.Ungrouped.SyncPriority, projectile); } - public void QueueSync_FireEvent(SorterWeaponLogic weapon, Projectile projectile) + /// + /// Enqueues a FireEvent for all players. + /// + /// + /// + public void QueueSync_FireEvent(Projectile projectile) { - QueueSync_FireEvent(null, weapon, projectile); + foreach (var player in HeartData.I.Players) + QueueSync_FireEvent(player, projectile); } - - public void QueueSync_FireEvent(IMyPlayer player, SorterWeaponLogic weapon, Projectile projectile) + + /// + /// Enqueues a FireEvent. + /// + /// + /// + /// + public void QueueSync_FireEvent(IMyPlayer player, Projectile projectile) { + if (!SyncStream_FireEvent.ContainsKey(player.SteamUserId)) // Avoid throwing an error if the player hasn't been added yet + return; + SyncStream_FireEvent[player.SteamUserId].Add(projectile.Definition.Ungrouped.SyncPriority, projectile); } + /// + /// Recieve a projectileInfos packet and generate its projectiles. + /// + /// internal void Recieve_PP(n_SerializableProjectileInfos projectileInfos) { if (MyAPIGateway.Session.IsServer) @@ -45,12 +73,36 @@ internal void Recieve_PP(n_SerializableProjectileInfos projectileInfos) for (int i = 0; i < projectileInfos.UniqueProjectileId.Length; i++) { - n_SerializableProjectile projectile = new n_SerializableProjectile() + if (ProjectileManager.I.IsIdAvailable(projectileInfos.UniqueProjectileId[i]) && projectileInfos.DefinitionId != null) { - - }; - - ProjectileManager.I.UpdateProjectileSync(projectile); + if (projectileInfos.FirerEntityId != null) + { + WeaponManager.I.GetWeapon(projectileInfos.FirerEntityId[i])?.MuzzleFlash(true); + } + ProjectileManager.I.AddProjectile(projectileInfos.ToProjectile(i)); + } + else + { + Projectile p = ProjectileManager.I.GetProjectile(projectileInfos.UniqueProjectileId[i]); + if (p != null) + { + p.Position = projectileInfos.PlayerRelativePosition(i) + MyAPIGateway.Session.Player.Character.GetPosition(); + p.Direction = projectileInfos.Direction(i); + p.LastUpdate = DateTime.Now.Date.AddMilliseconds(projectileInfos.MillisecondsFromMidnight[i]).Ticks; + + if (projectileInfos.ProjectileAge != null) + p.Age = projectileInfos.ProjectileAge[i]; + if (projectileInfos.TargetEntityId != null) + { + if (projectileInfos.TargetEntityId[i] == null) + p.Guidance.SetTarget(null); + else + p.Guidance.SetTarget(MyAPIGateway.Entities.GetEntityById(projectileInfos.TargetEntityId[i])); + } + } + else + HeartData.I.Net.SendToServer(new n_ProjectileRequest(projectileInfos.UniqueProjectileId[i])); + } } } @@ -64,12 +116,59 @@ internal void Recieve_FireEvent(n_SerializableFireEvents fireEvents) public void Update1() { ticks++; - if (ticks % TicksPerPacket != 0) + if (ticks % TicksPerPacket != 0 || !(MyAPIGateway.Session.IsServer && MyAPIGateway.Multiplayer.MultiplayerActive)) return; - + // Iterate through SyncStreams based on projectilesperpacket // you will need a way to combine all the projectiles into one packet // this will not work without many edits sorry + + foreach (var player in HeartData.I.Players) // Ensure that all players are being synced + { + if (!SyncStream_PP.ContainsKey(player.SteamUserId)) + { + SyncStream_PP.Add(player.SteamUserId, new SortedList()); + SyncStream_FireEvent.Add(player.SteamUserId, new SortedList()); + MyLog.Default.WriteLineAndConsole($"Heart Module: Registered player {player.SteamUserId}"); + } + } + + foreach (ulong syncedPlayerSteamId in SyncStream_PP.Keys.ToList()) + { + bool remove = true; + foreach (var player in HeartData.I.Players) + { + if (syncedPlayerSteamId == player.SteamUserId) + { + SyncPlayerProjectiles(player); // Sync individual players to lower network load + remove = false; + } + } + if (remove) // Remove disconnected players from sync list + { + SyncStream_PP.Remove(syncedPlayerSteamId); + SyncStream_FireEvent.Remove(syncedPlayerSteamId); + MyLog.Default.WriteLineAndConsole($"Heart Module: Deregistered player {syncedPlayerSteamId}"); + } + } + } + + private void SyncPlayerProjectiles(IMyPlayer player) + { + if (!SyncStream_PP.ContainsKey(player.SteamUserId)) // Avoid breaking if the player somehow hasn't been added + { + SoftHandle.RaiseSyncException("Player " + player.DisplayName + " is missing projectile sync queue!"); + return; + } + + List PPProjectiles = new List(); + for (int i = 0; i < SyncStream_PP[player.SteamUserId].Count && i < ProjectilesPerPacket; i++) // Add up to (n) projectiles to the queue + PPProjectiles.Add(SyncStream_PP[player.SteamUserId].Values[i]); + + n_SerializableProjectileInfos ppInfos = new n_SerializableProjectileInfos(PPProjectiles, player.Character); + HeartData.I.Net.SendToPlayer(ppInfos, player.SteamUserId); + + // TODO: FireEvents } public void Init() diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/n_SerializableProjectileInfos.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/n_SerializableProjectileInfos.cs index ee69c65a..4e2eb4cb 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/n_SerializableProjectileInfos.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileNetworking/n_SerializableProjectileInfos.cs @@ -1,5 +1,6 @@ using Heart_Module.Data.Scripts.HeartModule.Network; using ProtoBuf; +using Sandbox.ModAPI; using System; using System.Collections.Generic; using System.Linq; @@ -116,6 +117,11 @@ public n_SerializableProjectileInfos(List projectiles, IMyCharacter return array; } + public Vector3 PlayerRelativePosition(int index) + { + return new Vector3(playerRelativeX[index], playerRelativeY[index], playerRelativeZ[index]); + } + [ProtoMember(25)] private float[] directionX; [ProtoMember(26)] private float[] directionY; [ProtoMember(27)] private float[] directionZ; @@ -130,6 +136,11 @@ public n_SerializableProjectileInfos(List projectiles, IMyCharacter return array; } + public Vector3 Direction(int index) + { + return new Vector3(directionX[index], directionY[index], directionZ[index]); + } + [ProtoMember(28)] public int[] DefinitionId; [ProtoMember(29)] public int[] MillisecondsFromMidnight; [ProtoMember(30)] public long[] FirerEntityId; @@ -148,6 +159,44 @@ public enum ProjectileDetailLevel NoGuidance = 1, Minimal = 2, } + + /// + /// This is dangerous to call! + /// + /// + /// + public Projectile ToProjectile(int index) + { + if (DefinitionId == null) + return null; + + Projectile p = new Projectile( + DefinitionId[index], + PlayerRelativePosition(index) + MyAPIGateway.Session.Player.Character.GetPosition(), + Direction(index), + FirerEntityId[index], + ((IMyCubeBlock)MyAPIGateway.Entities.GetEntityById(FirerEntityId[index]))?.CubeGrid.LinearVelocity ?? Vector3D.Zero + ) + { + LastUpdate = DateTime.Now.Date.AddMilliseconds(MillisecondsFromMidnight[index]).Ticks + }; + + if (ProjectileAge != null) + p.Age = ProjectileAge[index]; + if (TargetEntityId != null) + { + if (TargetEntityId[index] == null) + p.Guidance.SetTarget(null); + else + p.Guidance.SetTarget(MyAPIGateway.Entities.GetEntityById(TargetEntityId[index])); + } + + + float delta = (DateTime.Now.Ticks - p.LastUpdate) / (float)TimeSpan.TicksPerSecond; + p.TickUpdate(delta); + + return p; + } } [ProtoContract] diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/ProjectileDefinitionBase.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/ProjectileDefinitionBase.cs index 82e10979..5a2375a6 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/ProjectileDefinitionBase.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/ProjectileDefinitionBase.cs @@ -51,6 +51,10 @@ public struct Ungrouped /// The item that needs to get consumed for the magazine to reload. Leave blank to not consume anything. The weapon model should probably have a conveyor port. /// [ProtoMember(5)] public string MagazineItemToConsume; + /// + /// The order in which projectiles are synced. + /// + [ProtoMember(6)] public ushort SyncPriority; } [ProtoContract]