diff --git a/Content.Server/Explosion/Components/ExplosiveComponent.cs b/Content.Server/Explosion/Components/ExplosiveComponent.cs index b9fb4c4eac..9752f92d3b 100644 --- a/Content.Server/Explosion/Components/ExplosiveComponent.cs +++ b/Content.Server/Explosion/Components/ExplosiveComponent.cs @@ -60,13 +60,20 @@ public sealed class ExplosiveComponent : Component public float TileBreakScale = 1f; /// - /// Maximum number of times that an explosive can break a tile. Currently, for normal space ships breaking a - /// tile twice will result in a vacuum. + /// Maximum number of times that an explosive can break a tile. Currently, for normal space stations breaking a + /// tile twice will generally result in a vacuum. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("maxTileBreak")] public int MaxTileBreak = int.MaxValue; + /// + /// Whether this explosive should be able to create a vacuum by breaking tiles. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("canCreateVacuum")] + public bool CanCreateVacuum = true; + /// /// Avoid somehow double-triggering this explosion (e.g. by damaging this entity from its own explosion. /// diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 973ffc5ec8..aa80aa6f74 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -182,8 +182,7 @@ public sealed partial class ExplosionSystem : EntitySystem string id, EntityQuery xformQuery, EntityQuery damageQuery, - EntityQuery physicsQuery, - EntityQuery metaQuery) + EntityQuery physicsQuery) { var gridBox = new Box2(tile * grid.TileSize, (tile + 1) * grid.TileSize); @@ -216,11 +215,23 @@ public sealed partial class ExplosionSystem : EntitySystem // process anchored entities var tileBlocked = false; - foreach (var entity in grid.GetAnchoredEntities(tile).ToList()) + var anchoredList = grid.GetAnchoredEntities(tile).ToList(); + foreach (var entity in anchoredList) { processed.Add(entity); ProcessEntity(entity, epicenter, damage, throwForce, id, damageQuery, physicsQuery); - tileBlocked |= IsBlockingTurf(entity, physicsQuery); + } + + // Walls and reinforced walls will break into girders. These girders will also be considered turf-blocking for + // the purposes of destroying floors. Again, ideally the process of damaging an entity should somehow return + // information about the entities that were spawned as a result, but without that information we just have to + // re-check for new anchored entities. Compared to entity spawning & deleting, this should still be relatively minor. + if (anchoredList.Count > 0) + { + foreach (var entity in grid.GetAnchoredEntities(tile)) + { + tileBlocked |= IsBlockingTurf(entity, physicsQuery); + } } // Next, we get the intersecting entities AGAIN, but purely for throwing. This way, glass shards spawned from @@ -261,8 +272,7 @@ public sealed partial class ExplosionSystem : EntitySystem string id, EntityQuery xformQuery, EntityQuery damageQuery, - EntityQuery physicsQuery, - EntityQuery metaQuery) + EntityQuery physicsQuery) { var gridBox = new Box2(tile * DefaultTileSize, (DefaultTileSize, DefaultTileSize)); var worldBox = spaceMatrix.TransformBox(gridBox); @@ -370,10 +380,15 @@ public sealed partial class ExplosionSystem : EntitySystem public void DamageFloorTile(TileRef tileRef, float effectiveIntensity, int maxTileBreak, + bool canCreateVacuum, List<(Vector2i GridIndices, Tile Tile)> damagedTiles, ExplosionPrototype type) { - var tileDef = _tileDefinitionManager[tileRef.Tile.TypeId]; + if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef) + return; + + if (tileDef.IsSpace) + canCreateVacuum = true; // is already a vacuum. int tileBreakages = 0; while (maxTileBreak > tileBreakages && _robustRandom.Prob(type.TileBreakChance(effectiveIntensity))) @@ -381,14 +396,17 @@ public sealed partial class ExplosionSystem : EntitySystem tileBreakages++; effectiveIntensity -= type.TileBreakRerollReduction; - if (tileDef is not ContentTileDefinition contentTileDef) - break; - // does this have a base-turf that we can break it down to? - if (contentTileDef.BaseTurfs.Count == 0) + if (tileDef.BaseTurfs.Count == 0) break; - tileDef = _tileDefinitionManager[contentTileDef.BaseTurfs[^1]]; + if (_tileDefinitionManager[tileDef.BaseTurfs[^1]] is not ContentTileDefinition newDef) + break; + + if (newDef.IsSpace && !canCreateVacuum) + break; + + tileDef = newDef; } if (tileDef.TileId == tileRef.Tile.TypeId) @@ -493,7 +511,6 @@ sealed class Explosion private readonly EntityQuery _xformQuery; private readonly EntityQuery _physicsQuery; private readonly EntityQuery _damageQuery; - private readonly EntityQuery _metaQuery; /// /// Total area that the explosion covers. @@ -510,6 +527,11 @@ sealed class Explosion /// private readonly int _maxTileBreak; + /// + /// Whether this explosion can turn non-vacuum tiles into vacuum-tiles. + /// + private readonly bool _canCreateVacuum; + private readonly IEntityManager _entMan; private readonly ExplosionSystem _system; @@ -526,6 +548,7 @@ sealed class Explosion int area, float tileBreakScale, int maxTileBreak, + bool canCreateVacuum, IEntityManager entMan, IMapManager mapMan) { @@ -537,12 +560,12 @@ sealed class Explosion _tileBreakScale = tileBreakScale; _maxTileBreak = maxTileBreak; + _canCreateVacuum = canCreateVacuum; _entMan = entMan; _xformQuery = entMan.GetEntityQuery(); _physicsQuery = entMan.GetEntityQuery(); _damageQuery = entMan.GetEntityQuery(); - _metaQuery = entMan.GetEntityQuery(); if (spaceData != null) { @@ -682,12 +705,11 @@ sealed class Explosion ExplosionType.ID, _xformQuery, _damageQuery, - _physicsQuery, - _metaQuery); + _physicsQuery); // If the floor is not blocked by some dense object, damage the floor tiles. if (canDamageFloor) - _system.DamageFloorTile(tileRef, _currentIntensity * _tileBreakScale, _maxTileBreak, tileUpdateList, ExplosionType); + _system.DamageFloorTile(tileRef, _currentIntensity * _tileBreakScale, _maxTileBreak, _canCreateVacuum, tileUpdateList, ExplosionType); } else { @@ -703,8 +725,7 @@ sealed class Explosion ExplosionType.ID, _xformQuery, _damageQuery, - _physicsQuery, - _metaQuery); + _physicsQuery); } if (!MoveNext()) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index a958d6684a..226e43bc73 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -122,6 +122,7 @@ public sealed partial class ExplosionSystem : EntitySystem explosive.MaxIntensity, explosive.TileBreakScale, explosive.MaxTileBreak, + explosive.CanCreateVacuum, user); if (delete) @@ -190,13 +191,14 @@ public sealed partial class ExplosionSystem : EntitySystem float maxTileIntensity, float tileBreakScale = 1f, int maxTileBreak = int.MaxValue, + bool canCreateVacuum = true, EntityUid? user = null, bool addLog = false) { var pos = Transform(uid).MapPosition; - QueueExplosion(pos, typeId, totalIntensity, slope, maxTileIntensity, tileBreakScale, maxTileBreak, addLog: false); + QueueExplosion(pos, typeId, totalIntensity, slope, maxTileIntensity, tileBreakScale, maxTileBreak, canCreateVacuum, addLog: false); if (!addLog) return; @@ -219,6 +221,7 @@ public sealed partial class ExplosionSystem : EntitySystem float maxTileIntensity, float tileBreakScale = 1f, int maxTileBreak = int.MaxValue, + bool canCreateVacuum = true, bool addLog = false) { if (totalIntensity <= 0 || slope <= 0) @@ -234,7 +237,7 @@ public sealed partial class ExplosionSystem : EntitySystem _logsSystem.Add(LogType.Explosion, LogImpact.High, $"Explosion spawned at {epicenter:coordinates} with intensity {totalIntensity} slope {slope}"); _explosionQueue.Enqueue(() => SpawnExplosion(epicenter, type, totalIntensity, - slope, maxTileIntensity, tileBreakScale, maxTileBreak)); + slope, maxTileIntensity, tileBreakScale, maxTileBreak, canCreateVacuum)); } /// @@ -248,7 +251,8 @@ public sealed partial class ExplosionSystem : EntitySystem float slope, float maxTileIntensity, float tileBreakScale, - int maxTileBreak) + int maxTileBreak, + bool canCreateVacuum) { if (!_mapManager.MapExists(epicenter.MapId)) return null; @@ -283,6 +287,7 @@ public sealed partial class ExplosionSystem : EntitySystem area, tileBreakScale, maxTileBreak, + canCreateVacuum, EntityManager, _mapManager); } diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index f0b810db00..ae8dc93588 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -80,7 +80,7 @@ - type: entity name: Syndicate minibomb - description: A syndicate manufactured explosive used to sow destruction and chaos. + description: Creates a small but powerful explosion that is strong enough to break through walls. parent: BaseItem id: SyndieMiniBomb components: @@ -95,10 +95,11 @@ delay: 5 - type: Explosive explosionType: Default - # About a 3-3.5 tile radius, strong enough to break reinforced walls near centre. - totalIntensity: 800 - intensitySlope: 15 - maxTileBreak: 1 # for destroying walls, not spacing the hallway + # About a 2.5 tile radius. If placed next to walls, will destroy a line of 3-normal walls and turn 3 reinforced walls into girders. + totalIntensity: 492 + intensitySlope: 20.5 + maxIntensity: 41 + canCreateVacuum: false # for destroying walls, not spacing the hallway - type: ExplodeOnTrigger - type: Damageable damageContainer: Inorganic