From 95cc36c41d1a01ce1568945e2734200c6add57a3 Mon Sep 17 00:00:00 2001
From: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Date: Sat, 17 May 2025 21:32:51 -0700
Subject: [PATCH] Atmos Air Grenades (#37531)
---
.../ReleaseGasOnTriggerSystem.cs | 92 +++++++++++++++++
.../OnTrigger/ReleaseGasOnTriggerComponent.cs | 93 ++++++++++++++++++
.../SharedReleaseGasOnTriggerSystem.cs | 5 +
.../Catalog/Fills/Lockers/engineer.yml | 2 +
.../Objects/Weapons/Throwable/grenades.yml | 32 ++++++
.../Weapons/Grenades/airboom.rsi/active.png | Bin 0 -> 905 bytes
.../Grenades/airboom.rsi/equipped-BELT.png | Bin 0 -> 229 bytes
.../Weapons/Grenades/airboom.rsi/icon.png | Bin 0 -> 306 bytes
.../Grenades/airboom.rsi/inhand-left.png | Bin 0 -> 363 bytes
.../Grenades/airboom.rsi/inhand-right.png | Bin 0 -> 378 bytes
.../Weapons/Grenades/airboom.rsi/meta.json | 44 +++++++++
.../Weapons/Grenades/airboom.rsi/primed.png | Bin 0 -> 299 bytes
.../Weapons/Grenades/airboom.rsi/spent.png | Bin 0 -> 350 bytes
13 files changed, 268 insertions(+)
create mode 100644 Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs
create mode 100644 Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs
create mode 100644 Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/active.png
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/equipped-BELT.png
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/icon.png
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/inhand-left.png
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/inhand-right.png
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/meta.json
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/primed.png
create mode 100644 Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/spent.png
diff --git a/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs
new file mode 100644
index 0000000000..09b6e1bf24
--- /dev/null
+++ b/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs
@@ -0,0 +1,92 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Explosion.Components.OnTrigger;
+using Content.Shared.Explosion.EntitySystems;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Explosion.EntitySystems;
+
+///
+/// Releases a gas mixture to the atmosphere when triggered.
+/// Can also release gas over a set timespan to prevent trolling people
+/// with the instant-wall-of-pressure-inator.
+///
+public sealed partial class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnTrigger);
+ }
+
+ ///
+ /// Shrimply sets the component to active when triggered, allowing it to release over time.
+ ///
+ private void OnTrigger(Entity ent, ref TriggerEvent args)
+ {
+ ent.Comp.Active = true;
+ ent.Comp.NextReleaseTime = _timing.CurTime;
+ ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles;
+ UpdateAppearance(ent.Owner, true);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var curTime = _timing.CurTime;
+ var query = EntityQueryEnumerator();
+
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (!comp.Active || comp.NextReleaseTime > curTime)
+ continue;
+
+ var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction);
+ var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
+
+ if (environment == null)
+ {
+ UpdateAppearance(uid, false);
+ RemCompDeferred(uid);
+ continue;
+ }
+
+ _atmosphereSystem.Merge(environment, giverGasMix);
+ comp.NextReleaseTime += comp.ReleaseInterval;
+
+ if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit ||
+ comp.Air.TotalMoles <= 0)
+ {
+ UpdateAppearance(uid, false);
+ RemCompDeferred(uid);
+ continue;
+ }
+
+ if (comp.ExponentialRise)
+ UpdateExponentialRise(comp, comp.RemoveFraction);
+ }
+ }
+
+ ///
+ /// Updates the RemoveFraction for exponential rise.
+ ///
+ /// See https://www.desmos.com/calculator/fx9gfrhoim
+ private static void UpdateExponentialRise(ReleaseGasOnTriggerComponent comp, float baseFraction)
+ {
+ comp.TimesReleased++;
+ comp.RemoveFraction = 1f - MathF.Pow(1f - baseFraction, comp.TimesReleased);
+ }
+
+ private void UpdateAppearance(Entity entity, bool state)
+ {
+ if (!Resolve(entity, ref entity.Comp, false))
+ return;
+
+ _appearance.SetData(entity, ReleaseGasOnTriggerComponent.ReleaseGasOnTriggerVisuals.Key, state);
+ }
+}
diff --git a/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs b/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs
new file mode 100644
index 0000000000..5072b89dd2
--- /dev/null
+++ b/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs
@@ -0,0 +1,93 @@
+using Content.Shared.Atmos;
+using Content.Shared.Explosion.EntitySystems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Explosion.Components.OnTrigger;
+
+///
+/// Contains a GasMixture that will release its contents to the atmosphere when triggered.
+///
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentPause]
+[Access(typeof(SharedReleaseGasOnTriggerSystem))]
+public sealed partial class ReleaseGasOnTriggerComponent : Component
+{
+ ///
+ /// Represents visual states for whatever visuals that need to be applied
+ /// on state changes.
+ ///
+ [Serializable] [NetSerializable]
+ public enum ReleaseGasOnTriggerVisuals : byte
+ {
+ Key,
+ }
+
+ ///
+ /// Whether this grenade is active and releasing gas.
+ /// Set to true when triggered, which starts gas release.
+ ///
+ [DataField]
+ public bool Active;
+
+ ///
+ /// The gas mixture that will be released to the current tile atmosphere when triggered.
+ ///
+ [DataField]
+ public GasMixture Air;
+
+ ///
+ /// If true, the gas will be released in an exponential manner.
+ ///
+ [DataField]
+ public bool ExponentialRise;
+
+ ///
+ /// Time at which the next release will occur.
+ /// This is automatically set when the grenade activates.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoPausedField]
+ public TimeSpan NextReleaseTime = TimeSpan.Zero;
+
+ ///
+ /// The cap at which this grenade can fill the exposed atmosphere to.
+ /// The grenade automatically deletes itself when the pressure is reached.
+ ///
+ /// If set to 101.325, the grenade will only fill the exposed
+ /// atmosphere up to 101.325 kPa.
+ /// If zero, this limit won't be respected.
+ [DataField]
+ public float PressureLimit;
+
+ ///
+ /// How often the grenade will release gas.
+ ///
+ [DataField]
+ public TimeSpan ReleaseInterval = TimeSpan.FromSeconds(1);
+
+ ///
+ /// A float from 0 to 1, representing a partial portion of the moles
+ /// of the gas mixture that will be
+ /// released to the current tile atmosphere when triggered.
+ ///
+ /// If undefined on the prototype, the entire molar amount will be transferred.
+ [DataField]
+ public float RemoveFraction = 1;
+
+ ///
+ /// Stores the total moles initially in the grenade upon activation.
+ /// Used to calculate the moles released over time.
+ ///
+ /// Set when the grenade is activated.
+ [DataField(readOnly: true)]
+ public float StartingTotalMoles;
+
+ ///
+ /// Stores the number of times the grenade has been released,
+ /// for exponential rise calculations.
+ ///
+ [DataField]
+ public int TimesReleased;
+}
diff --git a/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs
new file mode 100644
index 0000000000..5027b04517
--- /dev/null
+++ b/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs
@@ -0,0 +1,5 @@
+namespace Content.Shared.Explosion.EntitySystems;
+
+public abstract partial class SharedReleaseGasOnTriggerSystem : EntitySystem;
+
+// I have dreams of Atmos in shared.
diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml
index cac3eab043..b1c5b07aa6 100644
--- a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml
+++ b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml
@@ -115,6 +115,7 @@
- id: HolofanProjector
- id: RCD
- id: RCDAmmo
+ - id: AirGrenade
- type: entity
id: LockerAtmosphericsFilled
@@ -131,6 +132,7 @@
- id: HolofanProjector
- id: RCD
- id: RCDAmmo
+ - id: AirGrenade
- type: entity
id: LockerEngineerFilledHardsuit
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
index 66dc50c5c6..6cc8eb82c7 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
@@ -461,6 +461,38 @@
- type: StaticPrice
price: 350
+- type: entity
+ parent: [ BaseEngineeringContraband, GrenadeBase ] # Prevent inheriting DeleteOnTrigger from SmokeGrenade
+ id: AirGrenade
+ name: air grenade
+ description: A special solid state chemical grenade used for quickly releasing standard air into a spaced area. Fills up to 30 tiles!
+ components:
+ - type: Sprite
+ sprite: Objects/Weapons/Grenades/airboom.rsi
+ - type: SoundOnTrigger
+ sound: /Audio/Items/smoke_grenade_smoke.ogg
+ - type: TimerTriggerVisuals
+ primingSound:
+ path: /Audio/Items/smoke_grenade_prime.ogg
+ - type: OnUseTimerTrigger
+ delay: 3
+ - type: ReleaseGasOnTrigger
+ removeFraction: 0.25
+ air:
+ volume: 1000
+ moles: # Target is 3117.84 mols total for filling 30 tiles (goal is 101.325 kPa @ 20C)
+ - 654.7464 # oxygen
+ - 2463.0936 # nitrogen
+ temperature: 293.15
+ - type: StaticPrice
+ price: 350
+ - type: GenericVisualizer
+ visuals:
+ enum.ReleaseGasOnTriggerVisuals.Key:
+ enabled:
+ True: { state: active }
+ False: { state: spent }
+
# Non-explosive "dummy" grenades to use as a distraction.
- type: entity
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/active.png b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/active.png
new file mode 100644
index 0000000000000000000000000000000000000000..69d3caf891783908da2c328b01d710a0ec861b0e
GIT binary patch
literal 905
zcmV;419tq0P)001Be1^@s6m49>f00001b5ch_0Itp)
z=>Px&LrFwIRCt{2n!jt?KorNHN=>IOoirFN-D>EdKf=QmQ*ZqPveXdhn9*d&Rs!8T
zWXjgf(E1=##y||pVuyxc0>X9#X>sb3y7BTlwAad#Ea`s8#qRrnmfXAdJ>5OMdv^{1
zK@bE%5ClOG1VIoPIF5tj*>ru*c}ycsKgr&tc9y>BF{tS0<$+G;YPG7f~i9c*csfR|8tYBb_N!e%lQthj&Z+gpK#g&XIYf%G=pvqeV0_YzE0G8K*d1@ft
z`Y$bW%G6*g88{1*HK5cM%owx8V$^E2g;S90x>ixaC$`k^-C9&1;8%v$-F^jP;gIgW#kMgspb0WG9D|8nUo^YHh*tZd`ZsgroQce}&$ey6VsUC3*7imza%
z-^^v2pKa7DLynrg2O0qa|NM`JEhv${5Op*xVJrKJTU=9W`?rT}6^(eyn8V7z@Z+4c
X!C9N`9KNYrL8?7n{an^LB{Ts5Af{O|
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/icon.png b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..c3c7f6fbe0184ce953033be50a3f9a955c06bb72
GIT binary patch
literal 306
zcmV-20nPr2P)Px#>`6pHR9J=Wl|2f=KoEtWVz9FD0MmGvG?LmYn93uB^fq?6g7xZAwg|EaOZEa<
zq>R;SbZurLh`1jF!YniM<|kPo5{X3qosra$h&umntrf=^OA`C!X_^K$E)%YUua<~9
z=iHM`g2?e*wNer64tVlBhcO1uIcTk=VdMgoQfQh+oOrN+EXzL90ruR8sOz`iqqP?8
z0n34iy2EyklWkF-FTr`G61E8T2##hIRR4fK3igi(0Lroi093o#{gEPC!lEbu0PD?F
zoLDSix!B{Bw5X@y4rraQ1^m`m&=>J)8*(2+B9X6h0~(ZX1K?tGbN~PV07*qoM6N<$
Ef>t(zXaE2J
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/inhand-left.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ba282d6c19f4d42474464a73edada42d3621c9b
GIT binary patch
literal 363
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%zRXtrCLn`LH
zy|vNzh=ItlkL*snSnpiev4icTa>OFf^i@0$-+fz<#1iJ^*;M#+~nb
zc@8T~KfP4zu;p^wTZ^>MZdmvF)60jkarfV)&Qg;+zESJG#AH{OXO&@d^H27MUATT&
zoc?jE8fN9M(wM0lZybH}^ySz-L$mknmG6stKHfhc@SnxYQEBNC
z*}uMCo_+1bm(%}5YI>|GotnG$Zn$D{cXiD!W}v}S?HC_T%CFrcGtU%ckEg4j%Q~lo
FCIFX9p3MLN
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/inhand-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..84e4dc7578c157f99609955043b39e265f121794
GIT binary patch
literal 378
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%zO*~y3Ln`LH
zy=9o!Y#`F`@cODCy$7tJpC)L264d$15GF3))LUK9e6Lyc?J*%ucLz~tH8;HrC+AME
znsPzJXTz6z?MFX<-kf(&m=S0)1A~2Jc5{%{9qaxXa>wl=vYQRl&c-a-a4pXEz(uAl
zx!R$jo!{f81SM)+2$M`+C-yK=>p+3xuqmV&3pQZq_nMS%(?P9XCyUKiuoKp7W$8uB`W_
zf5Luiuj{4WPmET*wz~Pg@6*ShLZx$OPj%fEr5%+m+osrSdTn+|zn#tAdIp9D)>>wF
Wg@TqF&30cv4)ApKb6Mw<&;$Uh!k|t7
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/meta.json b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/meta.json
new file mode 100644
index 0000000000..b2739f6b31
--- /dev/null
+++ b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/meta.json
@@ -0,0 +1,44 @@
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Created by EmoGarbage404 (github) for Space Station 14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "primed"
+ },
+ {
+ "name": "active",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "spent"
+ },
+ {
+ "name": "equipped-BELT",
+ "directions": 4
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/primed.png b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/primed.png
new file mode 100644
index 0000000000000000000000000000000000000000..7acc406dbac6e0f12826e3cff18aeb614ebc8002
GIT binary patch
literal 299
zcmV+`0o4A9P)Px#lUB>c&r_g)e}Q55ChnMj31)b)MmoOqq7R0sfRn#RcE4S4UP4TwTF
z41bR_4Ks-d0P;MCwHDrcFtc1pr{XyNfp#8(6Q7
xxFjv=`M3s{lVAhCbqj_j-p68oL`6})$^*@+V)jbF+pz!u002ovPDHLkV1kx%df)&6
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/spent.png b/Resources/Textures/Objects/Weapons/Grenades/airboom.rsi/spent.png
new file mode 100644
index 0000000000000000000000000000000000000000..c261eff5d19b3d5bf7b255694f44a7eaf671861e
GIT binary patch
literal 350
zcmV-k0iphhP)Px$7)eAyR9J=WmOX02Koo>0AtG%EDaC72rA(E=q*W7CyYLBAxyS*8DmOkru8>;J
zA##8%j1hrF3&xoE2aHO&lC4D5&YGCOey||4%dRl0Wh8`w5nF(Lp+k_1Ijz!(Fi6nhMBAkTA@Wl3M`WS}<~;`#9b
zn_Y#)_XhLj3VQTP8E^{WDWF3bhT)GE{CNoDI6g@TtzC;q-JXC3r4&^KD}jjAlkqKP
zy2A8p+u48118Wo3E%+$j|F>T=G{vETR
w!t}bE0i^