Files
tbd-station-14/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
Pieter-Jan Briers 843d79be5f 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 <comedian_vs_clown@hotmail.com>
2025-04-14 19:00:47 +10:00

185 lines
7.1 KiB
C#

using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.EntitySystems;
using Content.Server.Forensics;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Nutrition.Components;
using Content.Shared.Smoking;
using Content.Shared.Temperature;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using System.Linq;
using Content.Shared.Atmos;
namespace Content.Server.Nutrition.EntitySystems
{
public sealed partial class SmokingSystem : EntitySystem
{
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedItemSystem _items = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!;
private const float UpdateTimer = 3f;
private float _timer;
/// <summary>
/// We keep a list of active smokables, because iterating all existing smokables would be dumb.
/// </summary>
private readonly HashSet<EntityUid> _active = new();
public override void Initialize()
{
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)
{
if (!Resolve(uid, ref smokable, ref appearance, ref clothing))
return;
smokable.State = state;
_appearance.SetData(uid, SmokingVisuals.Smoking, state, appearance);
var newState = state switch
{
SmokableState.Lit => smokable.LitPrefix,
SmokableState.Burnt => smokable.BurntPrefix,
_ => smokable.UnlitPrefix
};
_clothing.SetEquippedPrefix(uid, newState, clothing);
_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)
{
args.IsHot = entity.Comp.State == SmokableState.Lit;
}
private void OnSmokableShutdownEvent(Entity<SmokableComponent> entity, ref ComponentShutdown args)
{
_active.Remove(entity);
}
private void OnSmokeableEquipEvent(Entity<SmokableComponent> entity, ref GotEquippedEvent args)
{
if (args.Slot == "mask")
{
_forensics.TransferDna(entity.Owner, args.Equipee, false);
}
}
public override void Update(float frameTime)
{
_timer += frameTime;
if (_timer < UpdateTimer)
return;
// TODO Use an "active smoke" component instead, EntityQuery over that.
foreach (var uid in _active.ToArray())
{
if (!TryComp(uid, out SmokableComponent? smokable))
{
_active.Remove(uid);
continue;
}
if (!_solutionContainerSystem.TryGetSolution(uid, smokable.Solution, out var soln, out var solution))
{
_active.Remove(uid);
continue;
}
if (smokable.ExposeTemperature > 0 && smokable.ExposeVolume > 0)
{
var transform = Transform(uid);
if (transform.GridUid is { } gridUid)
{
var position = _transformSystem.GetGridOrMapTilePosition(uid, transform);
_atmos.HotspotExpose(gridUid, position, smokable.ExposeTemperature, smokable.ExposeVolume, uid, true);
}
}
var inhaledSolution = _solutionContainerSystem.SplitSolution(soln.Value, smokable.InhaleAmount * _timer);
if (solution.Volume == FixedPoint2.Zero)
{
RaiseLocalEvent(uid, new SmokableSolutionEmptyEvent(), true);
}
if (inhaledSolution.Volume == FixedPoint2.Zero)
continue;
// This is awful. I hate this so much.
// TODO: Please, someone refactor containers and free me from this bullshit.
if (!_container.TryGetContainingContainer((uid, null, null), out var containerManager) ||
!(_inventorySystem.TryGetSlotEntity(containerManager.Owner, "mask", out var inMaskSlotUid) && inMaskSlotUid == uid) ||
!TryComp(containerManager.Owner, out BloodstreamComponent? bloodstream))
{
continue;
}
_reactiveSystem.DoEntityReaction(containerManager.Owner, inhaledSolution, ReactionMethod.Ingestion);
_bloodstreamSystem.TryAddToChemicals(containerManager.Owner, inhaledSolution, bloodstream);
}
_timer -= UpdateTimer;
}
}
/// <summary>
/// Directed event raised when the smokable solution is empty.
/// </summary>
public sealed class SmokableSolutionEmptyEvent : EntityEventArgs
{
}
}