using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
-using Content.Shared.Buckle.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
-using Content.Shared.Movement.Events;
-using Content.Shared.StatusEffect;
-using Content.Shared.Stunnable;
+using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
-using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
-public sealed class WaddleAnimationSystem : EntitySystem
+public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
- SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
- SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
- SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
+ base.Initialize();
+
+ SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
- SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned);
- SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
- SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
+ SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
}
- 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
- // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
- if (!_timing.IsFirstTimePredicted)
- {
+ if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
+ StartWaddling((GetEntity(msg.Entity), comp));
+ }
+
+ private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
+ {
+ 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;
- }
- if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
- {
- var stopped = new StoppedWaddlingEvent(entity);
+ if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
+ return;
- RaiseLocalEvent(entity, ref stopped);
+ if (_gravity.IsWeightless(entity.Owner))
+ return;
+ if (!_actionBlocker.CanMove(entity.Owner, mover))
return;
- }
- // Only start waddling if we're not currently AND we're actually moving.
- if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
+ // Do nothing if buckled in
+ if (_buckle.IsBuckled(entity.Owner))
return;
- var started = new StartedWaddlingEvent(entity);
+ // Do nothing if crit or dead (for obvious reasons)
+ if (_mobState.IsIncapacitated(entity.Owner))
+ return;
- RaiseLocalEvent(entity, ref started);
+ PlayWaddleAnimationUsing(
+ (entity.Owner, entity.Comp),
+ CalculateAnimationLength(entity.Comp, mover),
+ CalculateTumbleIntensity(entity.Comp)
+ );
}
- private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
+ private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
{
- if (_animation.HasRunningAnimation(uid, component.KeyName))
- return;
+ 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;
+ }
- if (!TryComp<InputMoverComponent>(uid, out var mover))
+ private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
+ {
+ if (args.Key != entity.Comp.KeyName)
return;
- if (_gravity.IsWeightless(uid))
+ if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
+ PlayWaddleAnimationUsing(
+ (entity.Owner, entity.Comp),
+ CalculateAnimationLength(entity.Comp, mover),
+ CalculateTumbleIntensity(entity.Comp)
+ );
+ }
- if (!_actionBlocker.CanMove(uid, mover))
+ private void StopWaddling(Entity<WaddleAnimationComponent> entity)
+ {
+ if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
- // Do nothing if buckled in
- if (_buckle.IsBuckled(uid))
- return;
+ _animation.Stop(entity.Owner, entity.Comp.KeyName);
- // Do nothing if crit or dead (for obvious reasons)
- if (_mobState.IsIncapacitated(uid))
+ if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
return;
- var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
- var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
+ sprite.Offset = new Vector2();
+ sprite.Rotation = Angle.FromDegrees(0);
+ }
- component.LastStep = !component.LastStep;
- component.IsCurrentlyWaddling = true;
+ private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
+ {
+ entity.Comp.LastStep = !entity.Comp.LastStep;
var anim = new Animation()
{
KeyFrames =
{
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),
}
}
}
};
- _animation.Play(uid, anim, component.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;
+ _animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
}
using System.Numerics;
+using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Components;
/// <summary>
/// Declares that an entity has started to waddle like a duck/clown.
/// </summary>
-/// <param name="Entity">The newly be-waddled.</param>
-[ByRefEvent]
-public record struct StartedWaddlingEvent(EntityUid Entity)
+/// <param name="entity">The newly be-waddled.</param>
+[Serializable, NetSerializable]
+public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
- public EntityUid Entity = Entity;
+ public NetEntity Entity = entity;
}
/// <summary>
/// Declares that an entity has stopped waddling like a duck/clown.
/// </summary>
-/// <param name="Entity">The former waddle-er.</param>
-[ByRefEvent]
-public record struct StoppedWaddlingEvent(EntityUid Entity)
+/// <param name="entity">The former waddle-er.</param>
+[Serializable, NetSerializable]
+public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
- public EntityUid Entity = Entity;
+ public NetEntity Entity = entity;
}
/// <summary>
/// Defines something as having a waddle animation when it moves.
/// </summary>
-[RegisterComponent]
+[RegisterComponent, AutoGenerateComponentState]
public sealed partial class WaddleAnimationComponent : Component
{
/// <summary>
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public Vector2 HopIntensity = new(0, 0.25f);
/// <summary>
/// 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.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public float TumbleIntensity = 20.0f;
/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public float AnimationLength = 0.66f;
/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;
/// <summary>
/// <summary>
/// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state.
/// </summary>
+ [AutoNetworkedField]
public bool IsCurrentlyWaddling;
}
--- /dev/null
+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)));
+ }
+}