Throwing item scaling animation + recoil (#24724)
This commit is contained in:
87
Content.Client/Throwing/ThrownItemVisualizerSystem.cs
Normal file
87
Content.Client/Throwing/ThrownItemVisualizerSystem.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Camera;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Gravity;
|
using Content.Shared.Gravity;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
@@ -32,6 +33,7 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly ThrownItemSystem _thrownSystem = default!;
|
[Dependency] private readonly ThrownItemSystem _thrownSystem = default!;
|
||||||
|
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default!;
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
|
||||||
public void TryThrow(
|
public void TryThrow(
|
||||||
@@ -114,7 +116,7 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
if (projectileQuery.TryGetComponent(uid, out var proj) && !proj.OnlyCollideWhenShot)
|
if (projectileQuery.TryGetComponent(uid, out var proj) && !proj.OnlyCollideWhenShot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var comp = EnsureComp<ThrownItemComponent>(uid);
|
var comp = new ThrownItemComponent();
|
||||||
comp.Thrower = user;
|
comp.Thrower = user;
|
||||||
|
|
||||||
// Estimate time to arrival so we can apply OnGround status and slow it much faster.
|
// 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
|
else
|
||||||
comp.LandTime = time < FlyTime ? default : comp.ThrownTime + TimeSpan.FromSeconds(time - FlyTime);
|
comp.LandTime = time < FlyTime ? default : comp.ThrownTime + TimeSpan.FromSeconds(time - FlyTime);
|
||||||
comp.PlayLandSound = playSound;
|
comp.PlayLandSound = playSound;
|
||||||
|
AddComp(uid, comp, true);
|
||||||
|
|
||||||
ThrowingAngleComponent? throwingAngle = null;
|
ThrowingAngleComponent? throwingAngle = null;
|
||||||
|
|
||||||
@@ -160,9 +163,13 @@ public sealed class ThrowingSystem : EntitySystem
|
|||||||
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_recoil.KickCamera(user.Value, -direction * 0.3f);
|
||||||
|
|
||||||
// Give thrower an impulse in the other direction
|
// Give thrower an impulse in the other direction
|
||||||
if (user != null &&
|
if (pushbackRatio != 0.0f &&
|
||||||
pushbackRatio != 0.0f &&
|
|
||||||
physics.Mass > 0f &&
|
physics.Mass > 0f &&
|
||||||
TryComp(user.Value, out PhysicsComponent? userPhysics) &&
|
TryComp(user.Value, out PhysicsComponent? userPhysics) &&
|
||||||
_gravity.IsWeightless(user.Value, userPhysics))
|
_gravity.IsWeightless(user.Value, userPhysics))
|
||||||
|
|||||||
@@ -1,54 +1,47 @@
|
|||||||
|
using System.Numerics;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Throwing
|
namespace Content.Shared.Throwing
|
||||||
{
|
{
|
||||||
[RegisterComponent, NetworkedComponent]
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||||
public sealed partial class ThrownItemComponent : Component
|
public sealed partial class ThrownItemComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity that threw this entity.
|
/// The entity that threw this entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||||
public EntityUid? Thrower;
|
public EntityUid? Thrower;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="IGameTiming.CurTime"/> timestamp at which this entity was thrown.
|
/// The <see cref="IGameTiming.CurTime"/> timestamp at which this entity was thrown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||||
public TimeSpan? ThrownTime;
|
public TimeSpan? ThrownTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compared to <see cref="IGameTiming.CurTime"/> to land this entity, if any.
|
/// Compared to <see cref="IGameTiming.CurTime"/> to land this entity, if any.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||||
public TimeSpan? LandTime;
|
public TimeSpan? LandTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not this entity was already landed.
|
/// Whether or not this entity was already landed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||||
public bool Landed;
|
public bool Landed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not to play a sound when the entity lands.
|
/// Whether or not to play a sound when the entity lands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||||
public bool PlayLandSound;
|
public bool PlayLandSound;
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
/// <summary>
|
||||||
public sealed class ThrownItemComponentState : ComponentState
|
/// Used to restore state after the throwing scale animation is finished.
|
||||||
{
|
/// </summary>
|
||||||
public NetEntity? Thrower;
|
[DataField]
|
||||||
|
public Vector2? OriginalScale = null;
|
||||||
public TimeSpan? ThrownTime;
|
|
||||||
|
|
||||||
public TimeSpan? LandTime;
|
|
||||||
|
|
||||||
public bool Landed;
|
|
||||||
|
|
||||||
public bool PlayLandSound;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,41 +36,10 @@ namespace Content.Shared.Throwing
|
|||||||
SubscribeLocalEvent<ThrownItemComponent, PreventCollideEvent>(PreventCollision);
|
SubscribeLocalEvent<ThrownItemComponent, PreventCollideEvent>(PreventCollision);
|
||||||
SubscribeLocalEvent<ThrownItemComponent, ThrownEvent>(ThrowItem);
|
SubscribeLocalEvent<ThrownItemComponent, ThrownEvent>(ThrowItem);
|
||||||
SubscribeLocalEvent<ThrownItemComponent, EntityUnpausedEvent>(OnThrownUnpaused);
|
SubscribeLocalEvent<ThrownItemComponent, EntityUnpausedEvent>(OnThrownUnpaused);
|
||||||
SubscribeLocalEvent<ThrownItemComponent, ComponentGetState>(OnThrownGetState);
|
|
||||||
SubscribeLocalEvent<ThrownItemComponent, ComponentHandleState>(OnThrownHandleState);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<PullStartedMessage>(HandlePullStarted);
|
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)
|
private void OnMapInit(EntityUid uid, ThrownItemComponent component, MapInitEvent args)
|
||||||
{
|
{
|
||||||
component.ThrownTime ??= _gameTiming.CurTime;
|
component.ThrownTime ??= _gameTiming.CurTime;
|
||||||
|
|||||||
Reference in New Issue
Block a user