2022-06-03 21:37:35 +10:00
using System.Globalization ;
using Content.Server.Access.Systems ;
2022-08-07 20:21:56 -03:00
using Content.Server.Administration.Logs ;
2022-06-03 21:37:35 +10:00
using Content.Server.AlertLevel ;
2022-06-23 20:11:03 +10:00
using Content.Server.Chat.Systems ;
2024-01-27 05:51:24 -08:00
using Content.Server.DeviceNetwork ;
using Content.Server.DeviceNetwork.Components ;
using Content.Server.DeviceNetwork.Systems ;
2022-07-01 13:40:36 -07:00
using Content.Server.Interaction ;
2022-06-03 21:37:35 +10:00
using Content.Server.Popups ;
using Content.Server.RoundEnd ;
2024-01-27 05:51:24 -08:00
using Content.Server.Screens ;
using Content.Server.Screens.Components ;
2022-06-26 15:20:45 +10:00
using Content.Server.Shuttles.Systems ;
2022-06-03 21:37:35 +10:00
using Content.Server.Station.Systems ;
using Content.Shared.Access.Components ;
using Content.Shared.Access.Systems ;
2022-07-01 13:40:36 -07:00
using Content.Shared.CCVar ;
2024-01-21 13:14:01 +04:00
using Content.Shared.Chat ;
2022-06-03 21:37:35 +10:00
using Content.Shared.Communications ;
2022-08-07 20:21:56 -03:00
using Content.Shared.Database ;
2024-02-11 14:19:45 +11:00
using Content.Shared.DeviceNetwork ;
2023-02-19 01:03:06 +00:00
using Content.Shared.Emag.Components ;
2022-07-09 02:09:52 -07:00
using Content.Shared.Popups ;
2022-07-01 13:40:36 -07:00
using Robust.Server.GameObjects ;
using Robust.Shared.Configuration ;
2022-06-03 21:37:35 +10:00
namespace Content.Server.Communications
{
public sealed class CommunicationsConsoleSystem : EntitySystem
{
2022-06-26 15:20:45 +10:00
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default ! ;
2022-07-01 13:40:36 -07:00
[Dependency] private readonly InteractionSystem _interaction = default ! ;
2022-06-03 21:37:35 +10:00
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default ! ;
2022-06-26 15:20:45 +10:00
[Dependency] private readonly ChatSystem _chatSystem = default ! ;
2024-01-27 05:51:24 -08:00
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default ! ;
2023-03-24 12:54:41 +11:00
[Dependency] private readonly EmergencyShuttleSystem _emergency = default ! ;
2022-06-03 21:37:35 +10:00
[Dependency] private readonly IdCardSystem _idCardSystem = default ! ;
[Dependency] private readonly PopupSystem _popupSystem = default ! ;
2022-06-26 15:20:45 +10:00
[Dependency] private readonly RoundEndSystem _roundEndSystem = default ! ;
[Dependency] private readonly StationSystem _stationSystem = default ! ;
2023-09-11 09:42:41 +10:00
[Dependency] private readonly UserInterfaceSystem _uiSystem = default ! ;
2022-07-01 13:40:36 -07:00
[Dependency] private readonly IConfigurationManager _cfg = default ! ;
2022-08-07 20:21:56 -03:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2022-06-03 21:37:35 +10:00
2022-07-01 13:40:36 -07:00
private const float UIUpdateInterval = 5.0f ;
2022-06-03 21:37:35 +10:00
public override void Initialize ( )
{
// All events that refresh the BUI
SubscribeLocalEvent < AlertLevelChangedEvent > ( OnAlertLevelChanged ) ;
2023-07-08 09:02:17 -07:00
SubscribeLocalEvent < CommunicationsConsoleComponent , ComponentInit > ( ( uid , comp , _ ) = > UpdateCommsConsoleInterface ( uid , comp ) ) ;
2022-06-03 21:37:35 +10:00
SubscribeLocalEvent < RoundEndSystemChangedEvent > ( _ = > OnGenericBroadcastEvent ( ) ) ;
SubscribeLocalEvent < AlertLevelDelayFinishedEvent > ( _ = > OnGenericBroadcastEvent ( ) ) ;
// Messages from the BUI
SubscribeLocalEvent < CommunicationsConsoleComponent , CommunicationsConsoleSelectAlertLevelMessage > ( OnSelectAlertLevelMessage ) ;
SubscribeLocalEvent < CommunicationsConsoleComponent , CommunicationsConsoleAnnounceMessage > ( OnAnnounceMessage ) ;
2024-01-27 05:51:24 -08:00
SubscribeLocalEvent < CommunicationsConsoleComponent , CommunicationsConsoleBroadcastMessage > ( OnBroadcastMessage ) ;
2022-06-03 21:37:35 +10:00
SubscribeLocalEvent < CommunicationsConsoleComponent , CommunicationsConsoleCallEmergencyShuttleMessage > ( OnCallShuttleMessage ) ;
SubscribeLocalEvent < CommunicationsConsoleComponent , CommunicationsConsoleRecallEmergencyShuttleMessage > ( OnRecallShuttleMessage ) ;
2024-01-08 06:28:06 -06:00
// On console init, set cooldown
SubscribeLocalEvent < CommunicationsConsoleComponent , MapInitEvent > ( OnCommunicationsConsoleMapInit ) ;
2022-06-03 21:37:35 +10:00
}
public override void Update ( float frameTime )
{
2023-07-08 09:02:17 -07:00
var query = EntityQueryEnumerator < CommunicationsConsoleComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var comp ) )
2022-06-03 21:37:35 +10:00
{
2022-07-01 13:40:36 -07:00
// TODO refresh the UI in a less horrible way
if ( comp . AnnouncementCooldownRemaining > = 0f )
2022-06-03 21:37:35 +10:00
{
2022-07-01 13:40:36 -07:00
comp . AnnouncementCooldownRemaining - = frameTime ;
2022-06-03 21:37:35 +10:00
}
2022-07-01 13:40:36 -07:00
comp . UIUpdateAccumulator + = frameTime ;
if ( comp . UIUpdateAccumulator < UIUpdateInterval )
continue ;
comp . UIUpdateAccumulator - = UIUpdateInterval ;
2023-10-11 02:17:59 -07:00
if ( _uiSystem . TryGetUi ( uid , CommunicationsConsoleUiKey . Key , out var ui ) & & ui . SubscribedSessions . Count > 0 )
UpdateCommsConsoleInterface ( uid , comp , ui ) ;
2022-06-03 21:37:35 +10:00
}
base . Update ( frameTime ) ;
}
2024-01-08 06:28:06 -06:00
public void OnCommunicationsConsoleMapInit ( EntityUid uid , CommunicationsConsoleComponent comp , MapInitEvent args )
{
comp . AnnouncementCooldownRemaining = comp . InitialDelay ;
}
2022-06-03 21:37:35 +10:00
/// <summary>
/// Update the UI of every comms console.
/// </summary>
private void OnGenericBroadcastEvent ( )
{
2023-07-08 09:02:17 -07:00
var query = EntityQueryEnumerator < CommunicationsConsoleComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var comp ) )
2022-06-03 21:37:35 +10:00
{
2023-07-08 09:02:17 -07:00
UpdateCommsConsoleInterface ( uid , comp ) ;
2022-06-03 21:37:35 +10:00
}
}
/// <summary>
/// Updates all comms consoles belonging to the station that the alert level was set on
/// </summary>
/// <param name="args">Alert level changed event arguments</param>
private void OnAlertLevelChanged ( AlertLevelChangedEvent args )
{
2023-07-08 09:02:17 -07:00
var query = EntityQueryEnumerator < CommunicationsConsoleComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var comp ) )
2022-06-03 21:37:35 +10:00
{
2023-07-08 09:02:17 -07:00
var entStation = _stationSystem . GetOwningStation ( uid ) ;
2022-06-03 21:37:35 +10:00
if ( args . Station = = entStation )
2023-07-08 09:02:17 -07:00
UpdateCommsConsoleInterface ( uid , comp ) ;
2022-06-03 21:37:35 +10:00
}
}
2022-06-26 15:20:45 +10:00
/// <summary>
/// Updates the UI for all comms consoles.
/// </summary>
public void UpdateCommsConsoleInterface ( )
{
2023-07-08 09:02:17 -07:00
var query = EntityQueryEnumerator < CommunicationsConsoleComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var comp ) )
2022-06-26 15:20:45 +10:00
{
2023-07-08 09:02:17 -07:00
UpdateCommsConsoleInterface ( uid , comp ) ;
2022-06-26 15:20:45 +10:00
}
}
/// <summary>
/// Updates the UI for a particular comms console.
/// </summary>
2023-10-11 02:17:59 -07:00
public void UpdateCommsConsoleInterface ( EntityUid uid , CommunicationsConsoleComponent comp , PlayerBoundUserInterface ? ui = null )
2022-06-03 21:37:35 +10:00
{
2023-10-11 02:17:59 -07:00
if ( ui = = null & & ! _uiSystem . TryGetUi ( uid , CommunicationsConsoleUiKey . Key , out ui ) )
return ;
2022-06-03 21:37:35 +10:00
var stationUid = _stationSystem . GetOwningStation ( uid ) ;
List < string > ? levels = null ;
string currentLevel = default ! ;
float currentDelay = 0 ;
if ( stationUid ! = null )
{
if ( TryComp ( stationUid . Value , out AlertLevelComponent ? alertComp ) & &
alertComp . AlertLevels ! = null )
{
if ( alertComp . IsSelectable )
{
levels = new ( ) ;
foreach ( var ( id , detail ) in alertComp . AlertLevels . Levels )
{
if ( detail . Selectable )
{
levels . Add ( id ) ;
}
}
}
currentLevel = alertComp . CurrentLevel ;
currentDelay = _alertLevelSystem . GetAlertLevelDelay ( stationUid . Value , alertComp ) ;
}
}
2023-10-11 02:17:59 -07:00
_uiSystem . SetUiState ( ui , new CommunicationsConsoleInterfaceState (
CanAnnounce ( comp ) ,
2024-01-27 05:51:24 -08:00
CanBroadcast ( comp ) ,
2023-10-11 02:17:59 -07:00
CanCallOrRecall ( comp ) ,
levels ,
currentLevel ,
currentDelay ,
_roundEndSystem . ExpectedCountdownEnd
) ) ;
2022-06-03 21:37:35 +10:00
}
2023-07-08 09:02:17 -07:00
private static bool CanAnnounce ( CommunicationsConsoleComponent comp )
2022-06-03 21:37:35 +10:00
{
return comp . AnnouncementCooldownRemaining < = 0f ;
}
2024-01-27 05:51:24 -08:00
private static bool CanBroadcast ( CommunicationsConsoleComponent comp )
{
return comp . AnnouncementCooldownRemaining < = 0f ;
}
2022-06-03 21:37:35 +10:00
private bool CanUse ( EntityUid user , EntityUid console )
{
2022-07-01 13:40:36 -07:00
// This shouldn't technically be possible because of BUI but don't trust client.
if ( ! _interaction . InRangeUnobstructed ( console , user ) )
return false ;
2023-02-19 01:03:06 +00:00
if ( TryComp < AccessReaderComponent > ( console , out var accessReaderComponent ) & & ! HasComp < EmaggedComponent > ( console ) )
2022-06-03 21:37:35 +10:00
{
2023-09-03 13:05:22 +12:00
return _accessReaderSystem . IsAllowed ( user , console , accessReaderComponent ) ;
2022-06-03 21:37:35 +10:00
}
return true ;
}
2022-07-01 13:40:36 -07:00
private bool CanCallOrRecall ( CommunicationsConsoleComponent comp )
2022-06-03 21:37:35 +10:00
{
2022-07-01 13:40:36 -07:00
// Defer to what the round end system thinks we should be able to do.
2023-03-24 12:54:41 +11:00
if ( _emergency . EmergencyShuttleArrived | | ! _roundEndSystem . CanCallOrRecall ( ) )
2022-07-01 13:40:36 -07:00
return false ;
// Calling shuttle checks
if ( _roundEndSystem . ExpectedCountdownEnd is null )
2023-10-10 20:06:24 -07:00
return comp . CanShuttle ;
2022-06-26 15:20:45 +10:00
2022-07-01 13:40:36 -07:00
// Recalling shuttle checks
var recallThreshold = _cfg . GetCVar ( CCVars . EmergencyRecallTurningPoint ) ;
// shouldn't really be happening if we got here
if ( _roundEndSystem . ShuttleTimeLeft is not { } left
| | _roundEndSystem . ExpectedShuttleLength is not { } expected )
return false ;
return ! ( left . TotalSeconds / expected . TotalSeconds < recallThreshold ) ;
2022-06-03 21:37:35 +10:00
}
private void OnSelectAlertLevelMessage ( EntityUid uid , CommunicationsConsoleComponent comp , CommunicationsConsoleSelectAlertLevelMessage message )
{
2023-10-11 02:17:59 -07:00
if ( message . Session . AttachedEntity is not { Valid : true } mob )
return ;
2022-06-03 21:37:35 +10:00
if ( ! CanUse ( mob , uid ) )
{
2022-12-19 10:41:47 +13:00
_popupSystem . PopupCursor ( Loc . GetString ( "comms-console-permission-denied" ) , message . Session , PopupType . Medium ) ;
2022-06-03 21:37:35 +10:00
return ;
}
var stationUid = _stationSystem . GetOwningStation ( uid ) ;
if ( stationUid ! = null )
{
_alertLevelSystem . SetLevel ( stationUid . Value , message . Level , true , true ) ;
}
}
private void OnAnnounceMessage ( EntityUid uid , CommunicationsConsoleComponent comp ,
CommunicationsConsoleAnnounceMessage message )
{
2024-01-21 13:14:01 +04:00
var maxLength = _cfg . GetCVar ( CCVars . ChatMaxAnnouncementLength ) ;
var msg = SharedChatSystem . SanitizeAnnouncement ( message . Message , maxLength ) ;
2022-06-03 21:37:35 +10:00
var author = Loc . GetString ( "comms-console-announcement-unknown-sender" ) ;
2023-07-08 09:02:17 -07:00
if ( message . Session . AttachedEntity is { Valid : true } mob )
2022-06-03 21:37:35 +10:00
{
if ( ! CanAnnounce ( comp ) )
{
return ;
}
if ( ! CanUse ( mob , uid ) )
{
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "comms-console-permission-denied" ) , uid , message . Session ) ;
2022-06-03 21:37:35 +10:00
return ;
}
if ( _idCardSystem . TryFindIdCard ( mob , out var id ) )
{
2023-10-19 12:34:31 -07:00
author = $"{id.Comp.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id.Comp.JobTitle ?? string.Empty)})" . Trim ( ) ;
2022-06-03 21:37:35 +10:00
}
}
2023-10-10 20:06:24 -07:00
comp . AnnouncementCooldownRemaining = comp . Delay ;
2023-07-08 09:02:17 -07:00
UpdateCommsConsoleInterface ( uid , comp ) ;
2022-06-03 21:37:35 +10:00
2023-08-30 10:56:20 +03:00
var ev = new CommunicationConsoleAnnouncementEvent ( uid , comp , msg , message . Session . AttachedEntity ) ;
RaiseLocalEvent ( ref ev ) ;
2022-06-03 21:37:35 +10:00
// allow admemes with vv
2023-10-10 20:06:24 -07:00
Loc . TryGetString ( comp . Title , out var title ) ;
title ? ? = comp . Title ;
2022-06-03 21:37:35 +10:00
msg + = "\n" + Loc . GetString ( "comms-console-announcement-sent-by" ) + " " + author ;
2023-10-10 20:06:24 -07:00
if ( comp . Global )
2022-06-03 21:37:35 +10:00
{
2023-10-10 20:06:24 -07:00
_chatSystem . DispatchGlobalAnnouncement ( msg , title , announcementSound : comp . Sound , colorOverride : comp . Color ) ;
2022-08-07 20:21:56 -03:00
if ( message . Session . AttachedEntity ! = null )
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following global announcement: {msg}" ) ;
2022-06-04 19:09:04 +10:00
return ;
2022-06-03 21:37:35 +10:00
}
2023-10-10 20:06:24 -07:00
_chatSystem . DispatchStationAnnouncement ( uid , msg , title , colorOverride : comp . Color ) ;
2022-08-07 20:21:56 -03:00
if ( message . Session . AttachedEntity ! = null )
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following station announcement: {msg}" ) ;
2022-06-03 21:37:35 +10:00
}
2024-01-27 05:51:24 -08:00
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 ) ;
2024-03-12 12:57:05 +02:00
if ( message . Session . AttachedEntity ! = null )
_adminLogger . Add ( LogType . DeviceNetwork , LogImpact . Low , $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following broadcast: {message.Message:msg}" ) ;
2024-01-27 05:51:24 -08:00
}
2022-06-03 21:37:35 +10:00
private void OnCallShuttleMessage ( EntityUid uid , CommunicationsConsoleComponent comp , CommunicationsConsoleCallEmergencyShuttleMessage message )
{
2023-10-11 02:17:59 -07:00
if ( ! CanCallOrRecall ( comp ) )
return ;
if ( message . Session . AttachedEntity is not { Valid : true } mob )
return ;
2022-06-03 21:37:35 +10:00
if ( ! CanUse ( mob , uid ) )
{
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "comms-console-permission-denied" ) , uid , message . Session ) ;
2022-06-03 21:37:35 +10:00
return ;
}
2023-08-30 10:56:20 +03:00
var ev = new CommunicationConsoleCallShuttleAttemptEvent ( uid , comp , mob ) ;
RaiseLocalEvent ( ref ev ) ;
if ( ev . Cancelled )
{
_popupSystem . PopupEntity ( ev . Reason ? ? Loc . GetString ( "comms-console-shuttle-unavailable" ) , uid , message . Session ) ;
return ;
}
2022-06-03 21:37:35 +10:00
_roundEndSystem . RequestRoundEnd ( uid ) ;
2022-08-07 20:21:56 -03:00
_adminLogger . Add ( LogType . Action , LogImpact . Extreme , $"{ToPrettyString(mob):player} has called the shuttle." ) ;
2022-06-03 21:37:35 +10:00
}
private void OnRecallShuttleMessage ( EntityUid uid , CommunicationsConsoleComponent comp , CommunicationsConsoleRecallEmergencyShuttleMessage message )
{
2023-10-11 02:17:59 -07:00
if ( ! CanCallOrRecall ( comp ) )
return ;
if ( message . Session . AttachedEntity is not { Valid : true } mob )
return ;
2022-06-03 21:37:35 +10:00
if ( ! CanUse ( mob , uid ) )
{
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "comms-console-permission-denied" ) , uid , message . Session ) ;
2022-06-03 21:37:35 +10:00
return ;
}
2022-07-01 13:40:36 -07:00
2022-06-03 21:37:35 +10:00
_roundEndSystem . CancelRoundEndCountdown ( uid ) ;
2022-08-07 20:21:56 -03:00
_adminLogger . Add ( LogType . Action , LogImpact . Extreme , $"{ToPrettyString(mob):player} has recalled the shuttle." ) ;
2022-06-03 21:37:35 +10:00
}
}
2023-08-30 10:56:20 +03:00
/// <summary>
/// Raised on announcement
/// </summary>
[ByRefEvent]
public record struct CommunicationConsoleAnnouncementEvent ( EntityUid Uid , CommunicationsConsoleComponent Component , string Text , EntityUid ? Sender )
{
public EntityUid Uid = Uid ;
public CommunicationsConsoleComponent Component = Component ;
public EntityUid ? Sender = Sender ;
public string Text = Text ;
}
/// <summary>
/// Raised on shuttle call attempt. Can be cancelled
/// </summary>
[ByRefEvent]
public record struct CommunicationConsoleCallShuttleAttemptEvent ( EntityUid Uid , CommunicationsConsoleComponent Component , EntityUid ? Sender )
{
public bool Cancelled = false ;
public EntityUid Uid = Uid ;
public CommunicationsConsoleComponent Component = Component ;
public EntityUid ? Sender = Sender ;
public string? Reason ;
}
2022-06-03 21:37:35 +10:00
}