Pipebombs and IED rework (#25705)

* inital

* 2-9

* add crafting function, rename ied to fire bomb

* add firebomb ignition

* fikss

* change damage values

* add note

* fix tests i think

* ok

* good

* Review

* warning

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Mr. 27
2024-03-14 00:27:08 -04:00
committed by GitHub
parent 2f2cd4aab7
commit 3cb1c585c5
20 changed files with 291 additions and 44 deletions

View File

@@ -17,11 +17,15 @@ using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
namespace Content.Server.Explosion.EntitySystems;
public sealed partial class ExplosionSystem
{
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
/// <summary>
/// Used to limit explosion processing time. See <see cref="MaxProcessingTime"/>.
/// </summary>
@@ -95,7 +99,7 @@ public sealed partial class ExplosionSystem
{
// EXPLOSION TODO allow explosion spawning to be interrupted by time limit. In the meantime, ensure that
// there is at-least 1ms of time left before creating a new explosion
if (MathF.Max(MaxProcessingTime - 1, 0.1f) < Stopwatch.Elapsed.TotalMilliseconds)
if (MathF.Max(MaxProcessingTime - 1, 0.1f) < Stopwatch.Elapsed.TotalMilliseconds)
break;
if (!_explosionQueue.TryDequeue(out var spawnNextExplosion))
@@ -130,11 +134,11 @@ public sealed partial class ExplosionSystem
try
{
#endif
var processed = _activeExplosion.Process(tilesRemaining);
tilesRemaining -= processed;
var processed = _activeExplosion.Process(tilesRemaining);
tilesRemaining -= processed;
// has the explosion finished processing?
if (_activeExplosion.FinishedProcessing)
// has the explosion finished processing?
if (_activeExplosion.FinishedProcessing)
{
var comp = EnsureComp<TimedDespawnComponent>(_activeExplosion.VisualEnt);
comp.Lifetime = _cfg.GetCVar(CCVars.ExplosionPersistence);
@@ -203,7 +207,8 @@ public sealed partial class ExplosionSystem
DamageSpecifier damage,
MapCoordinates epicenter,
HashSet<EntityUid> processed,
string id)
string id,
float? fireStacks)
{
var size = grid.Comp.TileSize;
var gridBox = new Box2(tile * size, (tile + 1) * size);
@@ -222,7 +227,7 @@ public sealed partial class ExplosionSystem
// process those entities
foreach (var (uid, xform) in list)
{
ProcessEntity(uid, epicenter, damage, throwForce, id, xform);
ProcessEntity(uid, epicenter, damage, throwForce, id, xform, fireStacks);
}
// process anchored entities
@@ -232,7 +237,7 @@ public sealed partial class ExplosionSystem
foreach (var entity in _anchored)
{
processed.Add(entity);
ProcessEntity(entity, epicenter, damage, throwForce, id, null);
ProcessEntity(entity, epicenter, damage, throwForce, id, null, fireStacks);
}
// Walls and reinforced walls will break into girders. These girders will also be considered turf-blocking for
@@ -268,7 +273,7 @@ public sealed partial class ExplosionSystem
{
// 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.
ProcessEntity(uid, epicenter, null, throwForce, id, xform);
ProcessEntity(uid, epicenter, null, throwForce, id, xform, null);
}
return !tileBlocked;
@@ -303,7 +308,8 @@ public sealed partial class ExplosionSystem
DamageSpecifier damage,
MapCoordinates epicenter,
HashSet<EntityUid> processed,
string id)
string id,
float? fireStacks)
{
var gridBox = Box2.FromDimensions(tile * DefaultTileSize, new Vector2(DefaultTileSize, DefaultTileSize));
var worldBox = spaceMatrix.TransformBox(gridBox);
@@ -319,7 +325,7 @@ public sealed partial class ExplosionSystem
foreach (var (uid, xform) in state.Item1)
{
processed.Add(uid);
ProcessEntity(uid, epicenter, damage, throwForce, id, xform);
ProcessEntity(uid, epicenter, damage, throwForce, id, xform, fireStacks);
}
if (throwForce <= 0)
@@ -333,7 +339,7 @@ public sealed partial class ExplosionSystem
foreach (var (uid, xform) in list)
{
ProcessEntity(uid, epicenter, null, throwForce, id, xform);
ProcessEntity(uid, epicenter, null, throwForce, id, xform, fireStacks);
}
}
@@ -430,7 +436,8 @@ public sealed partial class ExplosionSystem
DamageSpecifier? originalDamage,
float throwForce,
string id,
TransformComponent? xform)
TransformComponent? xform,
float? fireStacksOnIgnite)
{
if (originalDamage != null)
{
@@ -439,6 +446,17 @@ public sealed partial class ExplosionSystem
{
// TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
_damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true);
}
}
// ignite
if (fireStacksOnIgnite != null)
{
if (_flammableQuery.TryGetComponent(uid, out var flammable))
{
flammable.FireStacks += fireStacksOnIgnite.Value;
_flammableSystem.Ignite(uid, uid, flammable);
}
}
@@ -705,14 +723,14 @@ sealed class Explosion
{
_currentIntensity = _tileSetIntensity[CurrentIteration];
#if DEBUG
#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
#endif
_currentDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
@@ -811,7 +829,8 @@ sealed class Explosion
_currentDamage,
Epicenter,
ProcessedEntities,
ExplosionType.ID);
ExplosionType.ID,
ExplosionType.FireStacks);
// If the floor is not blocked by some dense object, damage the floor tiles.
if (canDamageFloor)
@@ -828,7 +847,8 @@ sealed class Explosion
_currentDamage,
Epicenter,
ProcessedEntities,
ExplosionType.ID);
ExplosionType.ID,
ExplosionType.FireStacks);
}
if (!MoveNext())

