feat: перенос лазеров-снарядов

This commit is contained in:
Remuchi
2024-01-24 12:58:57 +07:00
parent 7120f4d85d
commit cf8709f1ea
36 changed files with 1920 additions and 144 deletions

View File

@@ -25,6 +25,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Spawners\" />
<Folder Include="White\Trail\" />
</ItemGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />

View File

@@ -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<StalinManager>();
IoCManager.Register<ClientJukeboxSongsSyncManager>();
IoCManager.Register<TTSManager>();
IoCManager.Register<ITrailLineManager, TrailSplineManager>();
//WD-EDIT
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.White.Trail.Line.Manager;
public interface ITrailLineHolder
{
public ITrailLine? TrailLine { get; set; }
}

View File

@@ -0,0 +1,15 @@
using Content.Shared.White.Trail;
using Robust.Shared.Map;
namespace Content.Client.White.Trail.Line.Manager;
public interface ITrailLineManager
{
IEnumerable<ITrailLine> Lines { get; }
ITrailLine CreateTrail(ITrailSettings settings, MapId mapId);
void Detach(ITrailLineHolder holder);
void Update(float dt);
}

View File

@@ -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<TrailSpline> _lines = new();
public IEnumerable<ITrailLine> 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);
}
}
}

View File

@@ -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<IRobustRandom>();
[ViewVariables]
private readonly LinkedList<TrailSplineSegment> _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<Vector2> SplineIterator { get; set; } = new SplineLinear2D();
[ViewVariables]
public ISpline<Vector4> 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; }
}
}

View File

@@ -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<Vector2> splineIterator,
ISpline<Vector4> gradientIterator,
ITrailSettings settings,
Vector2[] paPositions,
float[] paLifetimes
);
}

View File

@@ -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()
};
}
}

View File

@@ -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<Vector2> splineIterator,
ISpline<Vector4> 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;
}
}
}

View File

@@ -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<Vector2> splineIterator,
ISpline<Vector4> 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;
}
}
}

View File

@@ -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<Vector2> splineIterator,
ISpline<Vector4> 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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<string, ShaderInstance?> _shaderDict;
private readonly Dictionary<string, Texture?> _textureDict;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
public TrailOverlay(
IPrototypeManager protoManager,
IResourceCache cache,
ITrailLineManager lineManager
)
{
_protoManager = protoManager;
_cache = cache;
_lineManager = lineManager;
_shaderDict = new Dictionary<string, ShaderInstance?>();
_textureDict = new Dictionary<string, Texture?>();
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<ShaderPrototype>(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<TextureResource>(path, out var texRes))
texture = texRes;
_textureDict.Add(path, texture);
return texture;
}
}

View File

@@ -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<IOverlayManager>().AddOverlay(
new TrailOverlay(
IoCManager.Resolve<IPrototypeManager>(),
IoCManager.Resolve<IResourceCache>(),
_lineManager
));
SubscribeLocalEvent<TrailComponent, MoveEvent>(OnTrailMove);
SubscribeLocalEvent<TrailComponent, ComponentRemove>(OnTrailRemove);
SubscribeLocalEvent<TrailComponent, ComponentHandleState>(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<TrailComponent, TransformComponent>())
{
if (comp.СreationMethod == SegmentCreationMethod.OnFrameUpdate)
TryCreateSegment(comp, xform);
}
}
}

View File

@@ -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<GunComponent>(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<EntityPrototype>(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<HitscanPrototype>(hitscan.Prototype).Damage;
}
if (component is TwoModeEnergyAmmoProviderComponent twoMode)
{
if (twoMode.CurrentMode == EnergyModes.Stun)
{
if (ProtoManager.Index<EntityPrototype>(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<HitscanPrototype>(twoMode.HitscanPrototype).Damage;
}
return null;
HitscanBatteryAmmoProviderComponent hitscan =>
ProtoManager.Index<HitscanPrototype>(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)

View File

@@ -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; }
}

View File

@@ -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<TrailComponent, ComponentGetState>(OnGetState);
}
private void OnGetState(EntityUid uid, TrailComponent component, ref ComponentGetState args)
{
var settings = new TrailSettings();
TrailSettings.Inject(settings, component);
args.State = new TrailComponentState(settings);
}
}

View File

