Throwing item scaling animation + recoil (#24724)

(cherry picked from commit ef55039560814a98517552bd58493fc89a2a4812)
This commit is contained in:
Kara
2024-01-30 03:50:41 -07:00
committed by Debug
parent 2a0f83b7bc
commit 609b0b7421
4 changed files with 109 additions and 53 deletions

View File

@@ -0,0 +1,87 @@
using Content.Shared.Throwing;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
namespace Content.Client.Throwing;
/// <summary>
/// Handles animating thrown items.
/// </summary>
public sealed class ThrownItemVisualizerSystem : EntitySystem
{
[Dependency] private readonly AnimationPlayerSystem _anim = default!;
private const string AnimationKey = "thrown-item";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ThrownItemComponent, AfterAutoHandleStateEvent>(OnAutoHandleState);
SubscribeLocalEvent<ThrownItemComponent, ComponentShutdown>(OnShutdown);
}
private void OnAutoHandleState(EntityUid uid, ThrownItemComponent component, ref AfterAutoHandleStateEvent args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
var animationPlayer = EnsureComp<AnimationPlayerComponent>(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<SpriteComponent>(uid, out var sprite) && component.OriginalScale != null)
sprite.Scale = component.OriginalScale.Value;
_anim.Stop(uid, AnimationKey);
}
private static Animation? GetAnimation(Entity<ThrownItemComponent, SpriteComponent> 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
}
}
};
}
}

View File

@@ -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<ThrownItemComponent>(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))

View File

@@ -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
{
/// <summary>
/// The entity that threw this entity.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public EntityUid? Thrower;
/// <summary>
/// The <see cref="IGameTiming.CurTime"/> timestamp at which this entity was thrown.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan? ThrownTime;
/// <summary>
/// Compared to <see cref="IGameTiming.CurTime"/> to land this entity, if any.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan? LandTime;
/// <summary>
/// Whether or not this entity was already landed.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool Landed;
/// <summary>
/// Whether or not to play a sound when the entity lands.
/// </summary>
[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;
/// <summary>
/// Used to restore state after the throwing scale animation is finished.
/// </summary>
[DataField]
public Vector2? OriginalScale = null;
}
}

View File

@@ -36,41 +36,10 @@ namespace Content.Shared.Throwing
SubscribeLocalEvent<ThrownItemComponent, PreventCollideEvent>(PreventCollision);
SubscribeLocalEvent<ThrownItemComponent, ThrownEvent>(ThrowItem);
SubscribeLocalEvent<ThrownItemComponent, EntityUnpausedEvent>(OnThrownUnpaused);
SubscribeLocalEvent<ThrownItemComponent, ComponentGetState>(OnThrownGetState);
SubscribeLocalEvent<ThrownItemComponent, ComponentHandleState>(OnThrownHandleState);
SubscribeLocalEvent<PullStartedMessage>(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;