View File

@@ -52,7 +52,7 @@ public sealed partial class ExplosionSystem : EntitySystem
[Dependency] private readonly SharedMapSystem _map = default!;
private EntityQuery<TransformComponent> _transformQuery;
private EntityQuery<DamageableComponent> _damageQuery;
private EntityQuery<FlammableComponent> _flammableQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
@@ -104,7 +104,7 @@ public sealed partial class ExplosionSystem : EntitySystem
InitVisuals();
_transformQuery = GetEntityQuery<TransformComponent>();
_damageQuery = GetEntityQuery<DamageableComponent>();
_flammableQuery = GetEntityQuery<FlammableComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
}

View File

@@ -25,6 +25,12 @@ public sealed partial class ExplosionPrototype : IPrototype
[DataField("damagePerIntensity", required: true)]
public DamageSpecifier DamagePerIntensity = default!;
/// <summary>
/// Amount of firestacks to apply in addition to igniting.
/// </summary>
[DataField]
public float? FireStacks;
/// <summary>
/// This set of points, together with <see cref="_tileBreakIntensity"/> define a function that maps the
/// explosion intensity to a tile break chance via linear interpolation.

View File

@@ -25,6 +25,7 @@ materials-meat = meat
materials-web = silk
materials-bones = bone
materials-coal = coal
materials-gunpowder = gunpowder
# Ores
materials-raw-iron = raw iron

View File

@@ -571,3 +571,22 @@
components:
- type: Stack
count: 1
- type: entity
parent: MaterialBase
id: MaterialGunpowder
name: gunpowder
description: An explosive compound.
components:
- type: Stack
stackType: Gunpowder
count: 1
- type: Sprite
sprite: Objects/Misc/reagent_fillings.rsi
state: powderpile
color: darkgray
- type: PhysicalComposition
materialComposition:
Gunpowder: 100
- type: Item
size: Tiny

View File

