]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Fire extinguishers can now extinguish items, including when held/worn (#36267)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Mon, 14 Apr 2025 09:00:47 +0000 (11:00 +0200)
committerGitHub <noreply@github.com>
Mon, 14 Apr 2025 09:00:47 +0000 (19:00 +1000)
* Fire extinguishers now put out candles

This did not actually require any changes to flammable or extinguishers directly, the only necessary changes were to make the collision actually work.

Vapor entities (also used for fire extinguishers) now have a collision layer, so they can hit items.

Added a new FlammableSetCollisionWake component to actually enable collision on candles while they are lit, because otherwise CollisionWake on entities gets in the way too.

* Extinguishing items is now relayed to held/worn items

This means held candles get extinguished too.

Involved moving the core logic of ExtinguishReaction into an event so that it can be relayed via the existing hand/inventory relay logic.

* Add helper functions for subscribing to relayed events.

Use these in FlammableSystem

* Make extinguishers work on cigarettes too

A bunch of renaming to make the rest of my code work with SmokableComponent

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
12 files changed:
Content.Server/Atmos/EntitySystems/FlammableSystem.cs
Content.Server/EntityEffects/Effects/ExtinguishReaction.cs
Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
Content.Shared/Atmos/Components/ExtinguishableSetCollisionWakeComponent.cs [new file with mode: 0644]
Content.Shared/Atmos/EntitySystems/ExtinguishableSetCollisionWakeSystem.cs [new file with mode: 0644]
Content.Shared/Atmos/FireEvents.cs [new file with mode: 0644]
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs
Content.Shared/Inventory/InventorySystem.Relay.cs
Content.Shared/Inventory/RelaySubscriptionHelpers.cs [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml
Resources/Prototypes/Entities/Objects/Misc/candles.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml

index 412f0d476df0f9b1fc7e5431baa681e5f0b25db7..8681fc635cb5cef16cd3c803862599d94b75302a 100644 (file)
@@ -23,6 +23,7 @@ using Content.Shared.Timing;
 using Content.Shared.Toggleable;
 using Content.Shared.Weapons.Melee.Events;
 using Content.Shared.FixedPoint;
+using Content.Shared.Hands;
 using Robust.Server.Audio;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Events;
@@ -73,6 +74,7 @@ namespace Content.Server.Atmos.EntitySystems
             SubscribeLocalEvent<FlammableComponent, TileFireEvent>(OnTileFire);
             SubscribeLocalEvent<FlammableComponent, RejuvenateEvent>(OnRejuvenate);
             SubscribeLocalEvent<FlammableComponent, ResistFireAlertEvent>(OnResistFireAlert);
+            Subs.SubscribeWithRelay<FlammableComponent, ExtinguishEvent>(OnExtinguishEvent);
 
             SubscribeLocalEvent<IgniteOnCollideComponent, StartCollideEvent>(IgniteOnCollide);
             SubscribeLocalEvent<IgniteOnCollideComponent, LandEvent>(OnIgniteLand);
@@ -84,6 +86,14 @@ namespace Content.Server.Atmos.EntitySystems
             SubscribeLocalEvent<IgniteOnHeatDamageComponent, DamageChangedEvent>(OnDamageChanged);
         }
 
+        private void OnExtinguishEvent(Entity<FlammableComponent> ent, ref ExtinguishEvent args)
+        {
+            // You know I'm really not sure if having AdjustFireStacks *after* Extinguish,
+            // but I'm just moving this code, not questioning it.
+            Extinguish(ent, ent.Comp);
+            AdjustFireStacks(ent, args.FireStacksAdjustment, ent.Comp);
+        }
+
         private void OnMeleeHit(EntityUid uid, IgniteOnMeleeHitComponent component, MeleeHitEvent args)
         {
             foreach (var entity in args.HitEntities)
@@ -315,6 +325,9 @@ namespace Content.Server.Atmos.EntitySystems
 
             _ignitionSourceSystem.SetIgnited(uid, false);
 
+            var extinguished = new ExtinguishedEvent();
+            RaiseLocalEvent(uid, ref extinguished);
+
             UpdateAppearance(uid, flammable);
         }
 
@@ -336,6 +349,9 @@ namespace Content.Server.Atmos.EntitySystems
                 else
                     _adminLogger.Add(LogType.Flammable, $"{ToPrettyString(uid):target} set on fire by {ToPrettyString(ignitionSource):actor}");
                 flammable.OnFire = true;
+
+                var extinguished = new IgnitedEvent();
+                RaiseLocalEvent(uid, ref extinguished);
             }
 
             UpdateAppearance(uid, flammable);
