2022-06-26 15:20:45 +10:00
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
using Content.Server.Administration.Logs ;
using Content.Server.Administration.Managers ;
using Content.Server.Chat.Systems ;
using Content.Server.Communications ;
using Content.Server.GameTicking.Events ;
using Content.Server.Shuttles.Components ;
using Content.Server.Station.Components ;
using Content.Server.Station.Systems ;
using Content.Shared.CCVar ;
using Content.Shared.Database ;
using Content.Shared.Shuttles.Events ;
2023-03-22 20:29:55 +11:00
using Content.Shared.Tiles ;
2023-03-23 16:10:49 +11:00
using Content.Shared.Tag ;
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.Audio ;
using Robust.Shared.Configuration ;
using Robust.Shared.Map ;
2022-11-22 13:12:04 +11:00
using Robust.Shared.Map.Components ;
2022-07-09 15:19:52 +10:00
using Robust.Shared.Physics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2022-06-26 15:20:45 +10:00
using Robust.Shared.Player ;
using Robust.Shared.Random ;
namespace Content.Server.Shuttles.Systems ;
public sealed partial class ShuttleSystem
{
/ *
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 ! ;
[Dependency] private readonly IRobustRandom _random = default ! ;
[Dependency] private readonly ChatSystem _chatSystem = default ! ;
[Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default ! ;
[Dependency] private readonly DockingSystem _dockSystem = default ! ;
2022-11-13 17:47:48 +11:00
[Dependency] private readonly MapLoaderSystem _map = default ! ;
2022-06-26 15:20:45 +10:00
[Dependency] private readonly StationSystem _station = default ! ;
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 ;
2022-06-26 15:20:45 +10:00
private void InitializeEscape ( )
{
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 ) ;
}
2022-06-27 15:19:40 +10:00
private void SetEmergencyShuttleEnabled ( bool value )
{
if ( _emergencyShuttleEnabled = = value ) return ;
_emergencyShuttleEnabled = value ;
if ( value )
{
SetupEmergencyShuttle ( ) ;
}
else
{
CleanupEmergencyShuttle ( ) ;
}
}
private void ShutdownEscape ( )
{
_configManager . UnsubValueChanged ( CCVars . EmergencyShuttleEnabled , SetEmergencyShuttleEnabled ) ;
}
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 )
{
if ( ! _admin . IsAdmin ( ( IPlayerSession ) args . SenderSession ) ) return ;
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 ) | |
! TryComp < ShuttleComponent > ( stationData . EmergencyShuttle , out var shuttle ) ) return ;
2022-06-26 15:20:45 +10:00
2022-08-08 09:22:46 +10:00
var targetGrid = _station . GetLargestGrid ( stationData ) ;
2022-06-27 15:11:39 +02:00
if ( targetGrid = = null ) return ;
var config = GetDockingConfig ( shuttle , 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 ) | |
! TryComp < ShuttleComponent > ( stationData . EmergencyShuttle , out var shuttle ) ) return ;
2022-06-26 15:20:45 +10:00
2022-08-08 09:22:46 +10: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.
SoundSystem . Play ( "/Audio/Misc/notice1.ogg" , Filter . Broadcast ( ) ) ;
return ;
}
2022-06-26 15:20:45 +10:00
2022-07-19 21:47:49 +10:00
var xformQuery = GetEntityQuery < TransformComponent > ( ) ;
2022-07-15 14:11:41 +10:00
if ( TryFTLDock ( 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 ) )
{
2022-07-19 21:47:49 +10:00
var angle = GetAngle ( xform , 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.
SoundSystem . Play ( "/Audio/Announcements/shuttle_dock.ogg" , Filter . Broadcast ( ) ) ;
}
else
{
2022-07-19 21:47:49 +10:00
if ( TryComp < TransformComponent > ( targetGrid . Value , out var targetXform ) )
{
var angle = GetAngle ( xform , targetXform , xformQuery ) ;
_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.
SoundSystem . Play ( "/Audio/Misc/notice1.ogg" , Filter . Broadcast ( ) ) ;
}
}
2022-07-19 21:47:49 +10:00
private Angle GetAngle ( TransformComponent xform , TransformComponent targetXform , EntityQuery < TransformComponent > xformQuery )
{
var ( shuttlePos , shuttleRot ) = xform . GetWorldPositionRotation ( xformQuery ) ;
var ( targetPos , targetRot ) = targetXform . GetWorldPositionRotation ( xformQuery ) ;
var shuttleCOM = Robust . Shared . Physics . Transform . Mul ( new Transform ( shuttlePos , shuttleRot ) ,
Comp < PhysicsComponent > ( xform . Owner ) . LocalCenter ) ;
var targetCOM = Robust . Shared . Physics . Transform . Mul ( new Transform ( targetPos , targetRot ) ,
Comp < PhysicsComponent > ( targetXform . Owner ) . LocalCenter ) ;
var mapDiff = shuttleCOM - targetCOM ;
var targetRotation = targetRot ;
var angle = mapDiff . ToWorldAngle ( ) ;
angle - = targetRotation ;
return angle ;
}
2022-06-26 15:20:45 +10:00
/// <summary>
/// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
/// </summary>
private bool CanDock (
DockingComponent shuttleDock ,
2023-01-01 17:33:51 +11:00
TransformComponent shuttleDockXform ,
2022-06-26 15:20:45 +10:00
DockingComponent gridDock ,
2023-01-01 17:33:51 +11:00
TransformComponent gridDockXform ,
Angle targetGridRotation ,
2022-06-26 15:20:45 +10:00
Box2 shuttleAABB ,
2023-03-23 16:10:49 +11:00
EntityUid gridUid ,
2022-11-04 10:12:25 +11:00
MapGridComponent grid ,
2022-06-26 15:20:45 +10:00
[NotNullWhen(true)] out Box2 ? shuttleDockedAABB ,
out Matrix3 matty ,
2023-01-01 17:33:51 +11:00
out Angle gridRotation )
2022-06-26 15:20:45 +10:00
{
2023-01-01 17:33:51 +11:00
gridRotation = Angle . Zero ;
2022-06-26 15:20:45 +10:00
matty = Matrix3 . Identity ;
shuttleDockedAABB = null ;
if ( shuttleDock . Docked | |
gridDock . Docked | |
2023-01-01 17:33:51 +11:00
! shuttleDockXform . Anchored | |
! gridDockXform . Anchored )
2022-06-26 15:20:45 +10:00
{
return false ;
}
// First, get the station dock's position relative to the shuttle, this is where we rotate it around
2023-01-01 17:33:51 +11:00
var stationDockPos = shuttleDockXform . LocalPosition +
shuttleDockXform . LocalRotation . RotateVec ( new Vector2 ( 0f , - 1f ) ) ;
// Need to invert the grid's angle.
var shuttleDockAngle = shuttleDockXform . LocalRotation ;
var gridDockAngle = gridDockXform . LocalRotation . Opposite ( ) ;
2022-06-26 15:20:45 +10:00
2023-01-01 17:33:51 +11:00
var stationDockMatrix = Matrix3 . CreateInverseTransform ( stationDockPos , shuttleDockAngle ) ;
var gridXformMatrix = Matrix3 . CreateTransform ( gridDockXform . LocalPosition , gridDockAngle ) ;
2022-06-26 15:20:45 +10:00
Matrix3 . Multiply ( in stationDockMatrix , in gridXformMatrix , out matty ) ;
shuttleDockedAABB = matty . TransformBox ( shuttleAABB ) ;
2023-01-01 17:33:51 +11:00
// Rounding moment
shuttleDockedAABB = shuttleDockedAABB . Value . Enlarged ( - 0.01f ) ;
2022-06-26 15:20:45 +10:00
2023-03-23 16:10:49 +11:00
if ( ! ValidSpawn ( gridUid , grid , shuttleDockedAABB . Value ) )
return false ;
2022-06-26 15:20:45 +10:00
2023-01-01 17:33:51 +11:00
gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle ;
2022-06-26 15:20:45 +10:00
return true ;
}
private void OnStationStartup ( EntityUid uid , StationDataComponent component , ComponentStartup args )
{
AddEmergencyShuttle ( component ) ;
}
private void OnRoundStart ( RoundStartingEvent ev )
{
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 ( )
{
if ( EmergencyShuttleArrived ) return ;
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
foreach ( var comp in EntityQuery < StationDataComponent > ( true ) )
{
CallEmergencyShuttle ( comp . Owner ) ;
}
_commsConsole . UpdateCommsConsoleInterface ( ) ;
}
2023-01-01 01:44:30 -05:00
public List < DockingComponent > GetDocks ( EntityUid uid )
2022-06-26 15:20:45 +10:00
{
var result = new List < DockingComponent > ( ) ;
foreach ( var ( dock , xform ) in EntityQuery < DockingComponent , TransformComponent > ( true ) )
{
if ( xform . ParentUid ! = uid | | ! dock . Enabled ) continue ;
result . Add ( dock ) ;
}
return result ;
}
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 )
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 ;
2023-03-22 20:29:55 +11:00
EnsureComp < ProtectedGridComponent > ( shuttle . Value ) ;
2022-06-26 15:20:45 +10:00
}
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
}
/// <summary>
/// Stores the data for a valid docking configuration for the emergency shuttle
/// </summary>
private sealed class DockingConfig
{
/// <summary>
/// The pairs of docks that can connect.
/// </summary>
public List < ( DockingComponent DockA , DockingComponent DockB ) > Docks = new ( ) ;
/// <summary>
/// Area relative to the target grid the emergency shuttle will spawn in on.
/// </summary>
public Box2 Area ;
/// <summary>
/// Target grid for docking.
/// </summary>
public EntityUid TargetGrid ;
public EntityCoordinates Coordinates ;
public Angle Angle ;
}
}