From 67ed59a50d4f052f6b1919fd34188ab0f67db5c9 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Tue, 17 Jan 2023 18:01:53 -0400 Subject: [PATCH] Makes mobs visually float when weightless (#13391) --- .../Gravity/FloatingVisualizerSystem.cs | 61 +++++++++++++++ .../Components/PointingArrowComponent.cs | 21 ++++- Content.Client/Pointing/PointingSystem.cs | 50 ++---------- .../Gravity/FloatingVisualizerSystem.cs | 7 ++ .../Gravity/FloatingVisualsComponent.cs | 43 ++++++++++ .../Gravity/SharedFloatingVisualizerSystem.cs | 78 +++++++++++++++++++ .../Entities/Mobs/NPCs/simplemob.yml | 2 + .../Prototypes/Entities/Mobs/Species/base.yml | 1 + 8 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 Content.Client/Gravity/FloatingVisualizerSystem.cs create mode 100644 Content.Server/Gravity/FloatingVisualizerSystem.cs create mode 100644 Content.Shared/Gravity/FloatingVisualsComponent.cs create mode 100644 Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs diff --git a/Content.Client/Gravity/FloatingVisualizerSystem.cs b/Content.Client/Gravity/FloatingVisualizerSystem.cs new file mode 100644 index 0000000000..9a18670b2f --- /dev/null +++ b/Content.Client/Gravity/FloatingVisualizerSystem.cs @@ -0,0 +1,61 @@ +using Content.Shared.Gravity; +using Robust.Client.GameObjects; +using Robust.Client.Animations; +using Robust.Shared.Animations; + +namespace Content.Client.Gravity; + +/// +public sealed class FloatingVisualizerSystem : SharedFloatingVisualizerSystem +{ + [Dependency] private readonly AnimationPlayerSystem AnimationSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAnimationCompleted); + } + + /// + public override void FloatAnimation(EntityUid uid, Vector2 offset, string animationKey, float animationTime, bool stop = false) + { + if (stop) + { + AnimationSystem.Stop(uid, animationKey); + return; + } + + var animation = new Animation + { + // We multiply by the number of extra keyframes to make time for them + Length = TimeSpan.FromSeconds(animationTime*2), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), + new AnimationTrackProperty.KeyFrame(offset, animationTime), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, animationTime), + } + } + } + }; + + if (!AnimationSystem.HasRunningAnimation(uid, animationKey)) + AnimationSystem.Play(uid, animation, animationKey); + } + + private void OnAnimationCompleted(EntityUid uid, FloatingVisualsComponent component, AnimationCompletedEvent args) + { + if (args.Key != component.AnimationKey) + return; + + FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime, !component.CanFloat); + } +} diff --git a/Content.Client/Pointing/Components/PointingArrowComponent.cs b/Content.Client/Pointing/Components/PointingArrowComponent.cs index 3c82e62588..26a47b1925 100644 --- a/Content.Client/Pointing/Components/PointingArrowComponent.cs +++ b/Content.Client/Pointing/Components/PointingArrowComponent.cs @@ -1,7 +1,22 @@ using Content.Shared.Pointing.Components; -namespace Content.Client.Pointing.Components +namespace Content.Client.Pointing.Components; +[RegisterComponent] +public sealed class PointingArrowComponent : SharedPointingArrowComponent { - [RegisterComponent] - public sealed class PointingArrowComponent : SharedPointingArrowComponent {} + /// + /// How long it takes to go from the bottom of the animation to the top. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("animationTime")] + public readonly float AnimationTime = 0.5f; + + /// + /// How far it goes in any direction. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("offset")] + public readonly Vector2 Offset = (0, 0.25f); + + public readonly string AnimationKey = "pointingarrow"; } diff --git a/Content.Client/Pointing/PointingSystem.cs b/Content.Client/Pointing/PointingSystem.cs index a151600edd..7978f604ec 100644 --- a/Content.Client/Pointing/PointingSystem.cs +++ b/Content.Client/Pointing/PointingSystem.cs @@ -1,53 +1,17 @@ using Content.Client.Pointing.Components; +using Content.Client.Gravity; using Content.Shared.Mobs.Systems; using Content.Shared.Pointing; using Content.Shared.Verbs; -using Robust.Client.Animations; using Robust.Client.GameObjects; -using Robust.Shared.Animations; using DrawDepth = Content.Shared.DrawDepth.DrawDepth; namespace Content.Client.Pointing; public sealed class PointingSystem : SharedPointingSystem { - [Dependency] private readonly AnimationPlayerSystem _player = default!; [Dependency] private readonly MobStateSystem _mobState = default!; - - private const string AnimationKey = "pointingarrow"; - - /// - /// How far it goes in any direction. - /// - private const float Offset = 0.25f; - - /// - /// How long it takes to go from the bottom of the animation to the top. - /// - private const float UpTime = 0.5f; - - /// - /// Starts at the bottom then goes up and comes back down. Seems to look nicer than starting in the middle. - /// - private static readonly Animation PointingAnimation = new Animation() - { - Length = TimeSpan.FromSeconds(2 * UpTime), - AnimationTracks = - { - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Offset), - InterpolationMode = AnimationInterpolationMode.Linear, - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), - new AnimationTrackProperty.KeyFrame(new Vector2(0f, Offset), UpTime), - new AnimationTrackProperty.KeyFrame(Vector2.Zero, UpTime), - } - } - } - }; + [Dependency] private readonly FloatingVisualizerSystem _floatingSystem = default!; public override void Initialize() { @@ -61,7 +25,7 @@ public sealed class PointingSystem : SharedPointingSystem private void OnArrowAnimation(EntityUid uid, PointingArrowComponent component, AnimationCompletedEvent args) { - _player.Play(uid, PointingAnimation, AnimationKey); + _floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); } private void AddPointingVerb(GetVerbsEvent args) @@ -98,19 +62,19 @@ public sealed class PointingSystem : SharedPointingSystem args.Verbs.Add(verb); } - private void OnArrowStartup(EntityUid uid, PointingArrowComponent arrow, ComponentStartup args) + private void OnArrowStartup(EntityUid uid, PointingArrowComponent component, ComponentStartup args) { - if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) + if (TryComp(uid, out var sprite)) { sprite.DrawDepth = (int) DrawDepth.Overlays; } - _player.Play(uid, PointingAnimation, AnimationKey); + _floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); } private void OnRogueArrowStartup(EntityUid uid, RoguePointingArrowComponent arrow, ComponentStartup args) { - if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) + if (TryComp(uid, out var sprite)) { sprite.DrawDepth = (int) DrawDepth.Overlays; sprite.NoRotation = false; diff --git a/Content.Server/Gravity/FloatingVisualizerSystem.cs b/Content.Server/Gravity/FloatingVisualizerSystem.cs new file mode 100644 index 0000000000..94e783f9e5 --- /dev/null +++ b/Content.Server/Gravity/FloatingVisualizerSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Gravity; +using Robust.Shared.GameStates; + +namespace Content.Server.Gravity; + +/// +public sealed class FloatingVisualizerSystem : SharedFloatingVisualizerSystem { } diff --git a/Content.Shared/Gravity/FloatingVisualsComponent.cs b/Content.Shared/Gravity/FloatingVisualsComponent.cs new file mode 100644 index 0000000000..40186db844 --- /dev/null +++ b/Content.Shared/Gravity/FloatingVisualsComponent.cs @@ -0,0 +1,43 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Gravity; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedFloatingVisualizerSystem))] +public sealed class FloatingVisualsComponent : Component +{ + /// + /// How long it takes to go from the bottom of the animation to the top. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("animationTime")] + public float AnimationTime = 2f; + + /// + /// How far it goes in any direction. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("offset")] + public Vector2 Offset = new(0, 0.2f); + + [ViewVariables(VVAccess.ReadWrite)] + public bool CanFloat = false; + public readonly string AnimationKey = "gravity"; +} + + +[Serializable, NetSerializable] +public sealed class SharedFloatingVisualsComponentState : ComponentState +{ + public float AnimationTime; + public Vector2 Offset; + public bool HasGravity; + + public SharedFloatingVisualsComponentState(float animationTime, Vector2 offset, bool hasGravity) + { + AnimationTime = animationTime; + Offset = offset; + HasGravity = hasGravity; + } +} diff --git a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs new file mode 100644 index 0000000000..f4db37a585 --- /dev/null +++ b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs @@ -0,0 +1,78 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Gravity; + +/// +/// Handles offsetting a sprite when there is no gravity +/// +public abstract class SharedFloatingVisualizerSystem : EntitySystem +{ + [Dependency] private readonly SharedGravitySystem GravitySystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnGravityChanged); + SubscribeLocalEvent(OnEntParentChanged); + SubscribeLocalEvent(OnComponentGetState); + SubscribeLocalEvent(OnComponentHandleState); + } + + /// + /// Offsets a sprite with a linear interpolation animation + /// + public virtual void FloatAnimation(EntityUid uid, Vector2 offset, string animationKey, float animationTime, bool stop = false) { } + + protected bool CanFloat(EntityUid uid, FloatingVisualsComponent component, TransformComponent? transform = null) + { + component.CanFloat = GravitySystem.IsWeightless(uid, xform: transform); + Dirty(component); + return component.CanFloat; + } + + private void OnComponentStartup(EntityUid uid, FloatingVisualsComponent component, ComponentStartup args) + { + if (CanFloat(uid, component)) + FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); + } + + private void OnGravityChanged(ref GravityChangedEvent args) + { + foreach (var (floating, transform) in EntityQuery(true)) + { + if (transform.GridUid != args.ChangedGridIndex) + continue; + + floating.CanFloat = !args.HasGravity; + Dirty(floating); + + var uid = floating.Owner; + if (!args.HasGravity) + FloatAnimation(uid, floating.Offset, floating.AnimationKey, floating.AnimationTime); + } + } + + private void OnEntParentChanged(EntityUid uid, FloatingVisualsComponent component, ref EntParentChangedMessage args) + { + var transform = args.Transform; + if (CanFloat(uid, component, transform)) + FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); + } + + private void OnComponentGetState(EntityUid uid, FloatingVisualsComponent component, ref ComponentGetState args) + { + args.State = new SharedFloatingVisualsComponentState(component.AnimationTime, component.Offset, component.CanFloat); + } + + private void OnComponentHandleState(EntityUid uid, FloatingVisualsComponent component, ref ComponentHandleState args) + { + if (args.Current is not SharedFloatingVisualsComponentState state) + return; + + component.AnimationTime = state.AnimationTime; + component.Offset = state.Offset; + component.CanFloat = state.HasGravity; + } +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 6b8112f820..34b3d18bd3 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -222,4 +222,6 @@ bloodMaxVolume: 150 - type: MobPrice price: 150 + - type: Appearance + - type: FloatingVisuals diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 2ac1577bea..d2ecd7a9b5 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -229,6 +229,7 @@ - type: CreamPiedVisualizer state: creampie_human - type: RotationVisuals + - type: FloatingVisuals - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Generic_mob_burning