index 6d7e7c2fcd85b974b8772ffad5e18183ec39e541..d36ac9a576faf5c00de4711a2b488e0770764daf 100644 (file)
@@ -1,5 +1,4 @@
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos;
 using Content.Shared.EntityEffects;
 using JetBrains.Annotations;
 using Robust.Shared.Prototypes;
@@ -20,17 +19,17 @@ namespace Content.Server.EntityEffects.Effects
 
         public override void Effect(EntityEffectBaseArgs args)
         {
-            if (!args.EntityManager.TryGetComponent(args.TargetEntity, out FlammableComponent? flammable)) return;
+            var ev = new ExtinguishEvent
+            {
+                FireStacksAdjustment = FireStacksAdjustment,
+            };
 
-            var flammableSystem = args.EntityManager.System<FlammableSystem>();
-            flammableSystem.Extinguish(args.TargetEntity, flammable);
             if (args is EntityEffectReagentArgs reagentArgs)
             {
-                flammableSystem.AdjustFireStacks(reagentArgs.TargetEntity, FireStacksAdjustment * (float) reagentArgs.Quantity, flammable);
-            } else
-            {
-                flammableSystem.AdjustFireStacks(args.TargetEntity, FireStacksAdjustment, flammable);
+                ev.FireStacksAdjustment *= (float)reagentArgs.Quantity;
             }
+
+            args.EntityManager.EventBus.RaiseLocalEvent(args.TargetEntity, ref ev);
         }
     }
 }
index 0d637139d82cb2b4d5e8a0a674e643eb03fdd8c3..d2074a3d82d76d8e8625253a814412c06c33728c 100644 (file)
@@ -17,6 +17,7 @@ using Content.Shared.Temperature;
 using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
 using System.Linq;
+using Content.Shared.Atmos;
 
 namespace Content.Server.Nutrition.EntitySystems
 {
@@ -48,12 +49,19 @@ namespace Content.Server.Nutrition.EntitySystems
             SubscribeLocalEvent<SmokableComponent, IsHotEvent>(OnSmokableIsHotEvent);
             SubscribeLocalEvent<SmokableComponent, ComponentShutdown>(OnSmokableShutdownEvent);
             SubscribeLocalEvent<SmokableComponent, GotEquippedEvent>(OnSmokeableEquipEvent);
+            Subs.SubscribeWithRelay<SmokableComponent, ExtinguishEvent>(OnExtinguishEvent);
 
             InitializeCigars();
             InitializePipes();
             InitializeVapes();
         }
 
+        private void OnExtinguishEvent(Entity<SmokableComponent> ent, ref ExtinguishEvent args)
+        {
+            if (ent.Comp.State == SmokableState.Lit)
+                SetSmokableState(ent, SmokableState.Burnt, ent);
+        }
+
         public void SetSmokableState(EntityUid uid, SmokableState state, SmokableComponent? smokable = null,
             AppearanceComponent? appearance = null, ClothingComponent? clothing = null)
         {
@@ -74,9 +82,19 @@ namespace Content.Server.Nutrition.EntitySystems
             _items.SetHeldPrefix(uid, newState);
 
             if (state == SmokableState.Lit)
+            {
+                var igniteEvent = new IgnitedEvent();
+                RaiseLocalEvent(uid, ref igniteEvent);
+
                 _active.Add(uid);
+            }
             else
+            {
+                var igniteEvent = new ExtinguishedEvent();
+                RaiseLocalEvent(uid, ref igniteEvent);
+
                 _active.Remove(uid);
+            }
         }
 
         private void OnSmokableIsHotEvent(Entity<SmokableComponent> entity, ref IsHotEvent args)
