Skip to content

Commit

Permalink
Performance Improvements for Projectile Impact Checking
Browse files Browse the repository at this point in the history
  • Loading branch information
ari-steas committed Feb 9, 2024
1 parent 0acecc2 commit 007f0c3
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Projectile> hittableProjectiles = new List<Projectile>();
ProjectileManager.I.GetProjectilesInSphere(new BoundingSphereD(Position, len), ref hittableProjectiles, true);

float damageToProjectilesInAoE = 0;
List<Projectile> projectilesInAoE = new List<Projectile>();
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<IHitInfo> intersects = new List<IHitInfo>();
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Perform a cheap intersection check to see if it's worth doing a raycast.
/// </summary>
public void UpdateBoundingBoxCheck(HashSet<BoundingSphere> 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<Projectile> hittableProjectiles = new List<Projectile>();
ProjectileManager.I.GetProjectilesInSphere(new BoundingSphereD(Position, len), ref hittableProjectiles, true);

float damageToProjectilesInAoE = 0;
List<Projectile> projectilesInAoE = new List<Projectile>();
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<IHitInfo> intersects = new List<IHitInfo>();
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -47,9 +50,18 @@ public override void UpdateAfterSimulation()
{
if (HeartData.I.IsSuspended) return;

HashSet<BoundingSphere> allValidEntities = new HashSet<BoundingSphere>();
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);
Expand Down Expand Up @@ -201,6 +213,7 @@ public void GetProjectilesInSphere(BoundingSphereD sphere, ref List<Projectile>
{
projectiles.Clear();
double rangeSq = sphere.Radius * sphere.Radius;

Vector3D pos = sphere.Center;

if (onlyDamageable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,12 @@ public static ProjectileDefinitionBase GetProjectileDefinition(int projectileDef



public static List<uint> SpawnProjectilesInCone(int definitionId, Vector3D position, Vector3D direction, int count, double angleRads)
public static List<uint> SpawnProjectilesInCone(int definitionId, Vector3D position, Vector3D direction, int count, double angleRads, long firerId = 0)
{
List<uint> spawned = new List<uint>();
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
};
Expand Down

0 comments on commit 007f0c3

Please sign in to comment.