explosion minor rework + fix (#21718)

This commit is contained in:
deltanedas
2023-11-19 17:44:42 +00:00
committed by GitHub
parent db76d85f36
commit f25773ffec
10 changed files with 148 additions and 62 deletions

View File

@@ -1,16 +1,11 @@
using System.Linq;
using System.Numerics;
using System.Reflection;
using Content.Server.Explosion.Components;
using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Explosion;
using Content.Shared.Maps;
using Content.Shared.Mind.Components;
using Content.Shared.Physics;
using Content.Shared.Projectiles;
using Robust.Shared.Spawners;
using Content.Shared.Tag;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -55,6 +50,13 @@ public sealed partial class ExplosionSystem
/// </summary>
private int _previousTileIteration;
/// <summary>
/// This list is used when raising <see cref="BeforeExplodeEvent"/> to avoid allocating a new list per event.
/// </summary>
private readonly List<EntityUid> _containedEntities = new();
private readonly List<(EntityUid, DamageSpecifier)> _toDamage = new();
private List<EntityUid> _anchored = new();
private void OnMapChanged(MapChangedEvent ev)
@@ -84,8 +86,6 @@ public sealed partial class ExplosionSystem
Stopwatch.Restart();
var x = Stopwatch.Elapsed.TotalMilliseconds;
var availableTime = MaxProcessingTime;
var tilesRemaining = TilesPerTick;
while (tilesRemaining > 0 && MaxProcessingTime > Stopwatch.Elapsed.TotalMilliseconds)
{
@@ -369,64 +369,73 @@ public sealed partial class ExplosionSystem
return SpaceQueryCallback(ref state, in uid);
}
private DamageSpecifier GetDamage(EntityUid uid,
string id, DamageSpecifier damage)
{
// TODO Explosion Performance
// Cache this? I.e., instead of raising an event, check for a component?
var resistanceEv = new GetExplosionResistanceEvent(id);
RaiseLocalEvent(uid, ref resistanceEv);
resistanceEv.DamageCoefficient = Math.Max(0, resistanceEv.DamageCoefficient);
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (resistanceEv.DamageCoefficient != 1)
damage *= resistanceEv.DamageCoefficient;
return damage;
}
private void GetEntitiesToDamage(EntityUid uid, DamageSpecifier originalDamage, string prototype)
{
_toDamage.Clear();
_toDamage.Add((uid, GetDamage(uid, prototype, originalDamage)));
for (var i = 0; i < _toDamage.Count; i++)
{
var (ent, damage) = _toDamage[i];
_containedEntities.Clear();
var ev = new BeforeExplodeEvent(damage, prototype, _containedEntities);
RaiseLocalEvent(ent, ref ev);
if (_containedEntities.Count == 0)
continue;
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (ev.DamageCoefficient != 1)
damage *= ev.DamageCoefficient;
_toDamage.EnsureCapacity(_toDamage.Count + _containedEntities.Count);
foreach (var contained in _containedEntities)
{
var newDamage = GetDamage(contained, prototype, damage);
_toDamage.Add((contained, newDamage));
}
}
}
/// <summary>
/// This function actually applies the explosion affects to an entity.
/// </summary>
private void ProcessEntity(
EntityUid uid,
MapCoordinates epicenter,
DamageSpecifier? damage,
DamageSpecifier? originalDamage,
float throwForce,
string id,
TransformComponent? xform)
{
// damage
if (damage != null && _damageQuery.TryGetComponent(uid, out var damageable))
if (originalDamage != null)
{
// TODO Explosion Performance
// Cache this? I.e., instead of raising an event, check for a component?
var ev = new GetExplosionResistanceEvent(id);
RaiseLocalEvent(uid, ref ev);
ev.DamageCoefficient = Math.Max(0, ev.DamageCoefficient);
// TODO explosion entity
// 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))
GetEntitiesToDamage(uid, originalDamage, id);
foreach (var (entity, damage) in _toDamage)
{
_adminLogger.Add(LogType.Explosion, LogImpact.Medium,
$"Explosion caused [{damage.Total}] damage to {ToPrettyString(uid):target} at {xform?.Coordinates}");
}
_damageableSystem.TryChangeDamage(uid, damage, ignoreResistances: true, damageable: damageable);
}
// if it's a container, try to damage all its contents
if (_containersQuery.TryGetComponent(uid, out var containers))
{
foreach (var container in containers.Containers.Values)
{
foreach (var ent in container.ContainedEntities)
{
// setting throw force to 0 to prevent offset items inside containers
ProcessEntity(ent, epicenter, damage, 0f, id, _transformQuery.GetComponent(uid));
}
// TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
_damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true);
}
}
// throw
if (xform != null // null implies anchored
if (xform != null // null implies anchored or in a container
&& !xform.Anchored
&& throwForce > 0
&& !EntityManager.IsQueuedForDeletion(uid)
@@ -442,10 +451,6 @@ public sealed partial class ExplosionSystem
_projectileQuery,
throwForce);
}
// TODO EXPLOSION puddle / flammable ignite?
// TODO EXPLOSION deaf/ear damage? other explosion effects?
}
/// <summary>
@@ -845,4 +850,3 @@ sealed class Explosion
_tileUpdateDict.Clear();
}
}