diff --git a/Content.Shared/Atmos/Components/ExtinguishableSetCollisionWakeComponent.cs b/Content.Shared/Atmos/Components/ExtinguishableSetCollisionWakeComponent.cs
new file mode 100644 (file)
index 0000000..19e471f
--- /dev/null
@@ -0,0 +1,11 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Atmos.Components;
+
+/// <summary>
+/// Makes entities with extinguishing behavior automatically enable/disable <see cref="CollisionWakeComponent"/>,
+/// so they can be extinguished with fire extinguishers.
+/// </summary>
+[RegisterComponent]
+[NetworkedComponent]
+public sealed partial class ExtinguishableSetCollisionWakeComponent : Component;
diff --git a/Content.Shared/Atmos/EntitySystems/ExtinguishableSetCollisionWakeSystem.cs b/Content.Shared/Atmos/EntitySystems/ExtinguishableSetCollisionWakeSystem.cs
new file mode 100644 (file)
index 0000000..107ac5e
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Shared.Atmos.Components;
+
+namespace Content.Shared.Atmos.EntitySystems;
+
+/// <summary>
+/// Implements <see cref="ExtinguishableSetCollisionWakeComponent"/>.
+/// </summary>
+public sealed class ExtinguishableSetCollisionWakeSystem : EntitySystem
+{
+    [Dependency]
+    private readonly CollisionWakeSystem _collisionWake = null!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ExtinguishableSetCollisionWakeComponent, ExtinguishedEvent>(HandleExtinguished);
+        SubscribeLocalEvent<ExtinguishableSetCollisionWakeComponent, IgnitedEvent>(HandleIgnited);
+    }
+
+    private void HandleExtinguished(Entity<ExtinguishableSetCollisionWakeComponent> ent, ref ExtinguishedEvent args)
+    {
+        _collisionWake.SetEnabled(ent, true);
+    }
+
+    private void HandleIgnited(Entity<ExtinguishableSetCollisionWakeComponent> ent, ref IgnitedEvent args)
+    {
+        _collisionWake.SetEnabled(ent, false);
+    }
+}
diff --git a/Content.Shared/Atmos/FireEvents.cs b/Content.Shared/Atmos/FireEvents.cs
new file mode 100644 (file)
index 0000000..4e19ef6
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Shared.Inventory;
+using Content.Shared.Nutrition.Components;
+
+namespace Content.Shared.Atmos;
+
+// NOTE: These components are currently not raised on the client, only on the server.
+
+/// <summary>
+/// An entity has had an existing effect applied to it.
+/// </summary>
+/// <remarks>
+/// This does not necessarily mean the effect is strong enough to fully extinguish the entity in one go.
+/// </remarks>
+[ByRefEvent]
+public struct ExtinguishEvent : IInventoryRelayEvent
+{
+    /// <summary>
+    /// Amount of firestacks changed. Should be a negative number.
+    /// </summary>
+    public float FireStacksAdjustment;
+
+    SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET;
+}
+
+/// <summary>
+/// A flammable entity has been extinguished.
+/// </summary>
+/// <remarks>
+/// This can occur on both <c>Flammable</c> entities as well as <see cref="SmokableComponent"/>.
+/// </remarks>
+/// <seealso cref="ExtinguishEvent"/>
+[ByRefEvent]
+public struct ExtinguishedEvent;
+
+/// <summary>
+/// A flammable entity has been ignited.
+/// </summary>
+/// <remarks>
+/// This can occur on both <c>Flammable</c> entities as well as <see cref="SmokableComponent"/>.
+/// </remarks>
+[ByRefEvent]
+public struct IgnitedEvent;
index a0e02cf2e1ad0e5ee0d1d0b77762322727af64f6..742e63250101fcf054915f994ac56767868f927d 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Atmos;
 using Content.Shared.Camera;
 using Content.Shared.Hands.Components;
 using Content.Shared.Movement.Systems;
@@ -11,14 +12,31 @@ public abstract partial class SharedHandsSystem
         SubscribeLocalEvent<HandsComponent, GetEyeOffsetRelayedEvent>(RelayEvent);
         SubscribeLocalEvent<HandsComponent, GetEyePvsScaleRelayedEvent>(RelayEvent);
         SubscribeLocalEvent<HandsComponent, RefreshMovementSpeedModifiersEvent>(RelayEvent);
+
+        // By-ref events.
+        SubscribeLocalEvent<HandsComponent, ExtinguishEvent>(RefRelayEvent);
     }
 
     private void RelayEvent<T>(Entity<HandsComponent> entity, ref T args) where T : EntityEventArgs
