Ghost orbiting (#6784)
This commit is contained in:
151
Content.Client/Orbit/OrbitVisualsSystem.cs
Normal file
151
Content.Client/Orbit/OrbitVisualsSystem.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Content.Shared.Follower;
|
||||
using Content.Shared.Follower.Components;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Client.Orbit;
|
||||
|
||||
public sealed class OrbitVisualsSystem : VisualizerSystem<OrbitVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
private readonly string _orbitAnimationKey = "orbiting";
|
||||
private readonly string _orbitStopKey = "orbiting_stop";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args)
|
||||
{
|
||||
component.OrbitDistance =
|
||||
_robustRandom.NextFloat(0.75f * component.OrbitDistance, 1.25f * component.OrbitDistance);
|
||||
|
||||
component.OrbitLength = _robustRandom.NextFloat(0.5f * component.OrbitLength, 1.5f * component.OrbitLength);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var (orbit, sprite) in EntityManager.EntityQuery<OrbitVisualsComponent, ISpriteComponent>())
|
||||
{
|
||||
var angle = new Angle(Math.PI * 2 * orbit.Orbit);
|
||||
var vec = angle.RotateVec(new Vector2(orbit.OrbitDistance, 0));
|
||||
|
||||
sprite.Rotation = angle;
|
||||
sprite.Offset = vec;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, OrbitVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!args.Component.TryGetData<bool>(OrbitingVisuals.IsOrbiting, out var orbiting))
|
||||
return;
|
||||
|
||||
if (!TryComp<ISpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
var animationPlayer = EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
|
||||
|
||||
if (orbiting)
|
||||
{
|
||||
if (animationPlayer.HasRunningAnimation(_orbitAnimationKey))
|
||||
return;
|
||||
|
||||
if (animationPlayer.HasRunningAnimation(_orbitStopKey))
|
||||
{
|
||||
animationPlayer.Stop(_orbitStopKey);
|
||||
}
|
||||
|
||||
animationPlayer.Play(GetOrbitAnimation(component), _orbitAnimationKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemComp<OrbitVisualsComponent>(uid);
|
||||
if (animationPlayer.HasRunningAnimation(_orbitAnimationKey))
|
||||
{
|
||||
animationPlayer.Stop(_orbitAnimationKey);
|
||||
}
|
||||
|
||||
if (!animationPlayer.HasRunningAnimation(_orbitStopKey))
|
||||
{
|
||||
animationPlayer.Play(GetStopAnimation(component, sprite), _orbitStopKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, OrbitVisualsComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key == _orbitAnimationKey)
|
||||
{
|
||||
if(EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? animationPlayer))
|
||||
animationPlayer.Play(GetOrbitAnimation(component), _orbitAnimationKey);
|
||||
}
|
||||
}
|
||||
|
||||
private Animation GetOrbitAnimation(OrbitVisualsComponent component)
|
||||
{
|
||||
var length = component.OrbitLength;
|
||||
|
||||
return new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(length),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(OrbitVisualsComponent),
|
||||
Property = nameof(OrbitVisualsComponent.Orbit),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(0.0f, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(1.0f, length),
|
||||
},
|
||||
InterpolationMode = AnimationInterpolationMode.Linear
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Animation GetStopAnimation(OrbitVisualsComponent component, ISpriteComponent sprite)
|
||||
{
|
||||
var length = component.OrbitStopLength;
|
||||
|
||||
return new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(length),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(ISpriteComponent),
|
||||
Property = nameof(ISpriteComponent.Offset),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Offset, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length),
|
||||
},
|
||||
InterpolationMode = AnimationInterpolationMode.Linear
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(ISpriteComponent),
|
||||
Property = nameof(ISpriteComponent.Rotation),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Rotation.Reduced(), 0f),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.Zero, length),
|
||||
},
|
||||
InterpolationMode = AnimationInterpolationMode.Linear
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Content.Server.Entry
|
||||
"Icon",
|
||||
"ClientEntitySpawner",
|
||||
"CharacterInfo",
|
||||
"ItemCabinetVisuals"
|
||||
"ItemCabinetVisuals",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
28
Content.Shared/Follower/Components/OrbitVisualsComponent.cs
Normal file
28
Content.Shared/Follower/Components/OrbitVisualsComponent.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Robust.Shared.Animations;
|
||||
|
||||
namespace Content.Shared.Follower.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class OrbitVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long should the orbit animation last in seconds, before being randomized?
|
||||
/// </summary>
|
||||
public float OrbitLength = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How far away from the entity should the orbit be, before being randomized?
|
||||
/// </summary>
|
||||
public float OrbitDistance = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How long should the orbit stop animation last in seconds?
|
||||
/// </summary>
|
||||
public float OrbitStopLength = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How far along in the orbit, from 0 to 1, is this entity?
|
||||
/// </summary>
|
||||
[Animatable]
|
||||
public float Orbit { get; set; } = 0.0f;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Content.Shared.Follower.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Follower;
|
||||
|
||||
@@ -71,6 +72,18 @@ public sealed class FollowerSystem : EntitySystem
|
||||
var xform = Transform(follower);
|
||||
xform.AttachParent(entity);
|
||||
xform.LocalPosition = Vector2.Zero;
|
||||
|
||||
if (TryComp<AppearanceComponent>(follower, out var appearance))
|
||||
{
|
||||
EnsureComp<OrbitVisualsComponent>(follower);
|
||||
appearance.SetData(OrbitingVisuals.IsOrbiting, true);
|
||||
}
|
||||
|
||||
var followerEv = new StartedFollowingEntityEvent(entity, follower);
|
||||
var entityEv = new EntityStartedFollowingEvent(entity, follower);
|
||||
|
||||
RaiseLocalEvent(follower, followerEv);
|
||||
RaiseLocalEvent(entity, entityEv, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,7 +102,21 @@ public sealed class FollowerSystem : EntitySystem
|
||||
if (followed.Following.Count == 0)
|
||||
RemComp<FollowedComponent>(target);
|
||||
RemComp<FollowerComponent>(uid);
|
||||
|
||||
Transform(uid).AttachToGridOrMap();
|
||||
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
{
|
||||
// We don't remove OrbitVisuals here since the OrbitVisualsSystem will handle that itself
|
||||
// during the OnChangeData, which is deferred..
|
||||
appearance.SetData(OrbitingVisuals.IsOrbiting, false);
|
||||
}
|
||||
|
||||
var uidEv = new StoppedFollowingEntityEvent(target, uid);
|
||||
var targetEv = new EntityStoppedFollowingEvent(target, uid);
|
||||
|
||||
RaiseLocalEvent(uid, uidEv);
|
||||
RaiseLocalEvent(target, targetEv, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,3 +134,62 @@ public sealed class FollowerSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class FollowEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Following;
|
||||
public EntityUid Follower;
|
||||
|
||||
protected FollowEvent(EntityUid following, EntityUid follower)
|
||||
{
|
||||
Following = following;
|
||||
Follower = follower;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it start following another entity.
|
||||
/// </summary>
|
||||
public sealed class StartedFollowingEntityEvent : FollowEvent
|
||||
{
|
||||
public StartedFollowingEntityEvent(EntityUid following, EntityUid follower) : base(following, follower)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it stops following another entity.
|
||||
/// </summary>
|
||||
public sealed class StoppedFollowingEntityEvent : FollowEvent
|
||||
{
|
||||
public StoppedFollowingEntityEvent(EntityUid following, EntityUid follower) : base(following, follower)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it start following another entity.
|
||||
/// </summary>
|
||||
public sealed class EntityStartedFollowingEvent : FollowEvent
|
||||
{
|
||||
public EntityStartedFollowingEvent(EntityUid following, EntityUid follower) : base(following, follower)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it starts being followed by another entity.
|
||||
/// </summary>
|
||||
public sealed class EntityStoppedFollowingEvent : FollowEvent
|
||||
{
|
||||
public EntityStoppedFollowingEvent(EntityUid following, EntityUid follower) : base(following, follower)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum OrbitingVisuals : byte
|
||||
{
|
||||
IsOrbiting
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
mask:
|
||||
- GhostImpassable
|
||||
- type: PlayerInputMover
|
||||
- type: Appearance
|
||||
- type: Eye
|
||||
drawFov: false
|
||||
- type: Input
|
||||
|
||||
Reference in New Issue
Block a user