2023-05-01 13:46:59 +00:00
using Content.Shared.Administration.Logs ;
using Content.Shared.Charges.Components ;
using Content.Shared.Charges.Systems ;
2024-03-30 23:29:47 -05:00
using Content.Shared.Construction ;
2023-05-01 13:46:59 +00:00
using Content.Shared.Database ;
using Content.Shared.DoAfter ;
using Content.Shared.Examine ;
2024-03-30 23:29:47 -05:00
using Content.Shared.Hands.Components ;
2023-05-01 13:46:59 +00:00
using Content.Shared.Interaction ;
using Content.Shared.Maps ;
using Content.Shared.Physics ;
using Content.Shared.Popups ;
using Content.Shared.RCD.Components ;
using Content.Shared.Tag ;
2023-10-22 16:53:39 +11:00
using Content.Shared.Tiles ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2023-05-01 13:46:59 +00:00
using Robust.Shared.Map ;
using Robust.Shared.Map.Components ;
using Robust.Shared.Network ;
2024-03-30 23:29:47 -05:00
using Robust.Shared.Physics ;
using Robust.Shared.Physics.Collision.Shapes ;
using Robust.Shared.Physics.Dynamics ;
using Robust.Shared.Prototypes ;
2023-05-01 13:46:59 +00:00
using Robust.Shared.Serialization ;
using Robust.Shared.Timing ;
2024-03-30 23:29:47 -05:00
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
2023-05-01 13:46:59 +00:00
namespace Content.Shared.RCD.Systems ;
2024-03-30 23:29:47 -05:00
[Virtual]
public class RCDSystem : EntitySystem
2023-05-01 13:46:59 +00:00
{
2023-10-22 16:53:39 +11:00
[Dependency] private readonly IGameTiming _timing = default ! ;
[Dependency] private readonly INetManager _net = default ! ;
2023-05-01 13:46:59 +00:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2023-10-22 16:53:39 +11:00
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default ! ;
[Dependency] private readonly FloorTileSystem _floors = default ! ;
2023-05-01 13:46:59 +00:00
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly SharedChargesSystem _charges = default ! ;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default ! ;
[Dependency] private readonly SharedInteractionSystem _interaction = default ! ;
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
[Dependency] private readonly TurfSystem _turf = default ! ;
2024-03-30 23:29:47 -05:00
[Dependency] private readonly EntityLookupSystem _lookup = default ! ;
[Dependency] private readonly IPrototypeManager _protoManager = default ! ;
[Dependency] private readonly SharedMapSystem _mapSystem = default ! ;
[Dependency] private readonly TagSystem _tags = default ! ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
private readonly int _instantConstructionDelay = 0 ;
private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0" ;
private readonly ProtoId < RCDPrototype > _deconstructTileProto = "DeconstructTile" ;
private readonly ProtoId < RCDPrototype > _deconstructLatticeProto = "DeconstructLattice" ;
private HashSet < EntityUid > _intersectingEntities = new ( ) ;
2023-05-01 13:46:59 +00:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-03-30 23:29:47 -05:00
SubscribeLocalEvent < RCDComponent , MapInitEvent > ( OnMapInit ) ;
2023-05-01 13:46:59 +00:00
SubscribeLocalEvent < RCDComponent , ExaminedEvent > ( OnExamine ) ;
SubscribeLocalEvent < RCDComponent , AfterInteractEvent > ( OnAfterInteract ) ;
SubscribeLocalEvent < RCDComponent , RCDDoAfterEvent > ( OnDoAfter ) ;
SubscribeLocalEvent < RCDComponent , DoAfterAttemptEvent < RCDDoAfterEvent > > ( OnDoAfterAttempt ) ;
2024-03-30 23:29:47 -05:00
SubscribeLocalEvent < RCDComponent , RCDSystemMessage > ( OnRCDSystemMessage ) ;
SubscribeNetworkEvent < RCDConstructionGhostRotationEvent > ( OnRCDconstructionGhostRotationEvent ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
#region Event handling
private void OnMapInit ( EntityUid uid , RCDComponent component , MapInitEvent args )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
// On init, set the RCD to its first available recipe
if ( component . AvailablePrototypes . Any ( ) )
{
component . ProtoId = component . AvailablePrototypes . First ( ) ;
UpdateCachedPrototype ( uid , component ) ;
Dirty ( uid , component ) ;
2023-05-01 13:46:59 +00:00
return ;
2024-03-30 23:29:47 -05:00
}
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
// The RCD has no valid recipes somehow? Get rid of it
QueueDel ( uid ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
private void OnRCDSystemMessage ( EntityUid uid , RCDComponent component , RCDSystemMessage args )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
// Exit if the RCD doesn't actually know the supplied prototype
if ( ! component . AvailablePrototypes . Contains ( args . ProtoId ) )
2023-05-01 13:46:59 +00:00
return ;
2024-03-30 23:29:47 -05:00
if ( ! _protoManager . HasIndex ( args . ProtoId ) )
return ;
// Set the current RCD prototype to the one supplied
component . ProtoId = args . ProtoId ;
UpdateCachedPrototype ( uid , component ) ;
Dirty ( uid , component ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
private void OnExamine ( EntityUid uid , RCDComponent component , ExaminedEvent args )
{
if ( ! args . IsInDetailsRange )
return ;
// Update cached prototype if required
UpdateCachedPrototype ( uid , component ) ;
2024-04-07 22:17:28 -05:00
var msg = Loc . GetString ( "rcd-component-examine-mode-details" , ( "mode" , Loc . GetString ( component . CachedPrototype . SetName ) ) ) ;
if ( component . CachedPrototype . Mode = = RcdMode . ConstructTile | | component . CachedPrototype . Mode = = RcdMode . ConstructObject )
{
var name = Loc . GetString ( component . CachedPrototype . SetName ) ;
if ( component . CachedPrototype . Prototype ! = null & &
_protoManager . TryIndex ( component . CachedPrototype . Prototype , out var proto ) )
name = proto . Name ;
msg = Loc . GetString ( "rcd-component-examine-build-details" , ( "name" , name ) ) ;
}
2024-03-30 23:29:47 -05:00
args . PushMarkup ( msg ) ;
}
private void OnAfterInteract ( EntityUid uid , RCDComponent component , AfterInteractEvent args )
2023-05-01 13:46:59 +00:00
{
if ( args . Handled | | ! args . CanReach )
return ;
var user = args . User ;
2024-03-30 23:29:47 -05:00
var location = args . ClickLocation ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
// Initial validity checks
if ( ! location . IsValid ( EntityManager ) )
return ;
if ( ! TryGetMapGridData ( location , out var mapGridData ) )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
_popup . PopupClient ( Loc . GetString ( "rcd-component-no-valid-grid" ) , uid , user ) ;
2023-05-01 13:46:59 +00:00
return ;
}
2024-03-30 23:29:47 -05:00
if ( ! IsRCDOperationStillValid ( uid , component , mapGridData . Value , args . Target , args . User ) )
2023-05-01 13:46:59 +00:00
return ;
2024-03-30 23:29:47 -05:00
if ( ! _net . IsServer )
return ;
// Get the starting cost, delay, and effect from the prototype
var cost = component . CachedPrototype . Cost ;
var delay = component . CachedPrototype . Delay ;
var effectPrototype = component . CachedPrototype . Effect ;
#region : Operation modifiers
// Deconstruction modifiers
switch ( component . CachedPrototype . Mode )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
case RcdMode . Deconstruct :
// Deconstructing an object
if ( args . Target ! = null )
{
if ( TryComp < RCDDeconstructableComponent > ( args . Target , out var destructible ) )
{
cost = destructible . Cost ;
delay = destructible . Delay ;
effectPrototype = destructible . Effect ;
}
}
// Deconstructing a tile
else
{
var deconstructedTile = _mapSystem . GetTileRef ( mapGridData . Value . GridUid , mapGridData . Value . Component , mapGridData . Value . Location ) ;
2024-04-11 07:26:34 -05:00
var protoName = ! deconstructedTile . IsSpace ( ) ? _deconstructTileProto : _deconstructLatticeProto ;
2024-03-30 23:29:47 -05:00
if ( _protoManager . TryIndex ( protoName , out var deconProto ) )
{
cost = deconProto . Cost ;
delay = deconProto . Delay ;
effectPrototype = deconProto . Effect ;
}
}
break ;
case RcdMode . ConstructTile :
// If replacing a tile, make the construction instant
var contructedTile = _mapSystem . GetTileRef ( mapGridData . Value . GridUid , mapGridData . Value . Component , mapGridData . Value . Location ) ;
if ( ! contructedTile . Tile . IsEmpty )
{
delay = _instantConstructionDelay ;
effectPrototype = _instantConstructionFx ;
}
break ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
#endregion
// Try to start the do after
var effect = Spawn ( effectPrototype , mapGridData . Value . Location ) ;
2024-04-07 22:17:28 -05:00
var ev = new RCDDoAfterEvent ( GetNetCoordinates ( mapGridData . Value . Location ) , component . ConstructionDirection , component . ProtoId , cost , EntityManager . GetNetEntity ( effect ) ) ;
2024-03-30 23:29:47 -05:00
var doAfterArgs = new DoAfterArgs ( EntityManager , user , delay , ev , uid , target : args . Target , used : uid )
2023-05-01 13:46:59 +00:00
{
BreakOnDamage = true ,
BreakOnHandChange = true ,
2024-03-19 12:09:00 +02:00
BreakOnMove = true ,
2024-03-30 23:29:47 -05:00
AttemptFrequency = AttemptFrequency . EveryTick ,
CancelDuplicate = false ,
BlockDuplicate = false
2023-05-01 13:46:59 +00:00
} ;
args . Handled = true ;
2023-12-17 10:08:59 -06:00
2024-03-30 23:29:47 -05:00
if ( ! _doAfter . TryStartDoAfter ( doAfterArgs ) )
QueueDel ( effect ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
private void OnDoAfterAttempt ( EntityUid uid , RCDComponent component , DoAfterAttemptEvent < RCDDoAfterEvent > args )
2023-05-01 13:46:59 +00:00
{
if ( args . Event ? . DoAfter ? . Args = = null )
return ;
2024-03-30 23:29:47 -05:00
// Exit if the RCD prototype has changed
if ( component . ProtoId ! = args . Event . StartingProtoId )
return ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
// Ensure the RCD operation is still valid
var location = GetCoordinates ( args . Event . Location ) ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
if ( ! TryGetMapGridData ( location , out var mapGridData ) )
return ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
if ( ! IsRCDOperationStillValid ( uid , component , mapGridData . Value , args . Event . Target , args . Event . User ) )
2023-05-01 13:46:59 +00:00
args . Cancel ( ) ;
}
2024-03-30 23:29:47 -05:00
private void OnDoAfter ( EntityUid uid , RCDComponent component , RCDDoAfterEvent args )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
if ( args . Cancelled & & _net . IsServer )
QueueDel ( EntityManager . GetEntity ( args . Effect ) ) ;
2023-05-01 13:46:59 +00:00
if ( args . Handled | | args . Cancelled | | ! _timing . IsFirstTimePredicted )
return ;
2024-03-30 23:29:47 -05:00
args . Handled = true ;
2023-09-11 09:42:41 +10:00
var location = GetCoordinates ( args . Location ) ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
if ( ! TryGetMapGridData ( location , out var mapGridData ) )
return ;
// Ensure the RCD operation is still valid
if ( ! IsRCDOperationStillValid ( uid , component , mapGridData . Value , args . Target , args . User ) )
return ;
// Finalize the operation
2024-04-07 22:17:28 -05:00
FinalizeRCDOperation ( uid , component , mapGridData . Value , args . Direction , args . Target , args . User ) ;
2024-03-30 23:29:47 -05:00
// Play audio and consume charges
_audio . PlayPredicted ( component . SuccessSound , uid , args . User ) ;
_charges . UseCharges ( uid , args . Cost ) ;
}
private void OnRCDconstructionGhostRotationEvent ( RCDConstructionGhostRotationEvent ev , EntitySessionEventArgs session )
{
var uid = GetEntity ( ev . NetEntity ) ;
// Determine if player that send the message is carrying the specified RCD in their active hand
if ( session . SenderSession . AttachedEntity = = null )
return ;
if ( ! TryComp < HandsComponent > ( session . SenderSession . AttachedEntity , out var hands ) | |
uid ! = hands . ActiveHand ? . HeldEntity )
return ;
if ( ! TryComp < RCDComponent > ( uid , out var rcd ) )
return ;
// Update the construction direction
rcd . ConstructionDirection = ev . Direction ;
Dirty ( uid , rcd ) ;
}
#endregion
#region Entity construction / deconstruction rule checks
public bool IsRCDOperationStillValid ( EntityUid uid , RCDComponent component , MapGridData mapGridData , EntityUid ? target , EntityUid user , bool popMsgs = true )
{
// Update cached prototype if required
UpdateCachedPrototype ( uid , component ) ;
// Check that the RCD has enough ammo to get the job done
TryComp < LimitedChargesComponent > ( uid , out var charges ) ;
// Both of these were messages were suppose to be predicted, but HasInsufficientCharges wasn't being checked on the client for some reason?
if ( _charges . IsEmpty ( uid , charges ) )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-no-ammo-message" ) , uid , user ) ;
return false ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
if ( _charges . HasInsufficientCharges ( uid , component . CachedPrototype . Cost , charges ) )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-insufficient-ammo-message" ) , uid , user ) ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
return false ;
}
2023-10-22 16:53:39 +11:00
2024-03-30 23:29:47 -05:00
// Exit if the target / target location is obstructed
var unobstructed = ( target = = null )
? _interaction . InRangeUnobstructed ( user , _mapSystem . GridTileToWorld ( mapGridData . GridUid , mapGridData . Component , mapGridData . Position ) , popup : popMsgs )
: _interaction . InRangeUnobstructed ( user , target . Value , popup : popMsgs ) ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
if ( ! unobstructed )
return false ;
// Return whether the operation location is valid
switch ( component . CachedPrototype . Mode )
{
case RcdMode . ConstructTile : return IsConstructionLocationValid ( uid , component , mapGridData , user , popMsgs ) ;
case RcdMode . ConstructObject : return IsConstructionLocationValid ( uid , component , mapGridData , user , popMsgs ) ;
case RcdMode . Deconstruct : return IsDeconstructionStillValid ( uid , component , mapGridData , target , user , popMsgs ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
return false ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
private bool IsConstructionLocationValid ( EntityUid uid , RCDComponent component , MapGridData mapGridData , EntityUid user , bool popMsgs = true )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
// Check rule: Must build on empty tile
if ( component . CachedPrototype . ConstructionRules . Contains ( RcdConstructionRule . MustBuildOnEmptyTile ) & & ! mapGridData . Tile . Tile . IsEmpty )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-must-build-on-empty-tile-message" ) , uid , user ) ;
2023-05-01 13:46:59 +00:00
return false ;
2024-03-30 23:29:47 -05:00
}
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
// Check rule: Must build on non-empty tile
if ( ! component . CachedPrototype . ConstructionRules . Contains ( RcdConstructionRule . CanBuildOnEmptyTile ) & & mapGridData . Tile . Tile . IsEmpty )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-cannot-build-on-empty-tile-message" ) , uid , user ) ;
2023-05-01 13:46:59 +00:00
return false ;
2024-03-30 23:29:47 -05:00
}
// Check rule: Must place on subfloor
if ( component . CachedPrototype . ConstructionRules . Contains ( RcdConstructionRule . MustBuildOnSubfloor ) & & ! mapGridData . Tile . Tile . GetContentTileDefinition ( ) . IsSubFloor )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-must-build-on-subfloor-message" ) , uid , user ) ;
return false ;
}
// Tile specific rules
if ( component . CachedPrototype . Mode = = RcdMode . ConstructTile )
{
// Check rule: Tile placement is valid
if ( ! _floors . CanPlaceTile ( mapGridData . GridUid , mapGridData . Component , out var reason ) )
{
if ( popMsgs )
_popup . PopupClient ( reason , uid , user ) ;
return false ;
}
// Check rule: Tiles can't be identical
if ( mapGridData . Tile . Tile . GetContentTileDefinition ( ) . ID = = component . CachedPrototype . Prototype )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-cannot-build-identical-tile" ) , uid , user ) ;
return false ;
}
// Ensure that all construction rules shared between tiles and object are checked before exiting here
return true ;
}
// Entity specific rules
// Check rule: The tile is unoccupied
var isWindow = component . CachedPrototype . ConstructionRules . Contains ( RcdConstructionRule . IsWindow ) ;
var isCatwalk = component . CachedPrototype . ConstructionRules . Contains ( RcdConstructionRule . IsCatwalk ) ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
_intersectingEntities . Clear ( ) ;
_lookup . GetLocalEntitiesIntersecting ( mapGridData . GridUid , mapGridData . Position , _intersectingEntities , - 0.05f , LookupFlags . Uncontained ) ;
foreach ( var ent in _intersectingEntities )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
if ( isWindow & & HasComp < SharedCanBuildWindowOnTopComponent > ( ent ) )
continue ;
if ( isCatwalk & & _tags . HasTag ( ent , "Catwalk" ) )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-cannot-build-on-occupied-tile-message" ) , uid , user ) ;
return false ;
}
if ( component . CachedPrototype . CollisionMask ! = CollisionGroup . None & & TryComp < FixturesComponent > ( ent , out var fixtures ) )
{
foreach ( var fixture in fixtures . Fixtures . Values )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
// Continue if no collision is possible
2024-04-07 22:17:28 -05:00
if ( ! fixture . Hard | | fixture . CollisionLayer < = 0 | | ( fixture . CollisionLayer & ( int ) component . CachedPrototype . CollisionMask ) = = 0 )
2024-03-30 23:29:47 -05:00
continue ;
// Continue if our custom collision bounds are not intersected
if ( component . CachedPrototype . CollisionPolygon ! = null & &
! DoesCustomBoundsIntersectWithFixture ( component . CachedPrototype . CollisionPolygon , component . ConstructionTransform , ent , fixture ) )
continue ;
// Collision was detected
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-cannot-build-on-occupied-tile-message" ) , uid , user ) ;
2023-05-01 13:46:59 +00:00
return false ;
}
2024-03-30 23:29:47 -05:00
}
}
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
return true ;
}
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
private bool IsDeconstructionStillValid ( EntityUid uid , RCDComponent component , MapGridData mapGridData , EntityUid ? target , EntityUid user , bool popMsgs = true )
{
// Attempt to deconstruct a floor tile
if ( target = = null )
{
// The tile is empty
if ( mapGridData . Tile . Tile . IsEmpty )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-nothing-to-deconstruct-message" ) , uid , user ) ;
return false ;
}
// The tile has a structure sitting on it
if ( _turf . IsTileBlocked ( mapGridData . Tile , CollisionGroup . MobMask ) )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-tile-obstructed-message" ) , uid , user ) ;
return false ;
}
// The tile cannot be destroyed
var tileDef = ( ContentTileDefinition ) _tileDefMan [ mapGridData . Tile . Tile . TypeId ] ;
if ( tileDef . Indestructible )
{
if ( popMsgs )
_popup . PopupClient ( Loc . GetString ( "rcd-component-tile-indestructible-message" ) , uid , user ) ;
return false ;
}
}
// Attempt to deconstruct an object
else
{
// The object is not in the whitelist
if ( ! TryComp < RCDDeconstructableComponent > ( target , out var deconstructible ) | | ! deconstructible . Deconstructable )
{
if ( popMsgs )
2023-05-01 13:46:59 +00:00
_popup . PopupClient ( Loc . GetString ( "rcd-component-deconstruct-target-not-on-whitelist-message" ) , uid , user ) ;
2024-03-30 23:29:47 -05:00
return false ;
}
}
return true ;
}
#endregion
#region Entity construction / deconstruction
2024-04-07 22:17:28 -05:00
private void FinalizeRCDOperation ( EntityUid uid , RCDComponent component , MapGridData mapGridData , Direction direction , EntityUid ? target , EntityUid user )
2024-03-30 23:29:47 -05:00
{
if ( ! _net . IsServer )
return ;
if ( component . CachedPrototype . Prototype = = null )
return ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
switch ( component . CachedPrototype . Mode )
{
case RcdMode . ConstructTile :
_mapSystem . SetTile ( mapGridData . GridUid , mapGridData . Component , mapGridData . Position , new Tile ( _tileDefMan [ component . CachedPrototype . Prototype ] . TileId ) ) ;
_adminLogger . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} {mapGridData.Position} to {component.CachedPrototype.Prototype}" ) ;
break ;
case RcdMode . ConstructObject :
var ent = Spawn ( component . CachedPrototype . Prototype , _mapSystem . GridTileToLocal ( mapGridData . GridUid , mapGridData . Component , mapGridData . Position ) ) ;
switch ( component . CachedPrototype . Rotation )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
case RcdRotation . Fixed :
Transform ( ent ) . LocalRotation = Angle . Zero ;
break ;
case RcdRotation . Camera :
Transform ( ent ) . LocalRotation = Transform ( uid ) . LocalRotation ;
break ;
case RcdRotation . User :
2024-04-07 22:17:28 -05:00
Transform ( ent ) . LocalRotation = direction . ToAngle ( ) ;
2024-03-30 23:29:47 -05:00
break ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
_adminLogger . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(user):user} used RCD to spawn {ToPrettyString(ent)} at {mapGridData.Position} on grid {mapGridData.GridUid}" ) ;
break ;
case RcdMode . Deconstruct :
if ( target = = null )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
// Deconstruct tile (either converts the tile to lattice, or removes lattice)
var tile = ( mapGridData . Tile . Tile . GetContentTileDefinition ( ) . ID ! = "Lattice" ) ? new Tile ( _tileDefMan [ "Lattice" ] . TileId ) : Tile . Empty ;
_mapSystem . SetTile ( mapGridData . GridUid , mapGridData . Component , mapGridData . Position , tile ) ;
_adminLogger . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} tile: {mapGridData.Position} open to space" ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
else
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
// Deconstruct object
_adminLogger . Add ( LogType . RCD , LogImpact . High , $"{ToPrettyString(user):user} used RCD to delete {ToPrettyString(target):target}" ) ;
QueueDel ( target ) ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
break ;
2023-05-01 13:46:59 +00:00
}
}
2024-03-30 23:29:47 -05:00
#endregion
#region Utility functions
public bool TryGetMapGridData ( EntityCoordinates location , [ NotNullWhen ( true ) ] out MapGridData ? mapGridData )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
mapGridData = null ;
var gridUid = location . GetGridUid ( EntityManager ) ;
if ( ! TryComp < MapGridComponent > ( gridUid , out var mapGrid ) )
{
location = location . AlignWithClosestGridTile ( 1.75f , EntityManager ) ;
gridUid = location . GetGridUid ( EntityManager ) ;
// Check if we got a grid ID the second time round
if ( ! TryComp ( gridUid , out mapGrid ) )
return false ;
}
gridUid = mapGrid . Owner ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
var tile = _mapSystem . GetTileRef ( gridUid . Value , mapGrid , location ) ;
var position = _mapSystem . TileIndicesFor ( gridUid . Value , mapGrid , location ) ;
mapGridData = new MapGridData ( gridUid . Value , mapGrid , location , tile , position ) ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
return true ;
2023-05-01 13:46:59 +00:00
}
2024-03-30 23:29:47 -05:00
private bool DoesCustomBoundsIntersectWithFixture ( PolygonShape boundingPolygon , Transform boundingTransform , EntityUid fixtureOwner , Fixture fixture )
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
var entXformComp = Transform ( fixtureOwner ) ;
var entXform = new Transform ( new ( ) , entXformComp . LocalRotation ) ;
return boundingPolygon . ComputeAABB ( boundingTransform , 0 ) . Intersects ( fixture . Shape . ComputeAABB ( entXform , 0 ) ) ;
}
public void UpdateCachedPrototype ( EntityUid uid , RCDComponent component )
{
if ( component . ProtoId . Id ! = component . CachedPrototype ? . Prototype )
component . CachedPrototype = _protoManager . Index ( component . ProtoId ) ;
}
#endregion
}
public struct MapGridData
{
public EntityUid GridUid ;
public MapGridComponent Component ;
public EntityCoordinates Location ;
public TileRef Tile ;
public Vector2i Position ;
public MapGridData ( EntityUid gridUid , MapGridComponent component , EntityCoordinates location , TileRef tile , Vector2i position )
{
GridUid = gridUid ;
Component = component ;
Location = location ;
Tile = tile ;
Position = position ;
2023-05-01 13:46:59 +00:00
}
}
[Serializable, NetSerializable]
2023-08-22 18:14:33 -07:00
public sealed partial class RCDDoAfterEvent : DoAfterEvent
2023-05-01 13:46:59 +00:00
{
2024-03-30 23:29:47 -05:00
[DataField(required: true)]
public NetCoordinates Location { get ; private set ; } = default ! ;
2023-05-01 13:46:59 +00:00
2024-04-07 22:17:28 -05:00
[DataField]
public Direction Direction { get ; private set ; } = default ! ;
2024-03-30 23:29:47 -05:00
[DataField]
public ProtoId < RCDPrototype > StartingProtoId { get ; private set ; } = default ! ;
2023-05-01 13:46:59 +00:00
2024-03-30 23:29:47 -05:00
[DataField]
public int Cost { get ; private set ; } = 1 ;
[DataField("fx")]
public NetEntity ? Effect { get ; private set ; } = null ;
private RCDDoAfterEvent ( ) { }
2023-05-01 13:46:59 +00:00
2024-04-07 22:17:28 -05:00
public RCDDoAfterEvent ( NetCoordinates location , Direction direction , ProtoId < RCDPrototype > startingProtoId , int cost , NetEntity ? effect = null )
2023-05-01 13:46:59 +00:00
{
Location = location ;
2024-04-07 22:17:28 -05:00
Direction = direction ;
2024-03-30 23:29:47 -05:00
StartingProtoId = startingProtoId ;
Cost = cost ;
Effect = effect ;
2023-05-01 13:46:59 +00:00
}
public override DoAfterEvent Clone ( ) = > this ;
}