diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs index 6a135cab..5c77f237 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/Projectile.cs @@ -61,7 +61,7 @@ public Projectile(SerializableProjectile projectile) SyncUpdate(projectile); } - public Projectile(int DefinitionId) + public Projectile(int DefinitionId, long firer = 0, Vector3D InitialVelocity = new Vector3D()) { if (!ProjectileDefinitionManager.HasDefinition(DefinitionId)) { @@ -73,6 +73,9 @@ public Projectile(int DefinitionId) Definition = ProjectileDefinitionManager.GetDefinition(DefinitionId); Velocity = Definition.PhysicalProjectile.Velocity; + this.Firer = firer; + this.InheritedVelocity = InitialVelocity; + RemainingImpacts = Definition.Damage.MaxImpacts; } diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs index 4e72fd22..f285c99b 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDefinitionManager.cs @@ -35,12 +35,12 @@ internal class ProjectileDefinitionManager }, Visual = new Visual() { - Model = "", + Model = "Models\\Weapons\\Projectile_Missile.mwm", TrailTexture = MyStringId.GetOrCompute("WeaponLaser"), - TrailFadeTime = 2, + TrailFadeTime = 0, TrailLength = 1, TrailWidth = 0.1f, - TrailColor = new VRageMath.Vector4(255, 255, 255, 255), + //TrailColor = new VRageMath.Vector4(61, 24, 24, 200), AttachedParticle = "Smoke_Missile", ImpactParticle = "Explosion_LargeCaliberShell_Backup", VisibleChance = 1, diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs index aa97149b..5c5e2873 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs @@ -1,4 +1,5 @@ using Heart_Module.Data.Scripts.HeartModule.Debug; +using Sandbox.Game.Entities; using Sandbox.ModAPI; using System; using System.Collections.Generic; @@ -6,6 +7,8 @@ using VRage; using VRage.Game; using VRage.Game.Entity; +using VRage.Game.Models; +using VRage.ModAPI; using VRageMath; using VRageRender; @@ -13,51 +16,68 @@ namespace Heart_Module.Data.Scripts.HeartModule.Projectiles { partial class Projectile { - MyBillboard ProjectileBillboard; - MyEntity ProjectileEntity; + MyEntity ProjectileEntity = new MyEntity(); MyParticleEffect ProjectileEffect; uint RenderId = 0; - Dictionary, long> TrailFade = new Dictionary, long>(); + Dictionary, float> TrailFade = new Dictionary, float>(); + MatrixD ProjectileMatrix = MatrixD.Identity; internal void InitDrawing() { - ProjectileBillboard = new MyBillboard(); - ProjectileEntity = new MyEntity(); - RenderId = ProjectileEntity.Render.GetRenderObjectID(); + if (Definition.Visual.HasModel) + { + ProjectileEntity.Init(null, Definition.Visual.Model, null, null); + ProjectileEntity.Render.CastShadows = false; + ProjectileEntity.IsPreview = true; + ProjectileEntity.Save = false; + ProjectileEntity.SyncFlag = false; + ProjectileEntity.NeedsWorldMatrix = false; + ProjectileEntity.Flags |= EntityFlags.IsNotGamePrunningStructureObject; + MyEntities.Add(ProjectileEntity, true); + RenderId = ProjectileEntity.Render.GetRenderObjectID(); + } + else + RenderId = uint.MaxValue; } - public void DrawUpdate(float delta) + public void DrawUpdate(float deltaTick, float deltaDraw) { - Vector3D visualPosition = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta; - MatrixD matrix = MatrixD.CreateWorld(visualPosition, Direction, Vector3D.Cross(Direction, Vector3D.Up)); + // deltaTick is the current offset between tick and draw, to account for variance between FPS and tickrate + Vector3D visualPosition = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * deltaTick)) * deltaTick; + ProjectileMatrix = MatrixD.CreateWorld(visualPosition, Direction, Vector3D.Cross(Direction, Vector3D.Up)); // Temporary debug draw //DebugDraw.AddPoint(visualPosition, Color.Green, 0.000001f); - if (Definition.Visual.AttachedParticle != "" && !HeartData.I.IsPaused) + if (Definition.Visual.HasAttachedParticle && !HeartData.I.IsPaused) { if (ProjectileEffect == null) - MyParticlesManager.TryCreateParticleEffect(Definition.Visual.AttachedParticle, ref matrix, ref visualPosition, RenderId, out ProjectileEffect); - else - ProjectileEffect.WorldMatrix = matrix; + MyParticlesManager.TryCreateParticleEffect(Definition.Visual.AttachedParticle, ref MatrixD.Identity, ref Vector3D.Zero, RenderId, out ProjectileEffect); + if (RenderId == uint.MaxValue) + ProjectileEffect.WorldMatrix = ProjectileMatrix; } - if (Definition.Visual.TrailTexture != null && !HeartData.I.IsPaused) - TrailFade.Add(new MyTuple(visualPosition, visualPosition + Direction * Definition.Visual.TrailLength), DateTime.Now.Ticks + (long)(TimeSpan.TicksPerSecond * Definition.Visual.TrailFadeTime)); - UpdateTrailFade(); + ProjectileEntity.WorldMatrix = ProjectileMatrix; + + if (Definition.Visual.HasTrail && !HeartData.I.IsPaused) + TrailFade.Add(new MyTuple(visualPosition, visualPosition + Direction * Definition.Visual.TrailLength), Definition.Visual.TrailFadeTime); + UpdateTrailFade(deltaDraw); } /// /// Updates trail fade for this projectile. /// - private void UpdateTrailFade() + private void UpdateTrailFade(float delta) { foreach (var positionTuple in TrailFade.Keys.ToList()) { - float percentage = (TrailFade[positionTuple] - DateTime.Now.Ticks) / (Definition.Visual.TrailFadeTime * TimeSpan.TicksPerSecond); - Vector4 fadedColor = Definition.Visual.TrailColor * percentage; + float lifetimePct = TrailFade[positionTuple] / Definition.Visual.TrailFadeTime; + Vector4 fadedColor = Definition.Visual.TrailColor * (Definition.Visual.TrailFadeTime == 0 ? 1 : lifetimePct); MySimpleObjectDraw.DrawLine(positionTuple.Item1, positionTuple.Item2, Definition.Visual.TrailTexture, ref fadedColor, Definition.Visual.TrailWidth); - if (TrailFade[positionTuple] <= DateTime.Now.Ticks) + + if (!HeartData.I.IsPaused) + TrailFade[positionTuple] -= delta; + if (TrailFade[positionTuple] <= 0) TrailFade.Remove(positionTuple); } } @@ -72,7 +92,6 @@ private void DrawImpactParticle(Vector3D ImpactPosition) 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) @@ -83,6 +102,7 @@ private void DrawImpactParticle(Vector3D ImpactPosition) internal void CloseDrawing() { ProjectileEffect?.Close(); + ProjectileEntity.Close(); } } } diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs index 179f349a..e74254d9 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileManager.cs @@ -21,8 +21,16 @@ public class ProjectileManager : MySessionComponentBase private Dictionary> ProjectileSyncStream = new Dictionary>(); public uint NextId { get; private set; } = 0; private List QueuedCloseProjectiles = new List(); - private float delta = 0; - private Stopwatch clock = Stopwatch.StartNew(); + /// + /// Delta for engine ticks; 60tps + /// + private float deltaTick = 0; + /// + /// Delta for frames; varies + /// + private float deltaDraw = 0; + private Stopwatch clockTick = Stopwatch.StartNew(); + private Stopwatch clockDraw = Stopwatch.StartNew(); public override void LoadData() { @@ -59,7 +67,7 @@ public override void UpdateAfterSimulation() if (HeartData.I.IsSuspended) return; // spawn projectiles at world origin for debugging. Don't actually do this to spawn projectiles, please. - if (j >= 25 && MyAPIGateway.Session.IsServer) + if (j >= 75 && MyAPIGateway.Session.IsServer) { j = 0; try @@ -89,12 +97,12 @@ public override void UpdateAfterSimulation() j++; // Delta time for tickrate-independent projectile movement - delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond; + deltaTick = clockTick.ElapsedTicks / (float)TimeSpan.TicksPerSecond; // Tick projectiles foreach (var projectile in ActiveProjectiles.Values) { - projectile.TickUpdate(delta); + projectile.TickUpdate(deltaTick); if (projectile.QueuedDispose) QueuedCloseProjectiles.Add(projectile); } @@ -146,7 +154,7 @@ public override void UpdateAfterSimulation() DamageHandler.Update(); - clock.Restart(); + clockTick.Restart(); } private void SyncPlayerProjectiles(IMyPlayer player) @@ -179,20 +187,21 @@ private void SyncPlayerProjectiles(IMyPlayer player) public override void UpdatingStopped() { - clock.Stop(); + clockTick.Stop(); } - public override void Draw() + public override void Draw() // Called once per frame to avoid jitter { - if (HeartData.I.IsSuspended) return; - - if (MyAPIGateway.Utilities.IsDedicated) // We don't want to needlessly use server CPU time + if (HeartData.I.IsSuspended || 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 + deltaTick = (float)clockTick.ElapsedTicks / TimeSpan.TicksPerSecond; // deltaTick is the current offset between tick and draw, to account for variance between FPS and tickrate + deltaDraw = (float)clockDraw.ElapsedTicks / TimeSpan.TicksPerSecond; // deltaDraw is a standard delta value based on FPS + foreach (var projectile in ActiveProjectiles.Values) - projectile.DrawUpdate(delta); + projectile.DrawUpdate(deltaTick, deltaDraw); + + clockDraw.Restart(); } public void UpdateProjectile(SerializableProjectile projectile) diff --git a/Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/SerializableProjectileDefinition.cs b/Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/SerializableProjectileDefinition.cs index f5cf032b..e61e95b9 100644 --- a/Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/SerializableProjectileDefinition.cs +++ b/Heart Module/Data/Scripts/HeartModule/Projectiles/StandardClasses/SerializableProjectileDefinition.cs @@ -71,17 +71,21 @@ public struct PhysicalProjectile } [ProtoContract] - public struct Visual + public class Visual { [ProtoMember(1)] public string Model; [ProtoMember(2)] public MyStringId TrailTexture; - [ProtoMember(7)] public float TrailLength; - [ProtoMember(9)] public float TrailWidth; - [ProtoMember(8)] public Vector4 TrailColor; - [ProtoMember(3)] public float TrailFadeTime; + [ProtoMember(7)] public float TrailLength = 0; + [ProtoMember(9)] public float TrailWidth = 0; + [ProtoMember(8)] public Vector4 TrailColor = Vector4.Zero; + [ProtoMember(3)] public float TrailFadeTime = 0; [ProtoMember(4)] public string AttachedParticle; [ProtoMember(5)] public string ImpactParticle; [ProtoMember(6)] public float VisibleChance; + public bool HasModel => !Model?.Equals("") ?? false; + public bool HasTrail => TrailTexture != null && TrailLength > 0 && TrailWidth > 0 && TrailColor != null && TrailColor != Vector4.Zero; + public bool HasAttachedParticle => !AttachedParticle?.Equals("") ?? false; + public bool HasImpactParticle => !ImpactParticle?.Equals("") ?? false; } [ProtoContract] diff --git a/Heart Module/Data/Scripts/HeartModule/Weapons/Setup/Adding/SorterWeaponLogic.cs b/Heart Module/Data/Scripts/HeartModule/Weapons/Setup/Adding/SorterWeaponLogic.cs index 58cbf010..a938a391 100644 --- a/Heart Module/Data/Scripts/HeartModule/Weapons/Setup/Adding/SorterWeaponLogic.cs +++ b/Heart Module/Data/Scripts/HeartModule/Weapons/Setup/Adding/SorterWeaponLogic.cs @@ -27,7 +27,6 @@ public class SorterWeaponLogic : MyGameLogicComponent public override void Init(MyObjectBuilder_EntityBase objectBuilder) { NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME; - ShootState.ValueChanged += OnShootStateChanged; // Attach the handler }