From 007f0c31d000741ef81a0c1f2043cc53a332f7e1 Mon Sep 17 00:00:00 2001 From: Aristeas <94058548+Jnick-24@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:17:26 -0600 Subject: [PATCH] Performance Improvements for Projectile Impact Checking --- .../HeartModule/Projectiles/Projectile.cs | 91 ------------ .../HeartModule/Projectiles/ProjectileHits.cs | 134 ++++++++++++++++++ .../Projectiles/ProjectileManager.cs | 13 ++ .../HeartModule/Utility/CommandHandler.cs | 1 + .../Communication/HeartApi.cs | 4 +- .../OrreryFrameworkDemo/ExampleAmmos.cs | 4 +- 6 files changed, 152 insertions(+), 95 deletions(-) create mode 100644 Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileHits.cs 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 4768d20b..31671610 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 @@ -184,97 +184,6 @@ public void TickUpdate(float delta) UpdateAudio(); } - public float CheckHits() - { - if (NextMoveStep == Vector3D.Zero) - return -1; - - double len = IsHitscan ? Definition.PhysicalProjectile.MaxTrajectory : Vector3D.Distance(Position, NextMoveStep); - double dist = -1; - - if (RemainingImpacts > 0 && Definition.Damage.DamageToProjectiles > 0) - { - List hittableProjectiles = new List(); - ProjectileManager.I.GetProjectilesInSphere(new BoundingSphereD(Position, len), ref hittableProjectiles, true); - - float damageToProjectilesInAoE = 0; - List projectilesInAoE = new List(); - ProjectileManager.I.GetProjectilesInSphere(new BoundingSphereD(Position, Definition.Damage.DamageToProjectilesRadius), ref projectilesInAoE, true); - - RayD ray = new RayD(Position, Direction); - - foreach (var projectile in hittableProjectiles) - { - if (RemainingImpacts <= 0 || projectile == this) - continue; - - Vector3D offset = Vector3D.Half * projectile.Definition.PhysicalProjectile.ProjectileSize; - BoundingBoxD box = new BoundingBoxD(projectile.Position - offset, projectile.Position + offset); - double? intersectDist = ray.Intersects(box); - if (intersectDist != null) - { - dist = intersectDist.Value; - projectile.Health -= Definition.Damage.DamageToProjectiles; - - damageToProjectilesInAoE += Definition.Damage.DamageToProjectiles; - - Vector3D hitPos = Position + Direction * dist; - - if (MyAPIGateway.Session.IsServer) - PlayImpactAudio(hitPos); // Audio is global - if (!MyAPIGateway.Utilities.IsDedicated) - DrawImpactParticle(hitPos, Direction); // Visuals are clientside - - Definition.LiveMethods.OnImpact?.Invoke(Id, hitPos, Direction, null); - - RemainingImpacts--; - } - } - - if (damageToProjectilesInAoE > 0) - foreach (var projectile in projectilesInAoE) - if (projectile != this) - projectile.Health -= damageToProjectilesInAoE; - } - - if (RemainingImpacts > 0) - { - List intersects = new List(); - MyAPIGateway.Physics.CastRay(Position, NextMoveStep, intersects); - - foreach (var hitInfo in intersects) - { - if (RemainingImpacts <= 0) - break; - - if (hitInfo.HitEntity.EntityId == Firer) - continue; // Skip firer - - dist = hitInfo.Fraction * len; - - if (hitInfo.HitEntity is IMyCubeGrid) - DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Grid, this, hitInfo.Position, hitInfo.Normal)); - else if (hitInfo.HitEntity is IMyCharacter) - DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Character, this, hitInfo.Position, hitInfo.Normal)); - - if (MyAPIGateway.Session.IsServer) - PlayImpactAudio(hitInfo.Position); // Audio is global - if (!MyAPIGateway.Utilities.IsDedicated) - DrawImpactParticle(hitInfo.Position, hitInfo.Normal); // Visuals are clientside - - Definition.LiveMethods.OnImpact?.Invoke(Id, hitInfo.Position, Direction, (MyEntity)hitInfo.HitEntity); - - RemainingImpacts--; - } - } - - if (RemainingImpacts <= 0) - if (!IsHitscan) - QueueDispose(); - - return (float)dist; - } - public Vector3D NextMoveStep = Vector3D.Zero; public void UpdateFromSerializable(n_SerializableProjectile projectile) diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileHits.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileHits.cs new file mode 100644 index 00000000..e37d5b97 --- /dev/null +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileHits.cs @@ -0,0 +1,134 @@ +using Heart_Module.Data.Scripts.HeartModule.ExceptionHandler; +using Sandbox.ModAPI; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRage.Game.Entity; +using VRage.Game.ModAPI; +using VRage.ModAPI; +using VRageMath; + +namespace Heart_Module.Data.Scripts.HeartModule.Projectiles +{ + public partial class Projectile + { + private bool shouldCheckEntities = false; + + /// + /// Perform a cheap intersection check to see if it's worth doing a raycast. + /// + public void UpdateBoundingBoxCheck(HashSet entitiesToCheck) + { + shouldCheckEntities = false; + Ray travelLine = new Ray(Position, Direction); + double checkDistSq = (NextMoveStep - Position).LengthSquared(); + + foreach (var entity in entitiesToCheck) + { + double? dist = entity.Intersects(travelLine); // This seems to be the cheapest form of line checking + if (!dist.HasValue) + continue; + if (dist * dist > checkDistSq) + continue; + + shouldCheckEntities = true; + break; + } + } + + public float CheckHits() + { + if (NextMoveStep == Vector3D.Zero) + return -1; + + double len = IsHitscan ? Definition.PhysicalProjectile.MaxTrajectory : Vector3D.Distance(Position, NextMoveStep); + double dist = -1; + + if (RemainingImpacts > 0 && Definition.Damage.DamageToProjectiles > 0) + { + List hittableProjectiles = new List(); + ProjectileManager.I.GetProjectilesInSphere(new BoundingSphereD(Position, len), ref hittableProjectiles, true); + + float damageToProjectilesInAoE = 0; + List projectilesInAoE = new List(); + if (Definition.Damage.DamageToProjectilesRadius > 0) + ProjectileManager.I.GetProjectilesInSphere(new BoundingSphereD(Position, Definition.Damage.DamageToProjectilesRadius), ref projectilesInAoE, true); + + RayD ray = new RayD(Position, Direction); + + foreach (var projectile in hittableProjectiles) + { + if (RemainingImpacts <= 0 || projectile == this || projectile.Firer == Firer) + continue; + + BoundingSphereD sphere = new BoundingSphereD(projectile.Position, projectile.Definition.PhysicalProjectile.ProjectileSize); + double? intersectDist = ray.Intersects(sphere); + if (intersectDist != null) + { + dist = intersectDist.Value; + projectile.Health -= Definition.Damage.DamageToProjectiles; + + damageToProjectilesInAoE += Definition.Damage.DamageToProjectiles; + + Vector3D hitPos = Position + Direction * dist; + + if (MyAPIGateway.Session.IsServer) + PlayImpactAudio(hitPos); // Audio is global + if (!MyAPIGateway.Utilities.IsDedicated) + DrawImpactParticle(hitPos, Direction); // Visuals are clientside + + Definition.LiveMethods.OnImpact?.Invoke(Id, hitPos, Direction, null); + + RemainingImpacts--; + } + } + + if (damageToProjectilesInAoE > 0) + foreach (var projectile in projectilesInAoE) + if (projectile != this) + projectile.Health -= damageToProjectilesInAoE; + } + + if (shouldCheckEntities && RemainingImpacts > 0) + { + List intersects = new List(); + MyAPIGateway.Physics.CastRay(Position, NextMoveStep, intersects); + + foreach (var hitInfo in intersects) + { + if (RemainingImpacts <= 0) + break; + + if (hitInfo.HitEntity.EntityId == Firer) + continue; // Skip firer + + dist = hitInfo.Fraction * len; + + if (hitInfo.HitEntity is IMyCubeGrid) + DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Grid, this, hitInfo.Position, hitInfo.Normal)); + else if (hitInfo.HitEntity is IMyCharacter) + DamageHandler.QueueEvent(new DamageEvent(hitInfo.HitEntity, DamageEvent.DamageEntType.Character, this, hitInfo.Position, hitInfo.Normal)); + + if (MyAPIGateway.Session.IsServer) + PlayImpactAudio(hitInfo.Position); // Audio is global + if (!MyAPIGateway.Utilities.IsDedicated) + DrawImpactParticle(hitInfo.Position, hitInfo.Normal); // Visuals are clientside + + Definition.LiveMethods.OnImpact?.Invoke(Id, hitInfo.Position, Direction, (MyEntity)hitInfo.HitEntity); + + RemainingImpacts--; + } + } + + if (RemainingImpacts <= 0) + if (!IsHitscan) + QueueDispose(); + + return (float)dist; + } + } +} 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 43bd98c0..7cf38abd 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 @@ -1,12 +1,15 @@ using Heart_Module.Data.Scripts.HeartModule.ErrorHandler; using Heart_Module.Data.Scripts.HeartModule.Projectiles.StandardClasses; using Heart_Module.Data.Scripts.HeartModule.Weapons; +using Sandbox.Game.Entities; using Sandbox.ModAPI; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using VRage.Game.Components; +using VRage.Game.ModAPI; +using VRage.ModAPI; using VRageMath; namespace Heart_Module.Data.Scripts.HeartModule.Projectiles @@ -47,9 +50,18 @@ public override void UpdateAfterSimulation() { if (HeartData.I.IsSuspended) return; + HashSet allValidEntities = new HashSet(); + MyAPIGateway.Entities.GetEntities(null, (ent) => { + if (ent is IMyCubeGrid || ent is IMyCharacter) + allValidEntities.Add(ent.WorldVolume); + return false; + } + ); + // Tick projectiles foreach (var projectile in ActiveProjectiles.Values.ToArray()) // This can be modified by ModApi calls during run { + projectile.UpdateBoundingBoxCheck(allValidEntities); projectile.TickUpdate(deltaTick); if (projectile.QueuedDispose) QueuedCloseProjectiles.Add(projectile); @@ -201,6 +213,7 @@ public void GetProjectilesInSphere(BoundingSphereD sphere, ref List { projectiles.Clear(); double rangeSq = sphere.Radius * sphere.Radius; + Vector3D pos = sphere.Center; if (onlyDamageable) diff --git a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Utility/CommandHandler.cs b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Utility/CommandHandler.cs index 54ec19f8..5fdd7ecb 100644 --- a/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Utility/CommandHandler.cs +++ b/Orrery Combat Framework - Heart Module/Data/Scripts/HeartModule/Utility/CommandHandler.cs @@ -30,6 +30,7 @@ public class CommandHandler ["debug.fillammo"] = new Command("HeartMod.Debug", "Fills all magazines on your current grid.", (message) => I.FillGridWeapons()), ["debug.reloadammo"] = new Command("HeartMod.Debug", "Forces all weapons on your current grid to reload.", (message) => I.ReloadGridWeapons()), ["debug.reloaddefs"] = new Command("HeartMod.Debug", "Clears and refreshes all weapon definitions.", (message) => { HeartLoad.ResetDefinitions(); MyAPIGateway.Utilities.ShowMessage("[OCF]", "All definitions cleared. Good luck fixing the bug!"); }), + // TODO: Full on mod reload if possible }; private void ShowHelp() diff --git a/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/Communication/HeartApi.cs b/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/Communication/HeartApi.cs index cf901f12..cf5ee804 100644 --- a/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/Communication/HeartApi.cs +++ b/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/Communication/HeartApi.cs @@ -170,12 +170,12 @@ public static ProjectileDefinitionBase GetProjectileDefinition(int projectileDef - public static List SpawnProjectilesInCone(int definitionId, Vector3D position, Vector3D direction, int count, double angleRads) + public static List SpawnProjectilesInCone(int definitionId, Vector3D position, Vector3D direction, int count, double angleRads, long firerId = 0) { List spawned = new List(); Random random = new Random(); for (int i = 0; i < count; i++) - spawned.Add(SpawnProjectile(definitionId, position, direction.Rotate(Vector3D.CalculatePerpendicularVector(direction).Rotate(direction, Math.PI * 2 * random.NextDouble()), angleRads * random.NextDouble()), 0, Vector3D.Zero)); + spawned.Add(SpawnProjectile(definitionId, position, direction.Rotate(Vector3D.CalculatePerpendicularVector(direction).Rotate(direction, Math.PI * 2 * random.NextDouble()), angleRads * random.NextDouble()), firerId, Vector3D.Zero)); return spawned; } diff --git a/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/ExampleAmmos.cs b/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/ExampleAmmos.cs index cb10a302..50b20786 100644 --- a/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/ExampleAmmos.cs +++ b/OrreryFrameworkDemo/Data/Scripts/OrreryFrameworkDemo/ExampleAmmos.cs @@ -70,11 +70,11 @@ partial class HeartDefinitions }, LiveMethods = new LiveMethods() { - OnImpact = (projectileInfo, hitPosition, hitNormal, hitEntity) => + OnImpact = (projectileId, hitPosition, hitNormal, hitEntity) => { if (hitEntity == null) return; - HeartApi.SpawnProjectilesInCone(HeartApi.GetProjectileDefinitionId(ExampleAmmoMissile.Name), hitPosition - hitNormal * 50, hitNormal, 10, 0.1f); + HeartApi.SpawnProjectilesInCone(HeartApi.GetProjectileDefinitionId(ExampleAmmoMissile.Name), hitPosition - hitNormal * 50, hitNormal, 10, 0.1f, HeartApi.GetProjectileInfo(projectileId, 0).Firer.GetValueOrDefault()); } } };