SS14-26964 Clown Waddling Replicates, etc (#26983)

This commit is contained in:
Hannah Giovanna Dawson
2024-06-04 14:23:09 +01:00
committed by GitHub
parent 58647e7036
commit d4fe7eda51
6 changed files with 213 additions and 122 deletions

View File

@@ -2,94 +2,115 @@
using Content.Client.Buckle; using Content.Client.Buckle;
using Content.Client.Gravity; using Content.Client.Gravity;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Buckle.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Animations; using Robust.Shared.Animations;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems; namespace Content.Client.Movement.Systems;
public sealed class WaddleAnimationSystem : EntitySystem public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{ {
[Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!; [Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!; [Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput); base.Initialize();
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking); SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted); SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned); SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
} }
private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args) private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
{ {
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR. StartWaddling((GetEntity(msg.Entity), comp));
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
{
var stopped = new StoppedWaddlingEvent(entity);
RaiseLocalEvent(entity, ref stopped);
return;
}
// Only start waddling if we're not currently AND we're actually moving.
if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
return;
var started = new StartedWaddlingEvent(entity);
RaiseLocalEvent(entity, ref started);
} }
private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args) private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
{ {
if (_animation.HasRunningAnimation(uid, component.KeyName)) if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StopWaddling((GetEntity(msg.Entity), comp));
}
private void StartWaddling(Entity<WaddleAnimationComponent> entity)
{
if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return; return;
if (!TryComp<InputMoverComponent>(uid, out var mover)) if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return; return;
if (_gravity.IsWeightless(uid)) if (_gravity.IsWeightless(entity.Owner))
return; return;
if (!_actionBlocker.CanMove(entity.Owner, mover))
if (!_actionBlocker.CanMove(uid, mover))
return; return;
// Do nothing if buckled in // Do nothing if buckled in
if (_buckle.IsBuckled(uid)) if (_buckle.IsBuckled(entity.Owner))
return; return;
// Do nothing if crit or dead (for obvious reasons) // Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(uid)) if (_mobState.IsIncapacitated(entity.Owner))
return; return;
var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity; PlayWaddleAnimationUsing(
var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength; (entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
component.LastStep = !component.LastStep; private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
component.IsCurrentlyWaddling = true; {
return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
}
private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
{
return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
}
private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
{
if (args.Key != entity.Comp.KeyName)
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
_animation.Stop(entity.Owner, entity.Comp.KeyName);
if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
return;
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
}
private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
{
entity.Comp.LastStep = !entity.Comp.LastStep;
var anim = new Animation() var anim = new Animation()
{ {
@@ -116,58 +137,13 @@ public sealed class WaddleAnimationSystem : EntitySystem
KeyFrames = KeyFrames =
{ {
new AnimationTrackProperty.KeyFrame(new Vector2(), 0), new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2), new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2), new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
} }
} }
} }
}; };
_animation.Play(uid, anim, component.KeyName); _animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
{
StopWaddling(uid, component);
}
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
{
var started = new StartedWaddlingEvent(uid);
RaiseLocalEvent(uid, ref started);
}
private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
{
StopWaddling(uid, component);
}
private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
{
StopWaddling(uid, component);
}
private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
{
StopWaddling(uid, component);
}
private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
{
if (!component.IsCurrentlyWaddling)
return;
_animation.Stop(uid, component.KeyName);
if (!TryComp<SpriteComponent>(uid, out var sprite))
{
return;
}
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
component.IsCurrentlyWaddling = false;
} }
} }

View File

@@ -0,0 +1,5 @@
using Content.Shared.Movement.Systems;
namespace Content.Server.Movement.Systems;
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem;

View File

