Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clown shoes make you waddle, as God intended #26338

Merged
merged 6 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Content.Client/Clothing/Systems/Clown/WaddleClothingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Content.Shared.Clothing.Components.Clown;
using Content.Shared.Clown;
using Content.Shared.Inventory.Events;

namespace Content.Client.Clothing.Systems.Clown;
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved

public sealed class WaddleClothingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<WaddleComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<WaddleComponent, GotUnequippedEvent>(OnGotUnequipped);
}

private void OnGotEquipped(EntityUid entity, WaddleComponent comp, GotEquippedEvent args)
{
EnsureComp<WaddleAnimationComponent>(args.Equipee);
}

private void OnGotUnequipped(EntityUid entity, WaddleComponent comp, GotUnequippedEvent args)
{
RemComp<WaddleAnimationComponent>(args.Equipee);
}
}
130 changes: 130 additions & 0 deletions Content.Client/Clown/WaddleAnimationSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Numerics;
using Content.Client.Gravity;
using Content.Shared.Clown;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.Timing;

namespace Content.Client.Clown;
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved

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<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(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.Component.HeldMoveButtons == MoveButtons.None && component.IsCurrentlyWaddling)
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved
{
component.IsCurrentlyWaddling = false;

RaiseLocalEvent(entity, new StoppedWaddlingEvent(entity));

return;
}

if (component.IsCurrentlyWaddling)
return;

component.IsCurrentlyWaddling = true;

RaiseLocalEvent(entity, new StartedWaddlingEvent(entity));
}

private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
{
if (_animation.HasRunningAnimation(uid, component.KeyName))
{
return;
}

if (!TryComp<InputMoverComponent>(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 / 2 : 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<SpriteComponent>(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)
{
RaiseLocalEvent(uid, new StartedWaddlingEvent(uid));
}
}
29 changes: 29 additions & 0 deletions Content.Shared/Clothing/Components/Clown/Waddle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Numerics;
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved

namespace Content.Shared.Clothing.Components.Clown;
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Defines something as causing waddling when worn.
/// </summary>
[RegisterComponent]
public sealed partial class WaddleComponent : Component
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved
{
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[DataField]
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]
public float TumbleIntensity = 20.0f;

/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
[DataField]
public float AnimationLength = 0.75f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, these values are never actually copied over to the animation component, so it will always use the default values.

I might be getting carried away a bit here, but instead of duplicating three fields manually (and making sure to update the copying logic if more fields are added in the future) it might be cleaner to define a WaddlePrototype that has all the needed fields and just copy the ProtoId over to the animation component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need me walked through it by someone, potentially one of the contrib voice chatters. Does it block an approval itself or is there something simpler I could do to get this over the line?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be fine with just copying each field over instead of doing the whole prototype thing.

}
64 changes: 64 additions & 0 deletions Content.Shared/Clown/WaddleAnimationComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Numerics;

namespace Content.Shared.Clown;
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Declares that an entity has started to waddle like a duck/clown.
/// </summary>
/// <param name="entity">The newly be-waddled.</param>
public struct StartedWaddlingEvent(EntityUid entity)
{
public EntityUid Entity = entity;
}

/// <summary>
/// Declares that an entity has stopped waddling like a duck/clown.
/// </summary>
/// <param name="entity">The former waddle-er.</param>
public struct StoppedWaddlingEvent(EntityUid entity)
{
public EntityUid Entity = entity;
}
FairlySadPanda marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Defines something as having a waddle animation when it moves.
/// </summary>
[RegisterComponent]
public sealed partial class WaddleAnimationComponent : Component
{
/// <summary>
/// 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.
/// </summary>
[DataField]
public string KeyName = "Waddle";

///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[DataField]
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]
public float TumbleIntensity = 20.0f;

/// <summary>
/// How long should a complete step take? Less time = more chaos.
/// </summary>
[DataField]
public float AnimationLength = 0.75f;

/// <summary>
/// Stores which step we made last, so if someone cancels out of the animation mid-step then restarts it looks more natural.
/// </summary>
public bool LastStep;

/// <summary>
/// Stores if we're currently waddling so we can start/stop as appropriate and can tell other systems our state.
/// </summary>
public bool IsCurrentlyWaddling;
}
1 change: 1 addition & 0 deletions Resources/Prototypes/Entities/Clothing/Shoes/specific.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
parent: [ClothingShoesBaseButcherable, ClothingSlotBase]
id: ClothingShoesClownBase
components:
- type: Waddle
- type: ItemSlots
slots:
item:
Expand Down
Loading