2021-08-23 00:54:03 +10:00
using Content.Server.Ghost.Components ;
2021-06-09 22:19:39 +02:00
using Content.Server.Singularity.Components ;
2022-08-07 15:53:07 +12:00
using Content.Server.Station.Components ;
2021-06-24 04:48:11 +02:00
using Content.Shared.Singularity ;
2021-08-23 00:54:03 +10:00
using Content.Shared.Singularity.Components ;
2020-12-08 11:56:10 +01:00
using JetBrains.Annotations ;
2022-04-29 00:43:16 +12:00
using Robust.Server.GameStates ;
2021-07-21 20:33:22 +10:00
using Robust.Shared.Containers ;
2021-08-23 00:54:03 +10:00
using Robust.Shared.Map ;
using Robust.Shared.Physics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2021-07-21 20:33:22 +10:00
using Robust.Shared.Physics.Dynamics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Events ;
2020-10-28 19:19:47 +01:00
2021-06-09 22:19:39 +02:00
namespace Content.Server.Singularity.EntitySystems
2020-10-28 19:19:47 +01:00
{
2020-12-08 11:56:10 +01:00
[UsedImplicitly]
2021-12-15 13:47:12 +11:00
public sealed class SingularitySystem : SharedSingularitySystem
2020-10-28 19:19:47 +01:00
{
2022-03-03 21:18:35 +11:00
[Dependency] private readonly EntityLookupSystem _lookup = default ! ;
2021-08-23 00:54:03 +10:00
[Dependency] private readonly IMapManager _mapManager = default ! ;
2021-12-15 13:47:12 +11:00
[Dependency] private readonly SharedContainerSystem _container = default ! ;
2022-04-29 00:43:16 +12:00
[Dependency] private readonly PVSOverrideSystem _pvs = default ! ;
2021-08-23 00:54:03 +10:00
/// <summary>
/// How much energy the singulo gains from destroying a tile.
/// </summary>
private const int TileEnergyGain = 1 ;
private const float GravityCooldown = 0.5f ;
private float _gravityAccumulator ;
private int _updateInterval = 1 ;
2021-03-08 04:09:59 +11:00
private float _accumulator ;
2020-12-08 11:56:10 +01:00
2021-07-21 20:33:22 +10:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2021-12-15 13:47:12 +11:00
SubscribeLocalEvent < ServerSingularityComponent , StartCollideEvent > ( OnCollide ) ;
2022-04-29 00:43:16 +12:00
SubscribeLocalEvent < SingularityDistortionComponent , ComponentStartup > ( OnDistortionStartup ) ;
}
private void OnDistortionStartup ( EntityUid uid , SingularityDistortionComponent component , ComponentStartup args )
{
// to avoid distortion overlay pop-in, entities with distortion ignore PVS. Really this should probably be a
// PVS range-override, but this is good enough for now.
_pvs . AddGlobalOverride ( uid ) ;
2021-07-21 20:33:22 +10:00
}
2022-09-14 17:26:26 +10:00
protected override bool PreventCollide ( EntityUid uid , SharedSingularityComponent component , ref PreventCollideEvent args )
2021-07-21 20:33:22 +10:00
{
2022-09-14 17:26:26 +10:00
if ( base . PreventCollide ( uid , component , ref args ) ) return true ;
2021-12-15 13:47:12 +11:00
var otherUid = args . BodyB . Owner ;
if ( args . Cancelled ) return true ;
// If it's not cancelled then we'll cancel if we can't immediately destroy it on collision
if ( ! CanDestroy ( component , otherUid ) )
2022-09-14 17:26:26 +10:00
args . Cancelled = true ;
2021-12-15 13:47:12 +11:00
return true ;
}
2022-09-14 17:26:26 +10:00
private void OnCollide ( EntityUid uid , ServerSingularityComponent component , ref StartCollideEvent args )
2021-12-15 13:47:12 +11:00
{
if ( args . OurFixture . ID ! = "DeleteCircle" ) return ;
2021-08-23 00:54:03 +10:00
// This handles bouncing off of containment walls.
// If you want the delete behavior we do it under DeleteEntities for reasons (not everything has physics).
2021-07-21 20:33:22 +10:00
// If we're being deleted by another singularity, this call is probably for that singularity.
// Even if not, just don't bother.
if ( component . BeingDeletedByAnotherSingularity )
return ;
2021-12-15 13:47:12 +11:00
var otherUid = args . OtherFixture . Body . Owner ;
// HandleDestroy will also check CanDestroy for us
HandleDestroy ( component , otherUid ) ;
2021-08-23 00:54:03 +10:00
}
2021-07-21 20:33:22 +10:00
2021-08-23 00:54:03 +10:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
_gravityAccumulator + = frameTime ;
_accumulator + = frameTime ;
while ( _accumulator > _updateInterval )
2021-07-21 20:33:22 +10:00
{
2021-08-23 00:54:03 +10:00
_accumulator - = _updateInterval ;
2021-09-28 13:35:29 +02:00
foreach ( var singularity in EntityManager . EntityQuery < ServerSingularityComponent > ( ) )
2021-07-21 20:33:22 +10:00
{
2021-08-23 00:54:03 +10:00
singularity . Energy - = singularity . EnergyDrain ;
2021-07-21 20:33:22 +10:00
}
}
2021-08-23 00:54:03 +10:00
while ( _gravityAccumulator > GravityCooldown )
2021-07-21 20:33:22 +10:00
{
2021-08-23 00:54:03 +10:00
_gravityAccumulator - = GravityCooldown ;
2021-12-15 13:47:12 +11:00
foreach ( var ( singularity , xform ) in EntityManager . EntityQuery < ServerSingularityComponent , TransformComponent > ( ) )
2021-08-23 00:54:03 +10:00
{
2021-12-15 13:47:12 +11:00
Update ( singularity , xform , GravityCooldown ) ;
2021-08-23 00:54:03 +10:00
}
2021-07-21 20:33:22 +10:00
}
2021-08-23 00:54:03 +10:00
}
2021-07-21 20:33:22 +10:00
2021-12-15 13:47:12 +11:00
private void Update ( ServerSingularityComponent component , TransformComponent xform , float frameTime )
2021-08-23 00:54:03 +10:00
{
if ( component . BeingDeletedByAnotherSingularity ) return ;
2021-12-15 13:47:12 +11:00
var worldPos = xform . WorldPosition ;
DestroyEntities ( component , xform , worldPos ) ;
DestroyTiles ( component , xform , worldPos ) ;
PullEntities ( component , xform , worldPos , frameTime ) ;
2021-08-23 00:54:03 +10:00
}
private float PullRange ( ServerSingularityComponent component )
{
// Level 6 is normally 15 range but that's yuge.
return 2 + component . Level * 2 ;
}
private float DestroyTileRange ( ServerSingularityComponent component )
{
return component . Level - 0.5f ;
}
2021-12-05 18:09:01 +01:00
private bool CanDestroy ( SharedSingularityComponent component , EntityUid entity )
2021-08-23 00:54:03 +10:00
{
2021-12-15 13:47:12 +11:00
return entity ! = component . Owner & &
! EntityManager . HasComponent < IMapGridComponent > ( entity ) & &
! EntityManager . HasComponent < GhostComponent > ( entity ) & &
2022-08-14 07:28:34 +02:00
! EntityManager . HasComponent < StationDataComponent > ( entity ) & & // these SHOULD be in null-space... but just in case. Also, maybe someone moves a singularity there..
2021-12-15 13:47:12 +11:00
( component . Level > 4 | |
! EntityManager . HasComponent < ContainmentFieldComponent > ( entity ) & &
2022-08-14 22:37:58 -04:00
! ( EntityManager . TryGetComponent < ContainmentFieldGeneratorComponent > ( entity , out var fieldGen ) & & fieldGen . IsConnected ) ) ;
2021-08-23 00:54:03 +10:00
}
2021-12-05 18:09:01 +01:00
private void HandleDestroy ( ServerSingularityComponent component , EntityUid entity )
2021-08-23 00:54:03 +10:00
{
// TODO: Need singuloimmune tag
2021-12-15 13:47:12 +11:00
if ( ! CanDestroy ( component , entity ) ) return ;
2021-07-21 20:33:22 +10:00
// Singularity priority management / etc.
2021-12-08 13:00:43 +01:00
if ( EntityManager . TryGetComponent < ServerSingularityComponent ? > ( entity , out var otherSingulo ) )
2021-08-23 00:54:03 +10:00
{
// MERGE
if ( ! otherSingulo . BeingDeletedByAnotherSingularity )
{
component . Energy + = otherSingulo . Energy ;
}
2021-07-21 20:33:22 +10:00
otherSingulo . BeingDeletedByAnotherSingularity = true ;
2021-08-23 00:54:03 +10:00
}
2021-07-21 20:33:22 +10:00
2021-12-08 13:00:43 +01:00
if ( EntityManager . TryGetComponent < SinguloFoodComponent ? > ( entity , out var singuloFood ) )
2021-07-21 20:33:22 +10:00
component . Energy + = singuloFood . Energy ;
else
component . Energy + + ;
2021-12-15 13:47:12 +11:00
EntityManager . QueueDeleteEntity ( entity ) ;
2021-07-21 20:33:22 +10:00
}
2021-08-23 00:54:03 +10:00
/// <summary>
/// Handle deleting entities and increasing energy
/// </summary>
2021-12-15 13:47:12 +11:00
private void DestroyEntities ( ServerSingularityComponent component , TransformComponent xform , Vector2 worldPos )
2020-10-28 19:19:47 +01:00
{
2021-08-23 00:54:03 +10:00
// The reason we don't /just/ use collision is because we'll be deleting stuff that may not necessarily have physics (e.g. carpets).
var destroyRange = DestroyTileRange ( component ) ;
2020-10-28 19:19:47 +01:00
2021-12-15 13:47:12 +11:00
foreach ( var entity in _lookup . GetEntitiesInRange ( xform . MapID , worldPos , destroyRange ) )
2020-10-28 19:19:47 +01:00
{
2021-08-23 00:54:03 +10:00
HandleDestroy ( component , entity ) ;
}
}
2020-10-28 19:19:47 +01:00
2021-12-05 18:09:01 +01:00
private bool CanPull ( EntityUid entity )
2021-08-23 00:54:03 +10:00
{
2021-12-08 13:00:43 +01:00
return ! ( EntityManager . HasComponent < GhostComponent > ( entity ) | |
EntityManager . HasComponent < IMapGridComponent > ( entity ) | |
EntityManager . HasComponent < MapComponent > ( entity ) | |
2021-12-15 13:47:12 +11:00
EntityManager . HasComponent < ServerSingularityComponent > ( entity ) | |
_container . IsEntityInContainer ( entity ) ) ;
2021-08-23 00:54:03 +10:00
}
2021-12-15 13:47:12 +11:00
/// <summary>
/// Pull dynamic bodies in range to the singulo.
/// </summary>
private void PullEntities ( ServerSingularityComponent component , TransformComponent xform , Vector2 worldPos , float frameTime )
2021-08-23 00:54:03 +10:00
{
// TODO: When we split up dynamic and static trees we might be able to make items always on the broadphase
// in which case we can just query dynamictree directly for brrt
var pullRange = PullRange ( component ) ;
var destroyRange = DestroyTileRange ( component ) ;
2021-12-15 13:47:12 +11:00
foreach ( var entity in _lookup . GetEntitiesInRange ( xform . MapID , worldPos , pullRange ) )
2021-08-23 00:54:03 +10:00
{
// I tried having it so level 6 can de-anchor. BAD IDEA, MASSIVE LAG.
if ( entity = = component . Owner | |
2022-04-06 19:35:18 +10:00
! TryComp < PhysicsComponent ? > ( entity , out var collidableComponent ) | |
2021-08-23 00:54:03 +10:00
collidableComponent . BodyType = = BodyType . Static ) continue ;
if ( ! CanPull ( entity ) ) continue ;
2022-04-06 19:35:18 +10:00
var vec = worldPos - Transform ( entity ) . WorldPosition ;
2021-08-23 00:54:03 +10:00
if ( vec . Length < destroyRange - 0.01f ) continue ;
2022-06-26 23:54:18 +10:00
var speed = 1f / vec . Length * component . Level * collidableComponent . Mass * 10f ;
2021-08-23 00:54:03 +10:00
// Because tile friction is so high we'll just multiply by mass so stuff like closets can even move.
2021-12-15 13:47:12 +11:00
collidableComponent . ApplyLinearImpulse ( vec . Normalized * speed * frameTime ) ;
2021-08-23 00:54:03 +10:00
}
}
/// <summary>
/// Destroy any grid tiles within the relevant Level range.
/// </summary>
2021-12-15 13:47:12 +11:00
private void DestroyTiles ( ServerSingularityComponent component , TransformComponent xform , Vector2 worldPos )
2021-08-23 00:54:03 +10:00
{
var radius = DestroyTileRange ( component ) ;
var circle = new Circle ( worldPos , radius ) ;
var box = new Box2 ( worldPos - radius , worldPos + radius ) ;
2021-12-15 13:47:12 +11:00
foreach ( var grid in _mapManager . FindGridsIntersecting ( xform . MapID , box ) )
2021-08-23 00:54:03 +10:00
{
2021-12-15 13:47:12 +11:00
// Bundle these together so we can use the faster helper to set tiles.
var toDestroy = new List < ( Vector2i , Tile ) > ( ) ;
2021-08-23 00:54:03 +10:00
foreach ( var tile in grid . GetTilesIntersecting ( circle ) )
2020-10-28 19:19:47 +01:00
{
2021-08-23 00:54:03 +10:00
if ( tile . Tile . IsEmpty ) continue ;
2021-12-15 13:47:12 +11:00
// Avoid ripping up tiles that may be essential to containment
if ( component . Level < 5 )
{
var canDelete = true ;
foreach ( var ent in grid . GetAnchoredEntities ( tile . GridIndices ) )
{
if ( EntityManager . HasComponent < ContainmentFieldComponent > ( ent ) | |
EntityManager . HasComponent < ContainmentFieldGeneratorComponent > ( ent ) )
{
canDelete = false ;
break ;
}
}
if ( ! canDelete ) continue ;
}
toDestroy . Add ( ( tile . GridIndices , Tile . Empty ) ) ;
2020-10-28 19:19:47 +01:00
}
2021-12-15 13:47:12 +11:00
component . Energy + = TileEnergyGain * toDestroy . Count ;
grid . SetTiles ( toDestroy ) ;
2020-10-28 19:19:47 +01:00
}
}
}
}