[ViewVariables]
public bool CanAnnounce { get; private set; }
+ [ViewVariables]
+ public bool CanBroadcast { get; private set; }
[ViewVariables]
public bool CanCall { get; private set; }
SendMessage(new CommunicationsConsoleAnnounceMessage(msg));
}
+ public void BroadcastButtonPressed(string message)
+ {
+ SendMessage(new CommunicationsConsoleBroadcastMessage(message));
+ }
+
public void CallShuttle()
{
SendMessage(new CommunicationsConsoleCallEmergencyShuttleMessage());
return;
CanAnnounce = commsState.CanAnnounce;
+ CanBroadcast = commsState.CanBroadcast;
CanCall = commsState.CanCall;
_expectedCountdownTime = commsState.ExpectedCountdownEnd;
CountdownStarted = commsState.CountdownStarted;
_menu.AlertLevelButton.Disabled = !AlertLevelSelectable;
_menu.EmergencyShuttleButton.Disabled = !CanCall;
_menu.AnnounceButton.Disabled = !CanAnnounce;
+ _menu.BroadcastButton.Disabled = !CanBroadcast;
}
}
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
<TextEdit Name="MessageInput" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 0 5" MinHeight="100" />
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" StyleClasses="OpenLeft" Access="Public" />
+ <Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" StyleClasses="OpenLeft" Access="Public" />
<OptionButton Name="AlertLevelButton" StyleClasses="OpenRight" Access="Public" />
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope));
AnnounceButton.Disabled = !owner.CanAnnounce;
+ BroadcastButton.OnPressed += (_) => Owner.BroadcastButtonPressed(Rope.Collapse(MessageInput.TextRope));
+ BroadcastButton.Disabled = !owner.CanBroadcast;
+
AlertLevelButton.OnItemSelected += args =>
{
var metadata = AlertLevelButton.GetItemMetadata(args.Id);
/// Called by <see cref="SharedAppearanceSystem.SetData"/> to handle text updates,
/// and spawn a <see cref="TextScreenTimerComponent"/> if necessary
/// </summary>
+ /// <remarks>
+ /// The appearance updates are batched; order matters for both sender and receiver.
+ /// </remarks>
protected override void OnAppearanceChange(EntityUid uid, TextScreenVisualsComponent component, ref AppearanceChangeEvent args)
{
if (!Resolve(uid, ref args.Sprite))
return;
- var appearance = args.Component;
+ if (args.AppearanceData.TryGetValue(TextScreenVisuals.Color, out var color) && color is Color)
+ component.Color = (Color) color;
+
+ // DefaultText: broadcast updates from comms consoles
+ // ScreenText: the text accompanying shuttle timers e.g. "ETA"
+ if (args.AppearanceData.TryGetValue(TextScreenVisuals.DefaultText, out var newDefault) && newDefault is string)
+ {
+ string?[] defaultText = SegmentText((string) newDefault, component);
+ component.Text = defaultText;
+ component.TextToDraw = defaultText;
+ ResetText(uid, component);
+ BuildTextLayers(uid, component, args.Sprite);
+ DrawLayers(uid, component.LayerStatesToDraw);
+ }
+ if (args.AppearanceData.TryGetValue(TextScreenVisuals.ScreenText, out var text) && text is string)
+ {
+ component.TextToDraw = SegmentText((string) text, component);
+ ResetText(uid, component);
+ BuildTextLayers(uid, component, args.Sprite);
+ DrawLayers(uid, component.LayerStatesToDraw);
+ }
- if (AppearanceSystem.TryGetData(uid, TextScreenVisuals.TargetTime, out TimeSpan time, appearance))
+ if (args.AppearanceData.TryGetValue(TextScreenVisuals.TargetTime, out var time) && time is TimeSpan)
{
- if (time > _gameTiming.CurTime)
+ var target = (TimeSpan) time;
+ if (target > _gameTiming.CurTime)
{
var timer = EnsureComp<TextScreenTimerComponent>(uid);
- timer.Target = time;
+ timer.Target = target;
BuildTimerLayers(uid, timer, component);
DrawLayers(uid, timer.LayerStatesToDraw);
}
OnTimerFinish(uid, component);
}
}
-
- if (AppearanceSystem.TryGetData(uid, TextScreenVisuals.ScreenText, out string?[] text, appearance))
- {
- component.TextToDraw = text;
- ResetText(uid, component);
- BuildTextLayers(uid, component, args.Sprite);
- DrawLayers(uid, component.LayerStatesToDraw);
- }
}
/// <summary>
DrawLayers(uid, screen.LayerStatesToDraw);
}
+ /// <summary>
+ /// Converts string to string?[] based on
+ /// <see cref="TextScreenVisualsComponent.RowLength"/> and <see cref="TextScreenVisualsComponent.Rows"/>.
+ /// </summary>
+ private string?[] SegmentText(string text, TextScreenVisualsComponent component)
+ {
+ int segment = component.RowLength;
+ var segmented = new string?[Math.Min(component.Rows, (text.Length - 1) / segment + 1)];
+
+ // populate segmented with a string sliding window using Substring.
+ // (Substring(5, 5) will return the 5 characters starting from 5th index)
+ // the Mins are for the very short string case, the very long string case, and to not OOB the end of the string.
+ for (int i = 0; i < Math.Min(text.Length, segment * component.Rows); i += segment)
+ segmented[i / segment] = text.Substring(i, Math.Min(text.Length - i, segment)).Trim();
+
+ return segmented;
+ }
+
/// <summary>
/// Clears <see cref="TextScreenVisualsComponent.LayerStatesToDraw"/>, and instantiates new blank defaults.
/// </summary>
- public void ResetText(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
+ private void ResetText(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
{
if (!Resolve(uid, ref sprite))
return;
for (var row = 0; row < component.Rows; row++)
for (var i = 0; i < component.RowLength; i++)
{
- sprite.LayerMapReserveBlank(TextMapKey + row + i);
- component.LayerStatesToDraw.Add(TextMapKey + row + i, null);
- sprite.LayerSetRSI(TextMapKey + row + i, new ResPath(TextPath));
- sprite.LayerSetColor(TextMapKey + row + i, component.Color);
- sprite.LayerSetState(TextMapKey + row + i, DefaultState);
+ var key = TextMapKey + row + i;
+ sprite.LayerMapReserveBlank(key);
+ component.LayerStatesToDraw.Add(key, null);
+ sprite.LayerSetRSI(key, new ResPath(TextPath));
+ sprite.LayerSetColor(key, component.Color);
+ sprite.LayerSetState(key, DefaultState);
}
}
/// <remarks>
/// Remember to set <see cref="TextScreenVisualsComponent.TextToDraw"/> to a string?[] first.
/// </remarks>
- public void BuildTextLayers(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
+ private void BuildTextLayers(EntityUid uid, TextScreenVisualsComponent component, SpriteComponent? sprite = null)
{
if (!Resolve(uid, ref sprite))
return;
/// <summary>
/// Populates timer.LayerStatesToDraw & the sprite component's layer dict with calculated offsets.
/// </summary>
- public void BuildTimerLayers(EntityUid uid, TextScreenTimerComponent timer, TextScreenVisualsComponent screen)
+ private void BuildTimerLayers(EntityUid uid, TextScreenTimerComponent timer, TextScreenVisualsComponent screen)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
using System.Numerics;
-using Content.Shared.TextScreen;
using Robust.Client.Graphics;
namespace Content.Client.TextScreen;
/// Number of rows of text this screen can render.
/// </summary>
[DataField("rows")]
- public int Rows = 1;
+ public int Rows = 2;
/// <summary>
/// Spacing between each text row
/// <summary>
/// Remaining cooldown between making announcements.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
[DataField]
public float AnnouncementCooldownRemaining;
+ [ViewVariables]
+ [DataField]
+ public float BroadcastCooldownRemaining;
+
/// <summary>
/// Fluent ID for the announcement title
/// If a Fluent ID isn't found, just uses the raw string
/// <summary>
/// Announcement color
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
[DataField]
public Color Color = Color.Gold;
/// <summary>
/// Time in seconds between announcement delays on a per-console basis
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
[DataField]
public int Delay = 90;
/// <summary>
/// Time in seconds of announcement cooldown when a new console is created on a per-console basis
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
[DataField]
public int InitialDelay = 30;
/// <summary>
/// Can call or recall the shuttle
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
[DataField]
public bool CanShuttle = true;
using Content.Server.Administration.Logs;
using Content.Server.AlertLevel;
using Content.Server.Chat.Systems;
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.DeviceNetwork.Systems;
using Content.Server.Interaction;
using Content.Server.Popups;
using Content.Server.RoundEnd;
+using Content.Server.Screens;
+using Content.Server.Screens.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Access.Components;
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
+ [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
// Messages from the BUI
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleSelectAlertLevelMessage>(OnSelectAlertLevelMessage);
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleAnnounceMessage>(OnAnnounceMessage);
+ SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleBroadcastMessage>(OnBroadcastMessage);
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleCallEmergencyShuttleMessage>(OnCallShuttleMessage);
SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleRecallEmergencyShuttleMessage>(OnRecallShuttleMessage);
_uiSystem.SetUiState(ui, new CommunicationsConsoleInterfaceState(
CanAnnounce(comp),
+ CanBroadcast(comp),
CanCallOrRecall(comp),
levels,
currentLevel,
return comp.AnnouncementCooldownRemaining <= 0f;
}
+ private static bool CanBroadcast(CommunicationsConsoleComponent comp)
+ {
+ return comp.AnnouncementCooldownRemaining <= 0f;
+ }
+
private bool CanUse(EntityUid user, EntityUid console)
{
// This shouldn't technically be possible because of BUI but don't trust client.
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following station announcement: {msg}");
}
+ private void OnBroadcastMessage(EntityUid uid, CommunicationsConsoleComponent component, CommunicationsConsoleBroadcastMessage message)
+ {
+ if (!TryComp<DeviceNetworkComponent>(uid, out var net))
+ return;
+
+ var payload = new NetworkPayload
+ {
+ [ScreenMasks.Text] = message.Message
+ };
+
+ _deviceNetworkSystem.QueuePacket(uid, null, payload, net.TransmitFrequency);
+ }
+
private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message)
{
if (!CanCallOrRecall(comp))
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.GameTicking;
+using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components;
using Content.Shared.GameTicking;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
-using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
-
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _shuttle = default!;
- [Dependency] private readonly ShuttleTimerSystem _shuttleTimerSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
/// </summary>
public EntityUid? GetCentcomm()
{
- if (AllEntityQuery<StationCentcommComponent, TransformComponent>()
- .MoveNext(out var centcomm, out var xform))
- {
- return xform.MapUid;
- }
+ AllEntityQuery<StationCentcommComponent>().MoveNext(out var centcomm);
- return null;
+ return centcomm == null ? null : centcomm.MapEntity;
}
public bool CanCallOrRecall()
ActivateCooldown();
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
- // remove all active shuttle timers
+ // remove active clientside evac shuttle timers by zeroing the target time
var zero = TimeSpan.Zero;
var shuttle = _shuttle.GetShuttle();
if (shuttle != null && TryComp<DeviceNetworkComponent>(shuttle, out var net))
[ShuttleTimerMasks.ShuttleTime] = zero,
[ShuttleTimerMasks.SourceTime] = zero,
[ShuttleTimerMasks.DestTime] = zero,
- [ShuttleTimerMasks.Text] = new string?[] { string.Empty, string.Empty }
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, net.TransmitFrequency);
}
--- /dev/null
+namespace Content.Server.Screens.Components;
+
+[RegisterComponent]
+public sealed partial class ScreenComponent : Component
+{
+
+}
+
+/// <summary>
+/// Player-facing hashable string consts for NetworkPayload
+/// </summary>
+public sealed class ScreenMasks
+{
+ public static readonly string Text = Loc.GetString("screen-text");
+ public static readonly string Color = Loc.GetString("screen-color");
+}
+
+/// <summary>
+/// Player-facing hashable string consts for NetworkPayload
+/// </summary>
+public sealed class ShuttleTimerMasks
+{
+ public static readonly string ShuttleTime = Loc.GetString("shuttle-timer-shuttle-time");
+ public static readonly string DestTime = Loc.GetString("shuttle-timer-dest-time");
+ public static readonly string SourceTime = Loc.GetString("shuttle-timer-source-time");
+ public static readonly string ShuttleMap = Loc.GetString("shuttle-timer-shuttle-map");
+ public static readonly string SourceMap = Loc.GetString("shuttle-timer-source-map");
+ public static readonly string DestMap = Loc.GetString("shuttle-timer-dest-map");
+ public static readonly string Docked = Loc.GetString("shuttle-timer-docked");
+ public static readonly string ETA = Loc.GetString("shuttle-timer-eta");
+ public static readonly string ETD = Loc.GetString("shuttle-timer-etd");
+ public static readonly string Bye = Loc.GetString("shuttle-timer-bye");
+ public static readonly string Kill = Loc.GetString("shuttle-timer-kill");
+}
+
--- /dev/null
+using Content.Shared.TextScreen;
+using Content.Server.Screens.Components;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.DeviceNetwork.Systems;
+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 class ScreenSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ScreenComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
+ }
+
+ /// <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>
+ private void OnPacketReceived(EntityUid uid, ScreenComponent component, DeviceNetworkPacketEvent args)
+ {
+ if (args.Data.TryGetValue(ShuttleTimerMasks.ShuttleMap, out _))
+ ShuttleTimer(uid, component, args);
+ else
+ ScreenText(uid, component, args);
+ }
+
+ /// <summary>
+ /// Send a text update to every screen on the same MapUid as the originating comms console.
+ /// </summary>
+ private void ScreenText(EntityUid uid, ScreenComponent component, 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(uid, TextScreenVisuals.TargetTime, out TimeSpan target)
+ && target > _gameTiming.CurTime)
+ return;
+
+ var screenMap = Transform(uid).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(uid, TextScreenVisuals.DefaultText, text);
+ }
+ }
+
+ /// <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(EntityUid uid, ScreenComponent component, DeviceNetworkPacketEvent args)
+ {
+ var timerXform = Transform(uid);
+
+ // 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(uid, TextScreenVisuals.ScreenText, text);
+ _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, _gameTiming.CurTime + duration);
+
+ if (args.Data.TryGetValue(ScreenMasks.Color, out Color color))
+ _appearanceSystem.SetData(uid, TextScreenVisuals.Color, color);
+ }
+}
+++ /dev/null
-namespace Content.Server.Shuttles.Components;
-
-[RegisterComponent]
-public sealed partial class ShuttleTimerComponent : Component
-{
-
-}
-
-/// <summary>
-/// Awkward hashable string consts because NetworkPayload requires string keys
-/// TODO: Refactor NetworkPayload to accept bytes from enums?
-/// </summary>
-public sealed class ShuttleTimerMasks
-{
- public static readonly string ShuttleTime = "ShuttleTime";
- public static readonly string DestTime = "DestTime";
- public static readonly string SourceTime = "SourceTime";
- public static readonly string ShuttleMap = "ShuttleMap";
- public static readonly string SourceMap = "SourceMap";
- public static readonly string DestMap = "DestMap";
- public static readonly string Docked = "Docked";
- public static readonly string Text = "Text";
-}
-
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Salvage;
+using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Spawners.Components;
using System.Threading;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
+using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.UserInterface;
using Content.Server.GameTicking.Events;
using Content.Server.Popups;
using Content.Server.RoundEnd;
+using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Station.Components;
[ShuttleTimerMasks.ShuttleTime] = countdownTime,
[ShuttleTimerMasks.SourceTime] = countdownTime,
[ShuttleTimerMasks.DestTime] = countdownTime,
- [ShuttleTimerMasks.Text] = new string?[] { "BYE!" }
};
+
+ // by popular request
+ // https://discord.com/channels/310555209753690112/770682801607278632/1189989482234126356
+ if (_random.Next(1000) == 0)
+ {
+ payload.Add(ScreenMasks.Text, ShuttleTimerMasks.Kill);
+ payload.Add(ScreenMasks.Color, Color.Red);
+ }
+ else
+ payload.Add(ScreenMasks.Text, ShuttleTimerMasks.Bye);
+
_deviceNetworkSystem.QueuePacket(shuttle, null, payload, net.TransmitFrequency);
}
}
+++ /dev/null
-using Content.Shared.TextScreen;
-using Content.Server.Shuttles.Components;
-using Content.Server.DeviceNetwork.Systems;
-using Robust.Shared.Timing;
-
-
-namespace Content.Server.Shuttles.Systems;
-
-/// <summary>
-/// Controls the wallmounted screens on stations and shuttles displaying e.g. FTL duration, ETA
-/// </summary>
-public sealed class ShuttleTimerSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<ShuttleTimerComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
- }
-
- /// <summary>
- /// Determines if/how a broadcast packet affects this timer.
- /// All shuttle timer packets are broadcast in their network, and subnetting is implemented by filtering timer MapUid.
- /// </summary>
- private void OnPacketReceived(EntityUid uid, ShuttleTimerComponent component, DeviceNetworkPacketEvent args)
- {
- var timerXform = Transform(uid);
-
- // 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 = new string?[] { docked ? Loc.GetString("shuttle-timer-etd") : Loc.GetString("shuttle-timer-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 = new string?[] { Loc.GetString("shuttle-timer-eta") };
- break;
- default:
- return;
- }
-
- if (!args.Data.TryGetValue(key, out TimeSpan duration))
- return;
-
- if (args.Data.TryGetValue(ShuttleTimerMasks.Text, out string?[]? label))
- text = label;
-
- _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, _gameTiming.CurTime + duration);
- _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, text);
- }
-}
public sealed class CommunicationsConsoleInterfaceState : BoundUserInterfaceState
{
public readonly bool CanAnnounce;
+ public readonly bool CanBroadcast;
public readonly bool CanCall;
public readonly TimeSpan? ExpectedCountdownEnd;
public readonly bool CountdownStarted;
public string CurrentAlert;
public float CurrentAlertDelay;
- public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, List<string>? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null)
+ public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canBroadcast, bool canCall, List<string>? alertLevels, string currentAlert, float currentAlertDelay, TimeSpan? expectedCountdownEnd = null)
{
CanAnnounce = canAnnounce;
+ CanBroadcast = canBroadcast;
CanCall = canCall;
ExpectedCountdownEnd = expectedCountdownEnd;
CountdownStarted = expectedCountdownEnd != null;
}
}
+ [Serializable, NetSerializable]
+ public sealed class CommunicationsConsoleBroadcastMessage : BoundUserInterfaceMessage
+ {
+ public readonly string Message;
+ public CommunicationsConsoleBroadcastMessage(string message)
+ {
+ Message = message;
+ }
+ }
+
[Serializable, NetSerializable]
public sealed class CommunicationsConsoleCallEmergencyShuttleMessage : BoundUserInterfaceMessage
{
[Serializable, NetSerializable]
public enum TextScreenVisuals : byte
{
+ // TODO: support for a small image, I think. Probably want to rename textscreen to just screen then.
/// <summary>
- /// Should this show any text? <br/>
- /// Expects a <see cref="bool"/>.
+ /// What text to default to after timer completion?
+ /// Expects a <see cref="string"/>.
/// </summary>
- On,
-
+ DefaultText,
/// <summary>
- /// What text to show? <br/>
- /// Expects a <see cref="string?[]"/>.
+ /// What text to render? <br/>
+ /// Expects a <see cref="string"/>.
/// </summary>
ScreenText,
/// What is the target time? <br/>
/// Expects a <see cref="TimeSpan"/>.
/// </summary>
- TargetTime
+ TargetTime,
+
+ /// <summary>
+ /// Change text color on the entire screen
+ /// Expects a <see cref="Color"/>.
+ /// </summary>
+ Color
}
comms-console-menu-title = Communications Console
comms-console-menu-announcement-placeholder = Announcement text...
comms-console-menu-announcement-button = Announce
+comms-console-menu-broadcast-button = Broadcast
comms-console-menu-call-shuttle = Call emergency shuttle
comms-console-menu-recall-shuttle = Recall emergency shuttle
--- /dev/null
+screens-text = text
+screens-color = color
shuttle-timer-eta = ETA
shuttle-timer-etd = ETD
+shuttle-timer-shuttle-time = ShuttleTime
+shuttle-timer-source-time = SourceTime
+shuttle-timer-dest-time = DestTime
+shuttle-timer-shuttle-map = ShuttleMap
+shuttle-timer-source-map = SourceMap
+shuttle-timer-dest-map = DestMap
+shuttle-timer-docked = Docked
+shuttle-timer-bye = BYE!
+shuttle-timer-kill = KILL
access: [[ "Command" ]]
- type: CommunicationsConsole
title: comms-console-announcement-title-station
+ - type: DeviceNetwork
+ transmitFrequencyId: ShuttleTimer
- type: ActivatableUI
key: enum.CommunicationsConsoleUiKey.Key
- type: UserInterface
id: Screen
name: screen
description: Displays text or time.
+ placement:
+ mode: SnapgridCenter
+ snap:
+ - Wallmount
components:
- type: Transform
anchored: true
- type: WallMount
arc: 360
- type: InteractionOutline
+ - type: Clickable
- type: Appearance
- type: Rotatable
- type: TextScreenVisuals
enabled: false
usesApcPower: true
- type: ExtensionCableReceiver
- - type: ShuttleTimer
+ - type: Screen
- type: DeviceNetwork
receiveFrequencyId: ShuttleTimer
textOffset: 0,8
timerOffset: 0,8
textLength: 5
+ rows: 1
- type: Sprite
drawdepth: WallMountedItems
sprite: Structures/Wallmounts/signalscreen.rsi