Remove most IEntity usages from explosions (#5240)

* Remove most IEntity usages from Destructible and Explosions

* Perform a minute amount of cleanup

* Fix build
This commit is contained in:
Javier Guardia Fernández
2021-11-09 21:24:35 +01:00
committed by GitHub
parent 3a4186f6f6
commit 42aaba9a5d
28 changed files with 294 additions and 277 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Flash.Components;
using Content.Server.Throwing;
using Content.Shared.Explosion;

View File

@@ -1,3 +1,4 @@
using Content.Server.Explosion.EntitySystems;
using Robust.Shared.GameObjects;
namespace Content.Server.Explosion.Components

View File

@@ -15,7 +15,7 @@ namespace Content.Server.Explosion.Components
return;
var sourceLocation = eventArgs.Source;
var targetLocation = eventArgs.Target.Transform.Coordinates;
var targetLocation = Owner.EntityManager.GetComponent<TransformComponent>(eventArgs.Target).Coordinates;
if (sourceLocation.Equals(targetLocation)) return;
@@ -27,7 +27,7 @@ namespace Content.Server.Explosion.Components
ExplosionSeverity.Light => 20,
_ => 0,
};
Owner.TryThrow(direction, throwForce);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Trigger;
using Robust.Server.GameObjects;

View File