View File

@@ -21,7 +21,6 @@ using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
@@ -53,11 +52,9 @@ public sealed partial class ExplosionSystem : EntitySystem
[Dependency] private readonly SharedMapSystem _map = default!;
private EntityQuery<TransformComponent> _transformQuery;
private EntityQuery<ContainerManagerComponent> _containersQuery;
private EntityQuery<DamageableComponent> _damageQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
private EntityQuery<MindComponent> _mindQuery;
/// <summary>
/// "Tile-size" for space when there are no nearby grids to use as a reference.
@@ -107,11 +104,9 @@ public sealed partial class ExplosionSystem : EntitySystem
InitVisuals();
_transformQuery = GetEntityQuery<TransformComponent>();
_containersQuery = GetEntityQuery<ContainerManagerComponent>();
_damageQuery = GetEntityQuery<DamageableComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
_mindQuery = GetEntityQuery<MindComponent>();
}
private void OnReset(RoundRestartCleanupEvent ev)

View File

@@ -8,6 +8,7 @@ using Content.Server.Stunnable;
using Content.Shared.ActionBlocker;
using Content.Shared.Body.Part;
using Content.Shared.CombatMode;
using Content.Shared.Explosion;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
@@ -54,6 +55,8 @@ namespace Content.Server.Hands.Systems
SubscribeLocalEvent<HandsComponent, ComponentGetState>(GetComponentState);
SubscribeLocalEvent<HandsComponent, BeforeExplodeEvent>(OnExploded);
CommandBinds.Builder
.Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
.Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack))
@@ -73,6 +76,15 @@ namespace Content.Server.Hands.Systems
args.State = new HandsComponentState(hands);
}
private void OnExploded(Entity<HandsComponent> ent, ref BeforeExplodeEvent args)
{
foreach (var hand in ent.Comp.Hands.Values)
{
if (hand.HeldEntity is {} uid)
args.Contents.Add(uid);
}
}
private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent args)
{
if (args.Handled)

View File

@@ -1,5 +1,6 @@
using Content.Server.Storage.EntitySystems;
using Content.Shared.Clothing.Components;
using Content.Shared.Explosion;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
@@ -15,11 +16,26 @@ namespace Content.Server.Inventory
{
base.Initialize();
SubscribeLocalEvent<InventoryComponent, BeforeExplodeEvent>(OnExploded);
SubscribeLocalEvent<ClothingComponent, UseInHandEvent>(OnUseInHand);
SubscribeNetworkEvent<OpenSlotStorageNetworkMessage>(OnOpenSlotStorage);
}
private void OnExploded(Entity<InventoryComponent> ent, ref BeforeExplodeEvent args)
{
if (!TryGetContainerSlotEnumerator(ent, out var slots, ent.Comp))
return;
// explode each item in their inventory too
while (slots.MoveNext(out var slot))
{
if (slot.ContainedEntity != null)
args.Contents.Add(slot.ContainedEntity.Value);
}
}
private void OnUseInHand(EntityUid uid, ClothingComponent component, UseInHandEvent args)
{
if (args.Handled || !component.QuickEquip)

View File

@@ -4,6 +4,7 @@ using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Storage.Components;
using Content.Shared.Destructible;
using Content.Shared.Explosion;
using Content.Shared.Foldable;
using Content.Shared.Interaction;
using Content.Shared.Lock;
@@ -46,6 +47,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
SubscribeLocalEvent<EntityStorageComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<EntityStorageComponent, WeldableAttemptEvent>(OnWeldableAttempt);
SubscribeLocalEvent<EntityStorageComponent, BeforeExplodeEvent>(OnExploded);
SubscribeLocalEvent<InsideEntityStorageComponent, InhaleLocationEvent>(OnInsideInhale);
SubscribeLocalEvent<InsideEntityStorageComponent, ExhaleLocationEvent>(OnInsideExhale);
@@ -98,6 +100,15 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
}
}
private void OnExploded(Entity<EntityStorageComponent> ent, ref BeforeExplodeEvent args)
{
if (ent.Comp.ExplosionDamageCoefficient <= 0)
return;
args.Contents.AddRange(ent.Comp.Contents.ContainedEntities);
args.DamageCoefficient *= ent.Comp.ExplosionDamageCoefficient;
}
protected override void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
{
if (!component.Airtight)

View File

@@ -1,5 +1,6 @@
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.Explosion;
using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Lock;
@@ -27,6 +28,7 @@ public sealed partial class StorageSystem : SharedStorageSystem
base.Initialize();
SubscribeLocalEvent<StorageComponent, GetVerbsEvent<ActivationVerb>>(AddUiVerb);
SubscribeLocalEvent<StorageComponent, BoundUIClosedEvent>(OnBoundUIClosed);
SubscribeLocalEvent<StorageComponent, BeforeExplodeEvent>(OnExploded);
SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
}
@@ -97,6 +99,11 @@ public sealed partial class StorageSystem : SharedStorageSystem
}
}
private void OnExploded(Entity<StorageComponent> ent, ref BeforeExplodeEvent args)
{
args.Contents.AddRange(ent.Comp.Container.ContainedEntities);
}
/// <summary>
/// Opens the storage UI for an entity
/// </summary>

