From 2f624dc53b5914f35c7817b2fcce8e5496a993e5 Mon Sep 17 00:00:00 2001 From: COTE-LAPYX Date: Thu, 18 Apr 2024 20:31:28 +0300 Subject: [PATCH] =?UTF-8?q?=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=BE=D0=B4=D0=B5=D1=82=D1=8B=D1=85=20=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B8=D0=BD=D0=BA=D0=B0=D1=85=20=D0=BA=D0=BB=D0=BE?= =?UTF-8?q?=D1=83=D0=BD=D0=B0=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=85=D0=BE=D0=B4=D1=8C=D0=B1=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clothing/Systems/WaddleClothingSystem.cs | 31 ++++ .../Movement/Systems/WaddleAnimationSystem.cs | 135 ++++++++++++++++++ .../Components/WaddleWhenWornComponent.cs | 35 +++++ .../Components/WaddleAnimationComponent.cs | 72 ++++++++++ .../Entities/Clothing/Shoes/specific.yml | 1 + 5 files changed, 274 insertions(+) create mode 100644 Content.Client/Clothing/Systems/WaddleClothingSystem.cs create mode 100644 Content.Client/Movement/Systems/WaddleAnimationSystem.cs create mode 100644 Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs create mode 100644 Content.Shared/Movement/Components/WaddleAnimationComponent.cs diff --git a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs b/Content.Client/Clothing/Systems/WaddleClothingSystem.cs new file mode 100644 index 00000000000..b8ac3c207bf --- /dev/null +++ b/Content.Client/Clothing/Systems/WaddleClothingSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Movement.Components; +using Content.Shared.Inventory.Events; + +namespace Content.Client.Clothing.Systems; + +public sealed class WaddleClothingSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); + } + + private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args) + { + var waddleAnimComp = EnsureComp(args.Equipee); + + waddleAnimComp.AnimationLength = comp.AnimationLength; + waddleAnimComp.HopIntensity = comp.HopIntensity; + waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier; + waddleAnimComp.TumbleIntensity = comp.TumbleIntensity; + } + + private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args) + { + RemComp(args.Equipee); + } +} diff --git a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs new file mode 100644 index 00000000000..fe010500c59 --- /dev/null +++ b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs @@ -0,0 +1,135 @@ +using System.Numerics; +using Content.Client.Gravity; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +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 +{ + [Dependency] private readonly AnimationPlayerSystem _animation = default!; + [Dependency] private readonly GravitySystem _gravity = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnMovementInput); + SubscribeLocalEvent(OnStartedWalking); + SubscribeLocalEvent(OnStoppedWalking); + SubscribeLocalEvent(OnAnimationCompleted); + } + + private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, 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 && component.IsCurrentlyWaddling) + { + component.IsCurrentlyWaddling = false; + + 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; + + component.IsCurrentlyWaddling = true; + + var started = new StartedWaddlingEvent(entity); + + RaiseLocalEvent(entity, ref started); + } + + private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args) + { + if (_animation.HasRunningAnimation(uid, component.KeyName)) + { + return; + } + + if (!TryComp(uid, out var mover)) + { + return; + } + + if (_gravity.IsWeightless(uid)) + { + return; + } + + var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity; + var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength; + + component.LastStep = !component.LastStep; + component.IsCurrentlyWaddling = true; + + var anim = new Animation() + { + Length = TimeSpan.FromSeconds(len), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Rotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2), + } + }, + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(new Vector2(), 0), + new AnimationTrackProperty.KeyFrame(component.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) + { + _animation.Stop(uid, component.KeyName); + + if (!TryComp(uid, out var sprite)) + { + return; + } + + sprite.Offset = new Vector2(); + sprite.Rotation = Angle.FromDegrees(0); + component.IsCurrentlyWaddling = false; + } + + private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args) + { + var started = new StartedWaddlingEvent(uid); + + RaiseLocalEvent(uid, ref started); + } +} diff --git a/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs b/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs new file mode 100644 index 00000000000..5cd7a724577 --- /dev/null +++ b/Content.Shared/Clothing/Components/WaddleWhenWornComponent.cs @@ -0,0 +1,35 @@ +using System.Numerics; + +namespace Content.Shared.Clothing.Components; + +/// +/// Defines something as causing waddling when worn. +/// +[RegisterComponent] +public sealed partial class WaddleWhenWornComponent : Component +{ + /// + /// How high should they hop during the waddle? Higher hop = more energy. + /// + [DataField] + public Vector2 HopIntensity = new(0, 0.25f); + + /// + /// 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. + /// + [DataField] + public float TumbleIntensity = 20.0f; + + /// + /// How long should a complete step take? Less time = more chaos. + /// + [DataField] + public float AnimationLength = 0.66f; + + /// + /// How much shorter should the animation be when running? + /// + [DataField] + public float RunAnimationLengthMultiplier = 0.568f; +} diff --git a/Content.Shared/Movement/Components/WaddleAnimationComponent.cs b/Content.Shared/Movement/Components/WaddleAnimationComponent.cs new file mode 100644 index 00000000000..712c511931c --- /dev/null +++ b/Content.Shared/Movement/Components/WaddleAnimationComponent.cs @@ -0,0 +1,72 @@ +using System.Numerics; + +namespace Content.Shared.Movement.Components; + +/// +/// Declares that an entity has started to waddle like a duck/clown. +/// +/// The newly be-waddled. +[ByRefEvent] +public record struct StartedWaddlingEvent(EntityUid Entity) +{ + public EntityUid Entity = Entity; +} + +/// +/// Declares that an entity has stopped waddling like a duck/clown. +/// +/// The former waddle-er. +[ByRefEvent] +public record struct StoppedWaddlingEvent(EntityUid Entity) +{ + public EntityUid Entity = Entity; +} + +/// +/// Defines something as having a waddle animation when it moves. +/// +[RegisterComponent] +public sealed partial class WaddleAnimationComponent : Component +{ + /// + /// What's the name of this animation? Make sure it's unique so it can play along side other animations. + /// This prevents someone accidentally causing two identical waddling effects to play on someone at the same time. + /// + [DataField] + public string KeyName = "Waddle"; + + /// + /// How high should they hop during the waddle? Higher hop = more energy. + /// + [DataField] + public Vector2 HopIntensity = new(0, 0.25f); + + /// + /// 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. + /// + [DataField] + public float TumbleIntensity = 20.0f; + + /// + /// How long should a complete step take? Less time = more chaos. + /// + [DataField] + public float AnimationLength = 0.66f; + + /// + /// How much shorter should the animation be when running? + /// + [DataField] + public float RunAnimationLengthMultiplier = 0.568f; + + /// + /// Stores which step we made last, so if someone cancels out of the animation mid-step then restarts it looks more natural. + /// + public bool LastStep; + + /// + /// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state. + /// + public bool IsCurrentlyWaddling; +} diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index c0694fdb9e6..6438f61c7c7 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -15,6 +15,7 @@ parent: [ClothingShoesBaseButcherable, ClothingSlotBase] id: ClothingShoesClownBase components: + - type: WaddleWhenWorn - type: ItemSlots slots: item: