Small ExplosionSystem Cleanup (#20817)

This commit is contained in:
Leon Friedrich
2023-10-15 03:48:25 +11:00
committed by GitHub
parent 0775ab6a14
commit ce76a03d5e
2 changed files with 85 additions and 81 deletions

View File

@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection;
using Content.Server.Explosion.Components; using Content.Server.Explosion.Components;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -18,11 +19,12 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
namespace Content.Server.Explosion.EntitySystems; namespace Content.Server.Explosion.EntitySystems;
public sealed partial class ExplosionSystem : EntitySystem public sealed partial class ExplosionSystem
{ {
/// <summary> /// <summary>
/// Used to limit explosion processing time. See <see cref="MaxProcessingTime"/>. /// Used to limit explosion processing time. See <see cref="MaxProcessingTime"/>.
@@ -176,12 +178,12 @@ public sealed partial class ExplosionSystem : EntitySystem
/// Used for a variation of <see cref="TurfHelpers.IsBlockedTurf()"/> that makes use of the fact that we have /// Used for a variation of <see cref="TurfHelpers.IsBlockedTurf()"/> that makes use of the fact that we have
/// already done an entity lookup on a tile, and don't need to do so again. /// already done an entity lookup on a tile, and don't need to do so again.
/// </remarks> /// </remarks>
public bool IsBlockingTurf(EntityUid uid, EntityQuery<PhysicsComponent> physicsQuery) public bool IsBlockingTurf(EntityUid uid)
{ {
if (EntityManager.IsQueuedForDeletion(uid)) if (EntityManager.IsQueuedForDeletion(uid))
return false; return false;
if (!physicsQuery.TryGetComponent(uid, out var physics)) if (!_physicsQuery.TryGetComponent(uid, out var physics))
return false; return false;
return physics.CanCollide && physics.Hard && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0; return physics.CanCollide && physics.Hard && (physics.CollisionLayer & (int) CollisionGroup.Impassable) != 0;
@@ -198,19 +200,14 @@ public sealed partial class ExplosionSystem : EntitySystem
DamageSpecifier damage, DamageSpecifier damage,
MapCoordinates epicenter, MapCoordinates epicenter,
HashSet<EntityUid> processed, HashSet<EntityUid> processed,
string id, string id)
EntityQuery<TransformComponent> xformQuery,
EntityQuery<DamageableComponent> damageQuery,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TagComponent> tagQuery,
EntityQuery<ProjectileComponent> projectileQuery)
{ {
var gridBox = new Box2(tile * grid.TileSize, (tile + 1) * grid.TileSize); var gridBox = new Box2(tile * grid.TileSize, (tile + 1) * grid.TileSize);
// get the entities on a tile. Note that we cannot process them directly, or we get // get the entities on a tile. Note that we cannot process them directly, or we get
// enumerator-changed-while-enumerating errors. // enumerator-changed-while-enumerating errors.
List<TransformComponent> list = new(); List<(EntityUid, TransformComponent)> list = new();
var state = (list, processed, xformQuery); var state = (list, processed, _transformQuery);
// get entities: // get entities:
lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
@@ -219,9 +216,9 @@ public sealed partial class ExplosionSystem : EntitySystem
lookup.StaticSundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); lookup.StaticSundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
// process those entities // process those entities
foreach (var xform in list) foreach (var (uid, xform) in list)
{ {
ProcessEntity(xform.Owner, epicenter, damage, throwForce, id, xform, damageQuery, physicsQuery, xformQuery, tagQuery, projectileQuery); ProcessEntity(uid, epicenter, damage, throwForce, id, xform);
} }
// process anchored entities // process anchored entities
@@ -230,7 +227,7 @@ public sealed partial class ExplosionSystem : EntitySystem
foreach (var entity in anchoredList) foreach (var entity in anchoredList)
{ {
processed.Add(entity); processed.Add(entity);
ProcessEntity(entity, epicenter, damage, throwForce, id, null, damageQuery, physicsQuery, xformQuery, tagQuery, projectileQuery); ProcessEntity(entity, epicenter, damage, throwForce, id, null);
} }
// Walls and reinforced walls will break into girders. These girders will also be considered turf-blocking for // Walls and reinforced walls will break into girders. These girders will also be considered turf-blocking for
@@ -241,7 +238,7 @@ public sealed partial class ExplosionSystem : EntitySystem
{ {
foreach (var entity in grid.GetAnchoredEntities(tile)) foreach (var entity in grid.GetAnchoredEntities(tile))
{ {
tileBlocked |= IsBlockingTurf(entity, physicsQuery); tileBlocked |= IsBlockingTurf(entity);
} }
} }
@@ -260,28 +257,28 @@ public sealed partial class ExplosionSystem : EntitySystem
lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
lookup.SundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); lookup.SundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true);
foreach (var xform in list) foreach (var (uid, xform) in list)
{ {
// Here we only throw, no dealing damage. Containers n such might drop their entities after being destroyed, but // Here we only throw, no dealing damage. Containers n such might drop their entities after being destroyed, but
// they should handle their own damage pass-through, with their own damage reduction calculation. // they should handle their own damage pass-through, with their own damage reduction calculation.
ProcessEntity(xform.Owner, epicenter, null, throwForce, id, xform, damageQuery, physicsQuery, xformQuery, tagQuery, projectileQuery); ProcessEntity(uid, epicenter, null, throwForce, id, xform);
} }
return !tileBlocked; return !tileBlocked;
} }
private bool GridQueryCallback( private bool GridQueryCallback(
ref (List<TransformComponent> List, HashSet<EntityUid> Processed, EntityQuery<TransformComponent> XformQuery) state, ref (List<(EntityUid, TransformComponent)> List, HashSet<EntityUid> Processed, EntityQuery<TransformComponent> XformQuery) state,
in EntityUid uid) in EntityUid uid)
{ {
if (state.Processed.Add(uid) && state.XformQuery.TryGetComponent(uid, out var xform)) if (state.Processed.Add(uid) && state.XformQuery.TryGetComponent(uid, out var xform))
state.List.Add(xform); state.List.Add((uid, xform));
return true; return true;
} }
private bool GridQueryCallback( private bool GridQueryCallback(
ref (List<TransformComponent> List, HashSet<EntityUid> Processed, EntityQuery<TransformComponent> XformQuery) state, ref (List<(EntityUid, TransformComponent)> List, HashSet<EntityUid> Processed, EntityQuery<TransformComponent> XformQuery) state,
in FixtureProxy proxy) in FixtureProxy proxy)
{ {
var owner = proxy.Entity; var owner = proxy.Entity;
@@ -299,17 +296,12 @@ public sealed partial class ExplosionSystem : EntitySystem
DamageSpecifier damage, DamageSpecifier damage,
MapCoordinates epicenter, MapCoordinates epicenter,
HashSet<EntityUid> processed, HashSet<EntityUid> processed,
string id, string id)
EntityQuery<TransformComponent> xformQuery,
EntityQuery<DamageableComponent> damageQuery,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TagComponent> tagQuery,
EntityQuery<ProjectileComponent> projectileQuery)
{ {
var gridBox = Box2.FromDimensions(tile * DefaultTileSize, new Vector2(DefaultTileSize, DefaultTileSize)); var gridBox = Box2.FromDimensions(tile * DefaultTileSize, new Vector2(DefaultTileSize, DefaultTileSize));
var worldBox = spaceMatrix.TransformBox(gridBox); var worldBox = spaceMatrix.TransformBox(gridBox);
var list = new List<TransformComponent>(); var list = new List<(EntityUid, TransformComponent)>();
var state = (list, processed, invSpaceMatrix, lookup.Owner, xformQuery, gridBox); var state = (list, processed, invSpaceMatrix, lookup.Owner, _transformQuery, gridBox);
// get entities: // get entities:
lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
@@ -317,10 +309,10 @@ public sealed partial class ExplosionSystem : EntitySystem
lookup.SundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); lookup.SundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.StaticSundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); lookup.StaticSundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
foreach (var xform in state.Item1) foreach (var (uid, xform) in state.Item1)
{ {
processed.Add(xform.Owner); processed.Add(uid);
ProcessEntity(xform.Owner, epicenter, damage, throwForce, id, xform, damageQuery, physicsQuery, xformQuery, tagQuery, projectileQuery); ProcessEntity(uid, epicenter, damage, throwForce, id, xform);
} }
if (throwForce <= 0) if (throwForce <= 0)
@@ -332,14 +324,14 @@ public sealed partial class ExplosionSystem : EntitySystem
lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); lookup.DynamicTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
lookup.SundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true); lookup.SundriesTree.QueryAabb(ref state, SpaceQueryCallback, worldBox, true);
foreach (var xform in list) foreach (var (uid, xform) in list)
{ {
ProcessEntity(xform.Owner, epicenter, null, throwForce, id, xform, damageQuery, physicsQuery, xformQuery, tagQuery, projectileQuery); ProcessEntity(uid, epicenter, null, throwForce, id, xform);
} }
} }
private bool SpaceQueryCallback( private bool SpaceQueryCallback(
ref (List<TransformComponent> List, HashSet<EntityUid> Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery<TransformComponent> XformQuery, Box2 GridBox) state, ref (List<(EntityUid, TransformComponent)> List, HashSet<EntityUid> Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery<TransformComponent> XformQuery, Box2 GridBox) state,
in EntityUid uid) in EntityUid uid)
{ {
if (state.Processed.Contains(uid)) if (state.Processed.Contains(uid))
@@ -351,20 +343,20 @@ public sealed partial class ExplosionSystem : EntitySystem
{ {
// parented directly to the map, use local position // parented directly to the map, use local position
if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(xform.LocalPosition))) if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(xform.LocalPosition)))
state.List.Add(xform); state.List.Add((uid, xform));
return true; return true;
} }
// finally check if it intersects our tile // finally check if it intersects our tile
if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(_transformSystem.GetWorldPosition(xform, state.XformQuery)))) if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(_transformSystem.GetWorldPosition(xform, state.XformQuery))))
state.List.Add(xform); state.List.Add((uid, xform));
return true; return true;
} }
private bool SpaceQueryCallback( private bool SpaceQueryCallback(
ref (List<TransformComponent> List, HashSet<EntityUid> Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery<TransformComponent> XformQuery, Box2 GridBox) state, ref (List<(EntityUid, TransformComponent)> List, HashSet<EntityUid> Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery<TransformComponent> XformQuery, Box2 GridBox) state,
in FixtureProxy proxy) in FixtureProxy proxy)
{ {
var uid = proxy.Entity; var uid = proxy.Entity;
@@ -380,44 +372,38 @@ public sealed partial class ExplosionSystem : EntitySystem
DamageSpecifier? damage, DamageSpecifier? damage,
float throwForce, float throwForce,
string id, string id,
TransformComponent? xform, TransformComponent? xform)
EntityQuery<DamageableComponent> damageQuery,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> transformQuery,
EntityQuery<TagComponent> tagQuery,
EntityQuery<ProjectileComponent> projectileQuery)
{ {
// damage // damage
if (damage != null && damageQuery.TryGetComponent(uid, out var damageable)) if (damage != null && _damageQuery.TryGetComponent(uid, out var damageable))
{ {
// TODO Explosion Performance
// Cache this? I.e., instead of raising an event, check for a component?
var ev = new GetExplosionResistanceEvent(id); var ev = new GetExplosionResistanceEvent(id);
RaiseLocalEvent(uid, ref ev, false); RaiseLocalEvent(uid, ref ev);
ev.DamageCoefficient = Math.Max(0, ev.DamageCoefficient); ev.DamageCoefficient = Math.Max(0, ev.DamageCoefficient);
//todo need a way to track origin of explosion // TODO explosion entity
if (ev.DamageCoefficient == 1) // Move explosion data into the existing explosion visuals entity
// Give each explosion a unique name, include in admin logs.
// TODO Explosion Performance
// This creates a new dictionary. Maybe we should just re-use a private local damage specifier and update it.
// Though most entities shouldn't have explosion resistance, so maybe its fine.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (ev.DamageCoefficient != 1)
damage *= ev.DamageCoefficient;
// Log damage to players. Damage is logged before dealing damage so that the position can be logged before
// the entity gets deleted.
if (_mindQuery.HasComponent(uid))
{ {
// no damage-dict multiplication required. _adminLogger.Add(LogType.Explosion, LogImpact.Medium,
_damageableSystem.TryChangeDamage(uid, damage, ignoreResistances: true, damageable: damageable); $"Explosion caused [{damage.Total}] damage to {ToPrettyString(uid):target} at {xform?.Coordinates}");
if (HasComp<MindContainerComponent>(uid) || HasComp<ExplosiveComponent>(uid))
{
var damageStr = string.Join(", ", damage.DamageDict.Select(entry => $"{entry.Key}: {entry.Value}"));
_adminLogger.Add(LogType.Explosion, LogImpact.Medium,
$"Explosion caused [{damageStr}] to {ToPrettyString(uid):target} at {Transform(uid).Coordinates}");
}
}
else
{
var appliedDamage = damage * ev.DamageCoefficient;
_damageableSystem.TryChangeDamage(uid, appliedDamage, ignoreResistances: true, damageable: damageable);
if (HasComp<MindContainerComponent>(uid) || HasComp<ExplosiveComponent>(uid))
{
var damageStr = string.Join(", ", appliedDamage.DamageDict.Select(entry => $"{entry.Key}: {entry.Value}"));
_adminLogger.Add(LogType.Explosion, LogImpact.Medium,
$"Explosion caused [{damageStr}] to {ToPrettyString(uid):target} at {Transform(uid).Coordinates}");
}
} }
_damageableSystem.TryChangeDamage(uid, damage, ignoreResistances: true, damageable: damageable);
} }
// throw // throw
@@ -425,16 +411,16 @@ public sealed partial class ExplosionSystem : EntitySystem
&& !xform.Anchored && !xform.Anchored
&& throwForce > 0 && throwForce > 0
&& !EntityManager.IsQueuedForDeletion(uid) && !EntityManager.IsQueuedForDeletion(uid)
&& physicsQuery.TryGetComponent(uid, out var physics) && _physicsQuery.TryGetComponent(uid, out var physics)
&& physics.BodyType == BodyType.Dynamic) && physics.BodyType == BodyType.Dynamic)
{ {
var pos = _transformSystem.GetWorldPosition(xform, transformQuery); var pos = _transformSystem.GetWorldPosition(xform);
_throwingSystem.TryThrow( _throwingSystem.TryThrow(
uid, uid,
pos - epicenter.Position, pos - epicenter.Position,
physics, physics,
xform, xform,
projectileQuery, _projectileQuery,
throwForce); throwForce);
} }
@@ -564,6 +550,9 @@ sealed class Explosion
// Variables used for enumerating over tiles, grids, etc // Variables used for enumerating over tiles, grids, etc
private DamageSpecifier _currentDamage = default!; private DamageSpecifier _currentDamage = default!;
#if DEBUG
private DamageSpecifier? _expectedDamage;
#endif
private BroadphaseComponent _currentLookup = default!; private BroadphaseComponent _currentLookup = default!;
private MapGridComponent? _currentGrid; private MapGridComponent? _currentGrid;
private float _currentIntensity; private float _currentIntensity;
@@ -683,6 +672,16 @@ sealed class Explosion
while (CurrentIteration < _tileSetIntensity.Count) while (CurrentIteration < _tileSetIntensity.Count)
{ {
_currentIntensity = _tileSetIntensity[CurrentIteration]; _currentIntensity = _tileSetIntensity[CurrentIteration];
#if DEBUG
if (_expectedDamage != null)
{
// Check that explosion processing hasn't somehow accidentally mutated the damage set.
DebugTools.Assert(_expectedDamage.Equals(_currentDamage));
_expectedDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
}
#endif
_currentDamage = ExplosionType.DamagePerIntensity * _currentIntensity; _currentDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
// only throw if either the explosion is small, or if this is the outer ring of a large explosion. // only throw if either the explosion is small, or if this is the outer ring of a large explosion.
@@ -780,12 +779,7 @@ sealed class Explosion
_currentDamage, _currentDamage,
Epicenter, Epicenter,
ProcessedEntities, ProcessedEntities,
ExplosionType.ID, ExplosionType.ID);
_xformQuery,
_damageQuery,
_physicsQuery,
_tagQuery,
_projectileQuery);
// If the floor is not blocked by some dense object, damage the floor tiles. // If the floor is not blocked by some dense object, damage the floor tiles.
if (canDamageFloor) if (canDamageFloor)
@@ -802,12 +796,7 @@ sealed class Explosion
_currentDamage, _currentDamage,
Epicenter, Epicenter,
ProcessedEntities, ProcessedEntities,
ExplosionType.ID, ExplosionType.ID);
_xformQuery,
_damageQuery,
_physicsQuery,
_tagQuery,
_projectileQuery);
} }
if (!MoveNext()) if (!MoveNext())

