From 5053c8afdbd2c18dc6654eae0443ba39eadb1838 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 2 May 2024 12:40:07 +1000 Subject: [PATCH] Fix effects (#27533) * Fix effects - Fix muzzle flash rotations. - Fix effects so they update every frame. - Fix effects tanking client performance. * Fix merge artifact --- .../Animations/TrackUserComponent.cs | 17 +++++ .../MouseRotator/MouseRotatorSystem.cs | 2 +- .../Melee/MeleeWeaponSystem.Effects.cs | 26 ++++++-- .../Weapons/Melee/MeleeWeaponSystem.cs | 7 +- .../Weapons/Ranged/Systems/GunSystem.cs | 65 +++++++++++-------- .../Weapons/Ranged/Systems/GunSystem.cs | 8 +-- .../Weapons/Ranged/Events/MuzzleFlashEvent.cs | 9 +-- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 8 +-- 8 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 Content.Client/Animations/TrackUserComponent.cs diff --git a/Content.Client/Animations/TrackUserComponent.cs b/Content.Client/Animations/TrackUserComponent.cs new file mode 100644 index 0000000000..374c187398 --- /dev/null +++ b/Content.Client/Animations/TrackUserComponent.cs @@ -0,0 +1,17 @@ +using System.Numerics; + +namespace Content.Client.Animations; + +/// +/// Entities with this component tracks the user's world position every frame. +/// +[RegisterComponent] +public sealed partial class TrackUserComponent : Component +{ + public EntityUid? User; + + /// + /// Offset in the direction of the entity's rotation. + /// + public Vector2 Offset = Vector2.Zero; +} diff --git a/Content.Client/MouseRotator/MouseRotatorSystem.cs b/Content.Client/MouseRotator/MouseRotatorSystem.cs index 44e8205355..4894c17c4c 100644 --- a/Content.Client/MouseRotator/MouseRotatorSystem.cs +++ b/Content.Client/MouseRotator/MouseRotatorSystem.cs @@ -49,7 +49,7 @@ public sealed class MouseRotatorSystem : SharedMouseRotatorSystem if (angleDir == curRot.GetCardinalDir()) return; - RaisePredictiveEvent(new RequestMouseRotatorRotationSimpleEvent() + RaisePredictiveEvent(new RequestMouseRotatorRotationSimpleEvent() { Direction = angleDir, }); diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs index c33bc913d3..baac42d193 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Animations; using Content.Client.Weapons.Melee.Components; using Content.Shared.Weapons.Melee; using Robust.Client.Animations; @@ -59,17 +60,20 @@ public sealed partial class MeleeWeaponSystem var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f); var xform = _xformQuery.GetComponent(animationUid); + TrackUserComponent track; switch (arcComponent.Animation) { case WeaponArcAnimation.Slash: - arcComponent.User = user; + track = EnsureComp(animationUid); + track.User = user; _animation.Play(animationUid, GetSlashAnimation(sprite, angle, spriteRotation), SlashAnimationKey); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0.065f, 0.065f + 0.05f), FadeAnimationKey); break; case WeaponArcAnimation.Thrust: - arcComponent.User = user; + track = EnsureComp(animationUid); + track.User = user; _animation.Play(animationUid, GetThrustAnimation(sprite, distance, spriteRotation), ThrustAnimationKey); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0.05f, 0.15f), FadeAnimationKey); @@ -206,15 +210,23 @@ public sealed partial class MeleeWeaponSystem /// /// Updates the effect positions to follow the user /// - void UpdateEffects(float frameTime) + private void UpdateEffects() { - var arcQuery = EntityQueryEnumerator(); - while(arcQuery.MoveNext(out var uid, out var xform, out var arcComponent)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var arcComponent, out var xform)) { if (arcComponent.User == null) continue; - var userPos = TransformSystem.GetWorldPosition(arcComponent.User.Value); - TransformSystem.SetWorldPosition(xform, userPos); + + Vector2 targetPos = TransformSystem.GetWorldPosition(arcComponent.User.Value); + + if (arcComponent.Offset != Vector2.Zero) + { + var entRotation = TransformSystem.GetWorldRotation(xform); + targetPos += entRotation.RotateVec(arcComponent.Offset); + } + + TransformSystem.SetWorldPosition(xform, targetPos); } } } diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index 95de207471..039af55bd0 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -43,10 +43,15 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem UpdatesOutsidePrediction = true; } + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + UpdateEffects(); + } + public override void Update(float frameTime) { base.Update(frameTime); - UpdateEffects(frameTime); if (!Timing.IsFirstTimePredicted) return; diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index 9e50cab3e1..5aba04bdf8 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Animations; using Content.Client.Items; using Content.Client.Weapons.Ranged.Components; using Content.Shared.Camera; @@ -15,6 +16,7 @@ using Robust.Client.Player; using Robust.Shared.Animations; using Robust.Shared.Input; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem; @@ -24,13 +26,14 @@ namespace Content.Client.Weapons.Ranged.Systems; public sealed partial class GunSystem : SharedGunSystem { + [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly AnimationPlayerSystem _animPlayer = default!; [Dependency] private readonly InputSystem _inputSystem = default!; [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; - [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly SharedMapSystem _maps = default!; [ValidatePrototypeId] public const string HitscanProto = "HitscanEffect"; @@ -123,7 +126,7 @@ public sealed partial class GunSystem : SharedGunSystem } }; - _animPlayer.Play(ent, null, anim, "hitscan-effect"); + _animPlayer.Play(ent, anim, "hitscan-effect"); } } @@ -189,6 +192,7 @@ public sealed partial class GunSystem : SharedGunSystem // to just delete the spawned entities. This is for programmer sanity despite the wasted perf. // This also means any ammo specific stuff can be grabbed as necessary. var direction = fromCoordinates.ToMapPos(EntityManager, TransformSystem) - toCoordinates.ToMapPos(EntityManager, TransformSystem); + var worldAngle = direction.ToAngle().Opposite(); foreach (var (ent, shootable) in ammo) { @@ -208,7 +212,7 @@ public sealed partial class GunSystem : SharedGunSystem if (!cartridge.Spent) { SetCartridgeSpent(ent!.Value, cartridge, true); - MuzzleFlash(gunUid, cartridge, user); + MuzzleFlash(gunUid, cartridge, worldAngle, user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); Recoil(user, direction, gun.CameraRecoilScalarModified); // TODO: Can't predict entity deletions. @@ -226,7 +230,7 @@ public sealed partial class GunSystem : SharedGunSystem break; case AmmoComponent newAmmo: - MuzzleFlash(gunUid, newAmmo, user); + MuzzleFlash(gunUid, newAmmo, worldAngle, user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); Recoil(user, direction, gun.CameraRecoilScalarModified); if (IsClientSide(ent!.Value)) @@ -258,33 +262,41 @@ public sealed partial class GunSystem : SharedGunSystem PopupSystem.PopupEntity(message, uid.Value, user.Value); } - protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null) + protected override void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null) { if (!Timing.IsFirstTimePredicted) return; + var gunXform = Transform(gunUid); + var gridUid = gunXform.GridUid; EntityCoordinates coordinates; - if (message.MatchRotation) - coordinates = new EntityCoordinates(uid, Vector2.Zero); - else if (TryComp(uid, out var xform)) - coordinates = xform.Coordinates; + if (TryComp(gridUid, out MapGridComponent? mapGrid)) + { + coordinates = new EntityCoordinates(gridUid.Value, _maps.LocalToGrid(gridUid.Value, mapGrid, gunXform.Coordinates)); + } + else if (gunXform.MapUid != null) + { + coordinates = new EntityCoordinates(gunXform.MapUid.Value, TransformSystem.GetWorldPosition(gunXform)); + } else + { return; - - if (!coordinates.IsValid(EntityManager)) - return; + } var ent = Spawn(message.Prototype, coordinates); + TransformSystem.SetWorldRotationNoLerp(ent, message.Angle); - var effectXform = Transform(ent); - TransformSystem.SetLocalPositionRotation(effectXform, - effectXform.LocalPosition + new Vector2(0f, -0.5f), - effectXform.LocalRotation - MathF.PI / 2); + if (user != null) + { + var track = EnsureComp(ent); + track.User = user; + track.Offset = Vector2.UnitX / 2f; + } var lifetime = 0.4f; - if (TryComp(uid, out var despawn)) + if (TryComp(gunUid, out var despawn)) { lifetime = despawn.Lifetime; } @@ -309,18 +321,17 @@ public sealed partial class GunSystem : SharedGunSystem }; _animPlayer.Play(ent, anim, "muzzle-flash"); - if (!TryComp(uid, out PointLightComponent? light)) + if (!TryComp(gunUid, out PointLightComponent? light)) { light = (PointLightComponent) _factory.GetComponent(typeof(PointLightComponent)); - light.Owner = uid; light.NetSyncEnabled = false; - AddComp(uid, light); + AddComp(gunUid, light); } - Lights.SetEnabled(uid, true, light); - Lights.SetRadius(uid, 2f, light); - Lights.SetColor(uid, Color.FromHex("#cc8e2b"), light); - Lights.SetEnergy(uid, 5f, light); + Lights.SetEnabled(gunUid, true, light); + Lights.SetRadius(gunUid, 2f, light); + Lights.SetColor(gunUid, Color.FromHex("#cc8e2b"), light); + Lights.SetEnergy(gunUid, 5f, light); var animTwo = new Animation() { @@ -352,9 +363,9 @@ public sealed partial class GunSystem : SharedGunSystem } }; - var uidPlayer = EnsureComp(uid); + var uidPlayer = EnsureComp(gunUid); - _animPlayer.Stop(uid, uidPlayer, "muzzle-flash-light"); - _animPlayer.Play(uid, uidPlayer, animTwo,"muzzle-flash-light"); + _animPlayer.Stop(gunUid, uidPlayer, "muzzle-flash-light"); + _animPlayer.Play((gunUid, uidPlayer), animTwo,"muzzle-flash-light"); } } diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 7449b0c59e..f495f29e4a 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -154,7 +154,7 @@ public sealed partial class GunSystem : SharedGunSystem }); SetCartridgeSpent(ent.Value, cartridge, true); - MuzzleFlash(gunUid, cartridge, user); + MuzzleFlash(gunUid, cartridge, mapDirection.ToAngle(), user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); if (cartridge.DeleteOnSpawn) @@ -175,7 +175,7 @@ public sealed partial class GunSystem : SharedGunSystem // Ammo shoots itself case AmmoComponent newAmmo: shotProjectiles.Add(ent!.Value); - MuzzleFlash(gunUid, newAmmo, user); + MuzzleFlash(gunUid, newAmmo, mapDirection.ToAngle(), user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user); break; @@ -326,9 +326,9 @@ public sealed partial class GunSystem : SharedGunSystem protected override void Popup(string message, EntityUid? uid, EntityUid? user) { } - protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null) + protected override void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null) { - var filter = Filter.Pvs(uid, entityManager: EntityManager); + var filter = Filter.Pvs(gunUid, entityManager: EntityManager); if (TryComp(user, out var actor)) filter.RemovePlayer(actor.PlayerSession); diff --git a/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs b/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs index 91f5e6cd86..10d4c2fe3c 100644 --- a/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs @@ -11,15 +11,12 @@ public sealed class MuzzleFlashEvent : EntityEventArgs public NetEntity Uid; public string Prototype; - /// - /// Should the effect match the rotation of the entity. - /// - public bool MatchRotation; + public Angle Angle; - public MuzzleFlashEvent(NetEntity uid, string prototype, bool matchRotation = false) + public MuzzleFlashEvent(NetEntity uid, string prototype, Angle angle) { Uid = uid; Prototype = prototype; - MatchRotation = matchRotation; + Angle = angle; } } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 129d536a70..51e2e1358f 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -136,7 +136,6 @@ public abstract partial class SharedGunSystem : EntitySystem return; gun.ShootCoordinates = GetCoordinates(msg.Coordinates); - Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}"); AttemptShoot(user.Value, ent, gun); } @@ -195,7 +194,6 @@ public abstract partial class SharedGunSystem : EntitySystem if (gun.ShotCounter == 0) return; - Log.Debug($"Stopped shooting {ToPrettyString(uid)}"); gun.ShotCounter = 0; gun.ShootCoordinates = null; Dirty(uid, gun); @@ -461,7 +459,7 @@ public abstract partial class SharedGunSystem : EntitySystem RemCompDeferred(uid); } - protected void MuzzleFlash(EntityUid gun, AmmoComponent component, EntityUid? user = null) + protected void MuzzleFlash(EntityUid gun, AmmoComponent component, Angle worldAngle, EntityUid? user = null) { var attemptEv = new GunMuzzleFlashAttemptEvent(); RaiseLocalEvent(gun, ref attemptEv); @@ -473,7 +471,7 @@ public abstract partial class SharedGunSystem : EntitySystem if (sprite == null) return; - var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, user == gun); + var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, worldAngle); CreateEffect(gun, ev, user); } @@ -522,7 +520,7 @@ public abstract partial class SharedGunSystem : EntitySystem Dirty(gun); } - protected abstract void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null); + protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null); /// /// Used for animated effects on the client. -- 2.52.0