+    {
+        CoreRelayEvent(entity, ref args);
+    }
+
+    private void RefRelayEvent<T>(Entity<HandsComponent> entity, ref T args)
+    {
+        var ev = CoreRelayEvent(entity, ref args);
+        args = ev.Args;
+    }
+
+    private HeldRelayedEvent<T> CoreRelayEvent<T>(Entity<HandsComponent> entity, ref T args)
     {
         var ev = new HeldRelayedEvent<T>(args);
+
         foreach (var held in EnumerateHeld(entity, entity.Comp))
         {
             RaiseLocalEvent(held, ref ev);
         }
+
+        return ev;
     }
 }
index fada9822a39177b81b7a2ee2bc30a844cf435a54..8fac406eb558ef3feb4678d2f9fa183853a6051c 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Shared.Armor;
+using Content.Shared.Atmos;
 using Content.Shared.Chat;
 using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Hypospray.Events;
@@ -49,6 +50,7 @@ public partial class InventorySystem
         SubscribeLocalEvent<InventoryComponent, GetSpeedModifierContactCapEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, GetSlowedOverSlipperyModifierEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, ModifySlowOnDamageSpeedEvent>(RefRelayInventoryEvent);
+        SubscribeLocalEvent<InventoryComponent, ExtinguishEvent>(RefRelayInventoryEvent);
 
         // Eye/vision events
         SubscribeLocalEvent<InventoryComponent, CanSeeAttemptEvent>(RelayInventoryEvent);
