diff --git a/Content.Client/Shuttles/FtlArrivalOverlay.cs b/Content.Client/Shuttles/FtlArrivalOverlay.cs
new file mode 100644
index 00000000000..f24a1e96488
--- /dev/null
+++ b/Content.Client/Shuttles/FtlArrivalOverlay.cs
@@ -0,0 +1,82 @@
+using System.Numerics;
+using Content.Shared.Shuttles.Components;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Shared.Enums;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Shuttles;
+
+///
+/// Plays a visualization whenever a shuttle is arriving from FTL.
+///
+public sealed class FtlArrivalOverlay : Overlay
+{
+ public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
+
+ private EntityLookupSystem _lookups;
+ private SharedMapSystem _maps;
+ private SharedTransformSystem _transforms;
+ private SpriteSystem _sprites;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _protos = default!;
+
+ private readonly HashSet> _visualizers = new();
+
+ private ShaderInstance _shader;
+
+ public FtlArrivalOverlay()
+ {
+ IoCManager.InjectDependencies(this);
+ _lookups = _entManager.System();
+ _transforms = _entManager.System();
+ _maps = _entManager.System();
+ _sprites = _entManager.System();
+
+ _shader = _protos.Index("unshaded").Instance();
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ _visualizers.Clear();
+ _lookups.GetEntitiesOnMap(args.MapId, _visualizers);
+
+ return _visualizers.Count > 0;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ args.WorldHandle.UseShader(_shader);
+
+ foreach (var (uid, comp) in _visualizers)
+ {
+ var grid = comp.Grid;
+
+ if (!_entManager.TryGetComponent(grid, out MapGridComponent? mapGrid))
+ continue;
+
+ var texture = _sprites.GetFrame(comp.Sprite, TimeSpan.FromSeconds(comp.Elapsed), loop: false);
+ comp.Elapsed += (float) _timing.FrameTime.TotalSeconds;
+
+ // Need to manually transform the viewport in terms of the visualizer entity as the grid isn't in position.
+ var (_, _, worldMatrix, invMatrix) = _transforms.GetWorldPositionRotationMatrixWithInv(uid);
+ args.WorldHandle.SetTransform(worldMatrix);
+ var localAABB = invMatrix.TransformBox(args.WorldBounds);
+
+ var tilesEnumerator = _maps.GetLocalTilesEnumerator(grid, mapGrid, localAABB);
+
+ while (tilesEnumerator.MoveNext(out var tile))
+ {
+ var bounds = _lookups.GetLocalBounds(tile, mapGrid.TileSize);
+
+ args.WorldHandle.DrawTextureRect(texture, bounds);
+ }
+ }
+
+ args.WorldHandle.UseShader(null);
+ args.WorldHandle.SetTransform(Matrix3x2.Identity);
+ }
+}
diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
index d5154a87bef..73c11de2795 100644
--- a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
+++ b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
@@ -38,9 +38,8 @@ public bool EnableShuttlePosition
private bool _enableShuttlePosition;
private EmergencyShuttleOverlay? _overlay;
- public override void Initialize()
+ private void InitializeEmergency()
{
- base.Initialize();
SubscribeNetworkEvent(OnShuttlePosMessage);
}
diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.cs
new file mode 100644
index 00000000000..a2c048ff90e
--- /dev/null
+++ b/Content.Client/Shuttles/Systems/ShuttleSystem.cs
@@ -0,0 +1,21 @@
+using Robust.Client.Graphics;
+
+namespace Content.Client.Shuttles.Systems;
+
+public sealed partial class ShuttleSystem
+{
+ [Dependency] private readonly IOverlayManager _overlays = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ InitializeEmergency();
+ _overlays.AddOverlay(new FtlArrivalOverlay());
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _overlays.RemoveOverlay();
+ }
+}
diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
index 11cc16e0cd0..daf51038c14 100644
--- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
+++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
@@ -81,6 +81,8 @@ public sealed partial class ShuttleSystem
private void InitializeFTL()
{
SubscribeLocalEvent(OnStationPostInit);
+ SubscribeLocalEvent(OnFtlShutdown);
+
_bodyQuery = GetEntityQuery();
_buckleQuery = GetEntityQuery();
_beaconQuery = GetEntityQuery();
@@ -97,6 +99,12 @@ private void InitializeFTL()
_cfg.OnValueChanged(CCVars.HyperspaceKnockdownTime, time => _hyperspaceKnockdownTime = TimeSpan.FromSeconds(time), true);
}
+ private void OnFtlShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ Del(ent.Comp.VisualizerEntity);
+ ent.Comp.VisualizerEntity = null;
+ }
+
private void OnStationPostInit(ref StationPostInitEvent ev)
{
// Add all grid maps as ftl destinations that anyone can FTL to.
@@ -422,8 +430,16 @@ private void UpdateFTLTravelling(Entity entity)
var comp = entity.Comp1;
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime);
comp.State = FTLState.Arriving;
- // TODO: Arrival effects
- // For now we'll just use the ss13 bubbles but we can do fancier.
+
+ if (entity.Comp1.VisualizerProto != null)
+ {
+ comp.VisualizerEntity = SpawnAtPosition(entity.Comp1.VisualizerProto, entity.Comp1.TargetCoordinates);
+ var visuals = Comp(comp.VisualizerEntity.Value);
+ visuals.Grid = entity.Owner;
+ Dirty(comp.VisualizerEntity.Value, visuals);
+ _transform.SetLocalRotation(comp.VisualizerEntity.Value, entity.Comp1.TargetAngle);
+ _pvs.AddGlobalOverride(comp.VisualizerEntity.Value);
+ }
_thruster.DisableLinearThrusters(shuttle);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs
index 6fe2324d51e..a10d0d56144 100644
--- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs
+++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs
@@ -11,6 +11,7 @@
using Content.Shared.Throwing;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
+using Robust.Server.GameStates;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
@@ -40,6 +41,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
+ [Dependency] private readonly PvsOverrideSystem _pvs = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
diff --git a/Content.Server/Shuttles/Components/FTLComponent.cs b/Content.Shared/Shuttles/Components/FTLComponent.cs
similarity index 81%
rename from Content.Server/Shuttles/Components/FTLComponent.cs
rename to Content.Shared/Shuttles/Components/FTLComponent.cs
index c9b84064234..9acca7c1d45 100644
--- a/Content.Server/Shuttles/Components/FTLComponent.cs
+++ b/Content.Shared/Shuttles/Components/FTLComponent.cs
@@ -2,16 +2,16 @@
using Content.Shared.Tag;
using Content.Shared.Timing;
using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-namespace Content.Server.Shuttles.Components;
+namespace Content.Shared.Shuttles.Components;
///
/// Added to a component when it is queued or is travelling via FTL.
///
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class FTLComponent : Component
{
// TODO Full game save / add datafields
@@ -29,13 +29,19 @@ public sealed partial class FTLComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
public float TravelTime = 0f;
+ [DataField]
+ public EntProtoId? VisualizerProto = "FtlVisualizerEntity";
+
+ [DataField, AutoNetworkedField]
+ public EntityUid? VisualizerEntity;
+
///
/// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates.
///
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [DataField, AutoNetworkedField]
public EntityCoordinates TargetCoordinates;
- [DataField]
+ [DataField, AutoNetworkedField]
public Angle TargetAngle;
///
diff --git a/Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs b/Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs
new file mode 100644
index 00000000000..628a4f828b2
--- /dev/null
+++ b/Content.Shared/Shuttles/Components/FtlVisualizerComponent.cs
@@ -0,0 +1,23 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Shuttles.Components;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class FtlVisualizerComponent : Component
+{
+ ///
+ /// Clientside time tracker for the animation.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float Elapsed;
+
+ [DataField(required: true)]
+ public SpriteSpecifier.Rsi Sprite;
+
+ ///
+ /// Target grid to pull FTL visualization from.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid Grid;
+}
diff --git a/Resources/Prototypes/Entities/Effects/shuttle.yml b/Resources/Prototypes/Entities/Effects/shuttle.yml
new file mode 100644
index 00000000000..d4538116ac9
--- /dev/null
+++ b/Resources/Prototypes/Entities/Effects/shuttle.yml
@@ -0,0 +1,12 @@
+- type: entity
+ id: FtlVisualizerEntity
+ noSpawn: true
+ description: Visualizer for shuttles arriving. You shouldn't see this!
+ components:
+ - type: FtlVisualizer
+ sprite:
+ sprite: /Textures/Effects/medi_holo.rsi
+ state: medi_holo
+ - type: Tag
+ tags:
+ - HideContextMenu
diff --git a/Resources/Textures/Effects/medi_holo.rsi/medi_holo.png b/Resources/Textures/Effects/medi_holo.rsi/medi_holo.png
new file mode 100644
index 00000000000..9b024faa2d7
Binary files /dev/null and b/Resources/Textures/Effects/medi_holo.rsi/medi_holo.png differ
diff --git a/Resources/Textures/Effects/medi_holo.rsi/meta.json b/Resources/Textures/Effects/medi_holo.rsi/meta.json
new file mode 100644
index 00000000000..1be502223e5
--- /dev/null
+++ b/Resources/Textures/Effects/medi_holo.rsi/meta.json
@@ -0,0 +1,26 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/tgstation/tgstation/tree/217b39cc85e45302d407d5c1ab60809bd9e18987",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "medi_holo",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file