@@ -1,35 +1,36 @@
using System.Numerics; using System.Numerics;
using Robust.Shared.GameStates;
namespace Content.Shared.Clothing.Components; namespace Content.Shared.Clothing.Components;
/// <summary> /// <summary>
/// Defines something as causing waddling when worn. /// Defines something as causing waddling when worn.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class WaddleWhenWornComponent : Component public sealed partial class WaddleWhenWornComponent : Component
{ {
///<summary> ///<summary>
/// How high should they hop during the waddle? Higher hop = more energy. /// How high should they hop during the waddle? Higher hop = more energy.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f); public Vector2 HopIntensity = new(0, 0.25f);
/// <summary> /// <summary>
/// How far should they rock backward and forward during the waddle? /// How far should they rock backward and forward during the waddle?
/// Each step will alternate between this being a positive and negative rotation. More rock = more scary. /// Each step will alternate between this being a positive and negative rotation. More rock = more scary.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f; public float TumbleIntensity = 20.0f;
/// <summary> /// <summary>
/// How long should a complete step take? Less time = more chaos. /// How long should a complete step take? Less time = more chaos.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public float AnimationLength = 0.66f; public float AnimationLength = 0.66f;
/// <summary> /// <summary>
/// How much shorter should the animation be when running? /// How much shorter should the animation be when running?
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f; public float RunAnimationLengthMultiplier = 0.568f;
} }

View File

@@ -1,8 +1,9 @@
using Content.Shared.Clothing.Components; using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
namespace Content.Client.Clothing.Systems; namespace Content.Shared.Clothing.EntitySystems;
public sealed class WaddleClothingSystem : EntitySystem public sealed class WaddleClothingSystem : EntitySystem
{ {
@@ -10,13 +11,13 @@ public sealed class WaddleClothingSystem : EntitySystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<WaddleWhenWornComponent, GotEquippedEvent>(OnGotEquipped); SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<WaddleWhenWornComponent, GotUnequippedEvent>(OnGotUnequipped); SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
} }
private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args) private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotEquippedEvent args)
{ {
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Equipee); var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Wearer);
waddleAnimComp.AnimationLength = comp.AnimationLength; waddleAnimComp.AnimationLength = comp.AnimationLength;
waddleAnimComp.HopIntensity = comp.HopIntensity; waddleAnimComp.HopIntensity = comp.HopIntensity;
@@ -24,8 +25,8 @@ public sealed class WaddleClothingSystem : EntitySystem
waddleAnimComp.TumbleIntensity = comp.TumbleIntensity; waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
} }
private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args) private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, ClothingGotUnequippedEvent args)
{ {
RemComp<WaddleAnimationComponent>(args.Equipee); RemComp<WaddleAnimationComponent>(args.Wearer);
} }
} }

View File

@@ -1,31 +1,32 @@
using System.Numerics; using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Components; namespace Content.Shared.Movement.Components;
/// <summary> /// <summary>
/// Declares that an entity has started to waddle like a duck/clown. /// Declares that an entity has started to waddle like a duck/clown.
/// </summary> /// </summary>
/// <param name="Entity">The newly be-waddled.</param> /// <param name="entity">The newly be-waddled.</param>
[ByRefEvent] [Serializable, NetSerializable]
public record struct StartedWaddlingEvent(EntityUid Entity) public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs
{ {
public EntityUid Entity = Entity; public NetEntity Entity = entity;
} }
/// <summary> /// <summary>
/// Declares that an entity has stopped waddling like a duck/clown. /// Declares that an entity has stopped waddling like a duck/clown.
/// </summary> /// </summary>
/// <param name="Entity">The former waddle-er.</param> /// <param name="entity">The former waddle-er.</param>
[ByRefEvent] [Serializable, NetSerializable]
public record struct StoppedWaddlingEvent(EntityUid Entity) public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs
{ {
public EntityUid Entity = Entity; public NetEntity Entity = entity;
} }
/// <summary> /// <summary>
/// Defines something as having a waddle animation when it moves. /// Defines something as having a waddle animation when it moves.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent, AutoGenerateComponentState]
public sealed partial class WaddleAnimationComponent : Component public sealed partial class WaddleAnimationComponent : Component
{ {
/// <summary> /// <summary>
@@ -38,26 +39,26 @@ public sealed partial class WaddleAnimationComponent : Component
///<summary> ///<summary>
/// How high should they hop during the waddle? Higher hop = more energy. /// How high should they hop during the waddle? Higher hop = more energy.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f); public Vector2 HopIntensity = new(0, 0.25f);
/// <summary> /// <summary>
/// How far should they rock backward and forward during the waddle? /// How far should they rock backward and forward during the waddle?
/// Each step will alternate between this being a positive and negative rotation. More rock = more scary. /// Each step will alternate between this being a positive and negative rotation. More rock = more scary.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f; public float TumbleIntensity = 20.0f;
/// <summary> /// <summary>
/// How long should a complete step take? Less time = more chaos. /// How long should a complete step take? Less time = more chaos.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public float AnimationLength = 0.66f; public float AnimationLength = 0.66f;
/// <summary> /// <summary>
/// How much shorter should the animation be when running? /// How much shorter should the animation be when running?
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f; public float RunAnimationLengthMultiplier = 0.568f;
/// <summary> /// <summary>
@@ -68,5 +69,6 @@ public sealed partial class WaddleAnimationComponent : Component
/// <summary> /// <summary>
/// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state. /// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state.
/// </summary> /// </summary>
[AutoNetworkedField]
public bool IsCurrentlyWaddling; public bool IsCurrentlyWaddling;
} }

