Files
wwdpublic/Content.Client/Effects/ColorFlashEffectSystem.cs
RedFoxIV a9b48fc290 Fix Sprites Getting Stuck Red when Quickly Damaged (#1738)
# Description

If a damageable entity gets hit three times in a quick succession, it
will get stuck slightly red. This effect is clientside-only, but will be
present on all clients that had this happen in their PVS range.
Presumably this was happening because TryComp returns the requested
component even if it's shut down and is about to be removed.

---

# TODO

- [ ] Find a way to cope with the mental illness this gave me

---

<details><summary><h1>Media</h1></summary>
<p>

<details><summary>Before</summary>
<p>

https://github.com/user-attachments/assets/5f9a8510-8faa-49df-9cac-b9312e330f53

</p>
</details>

<details><summary>After</summary>
<p>

https://github.com/user-attachments/assets/f1d88ebb-3e6c-460d-b4cc-9e93918f1015

</p>
</details>

</p>
</details>

---

# Changelog

🆑
- fix: Fixed damageable entities being stuck red after being damaged
several times in a quick succession.

(cherry picked from commit 2f88303c8a855ebb0064edc7c1d32bf2c0f2684c)
2025-02-15 00:06:14 +03:00

124 lines
4.3 KiB
C#

using Content.Shared.Effects;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Client.Effects;
public sealed class ColorFlashEffectSystem : SharedColorFlashEffectSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
/// <summary>
/// It's a little on the long side but given we use multiple colours denoting what happened it makes it easier to register.
/// </summary>
private const float AnimationLength = 0.30f;
private const string AnimationKey = "color-flash-effect";
public override void Initialize()
{
base.Initialize();
SubscribeAllEvent<ColorFlashEffectEvent>(OnColorFlashEffect);
SubscribeLocalEvent<ColorFlashEffectComponent, AnimationCompletedEvent>(OnEffectAnimationCompleted);
}
public override void RaiseEffect(Color color, List<EntityUid> entities, Filter filter, float? animationLength = null)
{
if (!_timing.IsFirstTimePredicted)
return;
OnColorFlashEffect(new ColorFlashEffectEvent(color, GetNetEntityList(entities), animationLength));
}
private void OnEffectAnimationCompleted(EntityUid uid, ColorFlashEffectComponent component, AnimationCompletedEvent args)
{
if (args.Key != AnimationKey)
return;
if (TryComp<SpriteComponent>(uid, out var sprite))
{
sprite.Color = component.Color;
}
RemCompDeferred<ColorFlashEffectComponent>(uid);
}
private Animation? GetDamageAnimation(EntityUid uid, Color color, SpriteComponent? sprite = null, float? animationLength = null)
{
if (!Resolve(uid, ref sprite, false))
return null;
// 90% of them are going to be this so why allocate a new class.
return new Animation
{
Length = TimeSpan.FromSeconds(animationLength ?? AnimationLength),
AnimationTracks =
{
new AnimationTrackComponentProperty
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Color),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(color, 0f),
new AnimationTrackProperty.KeyFrame(sprite.Color, animationLength ?? AnimationLength)
}
}
}
};
}
private void OnColorFlashEffect(ColorFlashEffectEvent ev)
{
var color = ev.Color;
foreach (var nent in ev.Entities)
{
var ent = GetEntity(nent);
if (Deleted(ent))
{
continue;
}
var player = EnsureComp<AnimationPlayerComponent>(ent);
// Need to stop the existing animation first to ensure the sprite color is fixed.
// Otherwise we might lerp to a red colour instead.
if (_animation.HasRunningAnimation(ent, player, AnimationKey))
{
_animation.Stop(ent, player, AnimationKey);
}
if (!TryComp<SpriteComponent>(ent, out var sprite))
{
continue;
}
// having to check lifestage because trycomp is special needs and may return a component which was shut down via RemCompDeferred.
// EnsureComp isn't, but we want to get the Color value stored in the component, and EnsureComp would overwrite it with the default value.
if (TryComp<ColorFlashEffectComponent>(ent, out var effect) && effect.LifeStage <= ComponentLifeStage.Running)
{
sprite.Color = effect.Color;
}
var animation = GetDamageAnimation(ent, color, sprite, ev.AnimationLength);
if (animation == null)
continue;
var comp = EnsureComp<ColorFlashEffectComponent>(ent);
comp.Color = sprite.Color;
_animation.Play((ent, player), animation, AnimationKey);
}
}
}