2021-12-05 18:09:01 +01:00
using System ;
using System.Threading ;
2022-03-23 13:02:49 +11:00
using Content.Server.Administration.Logs ;
2021-10-24 06:34:00 +03:00
using Content.Server.DoAfter ;
2022-03-23 13:02:49 +11:00
using Content.Server.Popups ;
2021-10-24 06:34:00 +03:00
using Content.Server.RCD.Components ;
using Content.Shared.Coordinates ;
2022-03-23 13:02:49 +11:00
using Content.Shared.Database ;
2021-10-24 06:34:00 +03:00
using Content.Shared.Examine ;
using Content.Shared.Interaction ;
2022-03-13 01:33:23 +13:00
using Content.Shared.Interaction.Events ;
2021-10-24 06:34:00 +03:00
using Content.Shared.Interaction.Helpers ;
using Content.Shared.Maps ;
using Content.Shared.Popups ;
2021-12-06 00:52:58 +01:00
using Content.Shared.Tag ;
2021-10-24 06:34:00 +03:00
using Robust.Shared.Audio ;
using Robust.Shared.GameObjects ;
using Robust.Shared.IoC ;
using Robust.Shared.Localization ;
using Robust.Shared.Map ;
using Robust.Shared.Maths ;
using Robust.Shared.Player ;
namespace Content.Server.RCD.Systems
{
2022-02-16 00:23:23 -07:00
public sealed class RCDSystem : EntitySystem
2021-10-24 06:34:00 +03:00
{
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default ! ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
2022-03-23 13:02:49 +11:00
[Dependency] private readonly AdminLogSystem _logs = default ! ;
2021-10-24 06:34:00 +03:00
[Dependency] private readonly DoAfterSystem _doAfterSystem = default ! ;
2022-02-05 15:39:01 +13:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
2022-03-23 13:02:49 +11:00
[Dependency] private readonly PopupSystem _popup = default ! ;
2022-02-08 14:08:11 +11:00
[Dependency] private readonly TagSystem _tagSystem = default ! ;
2021-10-24 06:34:00 +03:00
2022-03-23 13:02:49 +11:00
private readonly int RCDModeCount = Enum . GetValues ( typeof ( RcdMode ) ) . Length ;
2021-10-24 06:34:00 +03:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < RCDComponent , ExaminedEvent > ( OnExamine ) ;
SubscribeLocalEvent < RCDComponent , UseInHandEvent > ( OnUseInHand ) ;
SubscribeLocalEvent < RCDComponent , AfterInteractEvent > ( OnAfterInteract ) ;
}
private void OnExamine ( EntityUid uid , RCDComponent component , ExaminedEvent args )
{
var msg = Loc . GetString ( "rcd-component-examine-detail-count" ,
( "mode" , component . Mode ) , ( "ammoCount" , component . CurrentAmmo ) ) ;
args . PushMarkup ( msg ) ;
}
private void OnUseInHand ( EntityUid uid , RCDComponent component , UseInHandEvent args )
{
if ( args . Handled )
return ;
NextMode ( uid , component , args . User ) ;
args . Handled = true ;
}
private async void OnAfterInteract ( EntityUid uid , RCDComponent rcd , AfterInteractEvent args )
{
2022-02-05 15:39:01 +13:00
if ( args . Handled | | ! args . CanReach )
2021-10-24 06:34:00 +03:00
return ;
2022-03-23 13:02:49 +11:00
if ( rcd . CancelToken ! = null )
{
rcd . CancelToken ? . Cancel ( ) ;
rcd . CancelToken = null ;
args . Handled = true ;
return ;
}
if ( ! args . ClickLocation . IsValid ( EntityManager ) ) return ;
2021-10-25 04:33:49 +01:00
var clickLocationMod = args . ClickLocation ;
// Initial validity check
if ( ! clickLocationMod . IsValid ( EntityManager ) )
2021-10-24 06:34:00 +03:00
return ;
2021-10-25 04:33:49 +01:00
// Try to fix it (i.e. if clicking on space)
// Note: Ideally there'd be a better way, but there isn't right now.
var gridID = clickLocationMod . GetGridId ( EntityManager ) ;
if ( ! gridID . IsValid ( ) )
{
clickLocationMod = clickLocationMod . AlignWithClosestGridTile ( ) ;
gridID = clickLocationMod . GetGridId ( EntityManager ) ;
}
// Check if fixing it failed / get final grid ID
2021-10-24 06:34:00 +03:00
if ( ! gridID . IsValid ( ) )
return ;
var mapGrid = _mapManager . GetGrid ( gridID ) ;
2021-10-25 04:33:49 +01:00
var tile = mapGrid . GetTileRef ( clickLocationMod ) ;
var snapPos = mapGrid . TileIndicesFor ( clickLocationMod ) ;
2021-10-24 06:34:00 +03:00
//No changing mode mid-RCD
var startingMode = rcd . Mode ;
2022-03-23 13:02:49 +11:00
args . Handled = true ;
var user = args . User ;
2021-10-24 06:34:00 +03:00
//Using an RCD isn't instantaneous
2022-03-23 13:02:49 +11:00
rcd . CancelToken = new CancellationTokenSource ( ) ;
var doAfterEventArgs = new DoAfterEventArgs ( user , rcd . Delay , rcd . CancelToken . Token , args . Target )
2021-10-24 06:34:00 +03:00
{
BreakOnDamage = true ,
BreakOnStun = true ,
NeedHand = true ,
2022-03-23 13:02:49 +11:00
ExtraCheck = ( ) = > IsRCDStillValid ( rcd , args , mapGrid , tile , startingMode ) //All of the sanity checks are here
2021-10-24 06:34:00 +03:00
} ;
var result = await _doAfterSystem . WaitDoAfter ( doAfterEventArgs ) ;
2022-03-23 13:02:49 +11:00
rcd . CancelToken = null ;
2021-10-24 06:34:00 +03:00
if ( result = = DoAfterStatus . Cancelled )
return ;
switch ( rcd . Mode )
{
//Floor mode just needs the tile to be a space tile (subFloor)
case RcdMode . Floors :
2022-03-23 13:02:49 +11:00
mapGrid . SetTile ( snapPos , new Tile ( _tileDefinitionManager [ "floor_steel" ] . TileId ) ) ;
_logs . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridIndex} {snapPos} to floor_steel" ) ;
2021-10-24 06:34:00 +03:00
break ;
//We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
case RcdMode . Deconstruct :
if ( ! tile . IsBlockedTurf ( true ) ) //Delete the turf
{
mapGrid . SetTile ( snapPos , Tile . Empty ) ;
2022-03-23 13:02:49 +11:00
_logs . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridIndex} tile: {snapPos} to space" ) ;
2021-10-24 06:34:00 +03:00
}
else //Delete what the user targeted
{
2021-12-06 00:52:58 +01:00
if ( args . Target is { Valid : true } target )
2021-12-03 11:43:03 +01:00
{
2022-03-23 13:02:49 +11:00
_logs . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(args.User):user} used RCD to delete {ToPrettyString(target):target}" ) ;
QueueDel ( target ) ;
2021-12-03 11:43:03 +01:00
}
2021-10-24 06:34:00 +03:00
}
break ;
//Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile,
// thus we early return to avoid the tile set code.
case RcdMode . Walls :
var ent = EntityManager . SpawnEntity ( "WallSolid" , mapGrid . GridTileToLocal ( snapPos ) ) ;
2022-03-23 13:02:49 +11:00
Transform ( ent ) . LocalRotation = Angle . Zero ; // Walls always need to point south.
_logs . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(args.User):user} used RCD to spawn {ToPrettyString(ent)} at {snapPos} on grid {mapGrid.Index}" ) ;
2021-10-24 06:34:00 +03:00
break ;
case RcdMode . Airlock :
var airlock = EntityManager . SpawnEntity ( "Airlock" , mapGrid . GridTileToLocal ( snapPos ) ) ;
2022-03-23 13:02:49 +11:00
Transform ( airlock ) . LocalRotation = Transform ( rcd . Owner ) . LocalRotation ; //Now apply icon smoothing.
_logs . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(args.User):user} used RCD to spawn {ToPrettyString(airlock)} at {snapPos} on grid {mapGrid.Index}" ) ;
2021-10-24 06:34:00 +03:00
break ;
default :
args . Handled = true ;
return ; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
}
2022-03-23 13:02:49 +11:00
SoundSystem . Play ( Filter . Pvs ( uid , entityManager : EntityManager ) , rcd . SuccessSound . GetSound ( ) , rcd . Owner ) ;
2021-10-24 06:34:00 +03:00
rcd . CurrentAmmo - - ;
args . Handled = true ;
}
2022-03-23 13:02:49 +11:00
private bool IsRCDStillValid ( RCDComponent rcd , AfterInteractEvent eventArgs , IMapGrid mapGrid , TileRef tile , RcdMode startingMode )
2021-10-24 06:34:00 +03:00
{
//Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed.
if ( rcd . CurrentAmmo < = 0 )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-no-ammo-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
if ( rcd . Mode ! = startingMode )
{
return false ;
}
2022-02-22 18:01:01 +13:00
var unobstructed = eventArgs . Target = = null
2022-03-03 02:38:19 +13:00
? _interactionSystem . InRangeUnobstructed ( eventArgs . User , mapGrid . GridTileToWorld ( tile . GridIndices ) , popup : true )
2022-02-22 18:01:01 +13:00
: _interactionSystem . InRangeUnobstructed ( eventArgs . User , eventArgs . Target . Value , popup : true ) ;
if ( ! unobstructed )
return false ;
2021-10-24 06:34:00 +03:00
switch ( rcd . Mode )
{
//Floor mode just needs the tile to be a space tile (subFloor)
case RcdMode . Floors :
if ( ! tile . Tile . IsEmpty )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-cannot-build-floor-tile-not-empty-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
return true ;
//We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
case RcdMode . Deconstruct :
if ( tile . Tile . IsEmpty )
{
return false ;
}
//They tried to decon a turf but the turf is blocked
if ( eventArgs . Target = = null & & tile . IsBlockedTurf ( true ) )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-tile-obstructed-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
//They tried to decon a non-turf but it's not in the whitelist
2022-02-08 14:08:11 +11:00
if ( eventArgs . Target ! = null & & ! _tagSystem . HasTag ( eventArgs . Target . Value , "RCDDeconstructWhitelist" ) )
2021-10-24 06:34:00 +03:00
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-deconstruct-target-not-on-whitelist-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
return true ;
//Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code.
case RcdMode . Walls :
if ( tile . Tile . IsEmpty )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-cannot-build-wall-tile-not-empty-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
if ( tile . IsBlockedTurf ( true ) )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-tile-obstructed-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
return true ;
case RcdMode . Airlock :
if ( tile . Tile . IsEmpty )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-cannot-build-airlock-tile-not-empty-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
if ( tile . IsBlockedTurf ( true ) )
{
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( Loc . GetString ( "rcd-component-tile-obstructed-message" ) , rcd . Owner , Filter . Entities ( eventArgs . User ) ) ;
2021-10-24 06:34:00 +03:00
return false ;
}
return true ;
default :
return false ; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
}
}
2021-12-05 18:09:01 +01:00
private void NextMode ( EntityUid uid , RCDComponent rcd , EntityUid user )
2021-10-24 06:34:00 +03:00
{
2022-03-23 13:02:49 +11:00
SoundSystem . Play ( Filter . Pvs ( uid , entityManager : EntityManager ) , rcd . SwapModeSound . GetSound ( ) , uid ) ;
2021-10-24 06:34:00 +03:00
2022-03-23 13:02:49 +11:00
var mode = ( int ) rcd . Mode ;
mode = + + mode % RCDModeCount ;
rcd . Mode = ( RcdMode ) mode ;
2021-10-24 06:34:00 +03:00
2021-12-26 15:32:45 +13:00
var msg = Loc . GetString ( "rcd-component-change-mode" , ( "mode" , rcd . Mode . ToString ( ) ) ) ;
2022-03-23 13:02:49 +11:00
_popup . PopupEntity ( msg , rcd . Owner , Filter . Entities ( user ) ) ;
2021-10-24 06:34:00 +03:00
}
}
}