View File

@@ -1,7 +1,9 @@
using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Radiation.Events;
@@ -16,12 +18,14 @@ namespace Content.Shared.Damage
public sealed class DamageableSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<DamageableComponent> _damageableQuery;
private EntityQuery<MindContainerComponent> _mindContainerQuery;
public override void Initialize()
{
@@ -33,6 +37,7 @@ namespace Content.Shared.Damage
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
_damageableQuery = GetEntityQuery<DamageableComponent>();
_mindContainerQuery = GetEntityQuery<MindContainerComponent>();
}
/// <summary>

View File

@@ -1,6 +1,5 @@
using Content.Shared.Damage;
using Content.Shared.Inventory;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
namespace Content.Shared.Explosion;
@@ -20,3 +19,34 @@ public record struct GetExplosionResistanceEvent(string ExplosionPrototype) : II
SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET;
}
/// <summary>
/// This event is raised directed at an entity that is about to receive damage from an explosion. It can be used to
/// recursively add contained/child entities that should also receive damage. E.g., entities in a player's inventory
/// or backpack. This event will be raised recursively so a matchbox in a backpack in a player's inventory
/// will also receive this event.
/// </summary>
[ByRefEvent]
public record struct BeforeExplodeEvent(DamageSpecifier Damage, string Id, List<EntityUid> Contents)
{
/// <summary>
/// The damage that will be received by this entity. Note that the entity's explosion resistance has already been
/// used to modify this damage.
/// </summary>
public readonly DamageSpecifier Damage = Damage;
/// <summary>
/// ID of the explosion prototype.
/// </summary>
public readonly string Id = Id;
/// <summary>
/// Damage multiplier for modifying the damage that will get dealt to contained entities.
/// </summary>
public float DamageCoefficient = 1;
/// <summary>
/// Contained/child entities that should receive recursive explosion damage.
/// </summary>
public readonly List<EntityUid> Contents = Contents;
}

View File

@@ -124,6 +124,12 @@ public abstract partial class SharedEntityStorageComponent : Component
/// </summary>
[ViewVariables]
public Container Contents = default!;
/// <summary>
/// Multiplier for explosion damage that gets applied to contained entities.
/// </summary>
[DataField]
public float ExplosionDamageCoefficient = 1;
}
[Serializable, NetSerializable]

View File

@@ -1,7 +1,7 @@
# TODO BODY: Part damage
- type: entity
id: PartSlime
parent: [BaseItem, PartBase]
parent: [BaseItem, BasePart]
name: "slime body part"
abstract: true