CatStation/Content.Server/Screens/Systems/ScreenSystem.cs
Whatstone 041a7e6504
Scrolling text screens (#43997)
* Scrolling text screens

* remove ScrollingTextScreenComponent, fix comments

* shave a pixel off of the frame, commas cut off

* logMissing to false, state does may not exist

* variable timing, offset array serializer

* namespaces are a state of mind

* Use CustomArraySerializer instead of a one-off

* better alphabetize punctuation text

* Unneeded VV access, 4-space meta.json

* Subscription attributes

* cleanup pass

* shift left

* AJCM's suggestions

Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>

* PadRight returns a string.

* kill the loop, use funny division instead

* smoother animations, dark stripe on outer frame

* Update for the new comms console UI

* communications console changes

* can't spell pain without UI

* the ocho

* it's big brain time

* just a touch wider

* UI polish pass - forgive the sins

* look at my attributions dog

* EDITED

* Text screen layer init deferred until startup

* that's a new one

---------

Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
2026-08-15 22:03:22 +00:00

110 lines
4.6 KiB
C#

using Content.Shared.TextScreen;
using Content.Server.Screens.Components;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Robust.Shared.Timing;
namespace Content.Server.Screens.Systems;
/// <summary>
/// Controls the wallmounted screens on stations and shuttles displaying e.g. FTL duration, ETA
/// </summary>
public sealed partial class ScreenSystem : EntitySystem
{
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private SharedAppearanceSystem _appearanceSystem = default!;
/// <summary>
/// Calls either a normal screen text update or shuttle timer update based on the presence of
/// <see cref="ShuttleTimerMasks.ShuttleMap"/> in <see cref="args.Data"/>
/// </summary>
[SubscribeLocalEvent]
private void OnPacketReceived(Entity<ScreenComponent> ent, ref DeviceNetworkPacketEvent args)
{
if (args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out _))
ShuttleTimer(ent, args);
else
ScreenText(ent, args);
}
/// <summary>
/// Send a text update to every screen on the same MapUid as the originating comms console.
/// </summary>
private void ScreenText(Entity<ScreenComponent> ent, DeviceNetworkPacketEvent args)
{
// don't allow text updates if there's an active timer
// (and just check here so the server doesn't have to track them)
if (_appearanceSystem.TryGetData(ent, TextScreenVisuals.TargetTime, out TimeSpan target)
&& target > _gameTiming.CurTime)
return;
var screenMap = Transform(ent).MapUid;
var argsMap = Transform(args.Sender).MapUid;
if (screenMap != null
&& argsMap != null
&& screenMap == argsMap
&& args.Data.TryGetValue(ScreenMasks.Text, out string? text)
&& text != null
)
{
_appearanceSystem.SetData(ent, TextScreenVisuals.DefaultText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime);
}
}
/// <summary>
/// Determines if/how a timer packet affects this screen.
/// Currently there are 2 broadcast domains: Arrivals, and every other screen.
/// Domain is determined by the <see cref="DeviceNetworkComponent.TransmitFrequencyId"/> on each timer.
/// Each broadcast domain is divided into subnets. Screen MapUid determines subnet.
/// Subnets are the shuttle, source, and dest. Source/dest change each jump.
/// This is required to send different timers to the shuttle/terminal/station.
/// </summary>
private void ShuttleTimer(Entity<ScreenComponent> ent, DeviceNetworkPacketEvent args)
{
var timerXform = Transform(ent);
// no false positives.
if (timerXform.MapUid == null)
return;
string key;
args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out EntityUid? shuttleMap);
args.Data.TryGetValue(ShuttleTimerMasks.SourceMap, out EntityUid? source);
args.Data.TryGetValue(ShuttleTimerMasks.DestMap, out EntityUid? dest);
args.Data.TryGetValue(ShuttleTimerMasks.Docked, out bool docked);
string text = docked ? ShuttleTimerMasks.ETD : ShuttleTimerMasks.ETA;
switch (timerXform.MapUid)
{
// sometimes the timer transforms on FTL shuttles have a hyperspace mapuid, so matching by grid works as a fallback.
case var local when local == shuttleMap || timerXform.GridUid == shuttleMap:
key = ShuttleTimerMasks.ShuttleTime;
break;
case var origin when origin == source:
key = ShuttleTimerMasks.SourceTime;
break;
case var remote when remote == dest:
key = ShuttleTimerMasks.DestTime;
text = ShuttleTimerMasks.ETA;
break;
default:
return;
}
if (!args.Data.TryGetValue(key, out TimeSpan duration))
return;
if (args.Data.TryGetValue(ScreenMasks.Text, out string? label) && label != null)
text = label;
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenText, text);
_appearanceSystem.SetData(ent, TextScreenVisuals.TargetTime, _gameTiming.CurTime + duration);
_appearanceSystem.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime);
if (args.Data.TryGetValue(ScreenMasks.Color, out Color color))
_appearanceSystem.SetData(ent, TextScreenVisuals.Color, color);
}
}