@@ -3,8 +3,8 @@
# with that you could make napalm ied instead of welding fuel with no additional complexity
- type: entity
parent: BaseItem
id: ImprovisedExplosive
name: improvised explosive device
id: FireBomb
name: fire bomb
description: A weak, improvised incendiary device.
components:
- type: Sprite
@@ -26,10 +26,10 @@
volume: 1
- type: RandomTimerTrigger
min: 0
max: 60
- type: Explosive # Weak explosion in a very small radius. Doesn't break underplating.
explosionType: Default
totalIntensity: 20
max: 15
- type: Explosive # Weak explosion in a very small radius. Ignites surrounding entities.
explosionType: FireBomb
totalIntensity: 25
intensitySlope: 5
maxIntensity: 3
canCreateVacuum: false
@@ -50,14 +50,14 @@
acts: ["Destruction"]
- !type:ExplodeBehavior
- type: Construction
graph: ImprovisedExplosive
node: ied
graph: FireBomb
node: firebomb
# has igniter but no fuel or wires
- type: entity
parent: DrinkColaCanEmpty
id: ImprovisedExplosiveEmpty
name: improvised explosive device
id: FireBombEmpty
name: fire bomb
suffix: empty
description: A weak, improvised incendiary device. This one has no fuel.
components:
@@ -74,17 +74,17 @@
True: {state: "base"}
False: {state: "base"}
- type: Construction
graph: ImprovisedExplosive
graph: FireBomb
node: empty
defaultTarget: ied
defaultTarget: firebomb
- type: Tag
tags:
- Trash
# no DrinkCan, prevent using it to make another ied
- type: entity
parent: ImprovisedExplosiveEmpty
id: ImprovisedExplosiveFuel
parent: FireBombEmpty
id: FireBombFuel
suffix: fuel
description: A weak, improvised incendiary device. This one is missing wires.
components:
@@ -102,6 +102,6 @@
Quantity: 30
- type: Construction
node: fuel
defaultTarget: ied
defaultTarget: firebomb
- type: Tag
tags: []

View File

@@ -0,0 +1,62 @@
- type: entity
parent: GrenadeBase
id: PipeBomb
name: pipe bomb
description: An improvised explosive made from pipes and wire.
components:
- type: Sprite
sprite: Objects/Weapons/Bombs/pipebomb.rsi
layers:
- state: base
map: ["enum.TriggerVisualLayers.Base"]
- state: wires
- type: OnUseTimerTrigger # todo: make it activate through welder/lighter/fire instead
delay: 5
examinable: false
initialBeepDelay: 0
beepSound: /Audio/Effects/lightburn.ogg
- type: RandomTimerTrigger
min: 1
max: 10
- type: ExplodeOnTrigger
- type: Explosive # Weak explosion in a very small radius. Doesn't break underplating.
explosionType: Default
totalIntensity: 50
intensitySlope: 5
maxIntensity: 6
canCreateVacuum: false
- type: Appearance
- type: TimerTriggerVisuals
- type: Construction
graph: PipeBomb
node: pipebomb
- type: entity
parent: BaseItem
id: PipeBombGunpowder
name: pipe bomb
description: An improvised explosive made from a pipe. This one has no gunpowder.
suffix: Gunpowder
components:
- type: Sprite
sprite: Objects/Weapons/Bombs/pipebomb.rsi
state: base
- type: Construction
graph: PipeBomb
node: gunpowder
defaultTarget: pipebomb
- type: entity
parent: BaseItem
id: PipeBombCable
name: pipe bomb
description: An improvised explosive made from a pipe. This one has no cable.
suffix: Cable
components:
- type: Sprite
sprite: Objects/Weapons/Bombs/pipebomb.rsi
state: base
- type: Construction
graph: PipeBomb
node: cable
defaultTarget: pipebomb

View File

@@ -116,3 +116,11 @@
icon: { sprite: Objects/Materials/ore.rsi, state: coal }
color: "#404040"
price: 0
- type: material
id: Gunpowder
name: materials-gunpowder
unit: materials-unit-piece
icon: { sprite: Objects/Misc/reagent_fillings.rsi, state: powderpile }
color: "#A9A9A9"
price: 0

View File

@@ -1,5 +1,5 @@
- type: constructionGraph
id: ImprovisedExplosive
id: FireBomb
start: start
graph:
- node: start
@@ -19,7 +19,7 @@
state: icon
doAfter: 1
- node: empty
entity: ImprovisedExplosiveEmpty
entity: FireBombEmpty
edges:
- to: start
completed:
@@ -42,7 +42,7 @@
- tool: Screwing
doAfter: 1
- node: fuel
entity: ImprovisedExplosiveFuel
entity: FireBombFuel
edges:
- to: empty
conditions:
@@ -51,7 +51,7 @@
steps:
- tool: Screwing
doAfter: 1
- to: ied
- to: firebomb
conditions: # no dumping out 29u of the fuel then adding wires :)
- !type:MinSolution
solution: drink
@@ -62,8 +62,8 @@
- material: Cable
amount: 5
doAfter: 2
- node: ied
entity: ImprovisedExplosive
- node: firebomb
entity: FireBomb
edges:
- to: fuel
completed:

