explosion minor rework + fix (#21718)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartSlime
|
||||
parent: [BaseItem, PartBase]
|
||||
parent: [BaseItem, BasePart]
|
||||
name: "slime body part"
|
||||
abstract: true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user