From bdaed2a7489bae969fd63d5651d8229cc05e4400 Mon Sep 17 00:00:00 2001 From: Aristeas <94058548+Jnick-24@users.noreply.github.com> Date: Sat, 6 Jan 2024 15:24:29 -0600 Subject: [PATCH 1/2] Projectile syncing based on sync range note - projectile spawning and despawning is global note 2 - sync range is calculated from player position. this may cause problems in the future. --- .../Data/Scripts/HeartModule/HeartData.cs | 4 + .../HeartModule/Network/HeartNetwork.cs | 27 ++++++ .../HeartModule/Projectiles/DamageHandler.cs | 2 - .../HeartModule/Projectiles/Projectile.cs | 7 +- .../HeartModule/Projectiles/ProjectileDraw.cs | 32 +++++++ .../Projectiles/ProjectileManager.cs | 93 ++++++++++++++----- 6 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs diff --git a/Heart Module/Data/Scripts/HeartModule/HeartData.cs b/Heart Module/Data/Scripts/HeartModule/HeartData.cs index 0abc1aba..3f10cad3 100644 --- a/Heart Module/Data/Scripts/HeartModule/HeartData.cs +++ b/Heart Module/Data/Scripts/HeartModule/HeartData.cs @@ -1,5 +1,7 @@ using Heart_Module.Data.Scripts.HeartModule.ExceptionHandler; using Heart_Module.Data.Scripts.HeartModule.Network; +using Sandbox.ModAPI; +using VRage.Game; namespace Heart_Module.Data.Scripts.HeartModule { @@ -11,5 +13,7 @@ internal class HeartData public bool IsSuspended = false; public HeartNetwork Net = new HeartNetwork(); public HeartLog Log = new HeartLog(); + public int SyncRange = MyAPIGateway.Session.SessionSettings.SyncDistance; + public int SyncRangeSq = MyAPIGateway.Session.SessionSettings.SyncDistance * MyAPIGateway.Session.SessionSettings.SyncDistance; } } diff --git a/Heart Module/Data/Scripts/HeartModule/Network/HeartNetwork.cs b/Heart Module/Data/Scripts/HeartModule/Network/HeartNetwork.cs index 251b3ded..e73b15d2 100644 --- a/Heart Module/Data/Scripts/HeartModule/Network/HeartNetwork.cs +++ b/Heart Module/Data/Scripts/HeartModule/Network/HeartNetwork.cs @@ -31,6 +31,11 @@ void ReceivedPacket(ushort channelId, byte[] serialized, ulong senderSteamId, bo } } + public void SendToPlayer(PacketBase packet, ulong playerSteamId, byte[] serialized = null) + { + RelayToClient(packet, playerSteamId, 0, serialized); + } + public void SendToEveryone(PacketBase packet, byte[] serialized = null) { RelayToClients(packet, 0, serialized); @@ -60,6 +65,28 @@ void RelayToClients(PacketBase packet, ulong senderSteamId = 0, byte[] serialize TempPlayers.Clear(); } + void RelayToClient(PacketBase packet, ulong playerSteamId, ulong senderSteamId, byte[] serialized = null) + { + if (playerSteamId == MyAPIGateway.Multiplayer.ServerId || playerSteamId == senderSteamId) + return; + + if (serialized == null) // only serialize if necessary, and only once. + serialized = MyAPIGateway.Utilities.SerializeToBinary(packet); + + MyAPIGateway.Multiplayer.SendMessageTo(HeartData.HeartNetworkId, serialized, playerSteamId); + } + + void RelayToServer(PacketBase packet, ulong senderSteamId = 0, byte[] serialized = null) + { + if (senderSteamId == MyAPIGateway.Multiplayer.ServerId) + return; + + if (serialized == null) // only serialize if necessary, and only once. + serialized = MyAPIGateway.Utilities.SerializeToBinary(packet); + + MyAPIGateway.Multiplayer.SendMessageToServer(HeartData.HeartNetworkId, serialized); + } + void HandlePacket(PacketBase packet, ulong senderSteamId) { packet.Received(senderSteamId); diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/DamageHandler.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/DamageHandler.cs index c73d2c2b..7959af05 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/DamageHandler.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/DamageHandler.cs @@ -56,8 +56,6 @@ private void m_Update() private void m_QueueEvent(DamageEvent damageEvent) { - if (MyAPIGateway.Session.IsServer) - CriticalHandle.ThrowCriticalException(new Exception("Testing"), typeof(DamageHandler)); DamageEvents.Add(damageEvent); } diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs index 69a3ad3b..e6e816dd 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs @@ -27,7 +27,7 @@ public partial class Projectile public float Velocity = 0; public int RemainingImpacts = 0; - public Action Close = (p) => { }; + public Action OnClose = (p) => { }; public long LastUpdate { get; private set; } public float DistanceTravelled { get; private set; } = 0; @@ -97,11 +97,6 @@ public void TickUpdate(float delta) NextMoveStep = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta; } - public void DrawUpdate(float delta) - { - DebugDraw.AddPoint(Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta, Color.Green, 0.000001f); - } - public void CheckHits(float delta) { List intersects = new List(); diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs new file mode 100644 index 00000000..ff7f8508 --- /dev/null +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs @@ -0,0 +1,32 @@ +using Heart_Module.Data.Scripts.HeartModule.Debug; +using Sandbox.ModAPI; +using VRage.Game.Entity; +using VRageMath; +using VRageRender; + +namespace Heart_Module.Data.Scripts.HeartModule.Projectiles +{ + partial class Projectile + { + MyBillboard ProjectileBillboard; + MyEntity ProjectileEntity; + + internal void InitDrawing() + { + ProjectileBillboard = new MyBillboard(); + ProjectileEntity = new MyEntity(); + } + + public void DrawUpdate(float delta) + { + // Temporary debug draw + DebugDraw.AddPoint(Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta, Color.Green, 0.000001f); + + } + + internal void CloseDrawing() + { + + } + } +} diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs index 651ef4dc..ad252e17 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using VRage.Game.Components; +using VRage.Game.ModAPI; using VRageMath; namespace Heart_Module.Data.Scripts.HeartModule.Projectiles @@ -14,10 +15,10 @@ namespace Heart_Module.Data.Scripts.HeartModule.Projectiles public class ProjectileManager : MySessionComponentBase { public static ProjectileManager I = new ProjectileManager(); - const int MaxProjectilesSynced = 25; // TODO: Sync within range of client. This value should be ~100kB/s per player + const int MaxProjectilesSynced = 25; // This value should result in ~100kB/s per player. private Dictionary ActiveProjectiles = new Dictionary(); - private List ProjectileSyncStream = new List(); + private Dictionary> ProjectileSyncStream = new Dictionary>(); public uint NextId { get; private set; } = 0; private List QueuedCloseProjectiles = new List(); private float delta = 0; @@ -57,7 +58,8 @@ public override void UpdateAfterSimulation() { if (HeartData.I.IsSuspended) return; - if (j >= 1 && MyAPIGateway.Session.IsServer) + // spawn projectiles at world origin for debugging. Don't actually do this to spawn projectiles, please. + if (j >= 25 && MyAPIGateway.Session.IsServer) { j = 0; try @@ -67,7 +69,7 @@ public override void UpdateAfterSimulation() IsActive = true, Id = 0, DefinitionId = 0, - Position = MyAPIGateway.Session.Player?.GetPosition() ?? Vector3D.Zero, + Position = MyAPIGateway.Session.Player?.GetPosition() ?? Vector3D.Zero, // CHECK OUT HOW HARD I CAN PISS Direction = MyAPIGateway.Session.Player?.Controller.ControlledEntity.Entity.WorldMatrix.Forward ?? Vector3D.Forward, Velocity = 100, Timestamp = DateTime.Now.Ticks, @@ -84,6 +86,7 @@ public override void UpdateAfterSimulation() } j++; + // Delta time for tickrate-independent projectile movement delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond; // Tick projectiles @@ -100,32 +103,39 @@ public override void UpdateAfterSimulation() //MyAPIGateway.Utilities.ShowMessage("Heart", $"Closing projectile {projectile.Id}. Age: {projectile.Age} "); if (MyAPIGateway.Session.IsServer) SyncProjectile(projectile, 2); - projectile.Close.Invoke(projectile); + + if (!MyAPIGateway.Utilities.IsDedicated) + projectile.CloseDrawing(); + + ActiveProjectiles.Remove(projectile.Id); + projectile.OnClose.Invoke(projectile); } QueuedCloseProjectiles.Clear(); // Sync stuff - int numSyncs = 0; if (MyAPIGateway.Session.IsServer && MyAPIGateway.Multiplayer.MultiplayerActive) { - for (int i = 0; i < MaxProjectilesSynced && i < ProjectileSyncStream.Count; i++) + List players = new List(); + MyAPIGateway.Multiplayer.Players.GetPlayers(players); + + foreach (var player in players) // Ensure that all players are being synced + if (!ProjectileSyncStream.ContainsKey(player.SteamUserId)) + ProjectileSyncStream.Add(player.SteamUserId, new List()); + + foreach (ulong syncedPlayerSteamId in ProjectileSyncStream.Keys.ToList()) { - uint id = ProjectileSyncStream[i]; - - if (ActiveProjectiles.ContainsKey(id)) + bool remove = true; + foreach (var player in players) { - SyncProjectile(ActiveProjectiles[id], 1); - numSyncs++; + if (syncedPlayerSteamId == player.SteamUserId) + { + SyncPlayerProjectiles(player); // Sync individual players to lower network load + remove = false; + } } + if (remove) // Remove disconnected players from sync list + ProjectileSyncStream.Remove(syncedPlayerSteamId); } - - if (ProjectileSyncStream.Count < MaxProjectilesSynced) - { - ProjectileSyncStream.Clear(); - ProjectileSyncStream = ActiveProjectiles.Keys.ToList(); - } - else - ProjectileSyncStream.RemoveRange(0, MaxProjectilesSynced); } else { @@ -137,6 +147,34 @@ public override void UpdateAfterSimulation() clock.Restart(); } + private void SyncPlayerProjectiles(IMyPlayer player) + { + int numSyncs = 0; + + for (int i = 0; i < MaxProjectilesSynced && i < ProjectileSyncStream[player.SteamUserId].Count; i++) + { + uint id = ProjectileSyncStream[player.SteamUserId][i]; + + if (ActiveProjectiles.ContainsKey(id)) + { + SyncProjectile(ActiveProjectiles[id], 1, player.SteamUserId); + numSyncs++; + } + } + + if (ProjectileSyncStream[player.SteamUserId].Count < MaxProjectilesSynced) + { + // Limits projectile syncing to within sync range. + // Syncing is based off of character position for now, camera position may be wise in the future + ProjectileSyncStream[player.SteamUserId].Clear(); + foreach (var projectile in ActiveProjectiles.Values) + if (Vector3D.DistanceSquared(projectile.Position, player.GetPosition()) < HeartData.I.SyncRangeSq) + ProjectileSyncStream[player.SteamUserId].Add(projectile.Id); + } + else + ProjectileSyncStream[player.SteamUserId].RemoveRange(0, MaxProjectilesSynced); + } + public override void UpdatingStopped() { clock.Stop(); @@ -146,15 +184,13 @@ public override void Draw() { if (HeartData.I.IsSuspended) return; - if (MyAPIGateway.Utilities.IsDedicated) + if (MyAPIGateway.Utilities.IsDedicated) // We don't want to needlessly use server CPU time return; delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond; // Triggered every frame, avoids jitter in projectiles foreach (var projectile in ActiveProjectiles.Values) - { projectile.DrawUpdate(delta); - } } public void UpdateProjectile(SerializableProjectile projectile) @@ -176,12 +212,19 @@ public void AddProjectile(Projectile projectile) while (!IsIdAvailable(NextId)) NextId++; projectile.SetId(NextId); - projectile.Close += (p) => ActiveProjectiles.Remove(p.Id); ActiveProjectiles.Add(projectile.Id, projectile); SyncProjectile(projectile, 0); + if (!MyAPIGateway.Utilities.IsDedicated) + projectile.InitDrawing(); } - public void SyncProjectile(Projectile projectile, int DetailLevel = 1) => HeartData.I.Net.SendToEveryone(projectile.AsSerializable(DetailLevel)); + public void SyncProjectile(Projectile projectile, int DetailLevel = 1, ulong PlayerSteamId = 0) + { + if (PlayerSteamId == 0) + HeartData.I.Net.SendToEveryone(projectile.AsSerializable(DetailLevel)); + else + HeartData.I.Net.SendToPlayer(projectile.AsSerializable(DetailLevel), PlayerSteamId); + } public Projectile GetProjectile(uint id) => ActiveProjectiles.GetValueOrDefault(id, null); public bool IsIdAvailable(uint id) => !ActiveProjectiles.ContainsKey(id); From 58595f63739f8232954889003565ae640973f98e Mon Sep 17 00:00:00 2001 From: Aristeas <94058548+Jnick-24@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:35:40 -0600 Subject: [PATCH 2/2] Projectile Particles --- .../HeartModule/Projectiles/Projectile.cs | 8 +++- .../ProjectileDefinitionManager.cs | 6 +-- .../HeartModule/Projectiles/ProjectileDraw.cs | 42 +++++++++++++++++-- .../Projectiles/ProjectileManager.cs | 4 +- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs index e6e816dd..6a135cab 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs @@ -107,12 +107,14 @@ public void CheckHits(float delta) foreach (var hitInfo in intersects) { + if (QueuedDispose) + break; double dist = len * hitInfo.Fraction; - ProjectileHit(hitInfo.HitEntity); + ProjectileHit(hitInfo.HitEntity, hitInfo.Position); } } - public void ProjectileHit(IMyEntity impact) + public void ProjectileHit(IMyEntity impact, Vector3D impactPosition) { if (impact.EntityId == Firer) return; @@ -122,6 +124,8 @@ public void ProjectileHit(IMyEntity impact) else if (impact is IMyCharacter) DamageHandler.QueueEvent(new DamageEvent(impact, DamageEvent.DamageEntType.Character, this)); + DrawImpactParticle(impactPosition); + RemainingImpacts -= 1; if (RemainingImpacts <= 0) QueueDispose(); diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs index 99c9bad6..e926f1b3 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs @@ -20,7 +20,7 @@ internal class ProjectileDefinitionManager SlimBlockDamageMod = 25, FatBlockDamageMod = 1, BaseDamage = 100, - AreaDamage = 50, + AreaDamage = 100, AreaRadius = 15, MaxImpacts = 1, }, @@ -37,8 +37,8 @@ internal class ProjectileDefinitionManager Model = "", TrailTexture = "", TrailFadeTime = 0, - AttachedParticle = "", - ImpactParticle = "", + AttachedParticle = "Smoke_Missile", + ImpactParticle = "Explosion_LargeCaliberShell_Backup", VisibleChance = 1, }, Audio = new Audio() diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs index ff7f8508..0b3a303a 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs @@ -1,5 +1,6 @@ using Heart_Module.Data.Scripts.HeartModule.Debug; using Sandbox.ModAPI; +using VRage.Game; using VRage.Game.Entity; using VRageMath; using VRageRender; @@ -10,23 +11,58 @@ partial class Projectile { MyBillboard ProjectileBillboard; MyEntity ProjectileEntity; + MyParticleEffect ProjectileEffect; + uint RenderId = 0; internal void InitDrawing() { ProjectileBillboard = new MyBillboard(); ProjectileEntity = new MyEntity(); + RenderId = ProjectileEntity.Render.GetRenderObjectID(); } public void DrawUpdate(float delta) { + Vector3D visualPosition = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta; // Temporary debug draw - DebugDraw.AddPoint(Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta, Color.Green, 0.000001f); - + //DebugDraw.AddPoint(visualPosition, Color.Green, 0.000001f); + + if (Definition.Visual.AttachedParticle != "") + { + MatrixD matrix = MatrixD.CreateWorld(visualPosition, Direction, Vector3D.Cross(Direction, Vector3D.Up)); + + if (ProjectileEffect == null) + { + MyParticlesManager.TryCreateParticleEffect(Definition.Visual.AttachedParticle, ref matrix, ref visualPosition, RenderId, out ProjectileEffect); + } + else + { + ProjectileEffect.WorldMatrix = matrix; + } + } } - internal void CloseDrawing() + private void DrawImpactParticle(Vector3D ImpactPosition) { + if (Definition.Visual.ImpactParticle == "") + return; + + MatrixD matrix = MatrixD.CreateTranslation(ImpactPosition); + MyParticleEffect hitEffect; + if (MyParticlesManager.TryCreateParticleEffect(Definition.Visual.ImpactParticle, ref matrix, ref ImpactPosition, uint.MaxValue, out hitEffect)) + { + MyAPIGateway.Utilities.ShowNotification("Spawned particle at " + hitEffect.WorldMatrix.Translation); + //hitEffect.UserScale = av.AmmoDef.AmmoGraphics.Particles.Hit.Extras.Scale; + //hitEffect.Velocity = av.Hit.HitVelocity; + if (hitEffect.Loop) + hitEffect.Stop(); + } + } + + internal void CloseDrawing() + { + ProjectileEffect?.Close(); } } } diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs index ad252e17..d4066194 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs @@ -64,13 +64,15 @@ public override void UpdateAfterSimulation() j = 0; try { + Random r = new Random(); + Vector3D randVec = new Vector3D(r.NextDouble(), r.NextDouble(), r.NextDouble()).Normalized(); Projectile p = new Projectile(new SerializableProjectile() { IsActive = true, Id = 0, DefinitionId = 0, Position = MyAPIGateway.Session.Player?.GetPosition() ?? Vector3D.Zero, // CHECK OUT HOW HARD I CAN PISS - Direction = MyAPIGateway.Session.Player?.Controller.ControlledEntity.Entity.WorldMatrix.Forward ?? Vector3D.Forward, + Direction = MyAPIGateway.Session.Player?.Controller.ControlledEntity.Entity.WorldMatrix.Forward.Rotate(randVec, r.NextDouble() * 0.0873 - 0.04365) ?? Vector3D.Forward, Velocity = 100, Timestamp = DateTime.Now.Ticks, InheritedVelocity = Vector3D.Zero,