View File

@@ -0,0 +1,43 @@
- type: constructionGraph
id: PipeBomb
start: start
graph:
- node: start
edges:
- to: gunpowder
steps:
- tag: Pipe
icon:
sprite: Structures/Piping/Atmospherics/pipe.rsi
state: pipeStraight
name: pipe
- material: Steel
amount: 1
doAfter: 3
- node: gunpowder
entity: PipeBombGunpowder
edges:
- to: cable
steps:
- material: Gunpowder
amount: 5
doAfter: 3
- node: cable
entity: PipeBombCable
edges:
- to: pipebomb
steps:
- material: Cable
amount: 5
doAfter: 2
- node: pipebomb
entity: PipeBomb
edges:
- to: cable
completed:
- !type:SpawnPrototype
prototype: CableApcStack1
amount: 5
steps:
- tool: Cutting
doAfter: 2

View File

@@ -190,11 +190,11 @@
state: icon
- type: construction
name: improvised explosive device
id: improvisedexplosive
graph: ImprovisedExplosive
name: fire bomb
id: firebomb
graph: FireBomb
startNode: start
targetNode: ied
targetNode: firebomb
category: construction-category-weapons
objectType: Item
description: A weak, improvised incendiary device.
@@ -227,3 +227,16 @@
sprite: Clothing/Head/Hats/straw_hat.rsi
state: icon
objectType: Item
- type: construction
name: pipe bomb
id: pipebomb
graph: PipeBomb
startNode: start
targetNode: pipebomb
category: construction-category-weapons
objectType: Item
description: An improvised explosive made from pipes and wire.
icon:
sprite: Objects/Weapons/Bombs/pipebomb.rsi
state: icon

View File

@@ -171,3 +171,18 @@
products:
Laughter: 2
- type: reaction
id: Gunpowder
impact: Low
quantized: true
minTemp: 374
reactants:
Potassium:
amount: 6
Sulfur:
amount: 2
Charcoal:
amount: 2
effects:
- !type:CreateEntityReactionEffect
entity: MaterialGunpowder

View File

@@ -85,3 +85,11 @@
spawn: MaterialBones1
maxCount: 30
itemSize: 1
- type: stack
id: Gunpowder
name: gunpowder
icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile }
spawn: MaterialGunpowder
maxCount: 60
itemSize: 1

View File

@@ -117,3 +117,19 @@
lightColor: Orange
texturePath: /Textures/Effects/fire.rsi
fireStates: 6
- type: explosion
id: FireBomb
damagePerIntensity:
types:
Heat: 1
Blunt: 2
Piercing: 3
lightColor: Orange
texturePath: /Textures/Effects/fire.rsi
fireStates: 6
fireStacks: 2
# STOP
# BEFORE YOU ADD MORE EXPLOSION TYPES CONSIDER IF AN EXISTING ONE IS SUITABLE
# ADDING NEW ONES IS PROHIBITIVELY EXPENSIVE

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

View File

@@ -0,0 +1,31 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Pipebomb sprites made by girthquake",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "wires"
},
{
"name": "primed",
"delays": [
[
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

View File

@@ -241,3 +241,8 @@ AirlockShuttleEasyPryLocked: AirlockExternalShuttleLocked
ClothingBackpackFilledDetective: ClothingBackpackSecurityFilledDetective
ClothingBackpackDuffelFilledDetective: ClothingBackpackDuffelSecurityFilledDetective
ClothingBackpackSatchelFilledDetective: ClothingBackpackSatchelSecurityFilledDetective
# 2024-03-11
ImprovisedExplosive: FireBomb
ImprovisedExplosiveEmpty: FireBombEmpty
ImprovisedExplosiveFuel: FireBombFuel