@@ -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<EntityPrototype>))]
public string ProjectilePrototype = default!;
DataField("stunPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string StunPrototype = default!;
[ViewVariables(VVAccess.ReadOnly),
DataField("hitscanProto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<HitscanPrototype>))]
public string HitscanPrototype = default!;
DataField("laserPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
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");

View File

@@ -38,15 +38,18 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<TwoModeEnergyAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
}
private void OnTwoModeInit(EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ComponentInit args)
{
if (!Timing.IsFirstTimePredicted || !TryComp<AppearanceComponent>(component.Owner, out var appearance)) return;
if (!Timing.IsFirstTimePredicted || !TryComp<AppearanceComponent>(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<AppearanceComponent>(uid, out var appearance))
return;
if (!TryComp<ItemComponent>(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
/// <summary>
/// Update the battery (server-only) whenever fired.
/// </summary>
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<HitscanPrototype>(hitscan.Prototype));
case TwoModeEnergyAmmoProviderComponent twoMode:
if (twoMode.CurrentMode == EnergyModes.Stun)
{
var projEnt = Spawn(twoMode.ProjectilePrototype, coordinates);
return (projEnt, EnsureComp<AmmoComponent>(projEnt));
}
return (null, ProtoManager.Index<HitscanPrototype>(twoMode.HitscanPrototype));
var projEntity =
Spawn(twoMode.CurrentMode == EnergyModes.Stun
? twoMode.StunPrototype
: twoMode.LaserPrototype,
coordinates);
return (projEntity, EnsureComp<AmmoComponent>(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;

View File

@@ -0,0 +1,85 @@
using System.Linq;
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.CatmullRom;
public abstract class SplineCatmullRom<T> : Spline<T>
{
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<T> controlPoints, float u)
{
return CalculateCatmullRom(GetCurrentControlPoints(controlPoints, (int) u),
PositionCoefficientLookup[GetLookupIndex(u % 1)]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T SampleVelocity(ReadOnlySpan<T> controlPoints, float u)
{
return CalculateCatmullRom(GetCurrentControlPoints(controlPoints, (int) u),
GradientCoefficientLookup[GetLookupIndex(u % 1)]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan<T> 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<T> 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);
}

View File

@@ -0,0 +1,38 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.CatmullRom;
public sealed class SplineCatmullRom2D : SplineCatmullRom<Vector2>
{
[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)
);
}
}

View File

@@ -0,0 +1,81 @@
using System.Linq;
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.CubicBezier;
public abstract class SplineCubicBezier<T> : Spline<T>
{
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<T> controlPoints, float u)
{
return CalculateBezier(GetCurrentControlPoints(controlPoints, (int) u), PositionCoefficientLookup[GetLookupIndex(u % 1)]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T SampleVelocity(ReadOnlySpan<T> controlPoints, float u)
{
return CalculateBezier(GetCurrentControlPoints(controlPoints, (int) u), GradientCoefficientLookup[GetLookupIndex(u % 1)]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan<T> 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<T> 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);
}

View File

@@ -0,0 +1,37 @@
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.CubicBezier;
public sealed class SplineCubicBezier4D : SplineCubicBezier<Vector4>
{
[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
);
}
}

View File

@@ -0,0 +1,37 @@
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.CubicBezier;
public sealed class SplineCubicBezierColor : SplineCubicBezier<Color>
{
[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
);
}
}

View File

@@ -0,0 +1,14 @@
namespace Content.Shared.White.Spline;
public interface ISpline<T>
{
T SamplePosition(ReadOnlySpan<T> controlPoints, float u);
T SampleVelocity(ReadOnlySpan<T> controlPoints, float u);
(T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan<T> controlPoints, float u);
IEnumerable<float> IteratePointParamsByLength(T[] controlPoints, float lengthStepSize);
float GetControlGroupAmount(int controlPointAmount);
}

View File

@@ -0,0 +1,34 @@
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.Linear;
public abstract class SplineLinear<T> : Spline<T>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T SamplePosition(ReadOnlySpan<T> 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<T> 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<T> controlPoints, float u)
{
return (
SamplePosition(controlPoints, u),
SampleVelocity(controlPoints, u)
);
}
protected abstract T Multiply(T op1, float scalar);
}

View File

@@ -0,0 +1,31 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.Linear;
public sealed class SplineLinear2D : SplineLinear<Vector2>
{
[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;
}
}

View File

@@ -0,0 +1,30 @@
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.Linear;
public sealed class SplineLinear4D : SplineLinear<Vector4>
{
[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);
}
}

View File

@@ -0,0 +1,30 @@
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline.Linear;
public sealed class SplineLinearColor : SplineLinear<Color>
{
[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);
}
}

View File

@@ -0,0 +1,44 @@
using System.Runtime.CompilerServices;
namespace Content.Shared.White.Spline;
public abstract class Spline<T> : ISpline<T>
{
public abstract T SamplePosition(ReadOnlySpan<T> controlPoints, float u);
public abstract T SampleVelocity(ReadOnlySpan<T> controlPoints, float u);
public abstract (T Position, T Velocity) SamplePositionVelocity(ReadOnlySpan<T> controlPoints, float u);
public virtual IEnumerable<float> 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<T> 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);
}

View File

@@ -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<Vector2> From2DType(Spline2DType type)
{
return type switch
{
Spline2DType.Linear => new SplineLinear2D(),
Spline2DType.CatmullRom => new SplineCatmullRom2D(),
_ => throw new NotImplementedException()
};
}
public static ISpline<Vector4> From4DType(Spline4DType type)
{
return type switch
{
Spline4DType.Linear => new SplineLinear4D(),
Spline4DType.Bezier => new SplineCubicBezier4D(),
_ => throw new NotImplementedException()
};
}
public static ISpline<Color> 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
}

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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