View File

@@ -0,0 +1,106 @@
using Content.Shared.Buckle.Components;
using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Content.Shared.Stunnable;
using Robust.Shared.Timing;
namespace Content.Shared.Movement.Systems;
public abstract class SharedWaddleAnimationSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
// Startup
SubscribeLocalEvent<WaddleAnimationComponent, ComponentStartup>(OnComponentStartup);
// Start moving possibilities
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
SubscribeLocalEvent<WaddleAnimationComponent, StoodEvent>(OnStood);
// Stop moving possibilities
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref StunnedEvent _) => StopWaddling(ent));
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref DownedEvent _) => StopWaddling(ent));
SubscribeLocalEvent((Entity<WaddleAnimationComponent> ent, ref BuckleChangeEvent _) => StopWaddling(ent));
SubscribeLocalEvent<WaddleAnimationComponent, GravityChangedEvent>(OnGravityChanged);
}
private void OnGravityChanged(Entity<WaddleAnimationComponent> ent, ref GravityChangedEvent args)
{
if (!args.HasGravity && ent.Comp.IsCurrentlyWaddling)
StopWaddling(ent);
}
private void OnComponentStartup(Entity<WaddleAnimationComponent> entity, ref ComponentStartup args)
{
if (!TryComp<InputMoverComponent>(entity.Owner, out var moverComponent))
return;
// If the waddler is currently moving, make them start waddling
if ((moverComponent.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.AnyDirection)
{
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
}
private void OnMovementInput(Entity<WaddleAnimationComponent> entity, ref MoveInputEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!args.HasDirectionalMovement && entity.Comp.IsCurrentlyWaddling)
{
StopWaddling(entity);
return;
}
// Only start waddling if we're not currently AND we're actually moving.
if (entity.Comp.IsCurrentlyWaddling || !args.HasDirectionalMovement)
return;
entity.Comp.IsCurrentlyWaddling = true;
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
private void OnStood(Entity<WaddleAnimationComponent> entity, ref StoodEvent args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
{
return;
}
if ((mover.HeldMoveButtons & MoveButtons.AnyDirection) == MoveButtons.None)
return;
if (entity.Comp.IsCurrentlyWaddling)
return;
entity.Comp.IsCurrentlyWaddling = true;
RaiseNetworkEvent(new StartedWaddlingEvent(GetNetEntity(entity.Owner)));
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
entity.Comp.IsCurrentlyWaddling = false;
RaiseNetworkEvent(new StoppedWaddlingEvent(GetNetEntity(entity.Owner)));
}
}