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",
|
"Icon",
|
||||||
"ClientEntitySpawner",
|
"ClientEntitySpawner",
|
||||||
"CharacterInfo",
|
"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 Robust.Shared.Localization;
|
||||||
using Content.Shared.Follower.Components;
|
using Content.Shared.Follower.Components;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Follower;
|
namespace Content.Shared.Follower;
|
||||||
|
|
||||||
@@ -71,6 +72,18 @@ public sealed class FollowerSystem : EntitySystem
|
|||||||
var xform = Transform(follower);
|
var xform = Transform(follower);
|
||||||
xform.AttachParent(entity);
|
xform.AttachParent(entity);
|
||||||
xform.LocalPosition = Vector2.Zero;
|
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>
|
/// <summary>
|
||||||
@@ -89,7 +102,21 @@ public sealed class FollowerSystem : EntitySystem
|
|||||||
if (followed.Following.Count == 0)
|
if (followed.Following.Count == 0)
|
||||||
RemComp<FollowedComponent>(target);
|
RemComp<FollowedComponent>(target);
|
||||||
RemComp<FollowerComponent>(uid);
|
RemComp<FollowerComponent>(uid);
|
||||||
|
|
||||||
Transform(uid).AttachToGridOrMap();
|
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>
|
/// <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:
|
mask:
|
||||||
- GhostImpassable
|
- GhostImpassable
|
||||||
- type: PlayerInputMover
|
- type: PlayerInputMover
|
||||||
|
- type: Appearance
|
||||||
- type: Eye
|
- type: Eye
|
||||||
drawFov: false
|
drawFov: false
|
||||||
- type: Input
|
- type: Input
|
||||||
|
|||||||
Reference in New Issue
Block a user