From: Pieter-Jan Briers Date: Mon, 14 Apr 2025 09:00:47 +0000 (+0200) Subject: Fire extinguishers can now extinguish items, including when held/worn (#36267) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=843d79be5f49855d14dc2875e879583b17e9cd4d;p=space-station-14.git Fire extinguishers can now extinguish items, including when held/worn (#36267) * 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 --- diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 412f0d476d..8681fc635c 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -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(OnTileFire); SubscribeLocalEvent(OnRejuvenate); SubscribeLocalEvent(OnResistFireAlert); + Subs.SubscribeWithRelay(OnExtinguishEvent); SubscribeLocalEvent(IgniteOnCollide); SubscribeLocalEvent(OnIgniteLand); @@ -84,6 +86,14 @@ namespace Content.Server.Atmos.EntitySystems SubscribeLocalEvent(OnDamageChanged); } + private void OnExtinguishEvent(Entity 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); diff --git a/Content.Server/EntityEffects/Effects/ExtinguishReaction.cs b/Content.Server/EntityEffects/Effects/ExtinguishReaction.cs index 6d7e7c2fcd..d36ac9a576 100644 --- a/Content.Server/EntityEffects/Effects/ExtinguishReaction.cs +++ b/Content.Server/EntityEffects/Effects/ExtinguishReaction.cs @@ -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.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); } } } diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs index 0d637139d8..d2074a3d82 100644 --- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs @@ -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(OnSmokableIsHotEvent); SubscribeLocalEvent(OnSmokableShutdownEvent); SubscribeLocalEvent(OnSmokeableEquipEvent); + Subs.SubscribeWithRelay(OnExtinguishEvent); InitializeCigars(); InitializePipes(); InitializeVapes(); } + private void OnExtinguishEvent(Entity 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 entity, ref IsHotEvent args) diff --git a/Content.Shared/Atmos/Components/ExtinguishableSetCollisionWakeComponent.cs b/Content.Shared/Atmos/Components/ExtinguishableSetCollisionWakeComponent.cs new file mode 100644 index 0000000000..19e471f0e5 --- /dev/null +++ b/Content.Shared/Atmos/Components/ExtinguishableSetCollisionWakeComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Atmos.Components; + +/// +/// Makes entities with extinguishing behavior automatically enable/disable , +/// so they can be extinguished with fire extinguishers. +/// +[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 index 0000000000..107ac5efd7 --- /dev/null +++ b/Content.Shared/Atmos/EntitySystems/ExtinguishableSetCollisionWakeSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Atmos.Components; + +namespace Content.Shared.Atmos.EntitySystems; + +/// +/// Implements . +/// +public sealed class ExtinguishableSetCollisionWakeSystem : EntitySystem +{ + [Dependency] + private readonly CollisionWakeSystem _collisionWake = null!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandleExtinguished); + SubscribeLocalEvent(HandleIgnited); + } + + private void HandleExtinguished(Entity ent, ref ExtinguishedEvent args) + { + _collisionWake.SetEnabled(ent, true); + } + + private void HandleIgnited(Entity 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 index 0000000000..4e19ef6147 --- /dev/null +++ b/Content.Shared/Atmos/FireEvents.cs @@ -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. + +/// +/// An entity has had an existing effect applied to it. +/// +/// +/// This does not necessarily mean the effect is strong enough to fully extinguish the entity in one go. +/// +[ByRefEvent] +public struct ExtinguishEvent : IInventoryRelayEvent +{ + /// + /// Amount of firestacks changed. Should be a negative number. + /// + public float FireStacksAdjustment; + + SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET; +} + +/// +/// A flammable entity has been extinguished. +/// +/// +/// This can occur on both Flammable entities as well as . +/// +/// +[ByRefEvent] +public struct ExtinguishedEvent; + +/// +/// A flammable entity has been ignited. +/// +/// +/// This can occur on both Flammable entities as well as . +/// +[ByRefEvent] +public struct IgnitedEvent; diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs index a0e02cf2e1..742e632501 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs @@ -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(RelayEvent); SubscribeLocalEvent(RelayEvent); SubscribeLocalEvent(RelayEvent); + + // By-ref events. + SubscribeLocalEvent(RefRelayEvent); } private void RelayEvent(Entity entity, ref T args) where T : EntityEventArgs + { + CoreRelayEvent(entity, ref args); + } + + private void RefRelayEvent(Entity entity, ref T args) + { + var ev = CoreRelayEvent(entity, ref args); + args = ev.Args; + } + + private HeldRelayedEvent CoreRelayEvent(Entity entity, ref T args) { var ev = new HeldRelayedEvent(args); + foreach (var held in EnumerateHeld(entity, entity.Comp)) { RaiseLocalEvent(held, ref ev); } + + return ev; } } diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index fada9822a3..8fac406eb5 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -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(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); // Eye/vision events SubscribeLocalEvent(RelayInventoryEvent); diff --git a/Content.Shared/Inventory/RelaySubscriptionHelpers.cs b/Content.Shared/Inventory/RelaySubscriptionHelpers.cs new file mode 100644 index 0000000000..e905231539 --- /dev/null +++ b/Content.Shared/Inventory/RelaySubscriptionHelpers.cs @@ -0,0 +1,123 @@ +using Content.Shared.Hands; + +namespace Content.Shared.Inventory; + +/// +/// Helper functions for subscribing to component events that are also relayed via hands/inventory. +/// +public static class RelaySubscriptionHelpers +{ + /// + /// Subscribe to an event, along with different relayed event wrappers, in one call. + /// + /// Subscriptions for the entity system we're subscribing on. + /// The event handler to be called for the event. + /// Whether to subscribe the base event type. + /// Whether to subscribe for . + /// Whether to subscribe for . + /// + public static void SubscribeWithRelay( + this EntitySystem.Subscriptions subs, + EntityEventRefHandler 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 ent, ref InventoryRelayedEvent ev) => + { + handler(ent, ref ev.Args); + }); + } + + if (held) + { + subs.SubscribeLocalEvent((Entity ent, ref HeldRelayedEvent ev) => + { + handler(ent, ref ev.Args); + }); + } + } + + /// + /// Subscribe to an event, along with different relayed event wrappers, in one call. + /// + /// Subscriptions for the entity system we're subscribing on. + /// The event handler to be called for the event. + /// Whether to subscribe the base event type. + /// Whether to subscribe for . + /// Whether to subscribe for . + /// + public static void SubscribeWithRelay( + this EntitySystem.Subscriptions subs, + ComponentEventHandler 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 args) => + { + handler(uid, component, args.Args); + }); + } + + if (held) + { + subs.SubscribeLocalEvent((EntityUid uid, TComp component, HeldRelayedEvent args) => + { + handler(uid, component, args.Args); + }); + } + } + + /// + /// Subscribe to an event, along with different relayed event wrappers, in one call. + /// + /// Subscriptions for the entity system we're subscribing on. + /// The event handler to be called for the event. + /// Whether to subscribe the base event type. + /// Whether to subscribe for . + /// Whether to subscribe for . + /// + public static void SubscribeWithRelay( + this EntitySystem.Subscriptions subs, + ComponentEventRefHandler 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 args) => + { + handler(uid, component, ref args.Args); + }); + } + + if (held) + { + subs.SubscribeLocalEvent((EntityUid uid, TComp component, ref HeldRelayedEvent args) => + { + handler(uid, component, ref args.Args); + }); + } + } +} diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml index 90d1fff0bc..84813154a8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/base_smokeables.yml @@ -4,6 +4,10 @@ parent: BaseItem abstract: true components: + - type: Reactive + groups: + Extinguish: [ Touch ] + - type: ExtinguishableSetCollisionWake - type: Smokable - type: Sprite - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Misc/candles.yml b/Resources/Prototypes/Entities/Objects/Misc/candles.yml index 55d3ecb9d3..b33d83f218 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/candles.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/candles.yml @@ -27,6 +27,7 @@ variation: 0.05 volume: 10 - type: UseDelay + - type: ExtinguishableSetCollisionWake - type: Flammable fireSpread: false canResistFire: false diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml index 998d3ecf03..2f91f0c0f8 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml @@ -130,6 +130,8 @@ mask: - FullTileMask - Opaque + layer: + - ItemMask - type: Appearance - type: VaporVisuals