diff --git a/Content.Client/Emoting/AnimatedEmotesSystem.cs b/Content.Client/Emoting/AnimatedEmotesSystem.cs new file mode 100644 index 00000000000..40766b4e13e --- /dev/null +++ b/Content.Client/Emoting/AnimatedEmotesSystem.cs @@ -0,0 +1,121 @@ +using Robust.Client.Animations; +using Robust.Shared.Animations; +using Robust.Shared.GameStates; +using Robust.Client.GameObjects; +using Content.Shared.Emoting; +using System.Numerics; +using Robust.Shared.Prototypes; +using Content.Shared.Chat.Prototypes; + +namespace Content.Client.Emoting; + +public sealed partial class AnimatedEmotesSystem : SharedAnimatedEmotesSystem +{ + [Dependency] private readonly AnimationPlayerSystem _anim = default!; + [Dependency] private readonly IPrototypeManager _prot = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandleState); + + SubscribeLocalEvent(OnFlip); + SubscribeLocalEvent(OnSpin); + SubscribeLocalEvent(OnJump); + } + + public void PlayEmote(EntityUid uid, Animation anim, string animationKey = "emoteAnimKeyId") + { + if (_anim.HasRunningAnimation(uid, animationKey)) + return; + + _anim.Play(uid, anim, animationKey); + } + + private void OnHandleState(EntityUid uid, AnimatedEmotesComponent component, ref ComponentHandleState args) + { + if (args.Current is not AnimatedEmotesComponentState state + || !_prot.TryIndex(state.Emote, out var emote)) + return; + + if (emote.Event != null) + RaiseLocalEvent(uid, emote.Event); + } + + private void OnFlip(Entity ent, ref AnimationFlipEmoteEvent args) + { + var a = new Animation + { + Length = TimeSpan.FromMilliseconds(500), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Rotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.25f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(360), 0.25f), + } + } + } + }; + PlayEmote(ent, a); + } + private void OnSpin(Entity ent, ref AnimationSpinEmoteEvent args) + { + var a = new Animation + { + Length = TimeSpan.FromMilliseconds(600), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(TransformComponent), + Property = nameof(TransformComponent.LocalRotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(90), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(270), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(90), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(180), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(270), 0.075f), + new AnimationTrackProperty.KeyFrame(Angle.Zero, 0.075f), + } + } + } + }; + PlayEmote(ent, a, "emoteAnimSpin"); + } + private void OnJump(Entity ent, ref AnimationJumpEmoteEvent args) + { + var a = new Animation + { + Length = TimeSpan.FromMilliseconds(250), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Cubic, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), + new AnimationTrackProperty.KeyFrame(new Vector2(0, .35f), 0.125f), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0.125f), + } + } + } + }; + PlayEmote(ent, a); + } +} diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 724cf8f2bb1..3ee94072ca2 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -176,7 +176,7 @@ private void TryEmoteChatInput(EntityUid uid, string textInput) private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto) { var ev = new EmoteEvent(proto); - RaiseLocalEvent(uid, ref ev); + RaiseLocalEvent(uid, ref ev, true); // goob edit } } diff --git a/Content.Server/Emoting/AnimatedEmotesSystem.cs b/Content.Server/Emoting/AnimatedEmotesSystem.cs new file mode 100644 index 00000000000..cc4863d31f7 --- /dev/null +++ b/Content.Server/Emoting/AnimatedEmotesSystem.cs @@ -0,0 +1,28 @@ +using Robust.Shared.GameStates; +using Content.Server.Chat.Systems; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Emoting; +using Robust.Shared.Prototypes; + +namespace Content.Server.Emoting; + +public sealed partial class AnimatedEmotesSystem : SharedAnimatedEmotesSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEmote); + } + + private void OnEmote(EntityUid uid, AnimatedEmotesComponent component, ref EmoteEvent args) + { + PlayEmoteAnimation(uid, component, args.Emote.ID); + } + + public void PlayEmoteAnimation(EntityUid uid, AnimatedEmotesComponent component, ProtoId prot) + { + component.Emote = prot; + Dirty(uid, component); + } +} diff --git a/Content.Shared/Chat/Prototypes/EmotePrototype.cs b/Content.Shared/Chat/Prototypes/EmotePrototype.cs index 7ee958ee6a7..34d54bc3600 100644 --- a/Content.Shared/Chat/Prototypes/EmotePrototype.cs +++ b/Content.Shared/Chat/Prototypes/EmotePrototype.cs @@ -68,6 +68,10 @@ public sealed partial class EmotePrototype : IPrototype /// [DataField] public HashSet ChatTriggers = new(); + + // goob edit - animations + [DataField] + public object? Event = null; } /// diff --git a/Content.Shared/Emoting/AnimatedEmotesComponent.cs b/Content.Shared/Emoting/AnimatedEmotesComponent.cs new file mode 100644 index 00000000000..fc8121bbe5a --- /dev/null +++ b/Content.Shared/Emoting/AnimatedEmotesComponent.cs @@ -0,0 +1,28 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Emoting; + +// use as a template +//[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationNameEmoteEvent : EntityEventArgs { } + +[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationFlipEmoteEvent : EntityEventArgs { } +[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationSpinEmoteEvent : EntityEventArgs { } +[Serializable, NetSerializable, DataDefinition] public sealed partial class AnimationJumpEmoteEvent : EntityEventArgs { } + +[RegisterComponent, NetworkedComponent] public sealed partial class AnimatedEmotesComponent : Component +{ + [DataField] public ProtoId? Emote; +} + +[Serializable, NetSerializable] public sealed partial class AnimatedEmotesComponentState : ComponentState +{ + public ProtoId? Emote; + + public AnimatedEmotesComponentState(ProtoId? emote) + { + Emote = emote; + } +} diff --git a/Content.Shared/Emoting/EmoteSystem.cs b/Content.Shared/Emoting/EmoteSystem.cs index 1e06d7e982b..fea322e950c 100644 --- a/Content.Shared/Emoting/EmoteSystem.cs +++ b/Content.Shared/Emoting/EmoteSystem.cs @@ -1,4 +1,4 @@ -namespace Content.Shared.Emoting; +namespace Content.Shared.Emoting; public sealed class EmoteSystem : EntitySystem { diff --git a/Content.Shared/Emoting/SharedAnimatedEmotesSystem.cs b/Content.Shared/Emoting/SharedAnimatedEmotesSystem.cs new file mode 100644 index 00000000000..b19e8c26a1e --- /dev/null +++ b/Content.Shared/Emoting/SharedAnimatedEmotesSystem.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Emoting; + +public abstract class SharedAnimatedEmotesSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + } + + private void OnGetState(Entity ent, ref ComponentGetState args) + { + args.State = new AnimatedEmotesComponentState(ent.Comp.Emote); + } +} diff --git a/Resources/Locale/en-US/emotes.ftl b/Resources/Locale/en-US/emotes.ftl new file mode 100644 index 00000000000..8efe738c94a --- /dev/null +++ b/Resources/Locale/en-US/emotes.ftl @@ -0,0 +1,7 @@ +chat-emote-name-flip = Do a flip +chat-emote-name-spin = Spin +chat-emote-name-jump = Jump + +chat-emote-msg-flip = does a flip! +chat-emote-msg-spin = spins! +chat-emote-msg-jump = jumps! \ No newline at end of file diff --git a/Resources/Prototypes/Actions/emotes.yml b/Resources/Prototypes/Actions/emotes.yml new file mode 100644 index 00000000000..6f34a4dc94d --- /dev/null +++ b/Resources/Prototypes/Actions/emotes.yml @@ -0,0 +1,23 @@ +- type: emote + id: Flip + name: chat-emote-name-flip + chatMessages: ["chat-emote-msg-flip"] + chatTriggers: + - does a flip + event: !type:AnimationFlipEmoteEvent + +- type: emote + id: Spin + name: chat-emote-name-spin + chatMessages: ["chat-emote-msg-spin"] + chatTriggers: + - spins + event: !type:AnimationSpinEmoteEvent + +- type: emote + id: Jump + name: chat-emote-name-jump + chatMessages: ["chat-emote-msg-jump"] + chatTriggers: + - jumps + event: !type:AnimationJumpEmoteEvent diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 5ab790feeef..da413c339d8 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -46,6 +46,14 @@ - type: LanguageSpeaker # Einstein Engines. This component is required to support speech, although it does not define known languages. - type: RequireProjectileTarget active: False + - type: AnimatedEmotes + +- type: entity + save: false + id: MobPolymorphable + abstract: true + components: + - type: Polymorphable - type: OwnInteractionVerbs allowedVerbs: [] # TODO: define something here, or don't.