View File

@@ -14,12 +14,15 @@ using Content.Shared.Database;
using Content.Shared.Explosion; using Content.Shared.Explosion;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.Projectiles;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Robust.Server.GameStates; using Robust.Server.GameStates;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
@@ -47,6 +50,12 @@ public sealed partial class ExplosionSystem : EntitySystem
[Dependency] private readonly PvsOverrideSystem _pvsSys = default!; [Dependency] private readonly PvsOverrideSystem _pvsSys = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
private EntityQuery<TransformComponent> _transformQuery;
private EntityQuery<DamageableComponent> _damageQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
private EntityQuery<MindComponent> _mindQuery;
/// <summary> /// <summary>
/// "Tile-size" for space when there are no nearby grids to use as a reference. /// "Tile-size" for space when there are no nearby grids to use as a reference.
/// </summary> /// </summary>
@@ -93,6 +102,12 @@ public sealed partial class ExplosionSystem : EntitySystem
SubscribeCvars(); SubscribeCvars();
InitAirtightMap(); InitAirtightMap();
InitVisuals(); InitVisuals();
_transformQuery = GetEntityQuery<TransformComponent>();
_damageQuery = GetEntityQuery<DamageableComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
_mindQuery = GetEntityQuery<MindComponent>();
} }
private void OnReset(RoundRestartCleanupEvent ev) private void OnReset(RoundRestartCleanupEvent ev)