2021-02-22 00:07:46 +00:00
using System ;
2020-08-13 12:39:23 -05:00
using System.Threading ;
2020-12-17 10:45:04 +03:00
using System.Threading.Tasks ;
2021-06-09 22:19:39 +02:00
using Content.Server.DoAfter ;
using Content.Shared.Coordinates ;
using Content.Shared.Examine ;
using Content.Shared.Interaction ;
using Content.Shared.Interaction.Helpers ;
2020-08-13 12:39:23 -05:00
using Content.Shared.Maps ;
2021-09-26 15:18:45 +02:00
using Content.Shared.Popups ;
2021-07-10 17:35:33 +02:00
using Content.Shared.Sound ;
2021-02-11 01:13:03 -08:00
using Robust.Server.GameObjects ;
2021-03-21 09:12:03 -07:00
using Robust.Shared.Audio ;
2020-08-13 12:39:23 -05:00
using Robust.Shared.GameObjects ;
using Robust.Shared.IoC ;
using Robust.Shared.Localization ;
using Robust.Shared.Map ;
2020-10-11 15:21:21 +02:00
using Robust.Shared.Maths ;
2021-03-21 09:12:03 -07:00
using Robust.Shared.Player ;
2021-03-05 01:08:38 +01:00
using Robust.Shared.Serialization.Manager.Attributes ;
2020-08-13 12:39:23 -05:00
using Robust.Shared.Utility ;
using Robust.Shared.ViewVariables ;
2021-06-09 22:19:39 +02:00
namespace Content.Server.RCD.Components
2020-08-13 12:39:23 -05:00
{
[RegisterComponent]
public class RCDComponent : Component , IAfterInteract , IUse , IExamine
{
2020-08-24 14:10:28 +02:00
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default ! ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default ! ;
2020-08-13 12:39:23 -05:00
public override string Name = > "RCD" ;
private RcdMode _mode = 0 ; //What mode are we on? Can be floors, walls, deconstruct.
2021-07-31 19:52:33 +02:00
private readonly RcdMode [ ] _modes = ( RcdMode [ ] ) Enum . GetValues ( typeof ( RcdMode ) ) ;
2021-03-31 12:41:23 -07:00
[ViewVariables(VVAccess.ReadWrite)] [ DataField ( "maxAmmo" ) ] public int MaxAmmo = 5 ;
2020-08-13 12:39:23 -05:00
public int _ammo ; //How much "ammo" we have left. You can refill this with RCD ammo.
2021-03-05 01:08:38 +01:00
[ViewVariables(VVAccess.ReadWrite)] [ DataField ( "delay" ) ] private float _delay = 2f ;
2021-03-31 12:41:23 -07:00
private DoAfterSystem _doAfterSystem = default ! ;
2020-08-13 12:39:23 -05:00
2021-07-10 17:35:33 +02:00
[DataField("swapModeSound")]
private SoundSpecifier _swapModeSound = new SoundPathSpecifier ( "/Audio/Items/genhit.ogg" ) ;
[DataField("successSound")]
private SoundSpecifier _successSound = new SoundPathSpecifier ( "/Audio/Items/deconstruct.ogg" ) ;
2020-08-13 12:39:23 -05:00
///Enum to store the different mode states for clarity.
private enum RcdMode
{
Floors ,
Walls ,
Airlock ,
Deconstruct
}
2021-06-19 19:41:26 -07:00
protected override void Initialize ( )
2020-08-13 12:39:23 -05:00
{
base . Initialize ( ) ;
2021-03-31 12:41:23 -07:00
_ammo = MaxAmmo ;
_doAfterSystem = EntitySystem . Get < DoAfterSystem > ( ) ;
2020-08-13 12:39:23 -05:00
}
///<summary>
/// Method called when the RCD is clicked in-hand, this will cycle the RCD mode.
///</summary>
2021-02-04 17:44:49 +01:00
bool IUse . UseEntity ( UseEntityEventArgs eventArgs )
2020-08-13 12:39:23 -05:00
{
SwapMode ( eventArgs ) ;
return true ;
}
///<summary>
///Method to allow the user to swap the mode of the RCD by clicking it in hand, the actual in hand clicking bit is done over on UseEntity()
///@param UseEntityEventArgs = The entity which triggered this method call, used to know where to play the "click" sound.
///</summary>
public void SwapMode ( UseEntityEventArgs eventArgs )
{
2021-07-31 19:52:33 +02:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , _swapModeSound . GetSound ( ) , Owner ) ;
2021-07-10 17:35:33 +02:00
var mode = ( int ) _mode ; //Firstly, cast our RCDmode mode to an int (enums are backed by ints anyway by default)
2020-08-13 12:39:23 -05:00
mode = ( + + mode ) % _modes . Length ; //Then, do a rollover on the value so it doesnt hit an invalid state
_mode = ( RcdMode ) mode ; //Finally, cast the newly acquired int mode to an RCDmode so we can use it.
2021-02-22 00:07:46 +00:00
Owner . PopupMessage ( eventArgs . User ,
Loc . GetString (
"rcd-component-change-mode" ,
( "mode" , _mode . ToString ( ) )
)
) ; //Prints an overhead message above the RCD
2020-08-13 12:39:23 -05:00
}
public void Examine ( FormattedMessage message , bool inDetailsRange )
{
2021-02-22 00:07:46 +00:00
if ( inDetailsRange )
{
message . AddMarkup (
Loc . GetString (
"rcd-component-examine-detail-count" ,
( "mode" , _mode ) ,
( "ammoCount" , _ammo )
)
) ;
}
2020-08-13 12:39:23 -05:00
}
2021-07-31 19:52:33 +02:00
async Task < bool > IAfterInteract . AfterInteract ( AfterInteractEventArgs eventArgs )
2020-08-13 12:39:23 -05:00
{
2021-05-08 02:57:13 +02:00
// FIXME: Make this work properly. Right now it relies on the click location being on a grid, which is bad.
if ( ! eventArgs . ClickLocation . IsValid ( Owner . EntityManager ) | | ! eventArgs . ClickLocation . GetGridId ( Owner . EntityManager ) . IsValid ( ) )
return false ;
2020-08-13 12:39:23 -05:00
//No changing mode mid-RCD
var startingMode = _mode ;
2020-09-06 16:11:53 +02:00
var mapGrid = _mapManager . GetGrid ( eventArgs . ClickLocation . GetGridId ( Owner . EntityManager ) ) ;
2020-08-13 12:39:23 -05:00
var tile = mapGrid . GetTileRef ( eventArgs . ClickLocation ) ;
2021-04-28 10:49:37 -07:00
var snapPos = mapGrid . TileIndicesFor ( eventArgs . ClickLocation ) ;
2020-08-13 12:39:23 -05:00
//Using an RCD isn't instantaneous
var cancelToken = new CancellationTokenSource ( ) ;
var doAfterEventArgs = new DoAfterEventArgs ( eventArgs . User , _delay , cancelToken . Token , eventArgs . Target )
{
BreakOnDamage = true ,
BreakOnStun = true ,
NeedHand = true ,
ExtraCheck = ( ) = > IsRCDStillValid ( eventArgs , mapGrid , tile , snapPos , startingMode ) //All of the sanity checks are here
} ;
2021-07-04 13:32:24 +02:00
var result = await _doAfterSystem . WaitDoAfter ( doAfterEventArgs ) ;
2020-08-13 12:39:23 -05:00
if ( result = = DoAfterStatus . Cancelled )
{
2021-02-03 14:05:31 +01:00
return true ;
2020-08-13 12:39:23 -05:00
}
switch ( _mode )
{
//Floor mode just needs the tile to be a space tile (subFloor)
case RcdMode . Floors :
2021-06-09 22:19:39 +02:00
mapGrid . SetTile ( eventArgs . ClickLocation , new Robust . Shared . Map . Tile ( _tileDefinitionManager [ "floor_steel" ] . TileId ) ) ;
2020-08-13 12:39:23 -05: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
{
2021-06-09 22:19:39 +02:00
mapGrid . SetTile ( snapPos , Robust . Shared . Map . Tile . Empty ) ;
2020-08-13 12:39:23 -05:00
}
else //Delete what the user targeted
{
2021-03-16 15:50:20 +01:00
eventArgs . Target ? . Delete ( ) ;
2020-08-13 12:39:23 -05: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 :
2021-05-08 03:10:56 +02:00
var ent = _serverEntityManager . SpawnEntity ( "WallSolid" , mapGrid . GridTileToLocal ( snapPos ) ) ;
2021-03-31 12:41:23 -07:00
ent . Transform . LocalRotation = Angle . Zero ; // Walls always need to point south.
2020-08-13 12:39:23 -05:00
break ;
case RcdMode . Airlock :
var airlock = _serverEntityManager . SpawnEntity ( "Airlock" , mapGrid . GridTileToLocal ( snapPos ) ) ;
airlock . Transform . LocalRotation = Owner . Transform . LocalRotation ; //Now apply icon smoothing.
break ;
default :
2021-02-03 14:05:31 +01:00
return true ; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
2020-08-13 12:39:23 -05:00
}
2021-07-31 19:52:33 +02:00
SoundSystem . Play ( Filter . Pvs ( Owner ) , _successSound . GetSound ( ) , Owner ) ;
2020-08-13 12:39:23 -05:00
_ammo - - ;
2021-02-03 14:05:31 +01:00
return true ;
2020-08-13 12:39:23 -05:00
}
2020-10-11 15:21:21 +02:00
private bool IsRCDStillValid ( AfterInteractEventArgs eventArgs , IMapGrid mapGrid , TileRef tile , Vector2i snapPos , RcdMode startingMode )
2020-08-13 12:39:23 -05:00
{
//Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed.
if ( _ammo < = 0 )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-no-ammo-message" ) ) ;
2020-08-13 12:39:23 -05:00
return false ;
}
if ( _mode ! = startingMode )
{
return false ;
}
2020-09-06 16:11:53 +02:00
var coordinates = mapGrid . ToCoordinates ( tile . GridIndices ) ;
if ( coordinates = = EntityCoordinates . Invalid | | ! eventArgs . InRangeUnobstructed ( ignoreInsideBlocker : true , popup : true ) )
2020-08-13 12:39:23 -05:00
{
return false ;
}
switch ( _mode )
{
//Floor mode just needs the tile to be a space tile (subFloor)
case RcdMode . Floors :
if ( ! tile . Tile . IsEmpty )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-cannot-build-floor-tile-not-empty-message" ) ) ;
2020-08-13 12:39:23 -05: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 ) )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-tile-obstructed-message" ) ) ;
2020-08-13 12:39:23 -05:00
return false ;
}
//They tried to decon a non-turf but it's not in the whitelist
2021-03-16 15:50:20 +01:00
if ( eventArgs . Target ! = null & & ! eventArgs . Target . TryGetComponent ( out RCDDeconstructWhitelist ? deCon ) )
2020-08-13 12:39:23 -05:00
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-deconstruct-target-not-on-whitelist-message" ) ) ;
2020-08-13 12:39:23 -05: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 )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-cannot-build-wall-tile-not-empty-message" ) ) ;
2020-08-13 12:39:23 -05:00
return false ;
}
if ( tile . IsBlockedTurf ( true ) )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-tile-obstructed-message" ) ) ;
2020-08-13 12:39:23 -05:00
return false ;
}
return true ;
case RcdMode . Airlock :
if ( tile . Tile . IsEmpty )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-cannot-build-airlock-tile-not-empty-message" ) ) ;
2020-08-13 12:39:23 -05:00
return false ;
}
if ( tile . IsBlockedTurf ( true ) )
{
2021-06-21 02:13:54 +02:00
Owner . PopupMessage ( eventArgs . User , Loc . GetString ( "rcd-component-tile-obstructed-message" ) ) ;
2020-08-13 12:39:23 -05: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!
}
}
}
}