From fae71aeb3eba5a71ebfda3a085093502cee7fd82 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 6 Sep 2022 18:01:35 +1000 Subject: [PATCH] Use red damage animation for guns too (#10938) --- .../Melee/MeleeWeaponSystem.Effects.cs | 112 ++++++++++++++++++ .../Weapons/Melee/MeleeWeaponSystem.cs | 6 +- .../Projectiles/SharedProjectileSystem.cs | 10 +- .../Weapon/Ranged/Systems/GunSystem.cs | 9 +- .../Weapons/DamageEffectComponent.cs | 11 ++ .../Weapons/Melee/DamageEffectEvent.cs | 17 +++ 6 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs create mode 100644 Content.Shared/Weapons/DamageEffectComponent.cs create mode 100644 Content.Shared/Weapons/Melee/DamageEffectEvent.cs diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs new file mode 100644 index 0000000000..50e46c9b36 --- /dev/null +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -0,0 +1,112 @@ +using Content.Shared.Weapons; +using Content.Shared.Weapons.Melee; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Shared.Utility; + +namespace Content.Client.Weapons.Melee; + +public sealed partial class MeleeWeaponSystem +{ + private static readonly Animation DefaultDamageAnimation = new() + { + Length = TimeSpan.FromSeconds(DamageAnimationLength), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Color), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Color.Red, 0f), + new AnimationTrackProperty.KeyFrame(Color.White, DamageAnimationLength) + } + } + } + }; + + private const float DamageAnimationLength = 0.15f; + private const string DamageAnimationKey = "damage-effect"; + + private void InitializeEffect() + { + SubscribeLocalEvent(OnEffectAnimation); + } + + private void OnEffectAnimation(EntityUid uid, DamageEffectComponent component, AnimationCompletedEvent args) + { + if (TryComp(uid, out var sprite)) + { + sprite.Color = component.Color; + } + + RemCompDeferred(uid); + } + + /// + /// Gets the red effect animation whenever the server confirms something is hit + /// + public Animation? GetDamageAnimation(EntityUid uid, SpriteComponent? sprite = null) + { + if (!Resolve(uid, ref sprite, false)) + return null; + + // 90% of them are going to be this so why allocate a new class. + if (sprite.Color.Equals(Color.White)) + return DefaultDamageAnimation; + + return new Animation + { + Length = TimeSpan.FromSeconds(DamageAnimationLength), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Color), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Color.Red * sprite.Color, 0f), + new AnimationTrackProperty.KeyFrame(sprite.Color, DamageAnimationLength) + } + } + } + }; + } + + private void OnDamageEffect(DamageEffectEvent ev) + { + if (Deleted(ev.Entity)) + return; + + var player = EnsureComp(ev.Entity); + + // 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(ev.Entity, player, DamageAnimationKey)) + { + _animation.Stop(ev.Entity, player, DamageAnimationKey); + } + + if (!TryComp(ev.Entity, out var sprite)) + { + return; + } + + if (TryComp(ev.Entity, out var effect)) + { + sprite.Color = effect.Color; + } + + var animation = GetDamageAnimation(ev.Entity, sprite); + + if (animation == null) + return; + + var comp = EnsureComp(ev.Entity); + comp.NetSyncEnabled = false; + comp.Color = sprite.Color; + _animation.Play(player, DefaultDamageAnimation, DamageAnimationKey); + } +} diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index bb7c6e0b1f..8ea211ac9c 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -13,17 +13,19 @@ using static Content.Shared.Weapons.Melee.MeleeWeaponSystemMessages; namespace Content.Client.Weapons.Melee { - [UsedImplicitly] - public sealed class MeleeWeaponSystem : EntitySystem + public sealed partial class MeleeWeaponSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly EffectSystem _effectSystem = default!; public override void Initialize() { + InitializeEffect(); SubscribeNetworkEvent(PlayWeaponArc); SubscribeNetworkEvent(PlayLunge); + SubscribeNetworkEvent(OnDamageEffect); } public override void FrameUpdate(float frameTime) diff --git a/Content.Server/Projectiles/SharedProjectileSystem.cs b/Content.Server/Projectiles/SharedProjectileSystem.cs index ede8964f17..a85d92be00 100644 --- a/Content.Server/Projectiles/SharedProjectileSystem.cs +++ b/Content.Server/Projectiles/SharedProjectileSystem.cs @@ -3,11 +3,14 @@ using Content.Server.Projectiles.Components; using Content.Shared.Camera; using Content.Shared.Damage; using Content.Shared.Database; +using Content.Shared.FixedPoint; using Content.Shared.Projectiles; +using Content.Shared.Weapons.Melee; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Player; using GunSystem = Content.Server.Weapon.Ranged.Systems.GunSystem; namespace Content.Server.Projectiles @@ -45,6 +48,11 @@ namespace Content.Server.Projectiles if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter)) { + if (modifiedDamage.Total > FixedPoint2.Zero) + { + RaiseNetworkEvent(new DamageEffectEvent(otherEntity), Filter.Pvs(otherEntity, entityManager: EntityManager)); + } + _adminLogger.Add(LogType.BulletHit, HasComp(otherEntity) ? LogImpact.Extreme : LogImpact.High, $"Projectile {ToPrettyString(component.Owner):projectile} shot by {ToPrettyString(component.Shooter):user} hit {ToPrettyString(otherEntity):target} and dealt {modifiedDamage.Total:damage} damage"); @@ -65,7 +73,7 @@ namespace Content.Server.Projectiles if (component.ImpactEffect != null && TryComp(uid, out var xform)) { - RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, xform.Coordinates)); + RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, xform.Coordinates), Filter.Pvs(xform.Coordinates, entityMan: EntityManager)); } } } diff --git a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs index a8183ba57a..2b2069fdb1 100644 --- a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs @@ -6,6 +6,8 @@ using Content.Server.Weapon.Ranged.Components; using Content.Shared.Audio; using Content.Shared.Damage; using Content.Shared.Database; +using Content.Shared.FixedPoint; +using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; @@ -124,6 +126,11 @@ public sealed partial class GunSystem : SharedGunSystem if (dmg != null) { + if (dmg.Total > FixedPoint2.Zero) + { + RaiseNetworkEvent(new DamageEffectEvent(result.HitEntity), Filter.Pvs(result.HitEntity, entityManager: EntityManager)); + } + PlayImpactSound(result.HitEntity, dmg, hitscan.Sound, hitscan.ForceSound); if (user != null) @@ -292,7 +299,7 @@ public sealed partial class GunSystem : SharedGunSystem if (sprites.Count > 0) { - RaiseNetworkEvent(new HitscanEvent() + RaiseNetworkEvent(new HitscanEvent { Sprites = sprites, }, Filter.Pvs(fromCoordinates, entityMan: EntityManager)); diff --git a/Content.Shared/Weapons/DamageEffectComponent.cs b/Content.Shared/Weapons/DamageEffectComponent.cs new file mode 100644 index 0000000000..8574e30065 --- /dev/null +++ b/Content.Shared/Weapons/DamageEffectComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Weapons; + +/// +/// Stores the original sprite color for a damaged entity to be able to restore it later. +/// +[RegisterComponent] +public sealed class DamageEffectComponent : Component +{ + [ViewVariables] + public Color Color = Color.White; +} diff --git a/Content.Shared/Weapons/Melee/DamageEffectEvent.cs b/Content.Shared/Weapons/Melee/DamageEffectEvent.cs new file mode 100644 index 0000000000..bf01ef61b7 --- /dev/null +++ b/Content.Shared/Weapons/Melee/DamageEffectEvent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Weapons.Melee; + +/// +/// Raised on the server and sent to a client to play the damage animation. +/// +[Serializable, NetSerializable] +public sealed class DamageEffectEvent : EntityEventArgs +{ + public EntityUid Entity; + + public DamageEffectEvent(EntityUid entity) + { + Entity = entity; + } +}