public void InvalidatePosition(Entity<MapGridComponent?> grid, Vector2i pos)
{
var query = GetEntityQuery<AirtightComponent>();
- _explosionSystem.UpdateAirtightMap(grid, pos, grid, query);
+ _explosionSystem.UpdateAirtightMap(grid, pos, grid);
_atmosphereSystem.InvalidateTile(grid.Owner, pos);
}
public sealed partial class ExplosionSystem
{
- [Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
-
private readonly Dictionary<string, int> _explosionTypes = new();
private void InitAirtightMap()
int index = 0;
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ExplosionPrototype>())
{
+ // TODO EXPLOSION
+ // just make this a field on the prototype
_explosionTypes.Add(prototype.ID, index);
index++;
}
// indices to this tile-data struct.
private Dictionary<EntityUid, Dictionary<Vector2i, TileData>> _airtightMap = new();
- public void UpdateAirtightMap(EntityUid gridId, Vector2i tile, MapGridComponent? grid = null, EntityQuery<AirtightComponent>? query = null)
+ public void UpdateAirtightMap(EntityUid gridId, Vector2i tile, MapGridComponent? grid = null)
{
if (Resolve(gridId, ref grid, false))
- UpdateAirtightMap(gridId, grid, tile, query);
+ UpdateAirtightMap(gridId, grid, tile);
}
/// <summary>
/// something like a normal and a reinforced windoor on the same tile. But given that this is a pretty rare
/// occurrence, I am fine with this.
/// </remarks>
- public void UpdateAirtightMap(EntityUid gridId, MapGridComponent grid, Vector2i tile, EntityQuery<AirtightComponent>? query = null)
+ public void UpdateAirtightMap(EntityUid gridId, MapGridComponent grid, Vector2i tile)
{
var tolerance = new float[_explosionTypes.Count];
var blockedDirections = AtmosDirection.Invalid;
if (!_airtightMap.ContainsKey(gridId))
_airtightMap[gridId] = new();
- query ??= GetEntityQuery<AirtightComponent>();
- var damageQuery = GetEntityQuery<DamageableComponent>();
- var destructibleQuery = GetEntityQuery<DestructibleComponent>();
- var anchoredEnumerator = _mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, tile);
+ var anchoredEnumerator = _map.GetAnchoredEntitiesEnumerator(gridId, grid, tile);
while (anchoredEnumerator.MoveNext(out var uid))
{
- if (!query.Value.TryGetComponent(uid, out var airtight) || !airtight.AirBlocked)
+ if (!_airtightQuery.TryGetComponent(uid, out var airtight) || !airtight.AirBlocked)
continue;
blockedDirections |= airtight.AirBlockedDirection;
- var entityTolerances = GetExplosionTolerance(uid.Value, damageQuery, destructibleQuery);
+ var entityTolerances = GetExplosionTolerance(uid.Value);
for (var i = 0; i < tolerance.Length; i++)
{
tolerance[i] = Math.Max(tolerance[i], entityTolerances[i]);
if (!TryComp<MapGridComponent>(transform.GridUid, out var grid))
return;
- UpdateAirtightMap(transform.GridUid.Value, grid, _mapSystem.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates));
+ UpdateAirtightMap(transform.GridUid.Value, grid, _map.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates));
}
/// <summary>
/// Return a dictionary that specifies how intense a given explosion type needs to be in order to destroy an entity.
/// </summary>
- public float[] GetExplosionTolerance(
- EntityUid uid,
- EntityQuery<DamageableComponent> damageQuery,
- EntityQuery<DestructibleComponent> destructibleQuery)
+ public float[] GetExplosionTolerance(EntityUid uid)
{
// How much total damage is needed to destroy this entity? This also includes "break" behaviors. This ASSUMES
// that this will result in a non-airtight entity.Entities that ONLY break via construction graph node changes
// are currently effectively "invincible" as far as this is concerned. This really should be done more rigorously.
var totalDamageTarget = FixedPoint2.MaxValue;
- if (destructibleQuery.TryGetComponent(uid, out var destructible))
+ if (_destructibleQuery.TryGetComponent(uid, out var destructible))
{
totalDamageTarget = _destructibleSystem.DestroyedAt(uid, destructible);
}
var explosionTolerance = new float[_explosionTypes.Count];
- if (totalDamageTarget == FixedPoint2.MaxValue || !damageQuery.TryGetComponent(uid, out var damageable))
+ if (totalDamageTarget == FixedPoint2.MaxValue || !_damageableQuery.TryGetComponent(uid, out var damageable))
{
for (var i = 0; i < explosionTolerance.Length; i++)
{
// does not support entities dynamically changing explosive resistances (e.g. via clothing). But these probably
// shouldn't be airtight structures anyways....
+ var mod = _damageableSystem.UniversalAllDamageModifier * _damageableSystem.UniversalExplosionDamageModifier;
foreach (var (id, index) in _explosionTypes)
{
- if (!_prototypeManager.TryIndex<ExplosionPrototype>(id, out var explosionType))
+ // TODO EXPLOSION SYSTEM
+ // cache explosion type damage.
+ if (!_prototypeManager.Resolve(id, out ExplosionPrototype? explosionType))
continue;
// evaluate the damage that this damage type would do to this entity
if (!damageable.Damage.DamageDict.ContainsKey(type))
continue;
+ // TODO EXPLOSION SYSTEM
+ // add a variant of the event that gets raised once, instead of once per prototype.
+ // Or better yet, just calculate this manually w/o the event.
+ // The event mainly exists for indirect resistances via things like inventory & clothing
+ // But this shouldn't matter for airtight entities.
var ev = new GetExplosionResistanceEvent(explosionType.ID);
RaiseLocalEvent(uid, ref ev);
- damagePerIntensity += value * Math.Max(0, ev.DamageCoefficient);
+ damagePerIntensity += value * mod * Math.Max(0, ev.DamageCoefficient);
}
explosionTolerance[index] = damagePerIntensity > 0
public float[] ExplosionTolerance;
public AtmosDirection BlockedDirections = AtmosDirection.Invalid;
}
+
+ public override void ReloadMap()
+ {
+ foreach (var(grid, dict) in _airtightMap)
+ {
+ var comp = Comp<MapGridComponent>(grid);
+ foreach (var index in dict.Keys)
+ {
+ UpdateAirtightMap(grid, comp, index);
+ }
+ }
+ }
}
{
var neighbourIndex = change.GridIndices + NeighbourVectors[i];
- if (_mapSystem.TryGetTileRef(ev.Entity, grid, neighbourIndex, out var neighbourTile) && !neighbourTile.Tile.IsEmpty)
+ if (_map.TryGetTileRef(ev.Entity, grid, neighbourIndex, out var neighbourTile) && !neighbourTile.Tile.IsEmpty)
{
var oppositeDirection = (NeighborFlag)(1 << ((i + 4) % 8));
edges[neighbourIndex] = edges.GetValueOrDefault(neighbourIndex) | oppositeDirection;
spaceDirections = NeighborFlag.Invalid;
for (var i = 0; i < NeighbourVectors.Length; i++)
{
- if (!_mapSystem.TryGetTileRef(grid, grid.Comp, index + NeighbourVectors[i], out var neighborTile) || neighborTile.Tile.IsEmpty)
+ if (!_map.TryGetTileRef(grid, grid.Comp, index + NeighbourVectors[i], out var neighborTile) || neighborTile.Tile.IsEmpty)
spaceDirections |= (NeighborFlag) (1 << i);
}
public sealed partial class ExplosionSystem
{
- [Dependency] private readonly FlammableSystem _flammableSystem = default!;
-
/// <summary>
/// Used to limit explosion processing time. See <see cref="MaxProcessingTime"/>.
/// </summary>
GetEntitiesToDamage(uid, originalDamage, id);
foreach (var (entity, damage) in _toDamage)
{
- if (damage.GetTotal() > 0 && TryComp<ActorComponent>(entity, out var actorComponent))
+ if (_actorQuery.HasComp(entity))
{
// Log damage to player entities only, cause this will create a massive amount of log spam otherwise.
if (cause != null)
}
// TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
- _damageableSystem.TryChangeDamage(entity, damage * _damageableSystem.UniversalExplosionDamageModifier, ignoreResistances: true);
+ _damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true, ignoreGlobalModifiers: true);
}
}
private readonly IEntityManager _entMan;
private readonly ExplosionSystem _system;
private readonly SharedMapSystem _mapSystem;
+ private readonly DamageableSystem _damageable;
public readonly EntityUid VisualEnt;
int maxTileBreak,
bool canCreateVacuum,
IEntityManager entMan,
- IMapManager mapMan,
EntityUid visualEnt,
EntityUid? cause,
- SharedMapSystem mapSystem)
+ SharedMapSystem mapSystem,
+ DamageableSystem damageable)
{
VisualEnt = visualEnt;
Cause = cause;
_maxTileBreak = maxTileBreak;
_canCreateVacuum = canCreateVacuum;
_entMan = entMan;
+ _damageable = damageable;
_xformQuery = entMan.GetEntityQuery<TransformComponent>();
_physicsQuery = entMan.GetEntityQuery<PhysicsComponent>();
_expectedDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
}
#endif
-
- _currentDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
+ var modifier = _currentIntensity
+ * _damageable.UniversalExplosionDamageModifier
+ * _damageable.UniversalAllDamageModifier;
+ _currentDamage = ExplosionType.DamagePerIntensity * modifier;
// only throw if either the explosion is small, or if this is the outer ring of a large explosion.
var doThrow = Area < _system.ThrowLimit || CurrentIteration > _tileSetIntensity.Count - 6;
// get the epicenter tile indices
if (_mapManager.TryFindGridAt(epicenter, out var gridUid, out var candidateGrid) &&
- _mapSystem.TryGetTileRef(gridUid, candidateGrid, _mapSystem.WorldToTile(gridUid, candidateGrid, epicenter.Position), out var tileRef) &&
+ _map.TryGetTileRef(gridUid, candidateGrid, _map.WorldToTile(gridUid, candidateGrid, epicenter.Position), out var tileRef) &&
!tileRef.Tile.IsEmpty)
{
epicentreGrid = gridUid;
{
// reference grid defines coordinate system that the explosion in space will use
var gridComp = Comp<MapGridComponent>(referenceGrid.Value);
- initialTile = _mapSystem.WorldToTile(referenceGrid.Value, gridComp, epicenter.Position);
+ initialTile = _map.WorldToTile(referenceGrid.Value, gridComp, epicenter.Position);
}
else
{
using System.Numerics;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Destructible;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Atmos.Components;
using Content.Shared.Inventory;
using Content.Shared.Projectiles;
using Content.Shared.Throwing;
-using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Audio.Systems;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
[Dependency] private readonly SharedCameraRecoilSystem _recoilSystem = default!;
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly PvsOverrideSystem _pvsSys = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
+ [Dependency] private readonly FlammableSystem _flammableSystem = default!;
+ [Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
private EntityQuery<FlammableComponent> _flammableQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
+ private EntityQuery<ActorComponent> _actorQuery;
+ private EntityQuery<DestructibleComponent> _destructibleQuery;
+ private EntityQuery<DamageableComponent> _damageableQuery;
+ private EntityQuery<AirtightComponent> _airtightQuery;
/// <summary>
/// "Tile-size" for space when there are no nearby grids to use as a reference.
_flammableQuery = GetEntityQuery<FlammableComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
+ _actorQuery = GetEntityQuery<ActorComponent>();
+ _destructibleQuery = GetEntityQuery<DestructibleComponent>();
+ _damageableQuery = GetEntityQuery<DamageableComponent>();
+ _airtightQuery = GetEntityQuery<AirtightComponent>();
}
private void OnReset(RoundRestartCleanupEvent ev)
private Explosion? SpawnExplosion(QueuedExplosion queued)
{
var pos = queued.Epicenter;
- if (!_mapSystem.MapExists(pos.MapId))
+ if (!_map.MapExists(pos.MapId))
return null;
var results = GetExplosionTiles(pos, queued.Proto.ID, queued.TotalIntensity, queued.Slope, queued.MaxTileIntensity);
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
//For whatever bloody reason, sound system requires ENTITY coordinates.
- var mapEntityCoords = _transformSystem.ToCoordinates(_mapSystem.GetMap(pos.MapId), pos);
+ var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
// play sound.
// for the normal audio, we want everyone in pvs range
queued.MaxTileBreak,
queued.CanCreateVacuum,
EntityManager,
- _mapManager,
visualEnt,
queued.Cause,
- _map);
+ _map,
+ _damageableSystem);
}
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)
/// <remarks>
/// If this data-field is specified, this allows damageable components to be initialized with non-zero damage.
/// </remarks>
- [DataField(readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier
+ [DataField(readOnly: true)] // TODO FULL GAME SAVE
public DamageSpecifier Damage = new();
/// <summary>
[DataDefinition, Serializable, NetSerializable]
public sealed partial class DamageSpecifier : IEquatable<DamageSpecifier>
{
+ // For the record I regret so many of the decisions i made when rewriting damageable
+ // Why is it just shitting out dictionaries left and right
+ // One day Arrays, stackalloc spans, and SIMD will save the day.
+ // TODO DAMAGEABLE REFACTOR
+
// These exist solely so the wiki works. Please do not touch them or use them.
[JsonPropertyName("types")]
[DataField("types", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, DamageTypePrototype>))]
using Content.Shared.CCVar;
using Content.Shared.Chemistry;
using Content.Shared.Damage.Prototypes;
+using Content.Shared.Explosion.EntitySystems;
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;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly SharedChemistryGuideDataSystem _chemistryGuideData = default!;
+ [Dependency] private readonly SharedExplosionSystem _explosion = default!;
private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<DamageableComponent> _damageableQuery;
- private EntityQuery<MindContainerComponent> _mindContainerQuery;
public float UniversalAllDamageModifier { get; private set; } = 1f;
public float UniversalAllHealModifier { get; private set; } = 1f;
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
_damageableQuery = GetEntityQuery<DamageableComponent>();
- _mindContainerQuery = GetEntityQuery<MindContainerComponent>();
// Damage modifier CVars are updated and stored here to be queried in other systems.
// Note that certain modifiers requires reloading the guidebook.
{
UniversalAllDamageModifier = value;
_chemistryGuideData.ReloadAllReagentPrototypes();
+ _explosion.ReloadMap();
}, true);
Subs.CVar(_config, CCVars.PlaytestAllHealModifier, value =>
{
UniversalReagentHealModifier = value;
_chemistryGuideData.ReloadAllReagentPrototypes();
}, true);
- Subs.CVar(_config, CCVars.PlaytestExplosionDamageModifier, value => UniversalExplosionDamageModifier = value, true);
+ Subs.CVar(_config, CCVars.PlaytestExplosionDamageModifier, value =>
+ {
+ UniversalExplosionDamageModifier = value;
+ _explosion.ReloadMap();
+ }, true);
Subs.CVar(_config, CCVars.PlaytestThrownDamageModifier, value => UniversalThrownDamageModifier = value, true);
Subs.CVar(_config, CCVars.PlaytestTopicalsHealModifier, value => UniversalTopicalsHealModifier = value, true);
Subs.CVar(_config, CCVars.PlaytestMobDamageModifier, value => UniversalMobDamageModifier = value, true);
var data = new DamageVisualizerGroupData(component.DamagePerGroup.Keys.ToList());
_appearance.SetData(uid, DamageVisualizerKeys.DamageUpdateGroups, data, appearance);
}
+
+ // TODO DAMAGE
+ // byref struct event.
RaiseLocalEvent(uid, new DamageChangedEvent(component, damageDelta, interruptsDoAfters, origin));
}
/// Returns a <see cref="DamageSpecifier"/> with information about the actual damage changes. This will be
/// null if the user had no applicable components that can take damage.
/// </returns>
- public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
- bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null)
+ /// <param name="ignoreResistances">If true, this will ignore the entity's damage modifier (<see cref="DamageableComponent.DamageModifierSetId"/> and skip raising a <see cref="DamageModifyEvent"/>.</param>
+ /// <param name="interruptsDoAfters">Whether the damage should cancel any damage sensitive do-afters</param>
+ /// <param name="origin">The entity that is causing this damage</param>
+ /// <param name="ignoreGlobalModifiers">If true, this will skip over applying the universal damage modifiers (see <see cref="ApplyUniversalAllModifiers"/>).</param>
+ /// <returns></returns>
+ public DamageSpecifier? TryChangeDamage(
+ EntityUid? uid,
+ DamageSpecifier damage,
+ bool ignoreResistances = false,
+ bool interruptsDoAfters = true,
+ DamageableComponent? damageable = null,
+ EntityUid? origin = null,
+ bool ignoreGlobalModifiers = false)
{
if (!uid.HasValue || !_damageableQuery.Resolve(uid.Value, ref damageable, false))
{
// TODO BODY SYSTEM pass damage onto body system
+ // BOBBY WHEN?
return null;
}
if (!ignoreResistances)
{
if (damageable.DamageModifierSetId != null &&
- _prototypeManager.Resolve<DamageModifierSetPrototype>(damageable.DamageModifierSetId, out var modifierSet))
+ _prototypeManager.Resolve(damageable.DamageModifierSetId, out var modifierSet))
{
- // TODO DAMAGE PERFORMANCE
- // use a local private field instead of creating a new dictionary here..
damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet);
}
+ // TODO DAMAGE
+ // byref struct event.
var ev = new DamageModifyEvent(damage, origin);
RaiseLocalEvent(uid.Value, ev);
damage = ev.Damage;
}
}
- damage = ApplyUniversalAllModifiers(damage);
+ if (!ignoreGlobalModifiers)
+ damage = ApplyUniversalAllModifiers(damage);
- // TODO DAMAGE PERFORMANCE
- // Consider using a local private field instead of creating a new dictionary here.
- // Would need to check that nothing ever tries to cache the delta.
var delta = new DamageSpecifier();
delta.DamageDict.EnsureCapacity(damage.DamageDict.Count);
bool addLog = true)
{
}
+
+ /// <summary>
+ /// This forces the explosion system to re-calculate the explosion intensity required to destroy all airtight entities.
+ /// </summary>
+ public virtual void ReloadMap()
+ {
+ }
}