2023-03-24 12:54:41 +11:00
using Content.Server.Access.Systems ;
2022-06-26 15:20:45 +10:00
using Content.Server.Administration.Logs ;
using Content.Server.Administration.Managers ;
using Content.Server.Chat.Systems ;
using Content.Server.Communications ;
using Content.Server.GameTicking.Events ;
2023-03-24 12:54:41 +11:00
using Content.Server.Popups ;
using Content.Server.RoundEnd ;
2022-06-26 15:20:45 +10:00
using Content.Server.Shuttles.Components ;
using Content.Server.Station.Components ;
using Content.Server.Station.Systems ;
2023-03-24 12:54:41 +11:00
using Content.Shared.Access.Systems ;
2022-06-26 15:20:45 +10:00
using Content.Shared.CCVar ;
using Content.Shared.Database ;
using Content.Shared.Shuttles.Events ;
2022-11-13 17:47:48 +11:00
using Robust.Server.GameObjects ;
2022-06-26 15:20:45 +10:00
using Robust.Server.Maps ;
using Robust.Server.Player ;
using Robust.Shared.Configuration ;
using Robust.Shared.Map ;
using Robust.Shared.Player ;
using Robust.Shared.Random ;
2023-03-24 12:54:41 +11:00
using Robust.Shared.Timing ;
2022-06-26 15:20:45 +10:00
namespace Content.Server.Shuttles.Systems ;
2023-03-24 12:54:41 +11:00
public sealed partial class EmergencyShuttleSystem : EntitySystem
2022-06-26 15:20:45 +10:00
{
/ *
2022-07-20 04:08:24 +00:00
* Handles the escape shuttle + CentCom .
2022-06-26 15:20:45 +10:00
* /
[Dependency] private readonly IAdminLogManager _logger = default ! ;
[Dependency] private readonly IAdminManager _admin = default ! ;
[Dependency] private readonly IConfigurationManager _configManager = default ! ;
2023-03-24 12:54:41 +11:00
[Dependency] private readonly IGameTiming _timing = default ! ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
2022-06-26 15:20:45 +10:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2023-03-24 12:54:41 +11:00
[Dependency] private readonly AccessReaderSystem _reader = default ! ;
2022-06-26 15:20:45 +10:00
[Dependency] private readonly ChatSystem _chatSystem = default ! ;
[Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default ! ;
2023-03-24 12:54:41 +11:00
[Dependency] private readonly DockingSystem _dock = default ! ;
[Dependency] private readonly IdCardSystem _idSystem = default ! ;
2022-11-13 17:47:48 +11:00
[Dependency] private readonly MapLoaderSystem _map = default ! ;
2023-03-24 12:54:41 +11:00
[Dependency] private readonly PopupSystem _popup = default ! ;
[Dependency] private readonly RoundEndSystem _roundEnd = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly ShuttleSystem _shuttle = default ! ;
2022-06-26 15:20:45 +10:00
[Dependency] private readonly StationSystem _station = default ! ;
2023-03-24 12:54:41 +11:00
[Dependency] private readonly UserInterfaceSystem _uiSystem = default ! ;
private ISawmill _sawmill = default ! ;
2022-06-26 15:20:45 +10:00
2022-09-13 19:42:19 -07:00
public MapId ? CentComMap { get ; private set ; }
public EntityUid ? CentCom { get ; private set ; }
2022-06-26 15:20:45 +10:00
/// <summary>
/// Used for multiple shuttle spawn offsets.
/// </summary>
private float _shuttleIndex ;
private const float ShuttleSpawnBuffer = 1f ;
2022-06-27 15:19:40 +10:00
private bool _emergencyShuttleEnabled ;
2023-03-24 12:54:41 +11:00
public override void Initialize ( )
2022-06-26 15:20:45 +10:00
{
2023-03-24 12:54:41 +11:00
_sawmill = Logger . GetSawmill ( "shuttle.emergency" ) ;
2022-06-27 15:19:40 +10:00
_emergencyShuttleEnabled = _configManager . GetCVar ( CCVars . EmergencyShuttleEnabled ) ;
// Don't immediately invoke as roundstart will just handle it.
_configManager . OnValueChanged ( CCVars . EmergencyShuttleEnabled , SetEmergencyShuttleEnabled ) ;
2022-06-26 15:20:45 +10:00
SubscribeLocalEvent < RoundStartingEvent > ( OnRoundStart ) ;
SubscribeLocalEvent < StationDataComponent , ComponentStartup > ( OnStationStartup ) ;
SubscribeNetworkEvent < EmergencyShuttleRequestPositionMessage > ( OnShuttleRequestPosition ) ;
2023-03-24 12:54:41 +11:00
InitializeEmergencyConsole ( ) ;
2022-06-26 15:20:45 +10:00
}
2022-06-27 15:19:40 +10:00
private void SetEmergencyShuttleEnabled ( bool value )
{
2023-03-24 12:54:41 +11:00
if ( _emergencyShuttleEnabled = = value )
return ;
2022-06-27 15:19:40 +10:00
_emergencyShuttleEnabled = value ;
if ( value )
{
SetupEmergencyShuttle ( ) ;
}
else
{
CleanupEmergencyShuttle ( ) ;
}
}
2023-03-24 12:54:41 +11:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
UpdateEmergencyConsole ( frameTime ) ;
}
public override void Shutdown ( )
2022-06-27 15:19:40 +10:00
{
2023-03-24 12:54:41 +11:00
_configManager . UnsubValueChanged ( CCVars . EmergencyShuttleEnabled , SetEmergencyShuttleEnabled ) ;
ShutdownEmergencyConsole ( ) ;
2022-06-27 15:19:40 +10:00
}
2022-06-26 15:20:45 +10:00
/// <summary>
/// If the client is requesting debug info on where an emergency shuttle would dock.
/// </summary>
private void OnShuttleRequestPosition ( EmergencyShuttleRequestPositionMessage msg , EntitySessionEventArgs args )
{
2023-03-24 12:54:41 +11:00
if ( ! _admin . IsAdmin ( ( IPlayerSession ) args . SenderSession ) )
return ;
2022-06-26 15:20:45 +10:00
var player = args . SenderSession . AttachedEntity ;
if ( player = = null | |
2022-06-27 15:11:39 +02:00
! TryComp < StationDataComponent > ( _station . GetOwningStation ( player . Value ) , out var stationData ) | |
2023-03-24 12:54:41 +11:00
! HasComp < ShuttleComponent > ( stationData . EmergencyShuttle ) )
{
return ;
}
2022-06-26 15:20:45 +10:00
2022-08-08 09:22:46 +10:00
var targetGrid = _station . GetLargestGrid ( stationData ) ;
2023-03-24 12:54:41 +11:00
if ( targetGrid = = null )
return ;
var config = _dock . GetDockingConfig ( stationData . EmergencyShuttle . Value , targetGrid . Value ) ;
if ( config = = null )
return ;
2022-06-26 15:20:45 +10:00
2022-06-27 15:11:39 +02:00
RaiseNetworkEvent ( new EmergencyShuttlePositionMessage ( )
2022-06-26 15:20:45 +10:00
{
2022-06-27 15:11:39 +02:00
StationUid = targetGrid ,
Position = config . Area ,
} ) ;
2022-06-26 15:20:45 +10:00
}
/// <summary>
/// Calls the emergency shuttle for the station.
/// </summary>
public void CallEmergencyShuttle ( EntityUid ? stationUid )
{
if ( ! TryComp < StationDataComponent > ( stationUid , out var stationData ) | |
2022-06-27 15:11:39 +02:00
! TryComp < TransformComponent > ( stationData . EmergencyShuttle , out var xform ) | |
2023-03-24 12:54:41 +11:00
! TryComp < ShuttleComponent > ( stationData . EmergencyShuttle , out var shuttle ) )
{
return ;
}
2022-06-26 15:20:45 +10:00
2023-03-24 12:54:41 +11:00
var targetGrid = _station . GetLargestGrid ( stationData ) ;
2022-06-26 15:20:45 +10:00
2022-06-27 15:11:39 +02:00
// UHH GOOD LUCK
if ( targetGrid = = null )
2022-06-26 15:20:45 +10:00
{
2022-06-27 15:11:39 +02:00
_logger . Add ( LogType . EmergencyShuttle , LogImpact . High , $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to dock with station {ToPrettyString(stationUid.Value)}" ) ;
_chatSystem . DispatchStationAnnouncement ( stationUid . Value , Loc . GetString ( "emergency-shuttle-good-luck" ) , playDefaultSound : false ) ;
// TODO: Need filter extensions or something don't blame me.
2023-03-24 12:54:41 +11:00
_audio . PlayGlobal ( "/Audio/Misc/notice1.ogg" , Filter . Broadcast ( ) , true ) ;
2022-06-27 15:11:39 +02:00
return ;
}
2022-06-26 15:20:45 +10:00
2022-07-19 21:47:49 +10:00
var xformQuery = GetEntityQuery < TransformComponent > ( ) ;
2023-03-24 12:54:41 +11:00
if ( _shuttle . TryFTLDock ( stationData . EmergencyShuttle . Value , shuttle , targetGrid . Value ) )
2022-06-27 15:11:39 +02:00
{
2022-07-09 15:19:52 +10:00
if ( TryComp < TransformComponent > ( targetGrid . Value , out var targetXform ) )
{
2023-03-24 12:54:41 +11:00
var angle = _dock . GetAngle ( stationData . EmergencyShuttle . Value , xform , targetGrid . Value , targetXform , xformQuery ) ;
2022-07-09 15:19:52 +10:00
_chatSystem . DispatchStationAnnouncement ( stationUid . Value , Loc . GetString ( "emergency-shuttle-docked" , ( "time" , $"{_consoleAccumulator:0}" ) , ( "direction" , angle . GetDir ( ) ) ) , playDefaultSound : false ) ;
}
2022-06-26 15:20:45 +10:00
_logger . Add ( LogType . EmergencyShuttle , LogImpact . High , $"Emergency shuttle {ToPrettyString(stationUid.Value)} docked with stations" ) ;
// TODO: Need filter extensions or something don't blame me.
2023-03-24 12:54:41 +11:00
_audio . PlayGlobal ( "/Audio/Announcements/shuttle_dock.ogg" , Filter . Broadcast ( ) , true ) ;
2022-06-26 15:20:45 +10:00
}
else
{
2022-07-19 21:47:49 +10:00
if ( TryComp < TransformComponent > ( targetGrid . Value , out var targetXform ) )
{
2023-03-24 12:54:41 +11:00
var angle = _dock . GetAngle ( stationData . EmergencyShuttle . Value , xform , targetGrid . Value , targetXform , xformQuery ) ;
2022-07-19 21:47:49 +10:00
_chatSystem . DispatchStationAnnouncement ( stationUid . Value , Loc . GetString ( "emergency-shuttle-nearby" , ( "direction" , angle . GetDir ( ) ) ) , playDefaultSound : false ) ;
}
2022-06-26 15:20:45 +10:00
_logger . Add ( LogType . EmergencyShuttle , LogImpact . High , $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to find a valid docking port for {ToPrettyString(stationUid.Value)}" ) ;
// TODO: Need filter extensions or something don't blame me.
2023-03-24 12:54:41 +11:00
_audio . PlayGlobal ( "/Audio/Misc/notice1.ogg" , Filter . Broadcast ( ) , true ) ;
2022-06-26 15:20:45 +10:00
}
}
private void OnStationStartup ( EntityUid uid , StationDataComponent component , ComponentStartup args )
{
AddEmergencyShuttle ( component ) ;
}
private void OnRoundStart ( RoundStartingEvent ev )
{
2023-03-24 12:54:41 +11:00
CleanupEmergencyConsole ( ) ;
2022-06-27 15:19:40 +10:00
SetupEmergencyShuttle ( ) ;
2022-06-26 15:20:45 +10:00
}
/// <summary>
/// Spawns the emergency shuttle for each station and starts the countdown until controls unlock.
/// </summary>
public void CallEmergencyShuttle ( )
{
2023-03-24 12:54:41 +11:00
if ( EmergencyShuttleArrived )
return ;
2022-06-26 15:20:45 +10:00
2022-06-27 15:19:40 +10:00
if ( ! _emergencyShuttleEnabled )
{
_roundEnd . EndRound ( ) ;
return ;
}
2022-06-26 15:20:45 +10:00
_consoleAccumulator = _configManager . GetCVar ( CCVars . EmergencyShuttleDockTime ) ;
EmergencyShuttleArrived = true ;
2022-09-13 19:42:19 -07:00
if ( CentComMap ! = null )
_mapManager . SetMapPaused ( CentComMap . Value , false ) ;
2022-06-26 15:20:45 +10:00
2023-03-24 12:54:41 +11:00
var query = AllEntityQuery < StationDataComponent > ( ) ;
2022-06-26 15:20:45 +10:00
2023-03-24 12:54:41 +11:00
while ( query . MoveNext ( out var uid , out var comp ) )
2022-06-26 15:20:45 +10:00
{
2023-03-24 12:54:41 +11:00
CallEmergencyShuttle ( uid ) ;
2022-06-26 15:20:45 +10:00
}
2023-03-24 12:54:41 +11:00
_commsConsole . UpdateCommsConsoleInterface ( ) ;
2022-06-26 15:20:45 +10:00
}
2022-06-27 15:19:40 +10:00
private void SetupEmergencyShuttle ( )
2022-06-26 15:20:45 +10:00
{
2022-09-13 19:42:19 -07:00
if ( ! _emergencyShuttleEnabled | | CentComMap ! = null & & _mapManager . MapExists ( CentComMap . Value ) ) return ;
2022-06-26 15:20:45 +10:00
2022-09-13 19:42:19 -07:00
CentComMap = _mapManager . CreateMap ( ) ;
_mapManager . SetMapPaused ( CentComMap . Value , true ) ;
2022-06-26 15:20:45 +10:00
2022-07-20 04:08:24 +00:00
// Load CentCom
2022-07-26 14:00:38 +00:00
var centComPath = _configManager . GetCVar ( CCVars . CentcommMap ) ;
2022-06-27 15:11:39 +02:00
2022-07-26 14:00:38 +00:00
if ( ! string . IsNullOrEmpty ( centComPath ) )
2022-06-27 15:11:39 +02:00
{
2022-11-13 17:47:48 +11:00
var centcomm = _map . LoadGrid ( CentComMap . Value , "/Maps/centcomm.yml" ) ;
2022-09-13 19:42:19 -07:00
CentCom = centcomm ;
2022-07-15 14:11:41 +10:00
2022-09-13 19:42:19 -07:00
if ( CentCom ! = null )
2023-03-24 12:54:41 +11:00
_shuttle . AddFTLDestination ( CentCom . Value , false ) ;
2022-06-27 15:11:39 +02:00
}
else
{
2022-07-20 04:08:24 +00:00
_sawmill . Info ( "No CentCom map found, skipping setup." ) ;
2022-06-27 15:11:39 +02:00
}
2022-06-26 15:20:45 +10:00
foreach ( var comp in EntityQuery < StationDataComponent > ( true ) )
{
AddEmergencyShuttle ( comp ) ;
}
}
private void AddEmergencyShuttle ( StationDataComponent component )
{
2022-11-24 13:28:03 -05:00
if ( ! _emergencyShuttleEnabled
| | CentComMap = = null
| | component . EmergencyShuttle ! = null
| | component . StationConfig = = null )
{
return ;
}
2022-06-26 15:20:45 +10:00
// Load escape shuttle
2022-11-24 13:28:03 -05:00
var shuttlePath = component . StationConfig . EmergencyShuttlePath ;
var shuttle = _map . LoadGrid ( CentComMap . Value , shuttlePath . ToString ( ) , new MapLoadOptions ( )
2022-06-26 15:20:45 +10:00
{
2022-07-20 04:08:24 +00:00
// Should be far enough... right? I'm too lazy to bounds check CentCom rn.
2022-06-26 15:20:45 +10:00
Offset = new Vector2 ( 500f + _shuttleIndex , 0f )
} ) ;
if ( shuttle = = null )
{
2022-11-24 13:28:03 -05:00
_sawmill . Error ( $"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(component.Owner)}" ) ;
2022-06-26 15:20:45 +10:00
return ;
}
_shuttleIndex + = _mapManager . GetGrid ( shuttle . Value ) . LocalAABB . Width + ShuttleSpawnBuffer ;
component . EmergencyShuttle = shuttle ;
}
private void CleanupEmergencyShuttle ( )
{
2022-06-27 15:19:40 +10:00
// If we get cleaned up mid roundend just end it.
if ( _launchedShuttles )
{
_roundEnd . EndRound ( ) ;
}
2022-06-26 15:20:45 +10:00
_shuttleIndex = 0f ;
2022-09-13 19:42:19 -07:00
if ( CentComMap = = null | | ! _mapManager . MapExists ( CentComMap . Value ) )
2022-06-26 15:20:45 +10:00
{
2022-09-13 19:42:19 -07:00
CentComMap = null ;
2022-06-26 15:20:45 +10:00
return ;
}
2022-09-13 19:42:19 -07:00
_mapManager . DeleteMap ( CentComMap . Value ) ;
2022-06-26 15:20:45 +10:00
}
2023-03-24 12:54:41 +11:00
private void OnEscapeUnpaused ( EntityUid uid , EscapePodComponent component , ref EntityUnpausedEvent args )
2022-06-26 15:20:45 +10:00
{
2023-03-24 12:54:41 +11:00
if ( component . LaunchTime = = null )
return ;
component . LaunchTime = component . LaunchTime . Value + args . PausedTime ;
2022-06-26 15:20:45 +10:00
}
}