Files
wwdpublic/Content.Server/Atmos/EntitySystems/IgniteFromGasSystem.cs
Skubman 618bb61d2b Allow Cloning Living People (#1679)
# Description

Allows living people to be cloned. Implements (but does not cherry-pick)
https://github.com/Goob-Station/Goob-Station/pull/932. Whether living
people can be cloned or not can be set with a new CVar
`cloning.allow_living_people`.

Also prevents Plasmamen from self-igniting while being cloned in a
cloning pod. (Lore reason: They are literally submerged in fluids while
being cloned)

Fixes a bug in `EndFailedCloning` that caused the server to crash if the
used biomass was too low:
02c020bb76

Fixed a bug that caused the cloning timer to be made twice:
d4620fdcef95e46769cf2dc4a5a02e204e197e00

## Media

Cloned living person

![2025-01-28-093759_hyprshot](https://github.com/user-attachments/assets/0af0737a-6f4d-40af-acf5-ce73754f1420)

# Changelog

🆑 Skubman
- tweak: You can now clone living people.
- tweak: Plasmamen no longer ignite while being cloned in a cloning pod.

(cherry picked from commit 695f47642c674961ca7dc8a343d64d0b2a816280)
2025-01-29 20:29:15 +03:00

138 lines
5.5 KiB
C#

using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Bed.Components;
using Content.Server.Cloning.Components;
using Content.Shared._Shitmed.Targeting;
using Content.Shared._Shitmed.Body.Events;
using Content.Shared.Body.Part;
using Content.Shared.Body.Systems;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
namespace Content.Server.Atmos.EntitySystems;
public sealed class IgniteFromGasSystem : EntitySystem
{
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
// All ignitions tick at the same time because FlammableSystem is also the same
private const float UpdateTimer = 1f;
private float _timer;
public override void Initialize()
{
SubscribeLocalEvent<FlammableComponent, BodyPartAddedEvent>(OnBodyPartAdded);
SubscribeLocalEvent<FlammableComponent, BodyPartAttachedEvent>(OnBodyPartAttached);
SubscribeLocalEvent<IgniteFromGasComponent, BodyPartRemovedEvent>(OnBodyPartRemoved);
SubscribeLocalEvent<IgniteFromGasComponent, BodyPartDroppedEvent>(OnBodyPartDropped);
SubscribeLocalEvent<IgniteFromGasImmunityComponent, GotEquippedEvent>(OnIgniteFromGasImmunityEquipped);
SubscribeLocalEvent<IgniteFromGasImmunityComponent, GotUnequippedEvent>(OnIgniteFromGasImmunityUnequipped);
}
private void OnBodyPartAdded(Entity<FlammableComponent> ent, ref BodyPartAddedEvent args) =>
HandleAddBodyPart(ent.Owner, args.Part);
private void OnBodyPartAttached(Entity<FlammableComponent> ent, ref BodyPartAttachedEvent args) =>
HandleAddBodyPart(ent.Owner, args.Part);
private void HandleAddBodyPart(EntityUid uid, Entity<BodyPartComponent> part)
{
if (!TryComp<IgniteFromGasPartComponent>(part, out var ignitePart) ||
_body.GetTargetBodyPart(part.Comp.PartType, part.Comp.Symmetry) is not { } targetBodyPart)
return;
if (!TryComp<IgniteFromGasComponent>(uid, out var ignite))
{
ignite = EnsureComp<IgniteFromGasComponent>(uid);
ignite.Gas = ignitePart.Gas;
}
ignite.IgnitableBodyParts[targetBodyPart] = ignitePart.FireStacks;
UpdateIgniteImmunity((uid, ignite));
}
private void OnBodyPartRemoved(Entity<IgniteFromGasComponent> ent, ref BodyPartRemovedEvent args) =>
HandleRemoveBodyPart(ent, args.Part);
private void OnBodyPartDropped(Entity<IgniteFromGasComponent> ent, ref BodyPartDroppedEvent args) =>
HandleRemoveBodyPart(ent, args.Part);
private void HandleRemoveBodyPart(Entity<IgniteFromGasComponent> ent, Entity<BodyPartComponent> part)
{
if (!HasComp<IgniteFromGasPartComponent>(part) ||
_body.GetTargetBodyPart(part.Comp.PartType, part.Comp.Symmetry) is not { } targetBodyPart)
return;
ent.Comp.IgnitableBodyParts.Remove(targetBodyPart);
if (ent.Comp.IgnitableBodyParts.Count == 0)
{
RemCompDeferred<IgniteFromGasComponent>(ent);
return;
}
UpdateIgniteImmunity((ent, ent.Comp));
}
private void OnIgniteFromGasImmunityEquipped(Entity<IgniteFromGasImmunityComponent> ent, ref GotEquippedEvent args) =>
UpdateIgniteImmunity(args.Equipee);
private void OnIgniteFromGasImmunityUnequipped(Entity<IgniteFromGasImmunityComponent> ent, ref GotUnequippedEvent args) =>
UpdateIgniteImmunity(args.Equipee);
public void UpdateIgniteImmunity(Entity<IgniteFromGasComponent?, InventoryComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false))
return;
var exposedBodyParts = new Dictionary<TargetBodyPart, float>(ent.Comp1.IgnitableBodyParts);
var containerSlotEnumerator = _inventory.GetSlotEnumerator((ent, ent.Comp2));
while (containerSlotEnumerator.NextItem(out var item, out _))
{
if (!TryComp<IgniteFromGasImmunityComponent>(item, out var immunity))
continue;
foreach (var immunePart in immunity.Parts)
exposedBodyParts.Remove(immunePart);
}
if (exposedBodyParts.Count == 0)
{
ent.Comp1.FireStacksPerUpdate = 0;
return;
}
ent.Comp1.FireStacksPerUpdate = ent.Comp1.BaseFireStacksPerUpdate + exposedBodyParts.Values.Sum();
}
public override void Update(float frameTime)
{
_timer += frameTime;
if (_timer < UpdateTimer)
return;
_timer -= UpdateTimer;
var enumerator = EntityQueryEnumerator<IgniteFromGasComponent, MobStateComponent, FlammableComponent>();
while (enumerator.MoveNext(out var uid, out var ignite, out var mobState, out var flammable))
{
if (ignite.FireStacksPerUpdate == 0 ||
mobState.CurrentState is MobState.Dead ||
HasComp<InStasisComponent>(uid) ||
HasComp<BeingClonedComponent>(uid) ||
_atmos.GetContainingMixture(uid, excite: true) is not { } gas ||
gas[(int) ignite.Gas] < ignite.MolesToIgnite
)
continue;
_flammable.AdjustFireStacks(uid, ignite.FireStacksPerUpdate, flammable);
_flammable.Ignite(uid, uid, flammable, ignoreFireProtection: true);
}
}
}