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