Sparks (#273)
* Add pointlight despawning visuals for sparks * Add sprite fade out * Add spark prototypes * Add sparking behavior to lights * Integrate sparks with some different stuff * Remove invalid field
This commit is contained in:
parent
d2e65e8b5c
commit
00ff4d5d9b
|
|
@ -0,0 +1,14 @@
|
|||
using Content.Shared._ES.Core.Timer.Components;
|
||||
|
||||
namespace Content.Client._ES.Lighting.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Handles a point light that fades out while synced to a <see cref="ESTimedDespawnComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(ESTimedDespawnLightFadeSystem))]
|
||||
public sealed partial class ESTimedDespawnLightFadeComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan FadeTime = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Content.Client._ES.Lighting.Components;
|
||||
using Content.Shared._ES.Core.Timer.Components;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._ES.Lighting;
|
||||
|
||||
public sealed class ESTimedDespawnLightFadeSystem : VisualizerSystem<ESTimedDespawnLightFadeComponent>
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
|
||||
private const string FadeTrack = "light-fade";
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, ESTimedDespawnLightFadeComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
base.OnAppearanceChange(uid, component, ref args);
|
||||
|
||||
if (_animationPlayer.HasRunningAnimation(uid, FadeTrack))
|
||||
return;
|
||||
|
||||
if (!AppearanceSystem.TryGetData<TimeSpan>(uid, ESTimedDespawnVisuals.DespawnTime, out var time, args.Component) ||
|
||||
!TryComp<PointLightComponent>(uid, out var light))
|
||||
return;
|
||||
|
||||
var duration = time - _timing.CurTime;
|
||||
|
||||
var animation = new Animation
|
||||
{
|
||||
Length = duration,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty
|
||||
{
|
||||
Property = nameof(PointLightComponent.Energy),
|
||||
ComponentType = typeof(PointLightComponent),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(light.Energy, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(light.Energy, (float) (duration - component.FadeTime).TotalSeconds),
|
||||
new AnimationTrackProperty.KeyFrame(0f, (float) component.FadeTime.TotalSeconds, Easings.OutSine),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_animationPlayer.Play(uid, animation, FadeTrack);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using Content.Shared._ES.Core.Timer.Components;
|
||||
|
||||
namespace Content.Client._ES.Sprite.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Handles a sprite that fades out while synced to a <see cref="ESTimedDespawnComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(ESTimedDespawnSpriteFadeSystem))]
|
||||
public sealed partial class ESTimedDespawnSpriteFadeComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan FadeTime = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using Content.Client._ES.Sprite.Components;
|
||||
using Content.Shared._ES.Core.Timer.Components;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._ES.Sprite;
|
||||
|
||||
/// <summary>
|
||||
/// This handles <see cref="ESTimedDespawnSpriteFadeComponent"/>
|
||||
/// </summary>
|
||||
public sealed class ESTimedDespawnSpriteFadeSystem : VisualizerSystem<ESTimedDespawnSpriteFadeComponent>
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
|
||||
private const string FadeTrack = "es-sprite-fade";
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, ESTimedDespawnSpriteFadeComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
base.OnAppearanceChange(uid, component, ref args);
|
||||
|
||||
if (args.Sprite is not { } sprite)
|
||||
return;
|
||||
|
||||
if (_animationPlayer.HasRunningAnimation(uid, FadeTrack))
|
||||
return;
|
||||
|
||||
if (!AppearanceSystem.TryGetData<TimeSpan>(uid, ESTimedDespawnVisuals.DespawnTime, out var time, args.Component))
|
||||
return;
|
||||
|
||||
var duration = time - _timing.CurTime;
|
||||
|
||||
var animation = new Animation
|
||||
{
|
||||
Length = duration,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty
|
||||
{
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color, MathF.Max((float) (duration - component.FadeTime).TotalSeconds, 0f)),
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), (float) component.FadeTime.TotalSeconds, Easings.OutSine),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
_animationPlayer.Play(uid, animation, FadeTrack);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@ namespace Content.Server.Entry
|
|||
public static class IgnoredComponents
|
||||
{
|
||||
public static string[] List => new[] {
|
||||
// ES START
|
||||
"ESTimedDespawnLightFade",
|
||||
"ESTimedDespawnSpriteFade",
|
||||
// ES END
|
||||
"ConstructionGhost",
|
||||
"IconSmooth",
|
||||
"InteractionOutline",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.Shared._ES.Core.Timer.Components;
|
||||
|
||||
/// <summary>
|
||||
/// ES-specific version of <see cref="TimedDespawnComponent"/> with networking capabilities
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
[Access(typeof(ESTimedDespawnSystem), Other = AccessPermissions.None)]
|
||||
public sealed partial class ESTimedDespawnComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long the entity will exist before despawning
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan Lifetime;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the entity will despawn
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan DespawnTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the entity spawned
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan SpawnTime => DespawnTime - Lifetime;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ESTimedDespawnVisuals : byte
|
||||
{
|
||||
DespawnTime,
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
using Content.Shared._ES.Core.Timer.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Spawners;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared._ES.Core.Timer;
|
||||
|
||||
/// <summary>
|
||||
/// This handles <see cref="ESTimedDespawnComponent"/>
|
||||
/// </summary>
|
||||
public sealed class ESTimedDespawnSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _toDelete = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ESTimedDespawnComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<ESTimedDespawnComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
SetLifetime(ent.AsNullable(), ent.Comp.Lifetime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the lifetime of the entity, adjusting the despawn time to compensate
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public void SetLifetime(Entity<ESTimedDespawnComponent?> ent, TimeSpan lifetime)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
DebugTools.Assert(lifetime >= TimeSpan.Zero, "Lifetime must be positive");
|
||||
|
||||
ent.Comp.Lifetime = lifetime;
|
||||
ent.Comp.DespawnTime = _timing.CurTime + ent.Comp.Lifetime;
|
||||
_appearance.SetData(ent, ESTimedDespawnVisuals.DespawnTime, ent.Comp.DespawnTime);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of time this entity will be alive before despawning
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public TimeSpan GetLifetime(Entity<ESTimedDespawnComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return TimeSpan.Zero;
|
||||
return ent.Comp.Lifetime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the lifetime of the entity, adjusting the despawn time to compensate
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public void SetDespawnTime(Entity<ESTimedDespawnComponent?> ent, TimeSpan despawnTime)
|
||||
{
|
||||
SetLifetime(ent, despawnTime - _timing.CurTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time at which the entity will despawn
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public TimeSpan GetDespawnTime(Entity<ESTimedDespawnComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return TimeSpan.Zero;
|
||||
return ent.Comp.DespawnTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns how far along through the timedDespawn the entity is (as a percentage [0, 1])
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public double GetProgress(Entity<ESTimedDespawnComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return 0;
|
||||
|
||||
return Math.Clamp((_timing.CurTime - ent.Comp.SpawnTime) / ent.Comp.Lifetime, 0, 1);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_toDelete.Clear();
|
||||
var query = EntityQueryEnumerator<ESTimedDespawnComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_timing.CurTime < comp.DespawnTime)
|
||||
continue;
|
||||
|
||||
_toDelete.Add(uid);
|
||||
}
|
||||
|
||||
foreach (var toDelete in _toDelete)
|
||||
{
|
||||
// Same event as engine TimedDespawn
|
||||
var ev = new TimedDespawnEvent();
|
||||
RaiseLocalEvent(toDelete, ref ev);
|
||||
PredictedQueueDel(toDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._ES.Sparks.Components;
|
||||
|
||||
/// <summary>
|
||||
/// An entity that sparks when damaged by something
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(ESSparkOnHitSystem))]
|
||||
public sealed partial class ESSparkOnHitComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of damage that needs to be dealt to cause sparks
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FixedPoint2 Threshold = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Number of sparks
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Count = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Spark prototypes
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId SparkPrototype = ESSparksSystem.DefaultSparks;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Content.Shared._ES.Sparks.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
||||
namespace Content.Shared._ES.Sparks;
|
||||
|
||||
public sealed class ESSparkOnHitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ESSparksSystem _sparks = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ESSparkOnHitComponent, DamageChangedEvent>(OnDamaged);
|
||||
}
|
||||
|
||||
private void OnDamaged(Entity<ESSparkOnHitComponent> ent, ref DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta is null)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta.GetTotal() < ent.Comp.Threshold)
|
||||
return;
|
||||
|
||||
_sparks.DoSparks(ent, ent.Comp.Count, ent.Comp.SparkPrototype);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using Content.Shared.Physics;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared._ES.Sparks;
|
||||
|
||||
public sealed class ESSparksSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public static readonly EntProtoId DefaultSparks = "ESEffectSparks";
|
||||
|
||||
public void DoSparks(EntityUid source, int number = 4, EntProtoId? sparksPrototype = null)
|
||||
{
|
||||
var coords = _transform.GetMapCoordinates(source);
|
||||
DoSparks(coords, number, sparksPrototype, source);
|
||||
}
|
||||
|
||||
public void DoSparks(EntityCoordinates coordinates, int number = 4, EntProtoId? sparksPrototype = null, EntityUid? ignored = null)
|
||||
{
|
||||
var mapCoordinates = _transform.ToMapCoordinates(coordinates);
|
||||
DoSparks(mapCoordinates, number, sparksPrototype, ignored);
|
||||
}
|
||||
|
||||
public void DoSparks(MapCoordinates coordinates, int number = 4, EntProtoId? sparksPrototype = null, EntityUid? ignored = null)
|
||||
{
|
||||
if (_net.IsClient)
|
||||
return;
|
||||
|
||||
sparksPrototype ??= DefaultSparks;
|
||||
|
||||
var angleDelta = (Angle) (MathF.Tau / number);
|
||||
var angle = _random.NextAngle();
|
||||
for (var i = 0; i < number; i++)
|
||||
{
|
||||
var sparks = EntityManager.Spawn(sparksPrototype, coordinates, rotation: angle);
|
||||
angle += angleDelta;
|
||||
_throwing.TryThrow(sparks, angle.ToVec(), 2f, animated: false);
|
||||
PreventCollide(sparks, ignored);
|
||||
}
|
||||
}
|
||||
|
||||
private void PreventCollide(EntityUid sparks, EntityUid? ignored)
|
||||
{
|
||||
if (!ignored.HasValue || TerminatingOrDeleted(ignored))
|
||||
return;
|
||||
var comp = EnsureComp<PreventCollideComponent>(sparks);
|
||||
comp.Uid = ignored.Value;
|
||||
Dirty(sparks, comp);
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +88,9 @@
|
|||
collection: GlassBreak
|
||||
- type: PlacementReplacement
|
||||
key: lights
|
||||
# ES START
|
||||
- type: ESSparkOnHit
|
||||
# ES END
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
snap:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
- type: entity
|
||||
id: ESEffectSparks
|
||||
name: sparks
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _ES/Effects/sparks.rsi
|
||||
state: sparks
|
||||
drawdepth: Effects
|
||||
noRot: true
|
||||
loop: false
|
||||
- type: Appearance
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
bodyStatus: InAir
|
||||
sleepingAllowed: false
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.25
|
||||
hard: true
|
||||
restitution: 0.0
|
||||
density: 100
|
||||
mask:
|
||||
- Impassable
|
||||
- type: ESTimedDespawn
|
||||
lifetime: 1.1
|
||||
- type: EmitSoundOnSpawn
|
||||
positional: true
|
||||
sound:
|
||||
collection: sparks
|
||||
params:
|
||||
variation: 0.250
|
||||
- type: ESTimedDespawnSpriteFade
|
||||
fadeTime: 0.3
|
||||
- type: ESTimedDespawnLightFade
|
||||
fadeTime: 0.5
|
||||
- type: AnimationPlayer
|
||||
- type: PointLight
|
||||
radius: 1.5
|
||||
energy: 2.4
|
||||
falloff: 6
|
||||
color: "#FAA019"
|
||||
netsync: false
|
||||
- type: IgnitionSource
|
||||
ignited: true
|
||||
- type: Tag
|
||||
tags:
|
||||
- HideContextMenu
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/29b6d6c129c56124200b7fbe46e41178f5373ca4/icons/effects/effects.dmi",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "sparks",
|
||||
"directions": 4,
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
1
|
||||
],
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
1
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Loading…
Reference in New Issue