using System.Linq; using System.Numerics; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Systems; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Shared._DV.NodeCrawl; using Content.Shared.Atmos; using Content.Shared.NodeContainer; using Content.Shared.Polymorph; using Content.Shared.Zombies; using Robust.Shared.Reflection; using Robust.Shared.Utility; namespace Content.Server._DV.NodeCrawl; public sealed class NodeCrawlSystem : SharedNodeCrawlSystem { [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly BarotraumaSystem _barotrauma = default!; [Dependency] private readonly IReflectionManager _reflection = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnNodeGroupsRebuilt); SubscribeLocalEvent(OnInhaleLocation); SubscribeLocalEvent(OnExhaleLocation); SubscribeLocalEvent(OnGetAir); SubscribeLocalEvent(OnPolymorph); SubscribeLocalEvent(OnZombify); } private GasMixture? GetExistingAir(Entity movement) { if (movement.Comp.Node is not { } node) return null; if (!TryComp(node, out var nodeContainer)) return null; foreach (var containedNode in nodeContainer.Nodes.Values) { if (containedNode is not PipeNode pipe) continue; return pipe.Air; } return null; } protected override void SetupAir(Entity movement) { base.SetupAir(movement); if (movement.Comp.HeldCrawler is not { } heldCrawler || !TryComp(heldCrawler, out var barotrauma)) { return; } if (GetExistingAir(movement) is { } existingAir) { var pressure = existingAir.Pressure switch { // Adjust pressure based on equipment. Works differently depending on if it's "high" or "low". <= Atmospherics.WarningLowPressure => _barotrauma.GetFeltLowPressure(heldCrawler, barotrauma, existingAir.Pressure), >= Atmospherics.WarningHighPressure => _barotrauma.GetFeltHighPressure(heldCrawler, barotrauma, existingAir.Pressure), _ => existingAir.Pressure, }; if (pressure is >= Atmospherics.HazardLowPressure and <= Atmospherics.HazardHighPressure) return; } var xform = Transform(movement); var indices = _transform.GetGridTilePositionOrDefault((movement, xform)); if (_atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, indices, true) is { Temperature: > 0f } environment) { // we want to get one atmosphere's worth of pressure in the air volume of the component // we need to take an amount of moles from the gas, so // PV = nRT // (Atmospherics.OneAtmosphere) * (movement.Comp.AirVolume) = (amount of mols) * R * (environment.Temperature) // solve for amount of mols // amount of mols = (Atmospherics.OneAtmosphere) * (movement.Comp.AirVolume) / R * (environment.Temperature) var transferMoles = Atmospherics.OneAtmosphere * movement.Comp.AirVolume / (environment.Temperature * Atmospherics.R); movement.Comp.Air = new(movement.Comp.AirVolume); _atmosphere.Merge(movement.Comp.Air, environment.Remove(transferMoles)); } } protected override void EjectAir(Entity movement) { base.EjectAir(movement); if (movement.Comp.Air is not { } air) return; var xform = Transform(movement); var indices = _transform.GetGridTilePositionOrDefault((movement, xform)); if (_atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, indices, true) is not { } environment) return; _atmosphere.Merge(environment, air); air.Clear(); } private Entity? GetMovement(Entity crawler) { if (!TryComp(crawler.Comp.Mover, out var mover)) return null; return new(crawler.Comp.Mover.Value, mover); } private Entity? GetNodeContainer(Entity crawler) { if (GetMovement(crawler) is not { } mover || mover.Comp.Node is not { } node) return null; if (!TryComp(node, out var nodeContainer)) return null; return (node, nodeContainer); } private GasMixture? GetAir(Entity crawler) { if (GetMovement(crawler)?.Comp.Air is { } air) return air; if (GetNodeContainer(crawler) is not { } nodeContainer) return null; foreach (var containedNode in nodeContainer.Comp.Nodes.Values) { if (containedNode is not PipeNode pipe) continue; return pipe.Air; } return null; } private void OnInhaleLocation(Entity ent, ref InhaleLocationEvent args) { if (GetAir(ent) is not { } air) return; args.Gas = air; } private void OnExhaleLocation(Entity ent, ref ExhaleLocationEvent args) { if (GetAir(ent) is not { } air) return; args.Gas = air; } private void OnGetAir(Entity ent, ref AtmosExposedGetAirEvent args) { if (args.Handled || GetAir(ent) is not { } air) return; args.Gas = air; args.Handled = true; } private void OnNodeGroupsRebuilt(Entity ent, ref NodeGroupsRebuilt args) { if (!TryComp(ent, out var nodeContainer)) return; // ugly workaround for https://github.com/space-wizards/RobustToolbox/issues/6694 not letting List // get serialized properly var possibleTypes = ent.Comp.ReachableNodeTypes.Select(it => _reflection.GetType(it)).ToList(); ent.Comp.DeadEnd = false; var set = new HashSet(); foreach (var node in nodeContainer.Nodes.Values) { foreach (var reachable in node.ReachableNodes) { if (possibleTypes.Count != 0 && !possibleTypes.TrueForAll(type => reachable.GetType() == type)) { continue; } DebugTools.Assert(HasComp(reachable.Owner), $"Node {ToPrettyString(reachable.Owner)} reachable from {ToPrettyString(ent)} should be a crawlable node, but wasn't"); set.Add(reachable.Owner); } if (node is PipeNode pipeNode && node.ReachableNodes.Count != BitOperations.PopCount((uint)pipeNode.CurrentPipeDirection)) { ent.Comp.DeadEnd = true; } } ent.Comp.ReachableNodes = set; Dirty(ent); } /// /// Lengthens the amount of time that it takes a zombified vent crawling mob to get back into the vent. /// They are really hard to kill otherwise. /// /// /// private void OnZombify(Entity ent, ref EntityZombifiedEvent args) { ent.Comp.EnterDelay = ent.Comp.ZombieEnterDelay; Dirty(ent); } /// /// Exit on polymorph or else the entity's movement bugs out. /// /// /// private void OnPolymorph(Entity ent, ref PolymorphActionEvent args) { if (ent.Comp.Mover.HasValue) ExitNodeCrawl(ent); } }