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

Return of clown waddling #1310

Merged
merged 8 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
152 changes: 152 additions & 0 deletions Content.Client/_Wizden/Movement/Systems/WaddleAnimationSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System.Numerics;
using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;

namespace Content.Client.Movement.Systems;

public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;

public override void Initialize()
{
base.Initialize();

SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
}

private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
{
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 (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;

if (_gravity.IsWeightless(entity.Owner))
return;

if (!_actionBlocker.CanMove(entity.Owner, mover))
return;

// Do nothing if buckled in
if (_buckle.IsBuckled(entity.Owner))
return;

// Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(entity.Owner))
return;

PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}

private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
{
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;

if (!entity.Comp.IsCurrentlyWaddling)
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()
{
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(entity.Comp.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
}
}
}
};

_animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared.Movement.Systems;

namespace Content.Server.Movement.Systems;

public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Content.Shared.Alert;
using System.Numerics;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Shared.Clothing.Components;

/// <summary>
/// Defines something as causing waddling when worn.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class WaddleWhenWornComponent : Component
{
///<summary>
/// How high should they hop during the waddle? Higher hop = more energy.
/// </summary>
[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, AutoNetworkedField]
public float TumbleIntensity = 20.0f;

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

/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
[DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;

/// <summary>
/// Slot the clothing has to be worn in to work.
/// </summary>
[DataField]
public string Slot = "shoes";

/// <summary>
/// Slot the clothing has to be worn in to work.
/// </summary>
[DataField]
public bool WaddlingOn = false;

/// <summary>
/// Alert displayed while waddling is on.
/// </summary>
[DataField]
public ProtoId<AlertPrototype> WaddlingAlert = "Waddling";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Content.Shared.Actions;
using Content.Shared.Alert;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Robust.Shared.Containers;

namespace Content.Shared.Clothing.EntitySystems;

public sealed class WaddleClothingSystem : EntitySystem
{
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<WaddleWhenWornComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<WaddleWhenWornComponent, ItemToggledEvent>(OnToggled);
}

private void OnGotEquipped(Entity<WaddleWhenWornComponent> ent, ref ClothingGotEquippedEvent args)
{
if(_toggle.IsActivated(ent.Owner))
AddAnimationComponent(ent, args.Wearer);
}

private void OnGotUnequipped(Entity<WaddleWhenWornComponent> ent, ref ClothingGotUnequippedEvent args)
{
RemoveAnimationComponent(ent, args.Wearer);
}

private void OnToggled(Entity<WaddleWhenWornComponent> ent, ref ItemToggledEvent args)
{
var (uid, comp) = ent;
// ensure clothing is in correct slot
if (_container.TryGetContainingContainer((uid, null, null), out var container) &&
_inventory.TryGetSlotEntity(container.Owner, comp.Slot, out var worn)
&& uid == worn)
{
{
if (args.Activated)
AddAnimationComponent(ent, container.Owner);
else
RemoveAnimationComponent(ent, container.Owner);
}
}
}

private void AddAnimationComponent(Entity<WaddleWhenWornComponent> ent, EntityUid wearer)
{
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(wearer);

waddleAnimComp.AnimationLength = ent.Comp.AnimationLength;
waddleAnimComp.HopIntensity = ent.Comp.HopIntensity;
waddleAnimComp.RunAnimationLengthMultiplier = ent.Comp.RunAnimationLengthMultiplier;
waddleAnimComp.TumbleIntensity = ent.Comp.TumbleIntensity;
_alerts.ShowAlert(wearer, ent.Comp.WaddlingAlert);
}

private void RemoveAnimationComponent(Entity<WaddleWhenWornComponent> ent, EntityUid wearer)
{
RemComp<WaddleAnimationComponent>(wearer);
_alerts.ClearAlert(wearer, ent.Comp.WaddlingAlert);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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>
[Serializable, NetSerializable]
public sealed class StartedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
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>
[Serializable, NetSerializable]
public sealed class StoppedWaddlingEvent(NetEntity entity) : EntityEventArgs
{
public NetEntity Entity = entity;
}

/// <summary>
/// Defines something as having a waddle animation when it moves.
/// </summary>
[RegisterComponent, AutoGenerateComponentState]
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, 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, AutoNetworkedField]
public float TumbleIntensity = 20.0f;

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

/// <summary>
/// How much shorter should the animation be when running?
/// </summary>
[DataField, AutoNetworkedField]
public float RunAnimationLengthMultiplier = 0.568f;

/// <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>
[AutoNetworkedField]
public bool IsCurrentlyWaddling;
}
Loading
Loading