diff --git a/Content.Shared/Inventory/RelaySubscriptionHelpers.cs b/Content.Shared/Inventory/RelaySubscriptionHelpers.cs
new file mode 100644 (file)
index 0000000..e905231
--- /dev/null
@@ -0,0 +1,123 @@
+using Content.Shared.Hands;
+
+namespace Content.Shared.Inventory;
+
+/// <summary>
+/// Helper functions for subscribing to component events that are also relayed via hands/inventory.
+/// </summary>
+public static class RelaySubscriptionHelpers
+{
+    /// <summary>
+    /// Subscribe to an event, along with different relayed event wrappers, in one call.
+    /// </summary>
+    /// <param name="subs">Subscriptions for the entity system we're subscribing on.</param>
+    /// <param name="handler">The event handler to be called for the event.</param>
+    /// <param name="baseEvent">Whether to subscribe the base event type.</param>
+    /// <param name="inventory">Whether to subscribe for <see cref="T:Content.Shared.Inventory.InventoryRelayedEvent`1"/>.</param>
+    /// <param name="held">Whether to subscribe for <see cref="T:Content.Shared.Hands.HeldRelayedEvent`1"/>.</param>
+    /// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.SubscribeLocalEvent``2(Robust.Shared.GameObjects.EntityEventRefHandler{``0,``1},System.Type[],System.Type[])"/>
+    public static void SubscribeWithRelay<TComp, TEvent>(
+        this EntitySystem.Subscriptions subs,
+        EntityEventRefHandler<TComp, TEvent> handler,
+        bool baseEvent = true,
+        bool inventory = true,
+        bool held = true)
+        where TEvent : notnull
+        where TComp : IComponent
+    {
+        if (baseEvent)
+            subs.SubscribeLocalEvent(handler);
+
+        if (inventory)
+        {
+            subs.SubscribeLocalEvent((Entity<TComp> ent, ref InventoryRelayedEvent<TEvent> ev) =>
+            {
+                handler(ent, ref ev.Args);
+            });
+        }
+
+        if (held)
+        {
+            subs.SubscribeLocalEvent((Entity<TComp> ent, ref HeldRelayedEvent<TEvent> ev) =>
+            {
+                handler(ent, ref ev.Args);
+            });
+        }
+    }
+
+    /// <summary>
+    /// Subscribe to an event, along with different relayed event wrappers, in one call.
+    /// </summary>
+    /// <param name="subs">Subscriptions for the entity system we're subscribing on.</param>
+    /// <param name="handler">The event handler to be called for the event.</param>
+    /// <param name="baseEvent">Whether to subscribe the base event type.</param>
+    /// <param name="inventory">Whether to subscribe for <see cref="T:Content.Shared.Inventory.InventoryRelayedEvent`1"/>.</param>
+    /// <param name="held">Whether to subscribe for <see cref="T:Content.Shared.Hands.HeldRelayedEvent`1"/>.</param>
+    /// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.SubscribeLocalEvent``2(Robust.Shared.GameObjects.ComponentEventHandler{``0,``1},System.Type[],System.Type[])"/>
+    public static void SubscribeWithRelay<TComp, TEvent>(
+        this EntitySystem.Subscriptions subs,
+        ComponentEventHandler<TComp, TEvent> handler,
+        bool baseEvent = true,
+        bool inventory = true,
+        bool held = true)
+        where TEvent : notnull
+        where TComp : IComponent
+    {
+        if (baseEvent)
+            subs.SubscribeLocalEvent(handler);
+
+        if (inventory)
+        {
+            subs.SubscribeLocalEvent((EntityUid uid, TComp component, InventoryRelayedEvent<TEvent> args) =>
+            {
+                handler(uid, component, args.Args);
+            });
+        }
+
+        if (held)
+        {
+            subs.SubscribeLocalEvent((EntityUid uid, TComp component, HeldRelayedEvent<TEvent> args) =>
+            {
+                handler(uid, component, args.Args);
+            });
+        }
+    }
+
+    /// <summary>
+    /// Subscribe to an event, along with different relayed event wrappers, in one call.
+    /// </summary>
+    /// <param name="subs">Subscriptions for the entity system we're subscribing on.</param>
+    /// <param name="handler">The event handler to be called for the event.</param>
+    /// <param name="baseEvent">Whether to subscribe the base event type.</param>
+    /// <param name="inventory">Whether to subscribe for <see cref="T:Content.Shared.Inventory.InventoryRelayedEvent`1"/>.</param>
+    /// <param name="held">Whether to subscribe for <see cref="T:Content.Shared.Hands.HeldRelayedEvent`1"/>.</param>
+    /// <seealso cref="M:Robust.Shared.GameObjects.EntitySystem.SubscribeLocalEvent``2(Robust.Shared.GameObjects.ComponentEventRefHandler{``0,``1},System.Type[],System.Type[])"/>
+    public static void SubscribeWithRelay<TComp, TEvent>(
+        this EntitySystem.Subscriptions subs,
+        ComponentEventRefHandler<TComp, TEvent> handler,
+        bool baseEvent = true,
+        bool inventory = true,
+        bool held = true)
+        where TEvent : notnull
+        where TComp : IComponent
+    {
+        if (baseEvent)
+            subs.SubscribeLocalEvent(handler);
+
+        if (inventory)
+        {
+            subs.SubscribeLocalEvent((EntityUid uid, TComp component, ref InventoryRelayedEvent<TEvent> args) =>
+            {
+                handler(uid, component, ref args.Args);
+            });
+        }
+
+        if (held)
+        {
+            subs.SubscribeLocalEvent((EntityUid uid, TComp component, ref HeldRelayedEvent<TEvent> args) =>
+            {
+                handler(uid, component, ref args.Args);
+            });
+        }
+    }
+}
index 90d1fff0bcc04cd59af3ea4210cf2d151363dad3..84813154a81c4207466081d093c2c8f9528bb7d0 100644 (file)
@@ -4,6 +4,10 @@
   parent: BaseItem
   abstract: true
   components:
+  - type: Reactive
+    groups:
+      Extinguish: [ Touch ]
+  - type: ExtinguishableSetCollisionWake
   - type: Smokable
   - type: Sprite
   - type: Appearance
index 55d3ecb9d3504482af1ba4dd1f9a6f54d3d71b70..b33d83f218760680f327a44742a3bacf1cc19e87 100644 (file)
@@ -27,6 +27,7 @@
           variation: 0.05
           volume: 10
     - type: UseDelay
+    - type: ExtinguishableSetCollisionWake
     - type: Flammable
       fireSpread: false
       canResistFire: false
index 998d3ecf03ea89b6aac816bf90f17c851348216f..2f91f0c0f8223515a7c1c96ba5e1f234ef00b21f 100644 (file)
         mask:
         - FullTileMask
         - Opaque
+        layer:
+        - ItemMask
   - type: Appearance
   - type: VaporVisuals