@@ -1,3 +1,4 @@
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Sound;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Camera;
@@ -21,9 +21,9 @@ using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Explosion
namespace Content.Server.Explosion.EntitySystems
{
public static class ExplosionHelper
public class ExplosionSystem : EntitySystem
{
/// <summary>
/// Distance used for camera shake when distance from explosion is (0.0, 0.0).
@@ -34,19 +34,35 @@ namespace Content.Server.Explosion
/// <summary>
/// Chance of a tile breaking if the severity is Light and Heavy
/// </summary>
private static readonly float LightBreakChance = 0.3f;
private static readonly float HeavyBreakChance = 0.8f;
private static SoundSpecifier _explosionSound = new SoundCollectionSpecifier("explosion");
private const float LightBreakChance = 0.3f;
private const float HeavyBreakChance = 0.8f;
private static bool IgnoreExplosivePassable(IEntity e) => e.HasTag("ExplosivePassable");
// TODO move this to the component
private static readonly SoundSpecifier ExplosionSound = new SoundCollectionSpecifier("explosion");
private static ExplosionSeverity CalculateSeverity(float distance, float devastationRange, float heaveyRange)
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _maps = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ITileDefinitionManager _tiles = default!;
[Dependency] private readonly ActSystem _acts = default!;
[Dependency] private readonly EffectSystem _effects = default!;
[Dependency] private readonly TriggerSystem _triggers = default!;
private bool IgnoreExplosivePassable(IEntity e)
{
return e.HasTag("ExplosivePassable");
}
private ExplosionSeverity CalculateSeverity(float distance, float devastationRange, float heavyRange)
{
if (distance < devastationRange)
{
return ExplosionSeverity.Destruction;
}
else if (distance < heaveyRange)
else if (distance < heavyRange)
{
return ExplosionSeverity.Heavy;
}
@@ -56,174 +72,10 @@ namespace Content.Server.Explosion
}
}
/// <summary>
/// Damage entities inside the range. The damage depends on a discrete
/// damage bracket [light, heavy, devastation] and the distance from the epicenter
/// </summary>
/// <returns>
/// A dictionary of coordinates relative to the parents of every grid of entities that survived the explosion,
/// have an airtight component and are currently blocking air. Like a wall.
/// </returns>
private static void DamageEntitiesInRange(EntityCoordinates epicenter, Box2 boundingBox,
float devastationRange,
float heaveyRange,
float maxRange,
MapId mapId)
private void CameraShakeInRange(EntityCoordinates epicenter, float maxRange)
{
var entityManager = IoCManager.Resolve<IEntityManager>();
var players = _players.GetPlayersInRange(epicenter, (int) Math.Ceiling(maxRange));
var exAct = EntitySystem.Get<ActSystem>();
var entitiesInRange = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(mapId, boundingBox, 0).ToList();
var impassableEntities = new List<Tuple<IEntity, float>>();
var nonImpassableEntities = new List<Tuple<IEntity, float>>();
// TODO: Given this seems to rely on physics it should just query directly like everything else.
// The entities are paired with their distance to the epicenter
// and splitted into two lists based on if they are Impassable or not
foreach (var entity in entitiesInRange)
{
if (entity.Deleted || entity.IsInContainer())
{
continue;
}
if (!entity.Transform.Coordinates.TryDistance(entityManager, epicenter, out var distance) || distance > maxRange)
{
continue;
}
if (!entity.TryGetComponent(out PhysicsComponent? body) || body.Fixtures.Count < 1)
{
continue;
}
if ((body.CollisionLayer & (int) CollisionGroup.Impassable) != 0)
{
impassableEntities.Add(Tuple.Create(entity, distance));
}
else
{
nonImpassableEntities.Add(Tuple.Create(entity, distance));
}
}
// The Impassable entities are sorted in descending order
// Entities closer to the epicenter are first
impassableEntities.Sort((x, y) => x.Item2.CompareTo(y.Item2));
// Impassable entities are handled first. If they are damaged enough, they are destroyed and they may
// be able to spawn a new entity. I.e Wall -> Girder.
// Girder has a tag ExplosivePassable, and the predicate make it so the entities with this tag are ignored
var epicenterMapPos = epicenter.ToMap(entityManager);
foreach (var (entity, distance) in impassableEntities)
{
if (!entity.InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: true, predicate: IgnoreExplosivePassable))
{
continue;
}
exAct.HandleExplosion(epicenter, entity, CalculateSeverity(distance, devastationRange, heaveyRange));
}
// Impassable entities were handled first so NonImpassable entities have a bigger chance to get hit. As now
// there are probably more ExplosivePassable entities around
foreach (var (entity, distance) in nonImpassableEntities)
{
if (!entity.InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: true, predicate: IgnoreExplosivePassable))
{
continue;
}
exAct.HandleExplosion(epicenter, entity, CalculateSeverity(distance, devastationRange, heaveyRange));
}
}
/// <summary>
/// Damage tiles inside the range. The type of tile can change depending on a discrete
/// damage bracket [light, heavy, devastation], the distance from the epicenter and
/// a probabilty bracket [<see cref="LightBreakChance"/>, <see cref="HeavyBreakChance"/>, 1.0].
/// </summary>
///
private static void DamageTilesInRange(EntityCoordinates epicenter,
GridId gridId,
Box2 boundingBox,
float devastationRange,
float heaveyRange,
float maxRange)
{
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.TryGetGrid(gridId, out var mapGrid))
{
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(mapGrid.GridEntityId, out var grid))
{
return;
}
var robustRandom = IoCManager.Resolve<IRobustRandom>();
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
var tilesInGridAndCircle = mapGrid.GetTilesIntersecting(boundingBox);
var epicenterMapPos = epicenter.ToMap(entityManager);
foreach (var tile in tilesInGridAndCircle)
{
var tileLoc = mapGrid.GridTileToLocal(tile.GridIndices);
if (!tileLoc.TryDistance(entityManager, epicenter, out var distance) || distance > maxRange)
{
continue;
}
if (tile.IsBlockedTurf(false))
{
continue;
}
if (!tileLoc.ToMap(entityManager).InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: false, predicate: IgnoreExplosivePassable))
{
continue;
}
var tileDef = (ContentTileDefinition) tileDefinitionManager[tile.Tile.TypeId];
var baseTurfs = tileDef.BaseTurfs;
if (baseTurfs.Count == 0)
{
continue;
}
var zeroTile = new Robust.Shared.Map.Tile(tileDefinitionManager[baseTurfs[0]].TileId);
var previousTile = new Robust.Shared.Map.Tile(tileDefinitionManager[baseTurfs[^1]].TileId);
var severity = CalculateSeverity(distance, devastationRange, heaveyRange);
switch (severity)
{
case ExplosionSeverity.Light:
if (!previousTile.IsEmpty && robustRandom.Prob(LightBreakChance))
{
mapGrid.SetTile(tileLoc, previousTile);
}
break;
case ExplosionSeverity.Heavy:
if (!previousTile.IsEmpty && robustRandom.Prob(HeavyBreakChance))
{
mapGrid.SetTile(tileLoc, previousTile);
}
break;
case ExplosionSeverity.Destruction:
mapGrid.SetTile(tileLoc, zeroTile);
break;
}
}
}
private static void CameraShakeInRange(EntityCoordinates epicenter, float maxRange)
{
var playerManager = IoCManager.Resolve<IPlayerManager>();
var players = playerManager.GetPlayersInRange(epicenter, (int) Math.Ceiling(maxRange));
foreach (var player in players)
{
if (player.AttachedEntity == null || !player.AttachedEntity.TryGetComponent(out CameraRecoilComponent? recoil))
@@ -231,10 +83,8 @@ namespace Content.Server.Explosion
continue;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var playerPos = player.AttachedEntity.Transform.WorldPosition;
var delta = epicenter.ToMapPos(entityManager) - playerPos;
var delta = epicenter.ToMapPos(EntityManager) - playerPos;
//Change if zero. Will result in a NaN later breaking camera shake if not changed
if (delta.EqualsApprox((0.0f, 0.0f)))
@@ -250,83 +100,250 @@ namespace Content.Server.Explosion
}
}
private static void FlashInRange(EntityCoordinates epicenter, float flashrange)
/// <summary>
/// Damage entities inside the range. The damage depends on a discrete
/// damage bracket [light, heavy, devastation] and the distance from the epicenter
/// </summary>
/// <returns>
/// A dictionary of coordinates relative to the parents of every grid of entities that survived the explosion,
/// have an airtight component and are currently blocking air. Like a wall.
/// </returns>
private void DamageEntitiesInRange(
EntityCoordinates epicenter,
Box2 boundingBox,
float devastationRange,
float heavyRange,
float maxRange,
MapId mapId)
{
if (flashrange > 0)
var entitiesInRange = _entityLookup.GetEntitiesInRange(mapId, boundingBox, 0).ToList();
var impassableEntities = new List<(IEntity, float)>();
var nonImpassableEntities = new List<(IEntity, float)>();
// TODO: Given this seems to rely on physics it should just query directly like everything else.
// The entities are paired with their distance to the epicenter
// and splitted into two lists based on if they are Impassable or not
foreach (var entity in entitiesInRange)
{
var entitySystemManager = IoCManager.Resolve<IEntitySystemManager>();
var time = IoCManager.Resolve<IGameTiming>().CurTime;
if (entity.Deleted || entity.IsInContainer())
{
continue;
}
if (!entity.Transform.Coordinates.TryDistance(EntityManager, epicenter, out var distance) ||
distance > maxRange)
{
continue;
}
if (!entity.TryGetComponent(out PhysicsComponent? body) || body.Fixtures.Count < 1)
{
continue;
}
if ((body.CollisionLayer & (int) CollisionGroup.Impassable) != 0)
{
impassableEntities.Add((entity, distance));
}
else
{
nonImpassableEntities.Add((entity, distance));
}
}
// The Impassable entities are sorted in descending order
// Entities closer to the epicenter are first
impassableEntities.Sort((x, y) => x.Item2.CompareTo(y.Item2));
// Impassable entities are handled first. If they are damaged enough, they are destroyed and they may
// be able to spawn a new entity. I.e Wall -> Girder.
// Girder has a tag ExplosivePassable, and the predicate make it so the entities with this tag are ignored
var epicenterMapPos = epicenter.ToMap(EntityManager);
foreach (var (entity, distance) in impassableEntities)
{
if (!entity.InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: true, predicate: IgnoreExplosivePassable))
{
continue;
}
_acts.HandleExplosion(epicenter, entity.Uid, CalculateSeverity(distance, devastationRange, heavyRange));
}
// Impassable entities were handled first so NonImpassable entities have a bigger chance to get hit. As now
// there are probably more ExplosivePassable entities around
foreach (var (entity, distance) in nonImpassableEntities)
{
if (!entity.InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: true, predicate: IgnoreExplosivePassable))
{
continue;
}
_acts.HandleExplosion(epicenter, entity.Uid, CalculateSeverity(distance, devastationRange, heavyRange));
}
}
/// <summary>
/// Damage tiles inside the range. The type of tile can change depending on a discrete
/// damage bracket [light, heavy, devastation], the distance from the epicenter and
/// a probability bracket [<see cref="LightBreakChance"/>, <see cref="HeavyBreakChance"/>, 1.0].
/// </summary>
///
private void DamageTilesInRange(EntityCoordinates epicenter,
GridId gridId,
Box2 boundingBox,
float devastationRange,
float heaveyRange,
float maxRange)
{
if (!_maps.TryGetGrid(gridId, out var mapGrid))
{
return;
}
if (!EntityManager.EntityExists(mapGrid.GridEntityId))
{
return;
}
var tilesInGridAndCircle = mapGrid.GetTilesIntersecting(boundingBox);
var epicenterMapPos = epicenter.ToMap(EntityManager);
foreach (var tile in tilesInGridAndCircle)
{
var tileLoc = mapGrid.GridTileToLocal(tile.GridIndices);
if (!tileLoc.TryDistance(EntityManager, epicenter, out var distance) ||
distance > maxRange)
{
continue;
}
if (tile.IsBlockedTurf(false))
{
continue;
}
if (!tileLoc.ToMap(EntityManager).InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: false, predicate: IgnoreExplosivePassable))
{
continue;
}
var tileDef = (ContentTileDefinition) _tiles[tile.Tile.TypeId];
var baseTurfs = tileDef.BaseTurfs;
if (baseTurfs.Count == 0)
{
continue;
}
var zeroTile = new Tile(_tiles[baseTurfs[0]].TileId);
var previousTile = new Tile(_tiles[baseTurfs[^1]].TileId);
var severity = CalculateSeverity(distance, devastationRange, heaveyRange);
switch (severity)
{
case ExplosionSeverity.Light:
if (!previousTile.IsEmpty && _random.Prob(LightBreakChance))
{
mapGrid.SetTile(tileLoc, previousTile);
}
break;
case ExplosionSeverity.Heavy:
if (!previousTile.IsEmpty && _random.Prob(HeavyBreakChance))
{
mapGrid.SetTile(tileLoc, previousTile);
}
break;
case ExplosionSeverity.Destruction:
mapGrid.SetTile(tileLoc, zeroTile);
break;
}
}
}
private void FlashInRange(EntityCoordinates epicenter, float flashRange)
{
if (flashRange > 0)
{
var time = _timing.CurTime;
var message = new EffectSystemMessage
{
EffectSprite = "Effects/explosion.rsi",
RsiState = "explosionfast",
Born = time,
DeathTime = time + TimeSpan.FromSeconds(5),
Size = new Vector2(flashrange / 2, flashrange / 2),
Size = new Vector2(flashRange / 2, flashRange / 2),
Coordinates = epicenter,
Rotation = 0f,
ColorDelta = new Vector4(0, 0, 0, -1500f),
Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), 0.5f),
Shaded = false
};
entitySystemManager.GetEntitySystem<EffectSystem>().CreateParticle(message);
_effects.CreateParticle(message);
}
}
// TODO: remove this shit
public static void SpawnExplosion(this EntityUid uid, int devastationRange = 0, int heavyImpactRange = 0,
int lightImpactRange = 0, int flashRange = 0, IEntityManager? entityManager = null)
public void SpawnExplosion(
EntityUid entity,
int devastationRange = 0,
int heavyImpactRange = 0,
int lightImpactRange = 0,
int flashRange = 0,
ExplosiveComponent? explosive = null,
TransformComponent? transform = null)
{
entityManager ??= IoCManager.Resolve<IEntityManager>();
SpawnExplosion(entityManager.GetEntity(uid), devastationRange, heavyImpactRange, lightImpactRange, flashRange);
}
public static void SpawnExplosion(this IEntity entity, int devastationRange = 0, int heavyImpactRange = 0,
int lightImpactRange = 0, int flashRange = 0)
{
// TODO: Need to refactor this stufferino
// If you want to directly set off the explosive
if (!entity.Deleted && entity.TryGetComponent(out ExplosiveComponent? explosive) && !explosive.Exploding)
if (!Resolve(entity, ref transform))
{
EntitySystem.Get<TriggerSystem>().Explode(entity.Uid, explosive);
return;
}
Resolve(entity, ref explosive, false);
if (explosive is { Exploding: false })
{
_triggers.Explode(entity, explosive);
}
else
{
while (entity.TryGetContainer(out var cont))
while (EntityManager.TryGetComponent(entity, out ContainerManagerComponent? container))
{
entity = cont.Owner;
entity = container.OwnerUid;
}
var epicenter = entity.Transform.Coordinates;
if (!EntityManager.TryGetComponent(entity, out transform))
{
return;
}
var epicenter = transform.Coordinates;
SpawnExplosion(epicenter, devastationRange, heavyImpactRange, lightImpactRange, flashRange);
}
}
public static void SpawnExplosion(EntityCoordinates epicenter, int devastationRange = 0,
int heavyImpactRange = 0, int lightImpactRange = 0, int flashRange = 0)
public void SpawnExplosion(
EntityCoordinates epicenter,
int devastationRange = 0,
int heavyImpactRange = 0,
int lightImpactRange = 0,
int flashRange = 0)
{
var mapId = epicenter.GetMapId(IoCManager.Resolve<IEntityManager>());
var mapId = epicenter.GetMapId(EntityManager);
if (mapId == MapId.Nullspace)
{
return;
}
var maxRange = MathHelper.Max(devastationRange, heavyImpactRange, lightImpactRange, 0);
var entityManager = IoCManager.Resolve<IEntityManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
var epicenterMapPos = epicenter.ToMapPos(entityManager);
var epicenterMapPos = epicenter.ToMapPos(EntityManager);
var boundingBox = new Box2(epicenterMapPos - new Vector2(maxRange, maxRange),
epicenterMapPos + new Vector2(maxRange, maxRange));
SoundSystem.Play(Filter.Broadcast(), _explosionSound.GetSound(), epicenter);
SoundSystem.Play(Filter.Broadcast(), ExplosionSound.GetSound(), epicenter);
DamageEntitiesInRange(epicenter, boundingBox, devastationRange, heavyImpactRange, maxRange, mapId);
var mapGridsNear = mapManager.FindGridsIntersecting(mapId, boundingBox);
var mapGridsNear = _maps.FindGridsIntersecting(mapId, boundingBox);
foreach (var gridId in mapGridsNear)
{

View File

@@ -3,7 +3,6 @@ using Content.Server.Doors.Components;
using Content.Server.Explosion.Components;
using Content.Server.Flash;
using Content.Server.Flash.Components;
using Content.Shared.Acts;
using Content.Shared.Audio;
using Content.Shared.Doors;
using JetBrains.Annotations;
@@ -14,7 +13,7 @@ using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.Explosion
namespace Content.Server.Explosion.EntitySystems
{
/// <summary>
/// Raised whenever something is Triggered on the entity.
@@ -34,6 +33,7 @@ namespace Content.Server.Explosion
[UsedImplicitly]
public sealed class TriggerSystem : EntitySystem
{
[Dependency] private readonly ExplosionSystem _explosions = default!;
[Dependency] private readonly FlashSystem _flashSystem = default!;
public override void Initialize()
@@ -65,7 +65,7 @@ namespace Content.Server.Explosion
}
component.Exploding = true;
component.Owner.SpawnExplosion(component.DevastationRange, component.HeavyImpactRange, component.LightImpactRange, component.FlashRange);
_explosions.SpawnExplosion(uid, component.DevastationRange, component.HeavyImpactRange, component.LightImpactRange, component.FlashRange);
EntityManager.QueueDeleteEntity(uid);
}
#endregion