From cf8709f1ea07a7578c6f9b8c0949910bd3293685 Mon Sep 17 00:00:00 2001 From: Remuchi Date: Wed, 24 Jan 2024 12:58:57 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D0=BB=D0=B0=D0=B7=D0=B5=D1=80=D0=BE=D0=B2-=D1=81?= =?UTF-8?q?=D0=BD=D0=B0=D1=80=D1=8F=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.Client/Content.Client.csproj | 1 + Content.Client/IoC/ClientContentIoC.cs | 2 + Content.Client/White/Trail/Line/ITrailLine.cs | 15 + .../Trail/Line/Manager/ITrailLineHolder.cs | 6 + .../Trail/Line/Manager/ITrailLineManager.cs | 15 + .../Trail/Line/Manager/TrailSplineManager.cs | 64 ++++ .../White/Trail/Line/TrailSpline.cs | 183 +++++++++++ .../SplineRenderer/ITrailSplineRenderer.cs | 20 ++ .../SplineRenderer/TrailSplineRenderer.cs | 17 + .../TrailSplineRendererContinuous.cs | 90 +++++ .../TrailSplineRendererDebug.cs | 54 +++ .../TrailSplineRendererPoint.cs | 58 ++++ Content.Client/White/Trail/TrailComponent.cs | 56 ++++ Content.Client/White/Trail/TrailOverlay.cs | 69 ++++ Content.Client/White/Trail/TrailSystem.cs | 76 +++++ .../Ranged/Systems/GunSystem.Battery.cs | 123 +++---- Content.Server/White/Trail/TrailComponent.cs | 51 +++ Content.Server/White/Trail/TrailSystem.cs | 21 ++ .../TwoModeEnergyAmmoProviderComponent.cs | 30 +- .../Ranged/Systems/SharedGunSystem.Battery.cs | 47 ++- .../Spline/CatmullRom/SplineCatmullRom.cs | 85 +++++ .../Spline/CatmullRom/SplineCatmullRom2D.cs | 38 +++ .../Spline/CubicBezier/SplineCubicBezier.cs | 81 +++++ .../Spline/CubicBezier/SplineCubicBezier4D.cs | 37 +++ .../CubicBezier/SplineCubicBezierColor.cs | 37 +++ Content.Shared/White/Spline/ISpline.cs | 14 + .../White/Spline/Linear/SplineLinear.cs | 34 ++ .../White/Spline/Linear/SplineLinear2D.cs | 31 ++ .../White/Spline/Linear/SplineLinear4D.cs | 30 ++ .../White/Spline/Linear/SplineLinearColor.cs | 30 ++ Content.Shared/White/Spline/Spline.cs | 44 +++ Content.Shared/White/Spline/SplineEnums.cs | 59 ++++ .../White/Trail/SharedTrailComponent.cs | 74 +++++ Content.Shared/White/Trail/TrailSettings.cs | 97 ++++++ .../Weapons/Guns/Battery/battery_guns.yml | 67 ++-- .../Weapons/Guns/Projectiles/projectiles.yml | 308 +++++++++++++++++- 36 files changed, 1920 insertions(+), 144 deletions(-) create mode 100644 Content.Client/White/Trail/Line/ITrailLine.cs create mode 100644 Content.Client/White/Trail/Line/Manager/ITrailLineHolder.cs create mode 100644 Content.Client/White/Trail/Line/Manager/ITrailLineManager.cs create mode 100644 Content.Client/White/Trail/Line/Manager/TrailSplineManager.cs create mode 100644 Content.Client/White/Trail/Line/TrailSpline.cs create mode 100644 Content.Client/White/Trail/SplineRenderer/ITrailSplineRenderer.cs create mode 100644 Content.Client/White/Trail/SplineRenderer/TrailSplineRenderer.cs create mode 100644 Content.Client/White/Trail/SplineRenderer/TrailSplineRendererContinuous.cs create mode 100644 Content.Client/White/Trail/SplineRenderer/TrailSplineRendererDebug.cs create mode 100644 Content.Client/White/Trail/SplineRenderer/TrailSplineRendererPoint.cs create mode 100644 Content.Client/White/Trail/TrailComponent.cs create mode 100644 Content.Client/White/Trail/TrailOverlay.cs create mode 100644 Content.Client/White/Trail/TrailSystem.cs create mode 100644 Content.Server/White/Trail/TrailComponent.cs create mode 100644 Content.Server/White/Trail/TrailSystem.cs create mode 100644 Content.Shared/White/Spline/CatmullRom/SplineCatmullRom.cs create mode 100644 Content.Shared/White/Spline/CatmullRom/SplineCatmullRom2D.cs create mode 100644 Content.Shared/White/Spline/CubicBezier/SplineCubicBezier.cs create mode 100644 Content.Shared/White/Spline/CubicBezier/SplineCubicBezier4D.cs create mode 100644 Content.Shared/White/Spline/CubicBezier/SplineCubicBezierColor.cs create mode 100644 Content.Shared/White/Spline/ISpline.cs create mode 100644 Content.Shared/White/Spline/Linear/SplineLinear.cs create mode 100644 Content.Shared/White/Spline/Linear/SplineLinear2D.cs create mode 100644 Content.Shared/White/Spline/Linear/SplineLinear4D.cs create mode 100644 Content.Shared/White/Spline/Linear/SplineLinearColor.cs create mode 100644 Content.Shared/White/Spline/Spline.cs create mode 100644 Content.Shared/White/Spline/SplineEnums.cs create mode 100644 Content.Shared/White/Trail/SharedTrailComponent.cs create mode 100644 Content.Shared/White/Trail/TrailSettings.cs diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 33ee0e0a34..8c9a66894a 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -25,6 +25,7 @@ + diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 6d1fcb28e9..687e856481 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -21,6 +21,7 @@ using Content.Client.White.JoinQueue; using Content.Client.White.Jukebox; using Content.Client.White.Sponsors; using Content.Client.White.Stalin; +using Content.Client.White.Trail.Line.Manager; using Content.Client.White.TTS; using Content.Shared.Administration.Managers; @@ -57,6 +58,7 @@ namespace Content.Client.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); //WD-EDIT } } diff --git a/Content.Client/White/Trail/Line/ITrailLine.cs b/Content.Client/White/Trail/Line/ITrailLine.cs new file mode 100644 index 0000000000..91b73d8b3a --- /dev/null +++ b/Content.Client/White/Trail/Line/ITrailLine.cs @@ -0,0 +1,15 @@ +using System.Numerics; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using Robust.Shared.Map; + +namespace Content.Client.White.Trail.Line; + +public interface ITrailLine +{ + ITrailSettings Settings { get; } + + void TryCreateSegment((Vector2 WorldPosition, Angle WorldRotation) worldPosRot, MapId mapId); + + void Render(DrawingHandleWorld handle, Texture? texture); +} diff --git a/Content.Client/White/Trail/Line/Manager/ITrailLineHolder.cs b/Content.Client/White/Trail/Line/Manager/ITrailLineHolder.cs new file mode 100644 index 0000000000..4bb9d6c430 --- /dev/null +++ b/Content.Client/White/Trail/Line/Manager/ITrailLineHolder.cs @@ -0,0 +1,6 @@ +namespace Content.Client.White.Trail.Line.Manager; + +public interface ITrailLineHolder +{ + public ITrailLine? TrailLine { get; set; } +} diff --git a/Content.Client/White/Trail/Line/Manager/ITrailLineManager.cs b/Content.Client/White/Trail/Line/Manager/ITrailLineManager.cs new file mode 100644 index 0000000000..663a7b4bd3 --- /dev/null +++ b/Content.Client/White/Trail/Line/Manager/ITrailLineManager.cs @@ -0,0 +1,15 @@ +using Content.Shared.White.Trail; +using Robust.Shared.Map; + +namespace Content.Client.White.Trail.Line.Manager; + +public interface ITrailLineManager +{ + IEnumerable Lines { get; } + + ITrailLine CreateTrail(ITrailSettings settings, MapId mapId); + + void Detach(ITrailLineHolder holder); + + void Update(float dt); +} diff --git a/Content.Client/White/Trail/Line/Manager/TrailSplineManager.cs b/Content.Client/White/Trail/Line/Manager/TrailSplineManager.cs new file mode 100644 index 0000000000..f55a5a7660 --- /dev/null +++ b/Content.Client/White/Trail/Line/Manager/TrailSplineManager.cs @@ -0,0 +1,64 @@ +using Content.Client.White.Trail.SplineRenderer; +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; +using Robust.Shared.Map; + +namespace Content.Client.White.Trail.Line.Manager; + +public sealed class TrailSplineManager : ITrailLineManager +{ + private readonly LinkedList _lines = new(); + + public IEnumerable Lines => _lines; + + public ITrailLine CreateTrail(ITrailSettings settings, MapId mapId) + { + var tline = new TrailSpline + { + Attached = true, + Settings = settings, + MapId = mapId, + SplineIterator = Spline.From2DType(settings.SplineIteratorType), + GradientIterator = Spline.From4DType(settings.GradientIteratorType), + Renderer = TrailSplineRenderer.FromType(settings.SplineRendererType) + }; + + _lines.AddLast(tline); + return tline; + } + + public void Detach(ITrailLineHolder holder) + { + if (holder.TrailLine is TrailSpline trailSpline) + { + trailSpline.Attached = false; + var detachedSettings = new TrailSettings(); + TrailSettings.Inject(detachedSettings, trailSpline.Settings); + trailSpline.Settings = detachedSettings; + } + } + + public void Update(float dt) + { + var curNode = _lines.First; + while (curNode != null) + { + var curLine = curNode.Value; + curNode = curNode.Next; + + if (!curLine.HasSegments()) + { + if (curLine.Attached) + curLine.ResetLifetime(); + else + _lines.Remove(curLine); + + continue; + } + + curLine.AddLifetime(dt); + curLine.RemoveExpiredSegments(); + curLine.UpdateSegments(dt); + } + } +} diff --git a/Content.Client/White/Trail/Line/TrailSpline.cs b/Content.Client/White/Trail/Line/TrailSpline.cs new file mode 100644 index 0000000000..a20e50d294 --- /dev/null +++ b/Content.Client/White/Trail/Line/TrailSpline.cs @@ -0,0 +1,183 @@ +using Content.Client.White.Trail.SplineRenderer; +using Content.Shared.White.Spline; +using Content.Shared.White.Spline.Linear; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using Robust.Shared.Map; +using Robust.Shared.Random; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Client.White.Trail.Line; + +public sealed class TrailSpline : ITrailLine +{ + private static readonly IRobustRandom Random = IoCManager.Resolve(); + + [ViewVariables] + private readonly LinkedList _segments = new(); + + [ViewVariables] + private Vector2 _lastCreationPos; + + [ViewVariables] + private float _curLifetime; + + [ViewVariables] + private Vector2? _virtualSegmentPos; + + [ViewVariables] + public MapId MapId { get; set; } + + [ViewVariables] + public bool Attached { get; set; } + + [ViewVariables] + public ITrailSettings Settings { get; set; } = TrailSettings.Default; + + [ViewVariables] + public ISpline SplineIterator { get; set; } = new SplineLinear2D(); + + [ViewVariables] + public ISpline GradientIterator { get; set; } = new SplineLinear4D(); + + [ViewVariables] + public ITrailSplineRenderer Renderer { get; set; } = new TrailSplineRendererDebug(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasSegments() + { + return _segments.Count > 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddLifetime(float time) + { + _curLifetime += time; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResetLifetime() + { + _curLifetime = 0f; + } + + public void TryCreateSegment((Vector2 WorldPosition, Angle WorldRotation) worldPosRot, MapId mapId) + { + if (!Attached) + return; + + if (mapId != MapId) + return; + + if (worldPosRot.WorldPosition == Vector2.Zero) + return; + + var pos = worldPosRot.WorldPosition + worldPosRot.WorldRotation.RotateVec(Settings.CreationOffset); + + _lastCreationPos = pos; + + if (_virtualSegmentPos.HasValue) + { + var vPos = _virtualSegmentPos.Value; + if ((vPos - pos).LengthSquared() > Settings.СreationDistanceThresholdSquared) + { + _segments.AddLast(new TrailSplineSegment() + { Position = vPos, ExistTil = _curLifetime + Settings.Lifetime }); + + _virtualSegmentPos = null; + } + + return; + } + + var lastPos = _segments.Last?.Value.Position; + if (!lastPos.HasValue || (lastPos.Value - pos).LengthSquared() > Settings.СreationDistanceThresholdSquared) + _virtualSegmentPos = pos; + } + + public void UpdateSegments(float dt) + { + var gravity = Settings.Gravity; + var maxRandomWalk = Settings.MaxRandomWalk; + var lifetime = Settings.Lifetime; + + if (_segments.Last != null) + { + var i = 0; + var positions = new Vector2[_segments.Count + 1]; + positions[_segments.Count] = _lastCreationPos; + + var curNode = _segments.First; + while (curNode != null) + { + var offset = gravity; + var curValue = curNode.Value; + if (maxRandomWalk != Vector2.Zero) + { + positions[i] = curValue.Position; + if (curNode.Next != null) + { + positions[i + 1] = curNode.Next.Value.Position; + if (curNode.Next.Next != null) + positions[i + 2] = curNode.Next.Next.Value.Position; + } + + var effectiveRandomWalk = maxRandomWalk * (curValue.ExistTil - _curLifetime) / lifetime; + var gradientNorm = -SplineIterator.SampleVelocity(positions, i).Normalized(); + offset += gradientNorm * effectiveRandomWalk.Y * Random.NextFloat(-1.0f, 1.0f); + var rotated90Degrees = new Vector2(-gradientNorm.Y, gradientNorm.X); + offset += rotated90Degrees * effectiveRandomWalk.X * Random.NextFloat(-1.0f, 1.0f); + } + + curValue.Position += offset; + i++; + curNode = curNode.Next; + } + } + + if (_virtualSegmentPos.HasValue) + _virtualSegmentPos = _virtualSegmentPos.Value + gravity; + + if (!Attached) + _lastCreationPos += gravity; + } + + public void RemoveExpiredSegments() + { + while (_segments.First?.Value.ExistTil < _curLifetime) + { + _segments.RemoveFirst(); + } + } + + public void Render(DrawingHandleWorld handle, Texture? texture) + { + if (_segments.Last == null) + return; + + var arrSize = _segments.Count + 1; + var paPositions = new Vector2[arrSize]; + var paLifetimes = new float[arrSize]; + paPositions[0] = _lastCreationPos; + paLifetimes[0] = 1f; + + var reversedIndexedSegments = _segments.Reverse().Select((x, i) => (x, i + 1)); + foreach (var (x, i) in reversedIndexedSegments) + { + paPositions[i] = x.Position; + paLifetimes[i] = (x.ExistTil - _curLifetime) / Settings.Lifetime; + } + + Renderer.Render(handle, texture, SplineIterator, GradientIterator, Settings, paPositions, paLifetimes); + } + + private sealed class TrailSplineSegment + { + public Vector2 Position { get; set; } + + public float ExistTil { get; init; } + } +} diff --git a/Content.Client/White/Trail/SplineRenderer/ITrailSplineRenderer.cs b/Content.Client/White/Trail/SplineRenderer/ITrailSplineRenderer.cs new file mode 100644 index 0000000000..460a3b43db --- /dev/null +++ b/Content.Client/White/Trail/SplineRenderer/ITrailSplineRenderer.cs @@ -0,0 +1,20 @@ +using System.Numerics; +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Client.White.Trail.SplineRenderer; + +public interface ITrailSplineRenderer +{ + void Render( + DrawingHandleWorld handle, + Texture? texture, + ISpline splineIterator, + ISpline gradientIterator, + ITrailSettings settings, + Vector2[] paPositions, + float[] paLifetimes + ); +} diff --git a/Content.Client/White/Trail/SplineRenderer/TrailSplineRenderer.cs b/Content.Client/White/Trail/SplineRenderer/TrailSplineRenderer.cs new file mode 100644 index 0000000000..53b0a6e3b0 --- /dev/null +++ b/Content.Client/White/Trail/SplineRenderer/TrailSplineRenderer.cs @@ -0,0 +1,17 @@ +using Content.Shared.White.Trail; + +namespace Content.Client.White.Trail.SplineRenderer; + +public static class TrailSplineRenderer +{ + public static ITrailSplineRenderer FromType(TrailSplineRendererType type) + { + return type switch + { + TrailSplineRendererType.Continuous => new TrailSplineRendererContinuous(), + TrailSplineRendererType.Point => new TrailSplineRendererPoint(), + TrailSplineRendererType.Debug => new TrailSplineRendererDebug(), + _ => throw new NotImplementedException() + }; + } +} diff --git a/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererContinuous.cs b/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererContinuous.cs new file mode 100644 index 0000000000..a7c74211bc --- /dev/null +++ b/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererContinuous.cs @@ -0,0 +1,90 @@ +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using System.Linq; +using System.Numerics; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Client.White.Trail.SplineRenderer; + +public sealed class TrailSplineRendererContinuous : ITrailSplineRenderer +{ + public void Render( + DrawingHandleWorld handle, + Texture? texture, + ISpline splineIterator, + ISpline gradientIterator, + ITrailSettings settings, + Vector2[] paPositions, + float[] paLifetimes + ) + { + float[] splinePointParams; + if (settings.LengthStep == 0f) + { + splinePointParams = Enumerable.Range(0, paPositions.Length - 1).Select(x => (float) x).ToArray(); + } + else + { + splinePointParams = splineIterator + .IteratePointParamsByLength(paPositions, Math.Max(settings.LengthStep, 0.1f)).ToArray(); + } + + var gradientControlGroups = gradientIterator.GetControlGroupAmount(settings.Gradient.Length); + var colorToPointMul = 0f; + if (gradientControlGroups > 0) + colorToPointMul = gradientControlGroups / splineIterator.GetControlGroupAmount(paPositions.Length); + + (Vector2, Vector2)? prevPoints = null; + foreach (var u in splinePointParams) + { + var (position, velocity) = splineIterator.SamplePositionVelocity(paPositions, u); + + var offset = new Vector2(-velocity.Y, velocity.X).Normalized() * + settings.Scale.X; // 90-degree anticlockwise rotation + + var curPoints = (position - offset, position + offset); + + if (prevPoints.HasValue) + { + var colorVec = Vector4.One; + if (settings.Gradient != null && settings.Gradient.Length > 0) + { + if (gradientControlGroups > 0) + colorVec = gradientIterator.SamplePosition(settings.Gradient, u * colorToPointMul); + else + colorVec = settings.Gradient[0]; + } + + if (texture != null) + { + var verts = new DrawVertexUV2D[] + { + new(curPoints.Item1, Vector2.Zero), + new(curPoints.Item2, Vector2.UnitY), + new(prevPoints.Value.Item2, Vector2.One), + new(prevPoints.Value.Item1, Vector2.UnitX), + }; + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, texture, verts, + new Color(colorVec.X, colorVec.Y, colorVec.Z, colorVec.W)); + } + else + { + var verts = new[] + { + curPoints.Item1, + curPoints.Item2, + prevPoints.Value.Item2, + prevPoints.Value.Item1, + }; + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, + new Color(colorVec.X, colorVec.Y, colorVec.Z, colorVec.W)); + } + } + + prevPoints = curPoints; + } + } +} diff --git a/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererDebug.cs b/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererDebug.cs new file mode 100644 index 0000000000..503b3e5d12 --- /dev/null +++ b/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererDebug.cs @@ -0,0 +1,54 @@ +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using System.Linq; +using System.Numerics; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Client.White.Trail.SplineRenderer; + +public sealed class TrailSplineRendererDebug : ITrailSplineRenderer +{ + public void Render( + DrawingHandleWorld handle, + Texture? texture, + ISpline splineIterator, + ISpline gradientIterator, + ITrailSettings settings, + Vector2[] paPositions, + float[] paLifetimes + ) + { + float[] splinePointParams; + if (settings.LengthStep == 0f) + { + splinePointParams = Enumerable.Range(0, paPositions.Length - 1).Select(x => (float) x).ToArray(); + } + else + { + splinePointParams = splineIterator + .IteratePointParamsByLength(paPositions, Math.Max(settings.LengthStep, 0.1f)).ToArray(); + } + + Vector2? prevPosControlPoint = null; + foreach (var item in paPositions) + { + if (prevPosControlPoint.HasValue) + handle.DrawLine(item, prevPosControlPoint.Value, Color.Blue); + + prevPosControlPoint = item; + } + + Vector2? prevPosSplinePoint = null; + foreach (var u in splinePointParams) + { + var (position, velocity) = splineIterator.SamplePositionVelocity(paPositions, u); + if (prevPosSplinePoint.HasValue) + handle.DrawLine(position, prevPosSplinePoint.Value, Color.Red); + + handle.DrawLine(position, position + velocity, Color.White); + handle.DrawCircle(position, 0.03f, new Color(0, 255, 0)); + prevPosSplinePoint = position; + } + } +} diff --git a/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererPoint.cs b/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererPoint.cs new file mode 100644 index 0000000000..3d84c42239 --- /dev/null +++ b/Content.Client/White/Trail/SplineRenderer/TrailSplineRendererPoint.cs @@ -0,0 +1,58 @@ +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using System.Linq; +using System.Numerics; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Client.White.Trail.SplineRenderer; + +public sealed class TrailSplineRendererPoint : ITrailSplineRenderer +{ + public void Render( + DrawingHandleWorld handle, + Texture? texture, + ISpline splineIterator, + ISpline gradientIterator, + ITrailSettings settings, + Vector2[] paPositions, + float[] paLifetimes + ) + { + if (texture == null) + return; + + float[] splinePointParams; + if (settings.LengthStep == 0f) + { + splinePointParams = Enumerable.Range(0, paPositions.Length - 1).Select(x => (float) x).ToArray(); + } + else + { + splinePointParams = splineIterator + .IteratePointParamsByLength(paPositions, Math.Max(settings.LengthStep, 0.1f)).ToArray(); + } + + var gradientControlGroups = gradientIterator.GetControlGroupAmount(settings.Gradient.Length); + var colorToPointMul = 0f; + if (gradientControlGroups > 0) + colorToPointMul = gradientControlGroups / gradientIterator.GetControlGroupAmount(paPositions.Length); + + foreach (var u in splinePointParams) + { + var (position, velocity) = splineIterator.SamplePositionVelocity(paPositions, u); + + var colorVec = Vector4.One; + if (settings.Gradient != null && settings.Gradient.Length > 0) + { + colorVec = gradientControlGroups > 0 + ? gradientIterator.SamplePosition(settings.Gradient, u * colorToPointMul) + : settings.Gradient[0]; + } + + var quad = Box2.FromDimensions(position, texture.Size * settings.Scale / EyeManager.PixelsPerMeter); + handle.DrawTextureRect(texture, new Box2Rotated(quad, velocity.ToAngle(), quad.Center), + new Color(colorVec.X, colorVec.Y, colorVec.Z, colorVec.W)); + } + } +} diff --git a/Content.Client/White/Trail/TrailComponent.cs b/Content.Client/White/Trail/TrailComponent.cs new file mode 100644 index 0000000000..31ae97cbcc --- /dev/null +++ b/Content.Client/White/Trail/TrailComponent.cs @@ -0,0 +1,56 @@ +using Content.Client.White.Trail.Line; +using Content.Client.White.Trail.Line.Manager; +using Content.Client.White.Trail.SplineRenderer; +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; + +namespace Content.Client.White.Trail; + +[RegisterComponent] +public sealed partial class TrailComponent : SharedTrailComponent, ITrailLineHolder +{ + [ViewVariables] + public ITrailLine? TrailLine { get; set; } + + public override Spline2DType SplineIteratorType + { + get => base.SplineIteratorType; + set + { + if (base.SplineIteratorType == value) + return; + + base.SplineIteratorType = value; + if (TrailLine is TrailSpline trailSpline) + trailSpline.SplineIterator = Spline.From2DType(value); + } + } + + public override Spline4DType GradientIteratorType + { + get => base.GradientIteratorType; + set + { + if (base.GradientIteratorType == value) + return; + + base.GradientIteratorType = value; + if (TrailLine is TrailSpline trailSpline) + trailSpline.GradientIterator = Spline.From4DType(value); + } + } + + public override TrailSplineRendererType SplineRendererType + { + get => base.SplineRendererType; + set + { + if (base.SplineRendererType == value) + return; + + base.SplineRendererType = value; + if (TrailLine is TrailSpline trailSpline) + trailSpline.Renderer = TrailSplineRenderer.FromType(value); + } + } +} diff --git a/Content.Client/White/Trail/TrailOverlay.cs b/Content.Client/White/Trail/TrailOverlay.cs new file mode 100644 index 0000000000..07b1380d74 --- /dev/null +++ b/Content.Client/White/Trail/TrailOverlay.cs @@ -0,0 +1,69 @@ +using Content.Client.White.Trail.Line.Manager; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.White.Trail; + +public sealed class TrailOverlay : Overlay +{ + private readonly IPrototypeManager _protoManager; + private readonly IResourceCache _cache; + private readonly ITrailLineManager _lineManager; + + private readonly Dictionary _shaderDict; + private readonly Dictionary _textureDict; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; + + public TrailOverlay( + IPrototypeManager protoManager, + IResourceCache cache, + ITrailLineManager lineManager + ) + { + _protoManager = protoManager; + _cache = cache; + _lineManager = lineManager; + + _shaderDict = new Dictionary(); + _textureDict = new Dictionary(); + + ZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; + } + + protected override void Draw(in OverlayDrawArgs args) + { + var handle = args.WorldHandle; + foreach (var item in _lineManager.Lines) + { + item.Render(handle, GetCachedTexture(item.Settings.TexurePath ?? "")); + } + } + + //влепить на ети два метода мемори кеш со слайдинг експирейшоном вместо дикта если проблемы будут + private ShaderInstance? GetCachedShader(string id) + { + if (_shaderDict.TryGetValue(id, out var shader)) + return shader; + + if (_protoManager.TryIndex(id, out var shaderRes)) + shader = shaderRes?.InstanceUnique(); + + _shaderDict.Add(id, shader); + return shader; + } + + private Texture? GetCachedTexture(string path) + { + if (_textureDict.TryGetValue(path, out var texture)) + return texture; + + if (_cache.TryGetResource(path, out var texRes)) + texture = texRes; + + _textureDict.Add(path, texture); + return texture; + } +} diff --git a/Content.Client/White/Trail/TrailSystem.cs b/Content.Client/White/Trail/TrailSystem.cs new file mode 100644 index 0000000000..6ad6d3b07a --- /dev/null +++ b/Content.Client/White/Trail/TrailSystem.cs @@ -0,0 +1,76 @@ +using Content.Client.White.Trail.Line.Manager; +using Content.Shared.White.Trail; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.White.Trail; + +public sealed class TrailSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ITrailLineManager _lineManager = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + IoCManager.Resolve().AddOverlay( + new TrailOverlay( + IoCManager.Resolve(), + IoCManager.Resolve(), + _lineManager + )); + + SubscribeLocalEvent(OnTrailMove); + SubscribeLocalEvent(OnTrailRemove); + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(EntityUid uid, TrailComponent component, ref ComponentHandleState args) + { + if (args.Current is not TrailComponentState state) + return; + + TrailSettings.Inject(component, state.Settings); + } + + private void OnTrailRemove(EntityUid uid, TrailComponent comp, ComponentRemove args) + { + _lineManager.Detach(comp); + } + + private void OnTrailMove(EntityUid uid, TrailComponent comp, ref MoveEvent args) + { + if (comp.СreationMethod != SegmentCreationMethod.OnMove || _gameTiming.InPrediction) + return; + + TryCreateSegment(comp, args.Component); + } + + private void TryCreateSegment(TrailComponent comp, TransformComponent xform) + { + if (xform.MapID == MapId.Nullspace) + return; + + comp.TrailLine ??= _lineManager.CreateTrail(comp, xform.MapID); + comp.TrailLine.TryCreateSegment(_transformSystem.GetWorldPositionRotation(xform), xform.MapID); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + _lineManager.Update(frameTime); + + foreach (var (comp, xform) in EntityQuery()) + { + if (comp.СreationMethod == SegmentCreationMethod.OnFrameUpdate) + TryCreateSegment(comp, xform); + } + } +} diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs index 35256ad976..5991299db0 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs @@ -1,13 +1,10 @@ using Content.Server.Power.Components; using Content.Shared.Damage; using Content.Shared.Damage.Events; -using Content.Shared.FixedPoint; using Content.Shared.Interaction.Events; -using Content.Shared.Projectiles; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Robust.Server.Audio; -using Robust.Shared.Prototypes; namespace Content.Server.Weapons.Ranged.Systems; @@ -41,28 +38,30 @@ public sealed partial class GunSystem if (!TryComp(uid, out var gun)) return; - if (component.CurrentMode == EnergyModes.Stun) + switch (component.CurrentMode) { - component.InStun = false; - gun.SoundGunshot = component.HitscanSound; - component.CurrentMode = EnergyModes.Laser; - component.FireCost = component.HitscanFireCost; - _audio.PlayPvs(component.ToggleSound, args.User); - } - else if (component.CurrentMode == EnergyModes.Laser) - { - component.InStun = true; - gun.SoundGunshot = component.ProjSound; - component.CurrentMode = EnergyModes.Stun; - component.FireCost = component.ProjFireCost; - _audio.PlayPvs(component.ToggleSound, args.User); + case EnergyModes.Stun: + component.InStun = false; + component.CurrentMode = EnergyModes.Laser; + component.FireCost = component.LaserFireCost; + gun.SoundGunshot = component.LaserSound; + gun.ProjectileSpeed = component.LaserProjectileSpeed; + _audio.PlayPvs(component.ToggleSound, args.User); + break; + case EnergyModes.Laser: + component.InStun = true; + component.CurrentMode = EnergyModes.Stun; + component.FireCost = component.StunFireCost; + gun.SoundGunshot = component.StunSound; + gun.ProjectileSpeed = component.StunProjectileSpeed; + _audio.PlayPvs(component.ToggleSound, args.User); + break; } + UpdateShots(uid, component); UpdateTwoModeAppearance(uid, component); UpdateBatteryAppearance(uid, component); UpdateAmmoCount(uid); - Dirty(gun); - Dirty(component); } private void OnBatteryStartup(EntityUid uid, BatteryAmmoProviderComponent component, ComponentStartup args) @@ -70,7 +69,10 @@ public sealed partial class GunSystem UpdateShots(uid, component); } - private void OnBatteryChargeChange(EntityUid uid, BatteryAmmoProviderComponent component, ref ChargeChangedEvent args) + private void OnBatteryChargeChange( + EntityUid uid, + BatteryAmmoProviderComponent component, + ref ChargeChangedEvent args) { UpdateShots(uid, component, args.Charge, args.MaxCharge); } @@ -90,7 +92,7 @@ public sealed partial class GunSystem if (component.Shots != shots || component.Capacity != maxShots) { - Dirty(component); + Dirty(uid, component); } component.Shots = shots; @@ -98,78 +100,41 @@ public sealed partial class GunSystem UpdateBatteryAppearance(uid, component); } - private void OnBatteryDamageExamine(EntityUid uid, BatteryAmmoProviderComponent component, ref DamageExamineEvent args) + private void OnBatteryDamageExamine( + EntityUid uid, + BatteryAmmoProviderComponent component, + ref DamageExamineEvent args) { var damageSpec = GetDamage(component); if (damageSpec == null) return; - string? damageType; - switch (component) + var damageType = component switch { - case HitscanBatteryAmmoProviderComponent: - damageType = Loc.GetString("damage-hitscan"); - break; - case ProjectileBatteryAmmoProviderComponent: - damageType = Loc.GetString("damage-projectile"); - break; - case TwoModeEnergyAmmoProviderComponent twoMode: - if (twoMode.CurrentMode == EnergyModes.Stun) - damageType = Loc.GetString("damage-projectile"); - else - damageType = Loc.GetString("damage-hitscan"); - break; - default: - throw new ArgumentOutOfRangeException(); - } + HitscanBatteryAmmoProviderComponent => Loc.GetString("damage-hitscan"), + ProjectileBatteryAmmoProviderComponent => Loc.GetString("damage-projectile"), + TwoModeEnergyAmmoProviderComponent twoMode => Loc.GetString(twoMode.CurrentMode == EnergyModes.Stun + ? "damage-projectile" + : "damage-hitscan"), + _ => throw new ArgumentOutOfRangeException() + }; _damageExamine.AddDamageExamine(args.Message, damageSpec, damageType); } private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component) { - if (component is ProjectileBatteryAmmoProviderComponent battery) + return component switch { - if (ProtoManager.Index(battery.Prototype).Components - .TryGetValue(_factory.GetComponentName(typeof(ProjectileComponent)), out var projectile)) - { - var p = (ProjectileComponent) projectile.Component; - - if (!p.Damage.Empty) - { - return p.Damage; - } - } - - return null; - } - - if (component is HitscanBatteryAmmoProviderComponent hitscan) - { - return ProtoManager.Index(hitscan.Prototype).Damage; - } - - if (component is TwoModeEnergyAmmoProviderComponent twoMode) - { - if (twoMode.CurrentMode == EnergyModes.Stun) - { - if (ProtoManager.Index(twoMode.ProjectilePrototype).Components - .TryGetValue(_factory.GetComponentName(typeof(ProjectileComponent)), out var projectile)) - { - var p = (ProjectileComponent) projectile.Component; - - if (p.Damage.Total > FixedPoint2.Zero) - { - return p.Damage; - } - } - - return null; - } - return ProtoManager.Index(twoMode.HitscanPrototype).Damage; - } - return null; + HitscanBatteryAmmoProviderComponent hitscan => + ProtoManager.Index(hitscan.Prototype).Damage, + ProjectileBatteryAmmoProviderComponent battery => GetProjectileDamage(battery.Prototype), + TwoModeEnergyAmmoProviderComponent twoMode => GetProjectileDamage(twoMode.CurrentMode == EnergyModes.Laser + ? twoMode.LaserPrototype + : twoMode.StunPrototype), + _ => null + }; } protected override void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) diff --git a/Content.Server/White/Trail/TrailComponent.cs b/Content.Server/White/Trail/TrailComponent.cs new file mode 100644 index 0000000000..02dfbc2914 --- /dev/null +++ b/Content.Server/White/Trail/TrailComponent.cs @@ -0,0 +1,51 @@ +using System.Numerics; +using Content.Shared.White.Spline; +using Content.Shared.White.Trail; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Server.White.Trail; + +[RegisterComponent] +public sealed partial class TrailComponent : SharedTrailComponent +{ + public TrailComponent() + { + var defaultTrail = TrailSettings.Default; + Scale = defaultTrail.Scale; + СreationDistanceThresholdSquared = defaultTrail.СreationDistanceThresholdSquared; + СreationMethod = defaultTrail.СreationMethod; + CreationOffset = defaultTrail.CreationOffset; + Gravity = defaultTrail.Gravity; + MaxRandomWalk = defaultTrail.MaxRandomWalk; + Lifetime = defaultTrail.Lifetime; + TexurePath = defaultTrail.TexurePath; + Gradient = defaultTrail.Gradient; + GradientIteratorType = defaultTrail.GradientIteratorType; + } + + public override Vector2 Gravity { get; set; } + + public override float Lifetime { get; set; } + + public override Vector2 MaxRandomWalk { get; set; } + + public override Vector2 Scale { get; set; } + + public override string? TexurePath { get; set; } + + public override Vector2 CreationOffset { get; set; } + + public override float СreationDistanceThresholdSquared { get; set; } + + public override SegmentCreationMethod СreationMethod { get; set; } + + public override Vector4[] Gradient { get; set; } + + public override float LengthStep { get; set; } + + public override Spline2DType SplineIteratorType { get; set; } + + public override TrailSplineRendererType SplineRendererType { get; set; } + + public override Spline4DType GradientIteratorType { get; set; } +} diff --git a/Content.Server/White/Trail/TrailSystem.cs b/Content.Server/White/Trail/TrailSystem.cs new file mode 100644 index 0000000000..d6b6d04204 --- /dev/null +++ b/Content.Server/White/Trail/TrailSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.White.Trail; +using Robust.Shared.GameStates; + +namespace Content.Server.White.Trail; + +public sealed class TrailSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + } + + private void OnGetState(EntityUid uid, TrailComponent component, ref ComponentGetState args) + { + var settings = new TrailSettings(); + TrailSettings.Inject(settings, component); + args.State = new TrailComponentState(settings); + } +} diff --git a/Content.Shared/Weapons/Ranged/Components/TwoModeEnergyAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/TwoModeEnergyAmmoProviderComponent.cs index 05756e2134..bc03fc4aba 100644 --- a/Content.Shared/Weapons/Ranged/Components/TwoModeEnergyAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/TwoModeEnergyAmmoProviderComponent.cs @@ -9,27 +9,33 @@ namespace Content.Shared.Weapons.Ranged.Components; public sealed partial class TwoModeEnergyAmmoProviderComponent : BatteryAmmoProviderComponent { [ViewVariables(VVAccess.ReadOnly), - DataField("projProto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ProjectilePrototype = default!; + DataField("stunPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string StunPrototype = default!; [ViewVariables(VVAccess.ReadOnly), - DataField("hitscanProto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string HitscanPrototype = default!; + DataField("laserPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string LaserPrototype = default!; - [ViewVariables(VVAccess.ReadOnly), DataField("projFireCost")] - public float ProjFireCost = 50; + [ViewVariables(VVAccess.ReadOnly), DataField("stunFireCost")] + public float StunFireCost = 142; - [ViewVariables(VVAccess.ReadOnly), DataField("hitscanFireCost")] - public float HitscanFireCost = 100; + [ViewVariables(VVAccess.ReadOnly), DataField("laserFireCost")] + public float LaserFireCost = 65; + + [ViewVariables(VVAccess.ReadOnly), DataField("stunProjectileSpeed")] + public float StunProjectileSpeed = 12; + + [ViewVariables(VVAccess.ReadOnly), DataField("laserProjectileSpeed")] + public float LaserProjectileSpeed = 25; [ViewVariables(VVAccess.ReadOnly), DataField("currentMode")] public EnergyModes CurrentMode { get; set; } = EnergyModes.Stun; - [ViewVariables(VVAccess.ReadOnly), DataField("projSound")] - public SoundSpecifier? ProjSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/taser2.ogg"); + [ViewVariables(VVAccess.ReadOnly), DataField("stunSound")] + public SoundSpecifier? StunSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/taser2.ogg"); - [ViewVariables(VVAccess.ReadOnly), DataField("hitscanSound")] - public SoundSpecifier? HitscanSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/laser_cannon.ogg"); + [ViewVariables(VVAccess.ReadOnly), DataField("laserSound")] + public SoundSpecifier? LaserSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/laser_cannon.ogg"); public SoundSpecifier? ToggleSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Misc/egun_toggle.ogg"); diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs index 6d9c4eaf12..7be1a36b23 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs @@ -38,15 +38,18 @@ public abstract partial class SharedGunSystem SubscribeLocalEvent(OnBatteryExamine); } - private void OnTwoModeInit(EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ComponentInit args) { - if (!Timing.IsFirstTimePredicted || !TryComp(component.Owner, out var appearance)) return; + if (!Timing.IsFirstTimePredicted || !TryComp(component.Owner, out var appearance)) + return; Appearance.SetData(appearance.Owner, AmmoVisuals.InStun, component.InStun, appearance); } - private void OnBatteryTwoModeHandleState(EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ref ComponentHandleState args) + private void OnBatteryTwoModeHandleState( + EntityUid uid, + TwoModeEnergyAmmoProviderComponent component, + ref ComponentHandleState args) { if (args.Current is not TwoModeComponentState state) return; @@ -58,7 +61,10 @@ public abstract partial class SharedGunSystem component.InStun = state.InStun; } - private void OnBatteryTwoModeGetState(EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ref ComponentGetState args) + private void OnBatteryTwoModeGetState( + EntityUid uid, + TwoModeEnergyAmmoProviderComponent component, + ref ComponentGetState args) { args.State = new TwoModeComponentState() { @@ -74,19 +80,20 @@ public abstract partial class SharedGunSystem { if (!TryComp(uid, out var appearance)) return; + if (!TryComp(uid, out var item)) return; - if (component.InStun) - _item.SetHeldPrefix(uid, null, false, item); - else - _item.SetHeldPrefix(uid, "laser", false, item); - + _item.SetHeldPrefix(uid, component.InStun ? null : "laser", false, item); Appearance.SetData(uid, AmmoVisuals.InStun, component.InStun, appearance); + Dirty(uid, component); } - private void OnBatteryHandleState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentHandleState args) + private void OnBatteryHandleState( + EntityUid uid, + BatteryAmmoProviderComponent component, + ref ComponentHandleState args) { if (args.Current is not BatteryAmmoProviderComponentState state) return; @@ -139,7 +146,7 @@ public abstract partial class SharedGunSystem /// /// Update the battery (server-only) whenever fired. /// - protected virtual void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) {} + protected virtual void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) { } protected void UpdateBatteryAppearance(EntityUid uid, BatteryAmmoProviderComponent component) { @@ -151,7 +158,9 @@ public abstract partial class SharedGunSystem Appearance.SetData(uid, AmmoVisuals.AmmoMax, component.Capacity, appearance); } - private (EntityUid? Entity, IShootable) GetShootable(BatteryAmmoProviderComponent component, EntityCoordinates coordinates) + private (EntityUid? Entity, IShootable) GetShootable( + BatteryAmmoProviderComponent component, + EntityCoordinates coordinates) { switch (component) { @@ -161,12 +170,13 @@ public abstract partial class SharedGunSystem case HitscanBatteryAmmoProviderComponent hitscan: return (null, ProtoManager.Index(hitscan.Prototype)); case TwoModeEnergyAmmoProviderComponent twoMode: - if (twoMode.CurrentMode == EnergyModes.Stun) - { - var projEnt = Spawn(twoMode.ProjectilePrototype, coordinates); - return (projEnt, EnsureComp(projEnt)); - } - return (null, ProtoManager.Index(twoMode.HitscanPrototype)); + var projEntity = + Spawn(twoMode.CurrentMode == EnergyModes.Stun + ? twoMode.StunPrototype + : twoMode.LaserPrototype, + coordinates); + + return (projEntity, EnsureComp(projEntity)); default: throw new ArgumentOutOfRangeException(); } @@ -184,6 +194,7 @@ public abstract partial class SharedGunSystem public sealed class TwoModeComponentState : ComponentState { public EnergyModes CurrentMode { get; init; } + public int Shots; public int MaxShots; public float FireCost; diff --git a/Content.Shared/White/Spline/CatmullRom/SplineCatmullRom.cs b/Content.Shared/White/Spline/CatmullRom/SplineCatmullRom.cs new file mode 100644 index 0000000000..2f610f6cef --- /dev/null +++ b/Content.Shared/White/Spline/CatmullRom/SplineCatmullRom.cs @@ -0,0 +1,85 @@ +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.CatmullRom; + +public abstract class SplineCatmullRom : Spline +{ + protected const int LookupPrecision = 100; + + protected static readonly (float c0, float c1, float c2, float c3)[] PositionCoefficientLookup + = Enumerable.Range(0, LookupPrecision + 1) + .Select(x => CalculateCoefficientsPosition((float) x / LookupPrecision)).ToArray(); + + protected static readonly (float c0, float c1, float c2, float c3)[] GradientCoefficientLookup + = Enumerable.Range(0, LookupPrecision + 1) + .Select(x => CalculateCoefficientsTangent((float) x / LookupPrecision)).ToArray(); + + protected static (float c0, float c1, float c2, float c3) CalculateCoefficientsPosition(float t) + { + var tt = t * t; + var ttt = tt * t; + return ( + -ttt + 2.0f * tt - t, + 3.0f * ttt - 5.0f * tt + 2.0f, + -3.0f * ttt + 4.0f * tt + t, + ttt - tt + ); + } + + protected static (float c0, float c1, float c2, float c3) CalculateCoefficientsTangent(float t) + { + var tt = t * t; + return ( + -3.0f * tt + 4.0f * t - 1, + 9.0f * tt - 10.0f * t, + -9.0f * tt + 8.0f * t + 1.0f, + 3.0f * tt - 2.0f * t + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetLookupIndex(float t) + { + return (int) (t * LookupPrecision); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T SamplePosition(ReadOnlySpan controlPoints, float u) + { + return CalculateCatmullRom(GetCurrentControlPoints(controlPoints, (int) u), + PositionCoefficientLookup[GetLookupIndex(u % 1)]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T SampleVelocity(ReadOnlySpan controlPoints, float u) + { + return CalculateCatmullRom(GetCurrentControlPoints(controlPoints, (int) u), + GradientCoefficientLookup[GetLookupIndex(u % 1)]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan controlPoints, float u) + { + var lookupIndex = GetLookupIndex(u % 1); + var currentControlPoints = GetCurrentControlPoints(controlPoints, (int) u); + return ( + CalculateCatmullRom(currentControlPoints, PositionCoefficientLookup[lookupIndex]), + CalculateCatmullRom(currentControlPoints, GradientCoefficientLookup[lookupIndex]) + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual (T p0, T p1, T p2, T p3) GetCurrentControlPoints(ReadOnlySpan controlPoints, int u) + { + var p1 = controlPoints[u]; + var p2 = controlPoints[u + 1]; + var p0 = u == 0 ? Add(p1, Subtract(p1, p2)) : controlPoints[u - 1]; + var p3 = u + 2 == controlPoints.Length ? Add(p2, Subtract(p2, p2)) : controlPoints[u + 2]; + return (p0, p1, p2, p3); + } + + protected abstract T CalculateCatmullRom( + (T p0, T p1, T p2, T p3) points, + (float c0, float c1, float c2, float c3) coeffs); +} diff --git a/Content.Shared/White/Spline/CatmullRom/SplineCatmullRom2D.cs b/Content.Shared/White/Spline/CatmullRom/SplineCatmullRom2D.cs new file mode 100644 index 0000000000..eaaa669027 --- /dev/null +++ b/Content.Shared/White/Spline/CatmullRom/SplineCatmullRom2D.cs @@ -0,0 +1,38 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.CatmullRom; + +public sealed class SplineCatmullRom2D : SplineCatmullRom +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector2 Add(Vector2 op1, Vector2 op2) + { + return op1 + op2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector2 Subtract(Vector2 op1, Vector2 op2) + { + return op1 - op2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float Magnitude(Vector2 op1) + { + return op1.Length(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector2 CalculateCatmullRom( + (Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) points, + (float c0, float c1, float c2, float c3) coeffs) + { + return new Vector2( + 0.5f * (points.p0.X * coeffs.c0 + points.p1.X * coeffs.c1 + points.p2.X * coeffs.c2 + + points.p3.X * coeffs.c3), + 0.5f * (points.p0.Y * coeffs.c0 + points.p1.Y * coeffs.c1 + points.p2.Y * coeffs.c2 + + points.p3.Y * coeffs.c3) + ); + } +} diff --git a/Content.Shared/White/Spline/CubicBezier/SplineCubicBezier.cs b/Content.Shared/White/Spline/CubicBezier/SplineCubicBezier.cs new file mode 100644 index 0000000000..fb4821bbbc --- /dev/null +++ b/Content.Shared/White/Spline/CubicBezier/SplineCubicBezier.cs @@ -0,0 +1,81 @@ +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.CubicBezier; + +public abstract class SplineCubicBezier : Spline +{ + protected const int LookupPrecision = 100; + + protected static readonly (float c0, float c1, float c2, float c3)[] PositionCoefficientLookup + = Enumerable.Range(0, LookupPrecision + 1).Select(x => CalculateCoefficientsPosition((float) x / LookupPrecision)).ToArray(); + + protected static readonly (float c0, float c1, float c2, float c3)[] GradientCoefficientLookup + = Enumerable.Range(0, LookupPrecision + 1).Select(x => CalculateCoefficientsTangent((float) x / LookupPrecision)).ToArray(); + + protected static (float c0, float c1, float c2, float c3) CalculateCoefficientsPosition(float t) + { + var tt = t * t; + var ttt = tt * t; + return ( + -ttt + 3f * tt - 3f * t + 1f, + 3f * ttt - 6f * tt + 3f * t, + -3f * ttt + 3f * tt, + ttt + ); + } + + protected static (float c0, float c1, float c2, float c3) CalculateCoefficientsTangent(float t) + { + var tt = t * t; + return ( + -3f * tt + 6f * t - 3, + 9f * tt - 12f * t + 3, + -9f * tt + 6f * t, + 3f * tt + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetLookupIndex(float t) + { + return (int) (t * LookupPrecision); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T SamplePosition(ReadOnlySpan controlPoints, float u) + { + return CalculateBezier(GetCurrentControlPoints(controlPoints, (int) u), PositionCoefficientLookup[GetLookupIndex(u % 1)]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T SampleVelocity(ReadOnlySpan controlPoints, float u) + { + return CalculateBezier(GetCurrentControlPoints(controlPoints, (int) u), GradientCoefficientLookup[GetLookupIndex(u % 1)]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan controlPoints, float u) + { + var lookupIndex = GetLookupIndex(u % 1); + var currentControlPoints = GetCurrentControlPoints(controlPoints, (int) u); + return ( + CalculateBezier(currentControlPoints, PositionCoefficientLookup[lookupIndex]), + CalculateBezier(currentControlPoints, GradientCoefficientLookup[lookupIndex]) + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override float GetControlGroupAmount(int controlPointAmount) + { + return (controlPointAmount - 1) / 3f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual (T p0, T p1, T p2, T p3) GetCurrentControlPoints(ReadOnlySpan controlPoints, int u) + { + return (controlPoints[u], controlPoints[u + 1], controlPoints[u + 2], controlPoints[u + 3]); + } + + protected abstract T CalculateBezier((T p0, T p1, T p2, T p3) points, (float c0, float c1, float c2, float c3) coeffs); +} diff --git a/Content.Shared/White/Spline/CubicBezier/SplineCubicBezier4D.cs b/Content.Shared/White/Spline/CubicBezier/SplineCubicBezier4D.cs new file mode 100644 index 0000000000..77954c4c68 --- /dev/null +++ b/Content.Shared/White/Spline/CubicBezier/SplineCubicBezier4D.cs @@ -0,0 +1,37 @@ +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.CubicBezier; + +public sealed class SplineCubicBezier4D : SplineCubicBezier +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector4 Add(Vector4 op1, Vector4 op2) + { + return new Vector4(op1.X + op2.X, op1.Y + op2.Y, op1.Z + op2.Z, op1.W + op2.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector4 Subtract(Vector4 op1, Vector4 op2) + { + return new Vector4(op1.X - op2.X, op1.Y - op2.Y, op1.Z - op2.Z, op1.W - op2.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float Magnitude(Vector4 op1) + { + return MathF.Sqrt(op1.X * op1.X + op1.Y * op1.Y + op1.Z * op1.Z + op1.W * op1.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector4 CalculateBezier( + (Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3) points, + (float c0, float c1, float c2, float c3) coeffs) + { + return new Vector4( + points.p0.X * coeffs.c0 + points.p1.X * coeffs.c1 + points.p2.X * coeffs.c2 + points.p3.X * coeffs.c3, + points.p0.Y * coeffs.c0 + points.p1.Y * coeffs.c1 + points.p2.Y * coeffs.c2 + points.p3.Y * coeffs.c3, + points.p0.Z * coeffs.c0 + points.p1.Z * coeffs.c1 + points.p2.Z * coeffs.c2 + points.p3.Z * coeffs.c3, + points.p0.W * coeffs.c0 + points.p1.W * coeffs.c1 + points.p2.W * coeffs.c2 + points.p3.W * coeffs.c3 + ); + } +} diff --git a/Content.Shared/White/Spline/CubicBezier/SplineCubicBezierColor.cs b/Content.Shared/White/Spline/CubicBezier/SplineCubicBezierColor.cs new file mode 100644 index 0000000000..0251a35e00 --- /dev/null +++ b/Content.Shared/White/Spline/CubicBezier/SplineCubicBezierColor.cs @@ -0,0 +1,37 @@ +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.CubicBezier; + +public sealed class SplineCubicBezierColor : SplineCubicBezier +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Color Add(Color op1, Color op2) + { + return new Color(op1.R + op2.R, op1.G + op2.G, op1.B + op2.B, op1.A + op2.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Color Subtract(Color op1, Color op2) + { + return new Color(op1.R - op2.R, op1.G - op2.G, op1.B - op2.B, op1.A - op2.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float Magnitude(Color op1) + { + return MathF.Sqrt(op1.R * op1.R + op1.G * op1.G + op1.B * op1.B + op1.A * op1.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Color CalculateBezier( + (Color p0, Color p1, Color p2, Color p3) points, + (float c0, float c1, float c2, float c3) coeffs) + { + return new Color( + points.p0.R * coeffs.c0 + points.p1.R * coeffs.c1 + points.p2.R * coeffs.c2 + points.p3.R * coeffs.c3, + points.p0.G * coeffs.c0 + points.p1.G * coeffs.c1 + points.p2.G * coeffs.c2 + points.p3.G * coeffs.c3, + points.p0.B * coeffs.c0 + points.p1.B * coeffs.c1 + points.p2.B * coeffs.c2 + points.p3.B * coeffs.c3, + points.p0.A * coeffs.c0 + points.p1.A * coeffs.c1 + points.p2.A * coeffs.c2 + points.p3.A * coeffs.c3 + ); + } +} diff --git a/Content.Shared/White/Spline/ISpline.cs b/Content.Shared/White/Spline/ISpline.cs new file mode 100644 index 0000000000..ffec44b860 --- /dev/null +++ b/Content.Shared/White/Spline/ISpline.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.White.Spline; + +public interface ISpline +{ + T SamplePosition(ReadOnlySpan controlPoints, float u); + + T SampleVelocity(ReadOnlySpan controlPoints, float u); + + (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan controlPoints, float u); + + IEnumerable IteratePointParamsByLength(T[] controlPoints, float lengthStepSize); + + float GetControlGroupAmount(int controlPointAmount); +} diff --git a/Content.Shared/White/Spline/Linear/SplineLinear.cs b/Content.Shared/White/Spline/Linear/SplineLinear.cs new file mode 100644 index 0000000000..06d33dd182 --- /dev/null +++ b/Content.Shared/White/Spline/Linear/SplineLinear.cs @@ -0,0 +1,34 @@ +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.Linear; + +public abstract class SplineLinear : Spline +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T SamplePosition(ReadOnlySpan controlPoints, float u) + { + var iu = (int) u; + var t = u % 1; + return Add(Multiply(controlPoints[iu], 1 - t), Multiply(controlPoints[iu + 1], t)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override T SampleVelocity(ReadOnlySpan controlPoints, float u) + { + var iu = (int) u; + return iu == 0 + ? Subtract(controlPoints[iu + 1], controlPoints[iu]) + : Subtract(controlPoints[iu + 1], controlPoints[iu - 1]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan controlPoints, float u) + { + return ( + SamplePosition(controlPoints, u), + SampleVelocity(controlPoints, u) + ); + } + + protected abstract T Multiply(T op1, float scalar); +} diff --git a/Content.Shared/White/Spline/Linear/SplineLinear2D.cs b/Content.Shared/White/Spline/Linear/SplineLinear2D.cs new file mode 100644 index 0000000000..04272f8b6c --- /dev/null +++ b/Content.Shared/White/Spline/Linear/SplineLinear2D.cs @@ -0,0 +1,31 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.Linear; + +public sealed class SplineLinear2D : SplineLinear +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector2 Add(Vector2 op1, Vector2 op2) + { + return op1 + op2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector2 Subtract(Vector2 op1, Vector2 op2) + { + return op1 - op2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float Magnitude(Vector2 op1) + { + return op1.Length(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector2 Multiply(Vector2 op1, float scalar) + { + return op1 * scalar; + } +} diff --git a/Content.Shared/White/Spline/Linear/SplineLinear4D.cs b/Content.Shared/White/Spline/Linear/SplineLinear4D.cs new file mode 100644 index 0000000000..c8bef048a1 --- /dev/null +++ b/Content.Shared/White/Spline/Linear/SplineLinear4D.cs @@ -0,0 +1,30 @@ +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.Linear; + +public sealed class SplineLinear4D : SplineLinear +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector4 Add(Vector4 op1, Vector4 op2) + { + return new Vector4(op1.X + op2.X, op1.Y + op2.Y, op1.Z + op2.Z, op1.W + op2.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector4 Subtract(Vector4 op1, Vector4 op2) + { + return new Vector4(op1.X - op2.X, op1.Y - op2.Y, op1.Z - op2.Z, op1.W - op2.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float Magnitude(Vector4 op1) + { + return MathF.Sqrt(op1.X * op1.X + op1.Y * op1.Y + op1.Z * op1.Z + op1.W * op1.W); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Vector4 Multiply(Vector4 op1, float scalar) + { + return new Vector4(op1.X * scalar, op1.Y * scalar, op1.Z * scalar, op1.W * scalar); + } +} diff --git a/Content.Shared/White/Spline/Linear/SplineLinearColor.cs b/Content.Shared/White/Spline/Linear/SplineLinearColor.cs new file mode 100644 index 0000000000..1661dddd4c --- /dev/null +++ b/Content.Shared/White/Spline/Linear/SplineLinearColor.cs @@ -0,0 +1,30 @@ +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline.Linear; + +public sealed class SplineLinearColor : SplineLinear +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Color Add(Color op1, Color op2) + { + return new Color(op1.R + op2.R, op1.G + op2.G, op1.B + op2.B, op1.A + op2.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Color Subtract(Color op1, Color op2) + { + return new Color(op1.R - op2.R, op1.G - op2.G, op1.B - op2.B, op1.A - op2.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float Magnitude(Color op1) + { + return MathF.Sqrt(op1.R * op1.R + op1.G * op1.G + op1.B * op1.B + op1.A * op1.A); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override Color Multiply(Color op1, float scalar) + { + return new Color(op1.R * scalar, op1.G * scalar, op1.B * scalar, op1.A * scalar); + } +} diff --git a/Content.Shared/White/Spline/Spline.cs b/Content.Shared/White/Spline/Spline.cs new file mode 100644 index 0000000000..70a4b3bcef --- /dev/null +++ b/Content.Shared/White/Spline/Spline.cs @@ -0,0 +1,44 @@ +using System.Runtime.CompilerServices; + +namespace Content.Shared.White.Spline; + +public abstract class Spline : ISpline +{ + public abstract T SamplePosition(ReadOnlySpan controlPoints, float u); + + public abstract T SampleVelocity(ReadOnlySpan controlPoints, float u); + + public abstract (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan controlPoints, float u); + + public virtual IEnumerable IteratePointParamsByLength(T[] controlPoints, float lengthStepSize) + { + //ну а хули нам наивным салюшонам + for (var u = 0; u < controlPoints.Length - 1; u++) + { + var segmentLength = ApproximateArcLength(controlPoints, u); + var tStepSize = lengthStepSize / segmentLength; + for (var t = 0f; t < 1; t += tStepSize) + { + yield return u + t; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual float GetControlGroupAmount(int controlPointAmount) + { + return controlPointAmount - 1f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual float ApproximateArcLength(ReadOnlySpan controlPoints, float u) + { + return Magnitude(Subtract(controlPoints[(int) u], controlPoints[(int) u + 1])); + } + + protected abstract T Add(T op1, T op2); + + protected abstract T Subtract(T op1, T op2); + + protected abstract float Magnitude(T op1); +} diff --git a/Content.Shared/White/Spline/SplineEnums.cs b/Content.Shared/White/Spline/SplineEnums.cs new file mode 100644 index 0000000000..ab4ceb613e --- /dev/null +++ b/Content.Shared/White/Spline/SplineEnums.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using Content.Shared.White.Spline.CatmullRom; +using Content.Shared.White.Spline.CubicBezier; +using Content.Shared.White.Spline.Linear; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Shared.White.Spline; + +public static class Spline +{ + public static ISpline From2DType(Spline2DType type) + { + return type switch + { + Spline2DType.Linear => new SplineLinear2D(), + Spline2DType.CatmullRom => new SplineCatmullRom2D(), + _ => throw new NotImplementedException() + }; + } + + public static ISpline From4DType(Spline4DType type) + { + return type switch + { + Spline4DType.Linear => new SplineLinear4D(), + Spline4DType.Bezier => new SplineCubicBezier4D(), + _ => throw new NotImplementedException() + }; + } + + public static ISpline FromColorType(SplineColorType type) + { + return type switch + { + SplineColorType.Linear => new SplineLinearColor(), + SplineColorType.Bezier => new SplineCubicBezierColor(), + _ => throw new NotImplementedException() + }; + } +} + +public enum Spline2DType : byte +{ + Linear, + CatmullRom +} + +public enum Spline4DType : byte +{ + Linear, + Bezier +} + +public enum SplineColorType : byte +{ + Linear, + Bezier +} + diff --git a/Content.Shared/White/Trail/SharedTrailComponent.cs b/Content.Shared/White/Trail/SharedTrailComponent.cs new file mode 100644 index 0000000000..62fab24df0 --- /dev/null +++ b/Content.Shared/White/Trail/SharedTrailComponent.cs @@ -0,0 +1,74 @@ +using System.Numerics; +using Content.Shared.White.Spline; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Shared.White.Trail; + +[NetworkedComponent] +public abstract partial class SharedTrailComponent : Component, ITrailSettings +{ + [DataField("gravity")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Vector2 Gravity { get; set; } + + [DataField("lifetime", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public virtual float Lifetime { get; set; } + + [DataField("lengthStep")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual float LengthStep { get; set; } + + [DataField("randomWalk")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Vector2 MaxRandomWalk { get; set; } + + [DataField("scale", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Vector2 Scale { get; set; } + + [DataField("texturePath")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual string? TexurePath { get; set; } + + [DataField("creationOffset")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Vector2 CreationOffset { get; set; } + + [DataField("сreationDistanceThresholdSquared")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual float СreationDistanceThresholdSquared { get; set; } + + [DataField("creationMethod")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual SegmentCreationMethod СreationMethod { get; set; } + + [DataField("gradient", required: true)] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Vector4[] Gradient { get; set; } = new[] { Vector4.One, new Vector4(1f, 1f, 1f, 0f) }; + + [DataField("gradientIteratorType")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Spline4DType GradientIteratorType { get; set; } + + [DataField("splineIteratorType")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual Spline2DType SplineIteratorType { get; set; } + + [DataField("splineRendererType")] + [ViewVariables(VVAccess.ReadWrite)] + public virtual TrailSplineRendererType SplineRendererType { get; set; } +} + +[Serializable, NetSerializable] +public sealed class TrailComponentState : ComponentState +{ + public TrailSettings Settings; + + public TrailComponentState(TrailSettings settings) + { + Settings = settings; + } +} diff --git a/Content.Shared/White/Trail/TrailSettings.cs b/Content.Shared/White/Trail/TrailSettings.cs new file mode 100644 index 0000000000..3d40444b86 --- /dev/null +++ b/Content.Shared/White/Trail/TrailSettings.cs @@ -0,0 +1,97 @@ +using System.Numerics; +using Content.Shared.White.Spline; +using Robust.Shared.Serialization; +using Vector4 = Robust.Shared.Maths.Vector4; + +namespace Content.Shared.White.Trail; + +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class TrailSettings : ITrailSettings +{ + public static readonly TrailSettings Default = new(); + + public Vector2 Scale { get; set; } = new(0.5f, 1f); + + public float СreationDistanceThresholdSquared { get; set; } = 0.1f; + + public SegmentCreationMethod СreationMethod { get; set; } = SegmentCreationMethod.OnFrameUpdate; + + public Vector2 CreationOffset { get; set; } = Vector2.Zero; + + public Vector2 Gravity { get; set; } = new(0.01f, 0.01f); + + public Vector2 MaxRandomWalk { get; set; } = new(0.005f, 0.005f); + + public float Lifetime { get; set; } + + public float LengthStep { get; set; } = 0.1f; + + public string? TexurePath { get; set; } + + public Vector4[] Gradient { get; set; } = { new(1f, 1f, 1f, 1f), new(1f, 1f, 1f, 0f) }; + + public Spline4DType GradientIteratorType { get; set; } + + public Spline2DType SplineIteratorType { get; set; } + + public TrailSplineRendererType SplineRendererType { get; set; } + + public static void Inject(ITrailSettings into, ITrailSettings from) + { + into.Scale = from.Scale; + into.СreationDistanceThresholdSquared = from.СreationDistanceThresholdSquared; + into.СreationMethod = from.СreationMethod; + into.CreationOffset = from.CreationOffset; + into.Gravity = from.Gravity; + into.MaxRandomWalk = from.MaxRandomWalk; + into.Lifetime = from.Lifetime; + into.LengthStep = from.LengthStep; + into.TexurePath = from.TexurePath; + into.Gradient = from.Gradient; + into.SplineIteratorType = from.SplineIteratorType; + into.SplineRendererType = from.SplineRendererType; + } +} + +public interface ITrailSettings +{ + Vector2 Gravity { get; set; } + + float Lifetime { get; set; } + + float LengthStep { get; set; } + + Vector2 MaxRandomWalk { get; set; } + + Vector2 Scale { get; set; } + + string? TexurePath { get; set; } + + Vector2 CreationOffset { get; set; } + + float СreationDistanceThresholdSquared { get; set; } + + SegmentCreationMethod СreationMethod { get; set; } + + Vector4[] Gradient { get; set; } + + Spline4DType GradientIteratorType { get; set; } + + Spline2DType SplineIteratorType { get; set; } + + TrailSplineRendererType SplineRendererType { get; set; } +} + +public enum SegmentCreationMethod : byte +{ + OnFrameUpdate, + OnMove +} + +public enum TrailSplineRendererType : byte +{ + Continuous, + Point, + Debug +} diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index a013376757..b658077d12 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -136,8 +136,8 @@ - state: mag-unshaded-4 map: ["enum.GunVisualLayers.MagUnshaded"] shader: unshaded - - type: HitscanBatteryAmmoProvider - proto: RedMediumLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaser fireCost: 62.5 - type: MagazineVisuals magState: mag @@ -161,8 +161,8 @@ shader: unshaded - type: Clothing sprite: Objects/Weapons/Guns/Battery/makeshift.rsi - - type: HitscanBatteryAmmoProvider - proto: RedLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaser fireCost: 62.5 - type: Battery maxCharge: 500 @@ -217,8 +217,8 @@ selectedMode: SemiAuto availableModes: - SemiAuto - - type: HitscanBatteryAmmoProvider - proto: RedLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaser fireCost: 62.5 - type: entity @@ -227,8 +227,8 @@ id: WeaponLaserCarbinePractice description: This modified laser rifle fires harmless beams in the 40-watt range, for target practice. components: - - type: HitscanBatteryAmmoProvider - proto: RedLaserPractice + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaserPractice fireCost: 62.5 - type: entity @@ -253,8 +253,8 @@ - SemiAuto soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - - type: HitscanBatteryAmmoProvider - proto: Pulse + - type: ProjectileBatteryAmmoProvider + proto: PulseBoltProjectile fireCost: 200 - type: Battery maxCharge: 2000 @@ -284,8 +284,8 @@ - FullAuto soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - - type: HitscanBatteryAmmoProvider - proto: Pulse + - type: ProjectileBatteryAmmoProvider + proto: PulseBoltProjectile fireCost: 200 - type: Battery maxCharge: 5000 @@ -311,8 +311,8 @@ fireRate: 1.5 soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser3.ogg - - type: HitscanBatteryAmmoProvider - proto: Pulse + - type: ProjectileBatteryAmmoProvider + proto: PulseBoltProjectile fireCost: 100 - type: Battery maxCharge: 40000 @@ -338,8 +338,8 @@ fireRate: 1.5 soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - - type: HitscanBatteryAmmoProvider - proto: RedHeavyLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaserHeavy fireCost: 100 - type: entity @@ -392,8 +392,8 @@ - type: Gun soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser3.ogg - - type: HitscanBatteryAmmoProvider - proto: XrayLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaserXray fireCost: 100 - type: MagazineVisuals magState: mag @@ -554,8 +554,8 @@ - type: Gun soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - - type: HitscanBatteryAmmoProvider - proto: RedMediumLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaserMedium fireCost: 100 - type: BatterySelfRecharger autoRecharge: true @@ -595,8 +595,8 @@ - type: Gun soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - - type: HitscanBatteryAmmoProvider - proto: RedMediumLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaserMedium fireCost: 100 - type: BatterySelfRecharger autoRecharge: true @@ -682,8 +682,8 @@ fireRate: 1 soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_clown.ogg - - type: HitscanBatteryAmmoProvider - proto: RedMediumLaser + - type: ProjectileBatteryAmmoProvider + proto: BulletTrailLaserMedium fireCost: 100 - type: BatterySelfRecharger autoRecharge: true @@ -721,15 +721,18 @@ - suitStorage - type: Gun soundGunshot: - path: /Audio/Weapons/Guns/Gunshots/taser2.ogg + path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg + projectileSpeed: 12 - type: TwoModeEnergyAmmoProvider - projProto: BulletDisabler - fireCost: 50 - projFireCost: 50 - hitscanProto: RedLaser - hitscanFireCost: 100 - projSound: "/Audio/Weapons/Guns/Gunshots/taser2.ogg" - hitscanSound: "/Audio/Weapons/Guns/Gunshots/laser_cannon.ogg" + stunPrototype: BulletDisabler + laserPrototype: BulletTrailLaser + fireCost: 100 + laserFireCost: 99 + stunFireCost: 49 + stunProjectileSpeed: 25 + laserProjectileSpeed: 25 + stunSound: "/Audio/Weapons/Guns/Gunshots/taser2.ogg" + laserSound: "/Audio/Weapons/Guns/Gunshots/laser_cannon.ogg" - type: MagazineVisuals magState: mag steps: 5 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index 5f909d221f..a35718fcac 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -192,9 +192,11 @@ soundHit: path: "/Audio/Weapons/Guns/Hits/taser_hit.ogg" soundForce: true - - type: StunOnCollide - stunAmount: 5 - knockdownAmount: 5 + - type: StaminaDamageOnCollide + damage: 100 + - type: Reflective + reflective: + - Energy - type: entity name : disabler bolt @@ -879,3 +881,303 @@ - type: Tag tags: - HideContextMenu + +- type: entity + name: laser bolt + id: BulletTrailLaser + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + layers: + - shader: unshaded + - type: Ammo + muzzleFlash: null + - type: Physics + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + hard: false + mask: + - Opaque + fly-by: *flybyfixture + - type: Projectile + # soundHit: Waiting on serv3 + damage: + types: + Heat: 23 + - type: TimedDespawn + lifetime: 3 + - type: Trail + splineIteratorType: Linear + splineRendererType: Continuous + creationMethod: OnMove + scale: 0.05, 0.0 + lifetime: 0.1 + randomWalk: 0.001, 0.001 + gravity: 0, 0 + gradient: + - 1, 0, 0, 1 + - 1, 0, 0, 0 + - type: PointLight + radius: 3.5 + color: red + energy: 1 + - type: Reflective + reflective: + - Energy + + +- type: entity + name: practice laser bolt + id: BulletTrailLaserPractice + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + layers: + - shader: unshaded + - type: Ammo + muzzleFlash: null + - type: Physics + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + hard: false + mask: + - Opaque + fly-by: *flybyfixture + - type: Projectile + # soundHit: Waiting on serv3 + damage: + types: + Heat: 0 + - type: TimedDespawn + lifetime: 3 + - type: Trail + splineIteratorType: Linear + splineRendererType: Continuous + creationMethod: OnMove + scale: 0.05, 0.0 + lifetime: 0.1 + randomWalk: 0.001, 0.001 + gravity: 0, 0 + gradient: + - 1, 0, 0, 1 + - 1, 0, 0, 0 + - type: PointLight + radius: 3.5 + color: red + energy: 1 + - type: Reflective + reflective: + - Energy + + + +- type: entity + name: pulse bolt + id: PulseBoltProjectile + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + layers: + - shader: unshaded + - type: Ammo + muzzleFlash: null + - type: Physics + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + hard: false + mask: + - Opaque + fly-by: *flybyfixture + - type: Projectile + # soundHit: Waiting on serv3 + damage: + types: + Heat: 35 + - type: TimedDespawn + lifetime: 3 + - type: Trail + splineIteratorType: Linear + splineRendererType: Continuous + creationMethod: OnMove + scale: 0.10, 0.0 + lifetime: 0.1 + randomWalk: 0.001, 0.001 + gravity: 0, 0 + gradient: + - 0, 0, 1, 1 + - 0, 0, 1, 0 + - type: PointLight + radius: 3.5 + color: blue + energy: 1 + - type: Reflective + reflective: + - Energy + + +- type: entity + name: heavy laser bolt + id: BulletTrailLaserHeavy + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + layers: + - shader: unshaded + - type: Ammo + muzzleFlash: null + - type: Physics + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + hard: false + mask: + - Opaque + fly-by: *flybyfixture + - type: Projectile + # soundHit: Waiting on serv3 + damage: + types: + Heat: 35 + - type: TimedDespawn + lifetime: 3 + - type: Trail + splineIteratorType: Linear + splineRendererType: Continuous + creationMethod: OnMove + scale: 0.11, 0.0 + lifetime: 0.1 + randomWalk: 0.001, 0.001 + gravity: 0, 0 + gradient: + - 1, 0, 0, 1 + - 1, 0, 0, 0 + - type: PointLight + radius: 3.5 + color: red + energy: 1 + - type: Reflective + reflective: + - Energy + +- type: entity + name: xray laser bolt + id: BulletTrailLaserXray + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + layers: + - shader: unshaded + - type: Ammo + muzzleFlash: null + - type: Physics + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + hard: false + mask: + - Opaque + fly-by: *flybyfixture + - type: Projectile + # soundHit: Waiting on serv3 + damage: + types: + Heat: 14 + Radiation: 14 + - type: TimedDespawn + lifetime: 3 + - type: Trail + splineIteratorType: Linear + splineRendererType: Continuous + creationMethod: OnMove + scale: 0.11, 0.0 + lifetime: 0.1 + randomWalk: 0.001, 0.001 + gravity: 0, 0 + gradient: + - 0, 1, 0, 1 + - 0, 1, 0, 0 + - type: PointLight + radius: 3.5 + color: green + energy: 1 + - type: Reflective + reflective: + - Energy + + +- type: entity + name: medium laser bolt + id: BulletTrailLaserMedium + parent: BaseBullet + noSpawn: true + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + layers: + - shader: unshaded + - type: Ammo + muzzleFlash: null + - type: Physics + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.2,-0.2,0.2,0.2" + hard: false + mask: + - Opaque + fly-by: *flybyfixture + - type: Projectile + # soundHit: Waiting on serv3 + damage: + types: + Heat: 23 + - type: TimedDespawn + lifetime: 3 + - type: Trail + splineIteratorType: Linear + splineRendererType: Continuous + creationMethod: OnMove + scale: 0.05, 0.0 + lifetime: 0.1 + randomWalk: 0.001, 0.001 + gravity: 0, 0 + gradient: + - 1, 0, 0, 1 + - 1, 0, 0, 0 + - type: PointLight + radius: 3.5 + color: red + energy: 1 + - type: Reflective + reflective: + - Energy