From ef55039560814a98517552bd58493fc89a2a4812 Mon Sep 17 00:00:00 2001 From: Kara Date: Tue, 30 Jan 2024 03:50:41 -0700 Subject: [PATCH] Throwing item scaling animation + recoil (#24724) --- .../Throwing/ThrownItemVisualizerSystem.cs | 87 +++++++++++++++++++ Content.Shared/Throwing/ThrowingSystem.cs | 13 ++- .../Throwing/ThrownItemComponent.cs | 31 +++---- Content.Shared/Throwing/ThrownItemSystem.cs | 31 ------- 4 files changed, 109 insertions(+), 53 deletions(-) create mode 100644 Content.Client/Throwing/ThrownItemVisualizerSystem.cs diff --git a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs new file mode 100644 index 0000000000..bbd3673110 --- /dev/null +++ b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs @@ -0,0 +1,87 @@ +using Content.Shared.Throwing; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Shared.Animations; + +namespace Content.Client.Throwing; + +/// +/// Handles animating thrown items. +/// +public sealed class ThrownItemVisualizerSystem : EntitySystem +{ + [Dependency] private readonly AnimationPlayerSystem _anim = default!; + + private const string AnimationKey = "thrown-item"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAutoHandleState); + SubscribeLocalEvent(OnShutdown); + } + + private void OnAutoHandleState(EntityUid uid, ThrownItemComponent component, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(uid, out var sprite)) + return; + + var animationPlayer = EnsureComp(uid); + + if (_anim.HasRunningAnimation(uid, animationPlayer, AnimationKey)) + return; + + var anim = GetAnimation((uid, component, sprite)); + if (anim == null) + return; + + component.OriginalScale = sprite.Scale; + _anim.Play((uid, animationPlayer), anim, AnimationKey); + } + + private void OnShutdown(EntityUid uid, ThrownItemComponent component, ComponentShutdown args) + { + if (!_anim.HasRunningAnimation(uid, AnimationKey)) + return; + + if (TryComp(uid, out var sprite) && component.OriginalScale != null) + sprite.Scale = component.OriginalScale.Value; + + _anim.Stop(uid, AnimationKey); + } + + private static Animation? GetAnimation(Entity ent) + { + if (ent.Comp1.LandTime - ent.Comp1.ThrownTime is not { } length) + return null; + + if (length <= TimeSpan.Zero) + return null; + + length += TimeSpan.FromSeconds(ThrowingSystem.FlyTime); + var scale = ent.Comp2.Scale; + var lenFloat = (float) length.TotalSeconds; + + // TODO use like actual easings here + return new Animation + { + Length = length, + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Scale), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(scale, 0.0f), + new AnimationTrackProperty.KeyFrame(scale * 1.4f, lenFloat * 0.25f), + new AnimationTrackProperty.KeyFrame(scale, lenFloat * 0.75f) + }, + InterpolationMode = AnimationInterpolationMode.Linear + } + } + }; + } +} diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index e631546411..5fe02a0571 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Shared.Administration.Logs; +using Content.Shared.Camera; using Content.Shared.Database; using Content.Shared.Gravity; using Content.Shared.Hands.Components; @@ -32,6 +33,7 @@ public sealed class ThrowingSystem : EntitySystem [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly ThrownItemSystem _thrownSystem = default!; + [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; public void TryThrow( @@ -114,7 +116,7 @@ public sealed class ThrowingSystem : EntitySystem if (projectileQuery.TryGetComponent(uid, out var proj) && !proj.OnlyCollideWhenShot) return; - var comp = EnsureComp(uid); + var comp = new ThrownItemComponent(); comp.Thrower = user; // Estimate time to arrival so we can apply OnGround status and slow it much faster. @@ -126,6 +128,7 @@ public sealed class ThrowingSystem : EntitySystem else comp.LandTime = time < FlyTime ? default : comp.ThrownTime + TimeSpan.FromSeconds(time - FlyTime); comp.PlayLandSound = playSound; + AddComp(uid, comp, true); ThrowingAngleComponent? throwingAngle = null; @@ -160,9 +163,13 @@ public sealed class ThrowingSystem : EntitySystem _physics.SetBodyStatus(physics, BodyStatus.InAir); } + if (user == null) + return; + + _recoil.KickCamera(user.Value, -direction * 0.3f); + // Give thrower an impulse in the other direction - if (user != null && - pushbackRatio != 0.0f && + if (pushbackRatio != 0.0f && physics.Mass > 0f && TryComp(user.Value, out PhysicsComponent? userPhysics) && _gravity.IsWeightless(user.Value, userPhysics)) diff --git a/Content.Shared/Throwing/ThrownItemComponent.cs b/Content.Shared/Throwing/ThrownItemComponent.cs index c6c9c4a446..ab80e07938 100644 --- a/Content.Shared/Throwing/ThrownItemComponent.cs +++ b/Content.Shared/Throwing/ThrownItemComponent.cs @@ -1,54 +1,47 @@ +using System.Numerics; using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Timing; namespace Content.Shared.Throwing { - [RegisterComponent, NetworkedComponent] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class ThrownItemComponent : Component { /// /// The entity that threw this entity. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public EntityUid? Thrower; /// /// The timestamp at which this entity was thrown. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public TimeSpan? ThrownTime; /// /// Compared to to land this entity, if any. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public TimeSpan? LandTime; /// /// Whether or not this entity was already landed. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public bool Landed; /// /// Whether or not to play a sound when the entity lands. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public bool PlayLandSound; - } - - [Serializable, NetSerializable] - public sealed class ThrownItemComponentState : ComponentState - { - public NetEntity? Thrower; - - public TimeSpan? ThrownTime; - - public TimeSpan? LandTime; - - public bool Landed; - public bool PlayLandSound; + /// + /// Used to restore state after the throwing scale animation is finished. + /// + [DataField] + public Vector2? OriginalScale = null; } } diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index 9b7ba3ebb0..8d84cf36fa 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -36,41 +36,10 @@ namespace Content.Shared.Throwing SubscribeLocalEvent(PreventCollision); SubscribeLocalEvent(ThrowItem); SubscribeLocalEvent(OnThrownUnpaused); - SubscribeLocalEvent(OnThrownGetState); - SubscribeLocalEvent(OnThrownHandleState); SubscribeLocalEvent(HandlePullStarted); } - private void OnThrownGetState(EntityUid uid, ThrownItemComponent component, ref ComponentGetState args) - { - // TODO: Throwing needs to handle this properly I just want the bad asserts to stop getting in my way. - TryGetNetEntity(component.Thrower, out var nent); - - args.State = new ThrownItemComponentState() - { - ThrownTime = component.ThrownTime, - LandTime = component.LandTime, - Thrower = nent, - Landed = component.Landed, - PlayLandSound = component.PlayLandSound, - }; - } - - private void OnThrownHandleState(EntityUid uid, ThrownItemComponent component, ref ComponentHandleState args) - { - if (args.Current is not ThrownItemComponentState state) - return; - - TryGetEntity(state.Thrower, out var thrower); - - component.ThrownTime = state.ThrownTime; - component.LandTime = state.LandTime; - component.Thrower = thrower; - component.Landed = state.Landed; - component.PlayLandSound = state.PlayLandSound; - } - private void OnMapInit(EntityUid uid, ThrownItemComponent component, MapInitEvent args) { component.ThrownTime ??= _gameTiming.CurTime; -- 2.52.0