Atmos pipe rework (#3833)
* Initial * Cleanup a bunch of things * some changes dunno * RequireAnchored * a * stuff * more work * Lots of progress * delete pipe visualizer * a * b * pipenet and pipenode cleanup * Fixes * Adds GasValve * Adds GasMiner * Fix stuff, maybe? * More fixes * Ignored components on the client * Adds thermomachine behavior, change a bunch of stuff * Remove Anchored * some work, but it's shitcode * significantly more ECS * ECS AtmosDevices * Cleanup * fix appearance * when the pipe direction is sus * Gas tanks and canisters * pipe anchoring and stuff * coding is my passion * Unsafe pipes take longer to unanchor * turns out we're no longer using eris canisters * Gas canister inserted tank appearance, improvements * Work on a bunch of appearances * Scrubber appearance * Reorganize AtmosphereSystem.Piping into a bunch of different systems * Appearance for vent/scrubber/pump turns off when leaving atmosphere * ThermoMachine appearance * Cleanup gas tanks * Remove passive gate unused imports * remove old canister UI functionality * PipeNode environment air, make everything use AssumeAir instead of merging manually * a * Reorganize atmos to follow new structure * ????? * Canister UI, restructure client * Restructure shared * Fix build tho * listen, at least the canister UI works entirely... * fix build : ) * Atmos device prototypes have names and descriptions * gas canister ui slider doesn't jitter * trinary prototypes * sprite for miners * ignore components * fix YAML * Fix port system doing useless thing * Fix build * fix thinking moment * fix build again because * canister direction * pipenode is a word * GasTank Air will throw on invalid states * fix build.... * Unhardcode volume pump thresholds * Volume pump and filter take time into account * Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event * Gas tank node volume is set by initial mixtuer * I love node container
This commit is contained in:
committed by
GitHub
parent
cfc3f2e7fc
commit
a2b737d945
171
Content.Server/Atmos/Components/AirtightComponent.cs
Normal file
171
Content.Server/Atmos/Components/AirtightComponent.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
#nullable enable
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class AirtightComponent : Component, IMapInit
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private (GridId, Vector2i) _lastPosition;
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override string Name => "Airtight";
|
||||
|
||||
[DataField("airBlockedDirection", customTypeSerializer: typeof(FlagSerializer<AtmosDirectionFlags>))]
|
||||
[ViewVariables]
|
||||
private int _initialAirBlockedDirection = (int) AtmosDirection.All;
|
||||
|
||||
[ViewVariables]
|
||||
private int _currentAirBlockedDirection;
|
||||
|
||||
[DataField("airBlocked")]
|
||||
private bool _airBlocked = true;
|
||||
|
||||
[DataField("fixVacuum")]
|
||||
private bool _fixVacuum = true;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("rotateAirBlocked")]
|
||||
private bool _rotateAirBlocked = true;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("fixAirBlockedDirectionInitialize")]
|
||||
private bool _fixAirBlockedDirectionInitialize = true;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("noAirWhenFullyAirBlocked")]
|
||||
public bool NoAirWhenFullyAirBlocked { get; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AirBlocked
|
||||
{
|
||||
get => _airBlocked;
|
||||
set
|
||||
{
|
||||
_airBlocked = value;
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
public AtmosDirection AirBlockedDirection
|
||||
{
|
||||
get => (AtmosDirection)_currentAirBlockedDirection;
|
||||
set
|
||||
{
|
||||
_currentAirBlockedDirection = (int) value;
|
||||
_initialAirBlockedDirection = (int)Rotate(AirBlockedDirection, -Owner.Transform.LocalRotation);
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool FixVacuum => _fixVacuum;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
|
||||
if (_fixAirBlockedDirectionInitialize)
|
||||
RotateEvent(new RotateEvent(Owner, Angle.Zero, Owner.Transform.WorldRotation));
|
||||
|
||||
// Adding this component will immediately anchor the entity, because the atmos system
|
||||
// requires airtight entities to be anchored for performance.
|
||||
Owner.Transform.Anchored = true;
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
public void RotateEvent(RotateEvent ev)
|
||||
{
|
||||
if (!_rotateAirBlocked || ev.Sender != Owner || _initialAirBlockedDirection == (int)AtmosDirection.Invalid)
|
||||
return;
|
||||
|
||||
_currentAirBlockedDirection = (int) Rotate((AtmosDirection)_initialAirBlockedDirection, ev.NewRotation);
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)
|
||||
{
|
||||
var newAirBlockedDirs = AtmosDirection.Invalid;
|
||||
|
||||
if (myAngle == Angle.Zero)
|
||||
return myDirection;
|
||||
|
||||
// TODO ATMOS MULTIZ When we make multiZ atmos, special case this.
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
if (!myDirection.IsFlagSet(direction)) continue;
|
||||
var angle = direction.ToAngle();
|
||||
angle += myAngle;
|
||||
newAirBlockedDirs |= angle.ToAtmosDirectionCardinal();
|
||||
}
|
||||
|
||||
return newAirBlockedDirs;
|
||||
}
|
||||
|
||||
public void MapInit()
|
||||
{
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_airBlocked = false;
|
||||
|
||||
InvalidatePosition(_lastPosition.Item1, _lastPosition.Item2);
|
||||
|
||||
if (_fixVacuum)
|
||||
{
|
||||
_atmosphereSystem.GetGridAtmosphere(_lastPosition.Item1)?.FixVacuum(_lastPosition.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSnapGridMove(SnapGridPositionChangedEvent ev)
|
||||
{
|
||||
// Invalidate old position.
|
||||
InvalidatePosition(ev.OldGrid, ev.OldPosition);
|
||||
|
||||
// Update and invalidate new position.
|
||||
_lastPosition = (ev.NewGrid, ev.Position);
|
||||
InvalidatePosition(ev.NewGrid, ev.Position);
|
||||
}
|
||||
|
||||
private void UpdatePosition()
|
||||
{
|
||||
if (!Owner.Transform.Anchored || !Owner.Transform.GridID.IsValid())
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
InvalidatePosition(_lastPosition.Item1, _lastPosition.Item2);
|
||||
}
|
||||
|
||||
private void InvalidatePosition(GridId gridId, Vector2i pos)
|
||||
{
|
||||
if (!gridId.IsValid())
|
||||
return;
|
||||
|
||||
var gridAtmos = _atmosphereSystem.GetGridAtmosphere(gridId);
|
||||
|
||||
gridAtmos?.UpdateAdjacentBits(pos);
|
||||
gridAtmos?.Invalidate(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Content.Server/Atmos/Components/AtmosExposedComponent.cs
Normal file
44
Content.Server/Atmos/Components/AtmosExposedComponent.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#nullable enable
|
||||
using Content.Server.Temperature.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents that entity can be exposed to Atmos
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class AtmosExposedComponent
|
||||
: Component
|
||||
{
|
||||
public override string Name => "AtmosExposed";
|
||||
|
||||
[ViewVariables]
|
||||
[ComponentDependency] private readonly TemperatureComponent? _temperatureComponent = null;
|
||||
|
||||
[ViewVariables]
|
||||
[ComponentDependency] private readonly BarotraumaComponent? _barotraumaComponent = null;
|
||||
|
||||
[ViewVariables]
|
||||
[ComponentDependency] private readonly FlammableComponent? _flammableComponent = null;
|
||||
|
||||
public void Update(TileAtmosphere tile, float frameDelta)
|
||||
{
|
||||
if (_temperatureComponent != null)
|
||||
{
|
||||
if (tile.Air != null)
|
||||
{
|
||||
var temperatureDelta = tile.Air.Temperature - _temperatureComponent.CurrentTemperature;
|
||||
var heat = temperatureDelta * (tile.Air.HeatCapacity * _temperatureComponent.HeatCapacity / (tile.Air.HeatCapacity + _temperatureComponent.HeatCapacity));
|
||||
_temperatureComponent.ReceiveHeat(heat);
|
||||
}
|
||||
_temperatureComponent.Update();
|
||||
}
|
||||
|
||||
_barotraumaComponent?.Update(tile.Air?.Pressure ?? 0);
|
||||
|
||||
_flammableComponent?.Update(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Content.Server/Atmos/Components/AtmosPlaqueComponent.cs
Normal file
106
Content.Server/Atmos/Components/AtmosPlaqueComponent.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class AtmosPlaqueComponent : Component, IMapInit
|
||||
{
|
||||
public override string Name => "AtmosPlaque";
|
||||
|
||||
[DataField("plaqueType")]
|
||||
private PlaqueType _type = PlaqueType.Unset;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public PlaqueType Type
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
_type = value;
|
||||
UpdateSign();
|
||||
}
|
||||
}
|
||||
|
||||
public void MapInit()
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var rand = random.Next(100);
|
||||
// Let's not pat ourselves on the back too hard.
|
||||
// 1% chance of zumos
|
||||
if (rand == 0) Type = PlaqueType.Zumos;
|
||||
// 9% FEA
|
||||
else if (rand <= 10) Type = PlaqueType.Fea;
|
||||
// 45% ZAS
|
||||
else if (rand <= 55) Type = PlaqueType.Zas;
|
||||
// 45% LINDA
|
||||
else Type = PlaqueType.Linda;
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
UpdateSign();
|
||||
}
|
||||
|
||||
private void UpdateSign()
|
||||
{
|
||||
if (!Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.Description = _type switch
|
||||
{
|
||||
PlaqueType.Zumos =>
|
||||
"This plaque commemorates the rise of the Atmos ZUM division. May they carry the torch that the Atmos ZAS, LINDA and FEA divisions left behind.",
|
||||
PlaqueType.Fea =>
|
||||
"This plaque commemorates the fall of the Atmos FEA division. For all the charred, dizzy, and brittle men who have died in its hands.",
|
||||
PlaqueType.Linda =>
|
||||
"This plaque commemorates the fall of the Atmos LINDA division. For all the charred, dizzy, and brittle men who have died in its hands.",
|
||||
PlaqueType.Zas =>
|
||||
"This plaque commemorates the fall of the Atmos ZAS division. For all the charred, dizzy, and brittle men who have died in its hands.",
|
||||
PlaqueType.Unset => "Uhm",
|
||||
_ => "Uhm",
|
||||
};
|
||||
|
||||
Owner.Name = _type switch
|
||||
{
|
||||
PlaqueType.Zumos =>
|
||||
"ZUM Atmospherics Division plaque",
|
||||
PlaqueType.Fea =>
|
||||
"FEA Atmospherics Division plaque",
|
||||
PlaqueType.Linda =>
|
||||
"LINDA Atmospherics Division plaque",
|
||||
PlaqueType.Zas =>
|
||||
"ZAS Atmospherics Division plaque",
|
||||
PlaqueType.Unset => "Uhm",
|
||||
_ => "Uhm",
|
||||
};
|
||||
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
var state = _type == PlaqueType.Zumos ? "zumosplaque" : "atmosplaque";
|
||||
|
||||
appearance.SetData(AtmosPlaqueVisuals.State, state);
|
||||
}
|
||||
}
|
||||
|
||||
public enum PlaqueType
|
||||
{
|
||||
Unset = 0,
|
||||
Zumos,
|
||||
Fea,
|
||||
Linda,
|
||||
Zas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If you get the ZUM plaque it means your round will be blessed with good engineering luck.
|
||||
90
Content.Server/Atmos/Components/BarotraumaComponent.cs
Normal file
90
Content.Server/Atmos/Components/BarotraumaComponent.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.Alert;
|
||||
using Content.Server.Pressure;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Barotrauma: injury because of changes in air pressure.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BarotraumaComponent : Component
|
||||
{
|
||||
public override string Name => "Barotrauma";
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float airPressure)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable)) return;
|
||||
|
||||
var status = Owner.GetComponentOrNull<ServerAlertsComponent>();
|
||||
var highPressureMultiplier = 1f;
|
||||
var lowPressureMultiplier = 1f;
|
||||
|
||||
foreach (var protection in Owner.GetAllComponents<IPressureProtection>())
|
||||
{
|
||||
highPressureMultiplier *= protection.HighPressureMultiplier;
|
||||
lowPressureMultiplier *= protection.LowPressureMultiplier;
|
||||
}
|
||||
|
||||
var pressure = MathF.Max(airPressure, 1f);
|
||||
|
||||
switch (pressure)
|
||||
{
|
||||
// Low pressure.
|
||||
case var p when p <= Atmospherics.WarningLowPressure:
|
||||
pressure *= lowPressureMultiplier;
|
||||
|
||||
if(pressure > Atmospherics.WarningLowPressure)
|
||||
goto default;
|
||||
|
||||
damageable.ChangeDamage(DamageType.Blunt, Atmospherics.LowPressureDamage, false, Owner);
|
||||
|
||||
if (status == null) break;
|
||||
|
||||
if (pressure <= Atmospherics.HazardLowPressure)
|
||||
{
|
||||
status.ShowAlert(AlertType.LowPressure, 2);
|
||||
break;
|
||||
}
|
||||
|
||||
status.ShowAlert(AlertType.LowPressure, 1);
|
||||
break;
|
||||
|
||||
// High pressure.
|
||||
case var p when p >= Atmospherics.WarningHighPressure:
|
||||
pressure *= highPressureMultiplier;
|
||||
|
||||
if(pressure < Atmospherics.WarningHighPressure)
|
||||
goto default;
|
||||
|
||||
var damage = (int) MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
|
||||
|
||||
damageable.ChangeDamage(DamageType.Blunt, damage, false, Owner);
|
||||
|
||||
if (status == null) break;
|
||||
|
||||
if (pressure >= Atmospherics.HazardHighPressure)
|
||||
{
|
||||
status.ShowAlert(AlertType.HighPressure, 2);
|
||||
break;
|
||||
}
|
||||
|
||||
status.ShowAlert(AlertType.HighPressure, 1);
|
||||
break;
|
||||
|
||||
// Normal pressure.
|
||||
default:
|
||||
status?.ClearAlertCategory(AlertCategory.Pressure);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// This component is used as a base class for classes like SolarControlConsoleComponent.
|
||||
/// These components operate the server-side logic for the "primary UI" of a computer.
|
||||
/// That means showing the UI when a user activates it, for example.
|
||||
/// </summary>
|
||||
public abstract class BaseComputerUserInterfaceComponent : Component
|
||||
{
|
||||
protected readonly object UserInterfaceKey;
|
||||
|
||||
[ViewVariables] protected BoundUserInterface? UserInterface => Owner.GetUIOrNull(UserInterfaceKey);
|
||||
[ViewVariables] public bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
|
||||
public BaseComputerUserInterfaceComponent(object key)
|
||||
{
|
||||
UserInterfaceKey = key;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (UserInterface != null)
|
||||
UserInterface.OnReceiveMessage += OnReceiveUIMessageCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal callback used to grab session and session attached entity before any more work is done.
|
||||
/// This is so that sessionEntity is always available to checks up and down the line.
|
||||
/// </summary>
|
||||
private void OnReceiveUIMessageCallback(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
var session = obj.Session;
|
||||
var sessionEntity = session.AttachedEntity;
|
||||
if (sessionEntity == null)
|
||||
return; // No session entity, so we're probably not able to touch this.
|
||||
OnReceiveUnfilteredUserInterfaceMessage(obj, sessionEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to handle messages from the UI before filtering them.
|
||||
/// Calling base is necessary if you want this class to have any meaning.
|
||||
/// </summary>
|
||||
protected void OnReceiveUnfilteredUserInterfaceMessage(ServerBoundUserInterfaceMessage obj, IEntity sessionEntity)
|
||||
{
|
||||
// "Across all computers" "anti-cheats" ought to be put here or at some parent level (BaseDeviceUserInterfaceComponent?)
|
||||
// Determine some facts about the session.
|
||||
// Powered?
|
||||
if (!Powered)
|
||||
{
|
||||
sessionEntity.PopupMessageCursor(Loc.GetString("base-computer-ui-component-not-powered"));
|
||||
return; // Not powered, so this computer should probably do nothing.
|
||||
}
|
||||
// Can we interact?
|
||||
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(sessionEntity))
|
||||
{
|
||||
sessionEntity.PopupMessageCursor(Loc.GetString("base-computer-ui-component-cannot-interact"));
|
||||
return;
|
||||
}
|
||||
// Good to go!
|
||||
OnReceiveUserInterfaceMessage(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to handle messages from the UI.
|
||||
/// Calling base is unnecessary.
|
||||
/// These messages will automatically be blocked if the user shouldn't be able to access this computer, or if the computer has lost power.
|
||||
/// </summary>
|
||||
protected virtual void OnReceiveUserInterfaceMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
// Nothing!
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PowerChangedMessage powerChanged:
|
||||
PowerReceiverOnOnPowerStateChanged(powerChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PowerReceiverOnOnPowerStateChanged(PowerChangedMessage e)
|
||||
{
|
||||
if (!e.Powered)
|
||||
{
|
||||
// We need to kick off users who are using it when it loses power.
|
||||
UserInterface?.CloseAll();
|
||||
// Now alert subclass.
|
||||
ComputerLostPower();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this if you want the computer to do something when it loses power (i.e. reset state)
|
||||
/// All UIs should have been closed by the time this is called.
|
||||
/// Calling base is unnecessary.
|
||||
/// </summary>
|
||||
public virtual void ComputerLostPower()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called from ComputerUIActivatorSystem.
|
||||
/// Override this to add additional activation conditions of some sort.
|
||||
/// Calling base runs standard activation logic.
|
||||
/// *This remains inside the component for overridability.*
|
||||
/// </summary>
|
||||
public virtual void ActivateThunk(ActivateInWorldEvent eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Powered)
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("base-computer-ui-component-not-powered"));
|
||||
return;
|
||||
}
|
||||
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Content.Server/Atmos/Components/BreathToolComponent.cs
Normal file
61
Content.Server/Atmos/Components/BreathToolComponent.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
#nullable enable
|
||||
using Content.Server.Body.Respiratory;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in internals as breath tool.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BreathToolComponent : Component, IEquipped, IUnequipped
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool is functional only in allowed slots
|
||||
/// </summary>
|
||||
[DataField("allowedSlots")]
|
||||
private EquipmentSlotDefines.SlotFlags _allowedSlots = EquipmentSlotDefines.SlotFlags.MASK;
|
||||
|
||||
public override string Name => "BreathMask";
|
||||
public bool IsFunctional { get; private set; }
|
||||
public IEntity? ConnectedInternalsEntity { get; private set; }
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
DisconnectInternals();
|
||||
}
|
||||
|
||||
void IEquipped.Equipped(EquippedEventArgs eventArgs)
|
||||
{
|
||||
if ((EquipmentSlotDefines.SlotMasks[eventArgs.Slot] & _allowedSlots) != _allowedSlots) return;
|
||||
IsFunctional = true;
|
||||
|
||||
if (eventArgs.User.TryGetComponent(out InternalsComponent? internals))
|
||||
{
|
||||
ConnectedInternalsEntity = eventArgs.User;
|
||||
internals.ConnectBreathTool(Owner);
|
||||
}
|
||||
}
|
||||
|
||||
void IUnequipped.Unequipped(UnequippedEventArgs eventArgs)
|
||||
{
|
||||
DisconnectInternals();
|
||||
}
|
||||
|
||||
public void DisconnectInternals()
|
||||
{
|
||||
var old = ConnectedInternalsEntity;
|
||||
ConnectedInternalsEntity = null;
|
||||
|
||||
if (old != null && old.TryGetComponent<InternalsComponent>(out var internalsComponent))
|
||||
{
|
||||
internalsComponent.DisconnectBreathTool();
|
||||
}
|
||||
|
||||
IsFunctional = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Content.Server/Atmos/Components/FirelockComponent.cs
Normal file
117
Content.Server/Atmos/Components/FirelockComponent.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
#nullable enable
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Doors;
|
||||
using Content.Server.Doors.Components;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, and not being openable on open-hand click.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDoorCheck))]
|
||||
public class FirelockComponent : Component, IDoorCheck
|
||||
{
|
||||
public override string Name => "Firelock";
|
||||
|
||||
[ComponentDependency]
|
||||
private readonly ServerDoorComponent? _doorComponent = null;
|
||||
|
||||
public bool EmergencyPressureStop()
|
||||
{
|
||||
if (_doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Open && _doorComponent.CanCloseGeneric())
|
||||
{
|
||||
_doorComponent.Close();
|
||||
if (Owner.TryGetComponent(out AirtightComponent? airtight))
|
||||
{
|
||||
airtight.AirBlocked = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IDoorCheck.OpenCheck()
|
||||
{
|
||||
return !IsHoldingFire() && !IsHoldingPressure();
|
||||
}
|
||||
|
||||
bool IDoorCheck.DenyCheck() => false;
|
||||
|
||||
float? IDoorCheck.GetPryTime()
|
||||
{
|
||||
if (IsHoldingFire() || IsHoldingPressure())
|
||||
{
|
||||
return 1.5f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs) => true;
|
||||
|
||||
void IDoorCheck.OnStartPry(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (_doorComponent == null || _doorComponent.State != SharedDoorComponent.DoorState.Closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsHoldingPressure())
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("A gush of air blows in your face... Maybe you should reconsider."));
|
||||
}
|
||||
else if (IsHoldingFire())
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("A gush of warm air blows in your face... Maybe you should reconsider."));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHoldingPressure(float threshold = 20)
|
||||
{
|
||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
|
||||
var gridAtmosphere = atmosphereSystem.GetGridAtmosphere(Owner.Transform.Coordinates);
|
||||
|
||||
var minMoles = float.MaxValue;
|
||||
var maxMoles = 0f;
|
||||
|
||||
foreach (var (_, adjacent) in gridAtmosphere.GetAdjacentTiles(Owner.Transform.Coordinates))
|
||||
{
|
||||
// includeAirBlocked remains false, and therefore Air must be present
|
||||
var moles = adjacent.Air!.TotalMoles;
|
||||
if (moles < minMoles)
|
||||
minMoles = moles;
|
||||
if (moles > maxMoles)
|
||||
maxMoles = moles;
|
||||
}
|
||||
|
||||
return (maxMoles - minMoles) > threshold;
|
||||
}
|
||||
|
||||
public bool IsHoldingFire()
|
||||
{
|
||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
|
||||
if (!Owner.Transform.Coordinates.TryGetTileAtmosphere(out var tileAtmos))
|
||||
return false;
|
||||
|
||||
if (tileAtmos.Hotspot.Valid)
|
||||
return true;
|
||||
|
||||
var gridAtmosphere = atmosphereSystem.GetGridAtmosphere(Owner.Transform.Coordinates);
|
||||
|
||||
foreach (var (_, adjacent) in gridAtmosphere.GetAdjacentTiles(tileAtmos.GridIndices))
|
||||
{
|
||||
if (adjacent.Hotspot.Valid)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Content.Server/Atmos/Components/FlammableComponent.cs
Normal file
222
Content.Server/Atmos/Components/FlammableComponent.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Alert;
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class FlammableComponent : SharedFlammableComponent, IStartCollide, IFireAct, IInteractUsing
|
||||
{
|
||||
private bool _resisting = false;
|
||||
private readonly List<EntityUid> _collided = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool OnFire { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float FireStacks { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("fireSpread")]
|
||||
public bool FireSpread { get; private set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("canResistFire")]
|
||||
public bool CanResistFire { get; private set; } = false;
|
||||
|
||||
public void Ignite()
|
||||
{
|
||||
if (FireStacks > 0 && !OnFire)
|
||||
{
|
||||
OnFire = true;
|
||||
|
||||
}
|
||||
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public void Extinguish()
|
||||
{
|
||||
if (!OnFire) return;
|
||||
OnFire = false;
|
||||
FireStacks = 0;
|
||||
|
||||
_collided.Clear();
|
||||
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public void AdjustFireStacks(float relativeFireStacks)
|
||||
{
|
||||
FireStacks = MathF.Min(MathF.Max(-10f, FireStacks + relativeFireStacks), 20f);
|
||||
if (OnFire && FireStacks <= 0)
|
||||
Extinguish();
|
||||
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public void Update(TileAtmosphere tile)
|
||||
{
|
||||
// Slowly dry ourselves off if wet.
|
||||
if (FireStacks < 0)
|
||||
{
|
||||
FireStacks = MathF.Min(0, FireStacks + 1);
|
||||
}
|
||||
|
||||
Owner.TryGetComponent(out ServerAlertsComponent? status);
|
||||
|
||||
if (!OnFire)
|
||||
{
|
||||
status?.ClearAlert(AlertType.Fire);
|
||||
return;
|
||||
}
|
||||
|
||||
status?.ShowAlert(AlertType.Fire);
|
||||
|
||||
if (FireStacks > 0)
|
||||
{
|
||||
if (Owner.TryGetComponent(out TemperatureComponent? temp))
|
||||
{
|
||||
temp.ReceiveHeat(200 * FireStacks);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
// TODO ATMOS Fire resistance from armor
|
||||
var damage = Math.Min((int) (FireStacks * 2.5f), 10);
|
||||
damageable.ChangeDamage(DamageClass.Burn, damage, false);
|
||||
}
|
||||
|
||||
AdjustFireStacks(-0.1f * (_resisting ? 10f : 1f));
|
||||
}
|
||||
else
|
||||
{
|
||||
Extinguish();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in an oxygenless environment, put the fire out.
|
||||
if (tile.Air?.GetMoles(Gas.Oxygen) < 1f)
|
||||
{
|
||||
Extinguish();
|
||||
return;
|
||||
}
|
||||
|
||||
tile.HotspotExpose(700, 50, true);
|
||||
|
||||
var physics = Owner.GetComponent<IPhysBody>();
|
||||
|
||||
foreach (var uid in _collided.ToArray())
|
||||
{
|
||||
if (!uid.IsValid() || !Owner.EntityManager.EntityExists(uid))
|
||||
{
|
||||
_collided.Remove(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entity = Owner.EntityManager.GetEntity(uid);
|
||||
var otherPhysics = entity.GetComponent<IPhysBody>();
|
||||
|
||||
if (!physics.GetWorldAABB().Intersects(otherPhysics.GetWorldAABB()))
|
||||
{
|
||||
_collided.Remove(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IStartCollide.CollideWith(Fixture ourFixture, Fixture otherFixture, in Manifold manifold)
|
||||
{
|
||||
if (!otherFixture.Body.Owner.TryGetComponent(out FlammableComponent? otherFlammable))
|
||||
return;
|
||||
|
||||
if (!FireSpread || !otherFlammable.FireSpread)
|
||||
return;
|
||||
|
||||
if (OnFire)
|
||||
{
|
||||
if (otherFlammable.OnFire)
|
||||
{
|
||||
var fireSplit = (FireStacks + otherFlammable.FireStacks) / 2;
|
||||
FireStacks = fireSplit;
|
||||
otherFlammable.FireStacks = fireSplit;
|
||||
}
|
||||
else
|
||||
{
|
||||
FireStacks /= 2;
|
||||
otherFlammable.FireStacks += FireStacks;
|
||||
otherFlammable.Ignite();
|
||||
}
|
||||
} else if (otherFlammable.OnFire)
|
||||
{
|
||||
otherFlammable.FireStacks /= 2;
|
||||
FireStacks += otherFlammable.FireStacks;
|
||||
Ignite();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (Owner.Deleted || !Owner.TryGetComponent(out AppearanceComponent? appearanceComponent)) return;
|
||||
appearanceComponent.SetData(FireVisuals.OnFire, OnFire);
|
||||
appearanceComponent.SetData(FireVisuals.FireStacks, FireStacks);
|
||||
}
|
||||
|
||||
public void FireAct(float temperature, float volume)
|
||||
{
|
||||
AdjustFireStacks(3);
|
||||
Ignite();
|
||||
}
|
||||
|
||||
// This needs some improvements...
|
||||
public void Resist()
|
||||
{
|
||||
if (!OnFire || !EntitySystem.Get<ActionBlockerSystem>().CanInteract(Owner) || _resisting || !Owner.TryGetComponent(out StunnableComponent? stunnable)) return;
|
||||
|
||||
_resisting = true;
|
||||
|
||||
Owner.PopupMessage(Loc.GetString("You stop, drop, and roll!"));
|
||||
stunnable.Paralyze(2f);
|
||||
|
||||
Owner.SpawnTimer(2000, () =>
|
||||
{
|
||||
_resisting = false;
|
||||
FireStacks -= 3f;
|
||||
UpdateAppearance();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
foreach (var hotItem in eventArgs.Using.GetAllComponents<IHotItem>())
|
||||
{
|
||||
if (hotItem.IsCurrentlyHot())
|
||||
{
|
||||
Ignite();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
287
Content.Server/Atmos/Components/GasAnalyzerComponent.cs
Normal file
287
Content.Server/Atmos/Components/GasAnalyzerComponent.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse
|
||||
{
|
||||
private GasAnalyzerDanger _pressureDanger;
|
||||
private float _timeSinceSync;
|
||||
private const float TimeBetweenSyncs = 2f;
|
||||
private bool _checkPlayer = false; // Check at the player pos or at some other tile?
|
||||
private EntityCoordinates? _position; // The tile that we scanned
|
||||
private AppearanceComponent? _appearance;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GasAnalyzerUiKey.Key);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
|
||||
UserInterface.OnClosed += UserInterfaceOnClose;
|
||||
}
|
||||
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new GasAnalyzerComponentState(_pressureDanger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this from other components to open the gas analyzer UI.
|
||||
/// Uses the player position.
|
||||
/// </summary>
|
||||
/// <param name="session">The session to open the ui for</param>
|
||||
public void OpenInterface(IPlayerSession session)
|
||||
{
|
||||
_checkPlayer = true;
|
||||
_position = null;
|
||||
UserInterface?.Open(session);
|
||||
UpdateUserInterface();
|
||||
UpdateAppearance(true);
|
||||
Resync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this from other components to open the gas analyzer UI.
|
||||
/// Uses a given position.
|
||||
/// </summary>
|
||||
/// <param name="session">The session to open the ui for</param>
|
||||
/// <param name="pos">The position to analyze the gas</param>
|
||||
public void OpenInterface(IPlayerSession session, EntityCoordinates pos)
|
||||
{
|
||||
_checkPlayer = false;
|
||||
_position = pos;
|
||||
UserInterface?.Open(session);
|
||||
UpdateUserInterface();
|
||||
UpdateAppearance(true);
|
||||
Resync();
|
||||
}
|
||||
|
||||
public void ToggleInterface(IPlayerSession session)
|
||||
{
|
||||
if (UserInterface == null)
|
||||
return;
|
||||
|
||||
if (UserInterface.SessionHasOpen(session))
|
||||
CloseInterface(session);
|
||||
else
|
||||
OpenInterface(session);
|
||||
}
|
||||
|
||||
public void CloseInterface(IPlayerSession session)
|
||||
{
|
||||
_position = null;
|
||||
UserInterface?.Close(session);
|
||||
// Our OnClose will do the appearance stuff
|
||||
Resync();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnClose(IPlayerSession obj)
|
||||
{
|
||||
UpdateAppearance(false);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(bool open)
|
||||
{
|
||||
_appearance?.SetData(GasAnalyzerVisuals.VisualState,
|
||||
open ? GasAnalyzerVisualState.Working : GasAnalyzerVisualState.Off);
|
||||
}
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
_timeSinceSync += frameTime;
|
||||
if (_timeSinceSync > TimeBetweenSyncs)
|
||||
{
|
||||
Resync();
|
||||
UpdateUserInterface();
|
||||
}
|
||||
}
|
||||
|
||||
private void Resync()
|
||||
{
|
||||
// Already get the pressure before Dirty(), because we can't get the EntitySystem in that thread or smth
|
||||
var pressure = 0f;
|
||||
var gam = EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.Coordinates);
|
||||
var tile = gam?.GetTile(Owner.Transform.Coordinates)?.Air;
|
||||
if (tile != null)
|
||||
{
|
||||
pressure = tile.Pressure;
|
||||
}
|
||||
|
||||
if (pressure >= Atmospherics.HazardHighPressure || pressure <= Atmospherics.HazardLowPressure)
|
||||
{
|
||||
_pressureDanger = GasAnalyzerDanger.Hazard;
|
||||
}
|
||||
else if (pressure >= Atmospherics.WarningHighPressure || pressure <= Atmospherics.WarningLowPressure)
|
||||
{
|
||||
_pressureDanger = GasAnalyzerDanger.Warning;
|
||||
}
|
||||
else
|
||||
{
|
||||
_pressureDanger = GasAnalyzerDanger.Nominal;
|
||||
}
|
||||
|
||||
Dirty();
|
||||
_timeSinceSync = 0f;
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
if (UserInterface == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string? error = null;
|
||||
|
||||
// Check if the player is still holding the gas analyzer => if not, don't update
|
||||
foreach (var session in UserInterface.SubscribedSessions)
|
||||
{
|
||||
if (session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
if (!session.AttachedEntity.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
return;
|
||||
|
||||
var activeHandEntity = handsComponent?.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
if (!_checkPlayer && _position.HasValue)
|
||||
{
|
||||
// Check if position is out of range => don't update
|
||||
if (!_position.Value.InRange(Owner.EntityManager, pos, SharedInteractionSystem.InteractionRange))
|
||||
return;
|
||||
|
||||
pos = _position.Value;
|
||||
}
|
||||
|
||||
var atmosSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
var gam = atmosSystem.GetGridAtmosphere(pos);
|
||||
var tile = gam.GetTile(pos)?.Air;
|
||||
if (tile == null)
|
||||
{
|
||||
error = "No Atmosphere!";
|
||||
UserInterface.SetState(
|
||||
new GasAnalyzerBoundUserInterfaceState(
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
error));
|
||||
return;
|
||||
}
|
||||
|
||||
var gases = new List<GasEntry>();
|
||||
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var gas = atmosSystem.GetGas(i);
|
||||
|
||||
if (tile.Gases[i] <= Atmospherics.GasMinMoles) continue;
|
||||
|
||||
gases.Add(new GasEntry(gas.Name, tile.Gases[i], gas.Color));
|
||||
}
|
||||
|
||||
UserInterface.SetState(
|
||||
new GasAnalyzerBoundUserInterfaceState(
|
||||
tile.Pressure,
|
||||
tile.Temperature,
|
||||
gases.ToArray(),
|
||||
error));
|
||||
}
|
||||
|
||||
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
|
||||
{
|
||||
var message = serverMsg.Message;
|
||||
switch (message)
|
||||
{
|
||||
case GasAnalyzerRefreshMessage msg:
|
||||
var player = serverMsg.Session.AttachedEntity;
|
||||
if (player == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
{
|
||||
Owner.PopupMessage(player, Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
|
||||
{
|
||||
serverMsg.Session.AttachedEntity?.PopupMessage(Loc.GetString("You need a Gas Analyzer in your hand!"));
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateUserInterface();
|
||||
Resync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.CanReach)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You can't reach there!"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
OpenInterface(actor.PlayerSession, eventArgs.ClickLocation);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void IDropped.Dropped(DroppedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
CloseInterface(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
ToggleInterface(actor.PlayerSession);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Content.Server/Atmos/Components/GasMixtureHolderComponent.cs
Normal file
15
Content.Server/Atmos/Components/GasMixtureHolderComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Content.Server.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasMixtureHolderComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
public override string Name => "GasMixtureHolder";
|
||||
|
||||
[ViewVariables] [DataField("air")] public GasMixture Air { get; set; } = new GasMixture();
|
||||
}
|
||||
}
|
||||
408
Content.Server/Atmos/Components/GasTankComponent.cs
Normal file
408
Content.Server/Atmos/Components/GasTankComponent.cs
Normal file
@@ -0,0 +1,408 @@
|
||||
#nullable enable
|
||||
#nullable disable warnings
|
||||
using System;
|
||||
using Content.Server.Body.Respiratory;
|
||||
using Content.Server.Explosion;
|
||||
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Behaviors.Item;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class GasTankComponent : Component, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
|
||||
{
|
||||
public override string Name => "GasTank";
|
||||
|
||||
private const float MaxExplosionRange = 14f;
|
||||
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
|
||||
|
||||
private int _integrity = 3;
|
||||
|
||||
[ComponentDependency] private readonly ItemActionsComponent? _itemActions = null;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? _userInterface;
|
||||
|
||||
[ViewVariables]
|
||||
public GasMixture Air
|
||||
{
|
||||
// TODO ATMOS Kill it with fire.
|
||||
get
|
||||
{
|
||||
if (!Owner.TryGetComponent(out NodeContainerComponent nodeContainer))
|
||||
throw new InvalidOperationException("Can't get tank air without a node container!");
|
||||
|
||||
if (!nodeContainer.TryGetNode(TankName, out PipeNode? node))
|
||||
throw new InvalidOperationException($"Node container doesn't have a pipenode called {TankName}!");
|
||||
|
||||
return node.Air;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// This will throw if the node container is not found.
|
||||
var nodeContainer = Owner.GetComponent<NodeContainerComponent>();
|
||||
|
||||
if (!nodeContainer.TryGetNode(TankName, out PipeNode? node))
|
||||
throw new InvalidOperationException($"Node container doesn't have a pipenode called {TankName}!");
|
||||
|
||||
node.Air = value;
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("air")] [ViewVariables]
|
||||
public GasMixture InitialMixture { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Distributed pressure.
|
||||
/// </summary>
|
||||
[DataField("outputPressure")]
|
||||
[ViewVariables]
|
||||
public float OutputPressure { get; private set; } = DefaultOutputPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Tank is connected to internals.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents that tank is functional and can be connected to internals.
|
||||
/// </summary>
|
||||
public bool IsFunctional => GetInternalsComponent() != null;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tanks start leaking.
|
||||
/// </summary>
|
||||
[DataField("tankLeakPressure")]
|
||||
public float TankLeakPressure { get; set; } = 30 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tank spills all contents into atmosphere.
|
||||
/// </summary>
|
||||
[DataField("tankRupturePressure")]
|
||||
public float TankRupturePressure { get; set; } = 40 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Base 3x3 explosion.
|
||||
/// </summary>
|
||||
[DataField("tankFragmentPressure")]
|
||||
public float TankFragmentPressure { get; set; } = 50 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Increases explosion for each scale kPa above threshold.
|
||||
/// </summary>
|
||||
[DataField("tankFragmentScale")]
|
||||
public float TankFragmentScale { get; set; } = 10 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// NodeContainer node.
|
||||
/// </summary>
|
||||
[DataField("tank")]
|
||||
public string TankName { get; set; } = "tank";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_userInterface = Owner.GetUIOrNull(SharedGasTankUiKey.Key);
|
||||
if (_userInterface != null)
|
||||
{
|
||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenInterface(IPlayerSession session)
|
||||
{
|
||||
_userInterface?.Open(session);
|
||||
UpdateUserInterface(true);
|
||||
}
|
||||
|
||||
public void Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("gas-tank-examine", ("pressure", Math.Round(Air?.Pressure ?? 0))));
|
||||
if (IsConnected)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("gas-tank-connected"));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
DisconnectFromInternals();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Air?.React(this);
|
||||
CheckStatus();
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
public GasMixture? RemoveAir(float amount)
|
||||
{
|
||||
var gas = Air?.Remove(amount);
|
||||
CheckStatus();
|
||||
return gas;
|
||||
}
|
||||
|
||||
public GasMixture RemoveAirVolume(float volume)
|
||||
{
|
||||
if (Air == null)
|
||||
return new GasMixture(volume);
|
||||
|
||||
var tankPressure = Air.Pressure;
|
||||
if (tankPressure < OutputPressure)
|
||||
{
|
||||
OutputPressure = tankPressure;
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
var molesNeeded = OutputPressure * volume / (Atmospherics.R * Air.Temperature);
|
||||
|
||||
var air = RemoveAir(molesNeeded);
|
||||
|
||||
if (air != null)
|
||||
air.Volume = volume;
|
||||
else
|
||||
return new GasMixture(volume);
|
||||
|
||||
return air;
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) return false;
|
||||
OpenInterface(actor.PlayerSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) return;
|
||||
OpenInterface(actor.PlayerSession);
|
||||
}
|
||||
|
||||
public void ConnectToInternals()
|
||||
{
|
||||
if (IsConnected || !IsFunctional) return;
|
||||
var internals = GetInternalsComponent();
|
||||
if (internals == null) return;
|
||||
IsConnected = internals.TryConnectTank(Owner);
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
public void DisconnectFromInternals(IEntity? owner = null)
|
||||
{
|
||||
if (!IsConnected) return;
|
||||
IsConnected = false;
|
||||
GetInternalsComponent(owner)?.DisconnectTank();
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(bool initialUpdate = false)
|
||||
{
|
||||
var internals = GetInternalsComponent();
|
||||
_userInterface?.SetState(
|
||||
new GasTankBoundUserInterfaceState
|
||||
{
|
||||
TankPressure = Air?.Pressure ?? 0,
|
||||
OutputPressure = initialUpdate ? OutputPressure : (float?) null,
|
||||
InternalsConnected = IsConnected,
|
||||
CanConnectInternals = IsFunctional && internals != null
|
||||
});
|
||||
|
||||
if (internals == null) return;
|
||||
_itemActions?.GrantOrUpdate(ItemActionType.ToggleInternals, IsFunctional, IsConnected);
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message.Message)
|
||||
{
|
||||
case GasTankSetPressureMessage msg:
|
||||
OutputPressure = msg.Pressure;
|
||||
break;
|
||||
case GasTankToggleInternalsMessage _:
|
||||
ToggleInternals();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ToggleInternals()
|
||||
{
|
||||
var user = GetInternalsComponent()?.Owner;
|
||||
|
||||
if (user == null || !EntitySystem.Get<ActionBlockerSystem>().CanUse(user))
|
||||
return;
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
DisconnectFromInternals();
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectToInternals();
|
||||
}
|
||||
|
||||
private InternalsComponent? GetInternalsComponent(IEntity? owner = null)
|
||||
{
|
||||
if (Owner.Deleted) return null;
|
||||
if (owner != null) return owner.GetComponentOrNull<InternalsComponent>();
|
||||
return Owner.TryGetContainer(out var container)
|
||||
? container.Owner.GetComponentOrNull<InternalsComponent>()
|
||||
: null;
|
||||
}
|
||||
|
||||
public void AssumeAir(GasMixture giver)
|
||||
{
|
||||
Air?.Merge(giver);
|
||||
CheckStatus();
|
||||
}
|
||||
|
||||
private void CheckStatus()
|
||||
{
|
||||
if (Air == null)
|
||||
return;
|
||||
|
||||
var pressure = Air.Pressure;
|
||||
|
||||
if (pressure > TankFragmentPressure)
|
||||
{
|
||||
// Give the gas a chance to build up more pressure.
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Air.React(this);
|
||||
}
|
||||
|
||||
pressure = Air.Pressure;
|
||||
var range = (pressure - TankFragmentPressure) / TankFragmentScale;
|
||||
|
||||
// Let's cap the explosion, yeah?
|
||||
if (range > MaxExplosionRange)
|
||||
{
|
||||
range = MaxExplosionRange;
|
||||
}
|
||||
|
||||
Owner.SpawnExplosion((int) (range * 0.25f), (int) (range * 0.5f), (int) (range * 1.5f), 1);
|
||||
|
||||
Owner.QueueDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressure > TankRupturePressure)
|
||||
{
|
||||
if (_integrity <= 0)
|
||||
{
|
||||
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
tileAtmos?.AssumeAir(Air);
|
||||
|
||||
SoundSystem.Play(Filter.Pvs(Owner), "Audio/Effects/spray.ogg", Owner.Transform.Coordinates,
|
||||
AudioHelpers.WithVariation(0.125f));
|
||||
|
||||
Owner.QueueDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
_integrity--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressure > TankLeakPressure)
|
||||
{
|
||||
if (_integrity <= 0)
|
||||
{
|
||||
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
if (tileAtmos == null)
|
||||
return;
|
||||
|
||||
var leakedGas = Air.RemoveRatio(0.25f);
|
||||
tileAtmos.AssumeAir(leakedGas);
|
||||
}
|
||||
else
|
||||
{
|
||||
_integrity--;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_integrity < 3)
|
||||
_integrity++;
|
||||
}
|
||||
|
||||
void IDropped.Dropped(DroppedEventArgs eventArgs)
|
||||
{
|
||||
DisconnectFromInternals(eventArgs.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open interaction window
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class ControlVerb : Verb<GasTankComponent>
|
||||
{
|
||||
public override bool RequireInteractionRange => true;
|
||||
|
||||
protected override void GetData(IEntity user, GasTankComponent component, VerbData data)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
if (!user.HasComponent<ActorComponent>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = "Open Control Panel";
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, GasTankComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<ActorComponent>(out var actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
component.OpenInterface(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[DataDefinition]
|
||||
public class ToggleInternalsAction : IToggleItemAction
|
||||
{
|
||||
public bool DoToggleAction(ToggleItemActionEventArgs args)
|
||||
{
|
||||
if (!args.Item.TryGetComponent<GasTankComponent>(out var gasTankComponent)) return false;
|
||||
// no change
|
||||
if (gasTankComponent.IsConnected == args.ToggledOn) return false;
|
||||
gasTankComponent.ToggleInternals();
|
||||
// did we successfully toggle to the desired status?
|
||||
return gasTankComponent.IsConnected == args.ToggledOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
948
Content.Server/Atmos/Components/GridAtmosphereComponent.cs
Normal file
948
Content.Server/Atmos/Components/GridAtmosphereComponent.cs
Normal file
@@ -0,0 +1,948 @@
|
||||
#nullable enable
|
||||
// ReSharper disable once RedundantUsingDirective
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Dependency = Robust.Shared.IoC.DependencyAttribute;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// This is our SSAir equivalent.
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
||||
[RegisterComponent, Serializable]
|
||||
public class GridAtmosphereComponent : Component, IGridAtmosphereComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private IMapManager _mapManager = default!;
|
||||
[Dependency] private ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private IServerEntityManager _serverEntityManager = default!;
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
|
||||
public GridTileLookupSystem GridTileLookupSystem { get; private set; } = default!;
|
||||
internal GasTileOverlaySystem GasTileOverlaySystem { get; private set; } = default!;
|
||||
public AtmosphereSystem AtmosphereSystem { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Check current execution time every n instances processed.
|
||||
/// </summary>
|
||||
private const int LagCheckIterations = 30;
|
||||
|
||||
public override string Name => "GridAtmosphere";
|
||||
|
||||
private bool _paused;
|
||||
private float _timer;
|
||||
private Stopwatch _stopwatch = new();
|
||||
private GridId _gridId;
|
||||
|
||||
[ComponentDependency] private IMapGridComponent? _mapGridComponent;
|
||||
|
||||
public virtual bool Simulated => true;
|
||||
|
||||
[ViewVariables]
|
||||
public int UpdateCounter { get; private set; } = 0;
|
||||
|
||||
[ViewVariables]
|
||||
private double _tileEqualizeLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<ExcitedGroup> _excitedGroups = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private int ExcitedGroupCount => _excitedGroups.Count;
|
||||
|
||||
[ViewVariables]
|
||||
private double _excitedGroupLastProcess;
|
||||
|
||||
[DataField("uniqueMixes")]
|
||||
private List<GasMixture>? _uniqueMixes;
|
||||
|
||||
[DataField("tiles")]
|
||||
private Dictionary<Vector2i, int>? _tiles;
|
||||
|
||||
[ViewVariables]
|
||||
protected readonly Dictionary<Vector2i, TileAtmosphere> Tiles = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<TileAtmosphere> _activeTiles = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private int ActiveTilesCount => _activeTiles.Count;
|
||||
|
||||
[ViewVariables]
|
||||
private double _activeTilesLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<TileAtmosphere> _hotspotTiles = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private int HotspotTilesCount => _hotspotTiles.Count;
|
||||
|
||||
[ViewVariables]
|
||||
private double _hotspotsLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<TileAtmosphere> _superconductivityTiles = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private int SuperconductivityTilesCount => _superconductivityTiles.Count;
|
||||
|
||||
[ViewVariables]
|
||||
private double _superconductivityLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<Vector2i> _invalidatedCoords = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private int InvalidatedCoordsCount => _invalidatedCoords.Count;
|
||||
|
||||
[ViewVariables]
|
||||
private HashSet<TileAtmosphere> _highPressureDelta = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private int HighPressureDeltaCount => _highPressureDelta.Count;
|
||||
|
||||
[ViewVariables]
|
||||
private double _highPressureDeltaLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<IPipeNet> _pipeNets = new();
|
||||
|
||||
[ViewVariables]
|
||||
private double _pipeNetLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<AtmosDeviceComponent> _atmosDevices = new();
|
||||
|
||||
[ViewVariables]
|
||||
private double _atmosDevicesLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<TileAtmosphere> _currentRunTiles = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<ExcitedGroup> _currentRunExcitedGroups = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<IPipeNet> _currentRunPipeNet = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<AtmosDeviceComponent> _currentRunAtmosDevices = new();
|
||||
|
||||
[ViewVariables]
|
||||
private ProcessState _state = ProcessState.TileEqualize;
|
||||
|
||||
public GridAtmosphereComponent()
|
||||
{
|
||||
_paused = false;
|
||||
}
|
||||
|
||||
private enum ProcessState
|
||||
{
|
||||
TileEqualize,
|
||||
ActiveTiles,
|
||||
ExcitedGroups,
|
||||
HighPressureDelta,
|
||||
Hotspots,
|
||||
Superconductivity,
|
||||
PipeNet,
|
||||
AtmosDevices,
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void PryTile(Vector2i indices)
|
||||
{
|
||||
if (IsSpace(indices) || IsAirBlocked(indices)) return;
|
||||
|
||||
indices.PryTile(_gridId, _mapManager, _tileDefinitionManager, _serverEntityManager);
|
||||
}
|
||||
|
||||
void ISerializationHooks.BeforeSerialization()
|
||||
{
|
||||
var uniqueMixes = new List<GasMixture>();
|
||||
var uniqueMixHash = new Dictionary<GasMixture, int>();
|
||||
var tiles = new Dictionary<Vector2i, int>();
|
||||
|
||||
foreach (var (indices, tile) in Tiles)
|
||||
{
|
||||
if (tile.Air == null) continue;
|
||||
|
||||
if (uniqueMixHash.TryGetValue(tile.Air, out var index))
|
||||
{
|
||||
tiles[indices] = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueMixes.Add(tile.Air);
|
||||
var newIndex = uniqueMixes.Count - 1;
|
||||
uniqueMixHash[tile.Air] = newIndex;
|
||||
tiles[indices] = newIndex;
|
||||
}
|
||||
|
||||
if (uniqueMixes.Count == 0) uniqueMixes = null;
|
||||
if (tiles.Count == 0) tiles = null;
|
||||
|
||||
_uniqueMixes = uniqueMixes;
|
||||
_tiles = tiles;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Tiles.Clear();
|
||||
|
||||
if (_tiles != null && Owner.TryGetComponent(out IMapGridComponent? mapGrid))
|
||||
{
|
||||
foreach (var (indices, mix) in _tiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
Tiles.Add(indices, new TileAtmosphere(this, mapGrid.GridIndex, indices, (GasMixture) _uniqueMixes![mix].Clone()));
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Logger.Error($"Error during atmos serialization! Tile at {indices} points to an unique mix ({mix}) out of range!");
|
||||
throw;
|
||||
}
|
||||
|
||||
Invalidate(indices);
|
||||
}
|
||||
}
|
||||
|
||||
GridTileLookupSystem = EntitySystem.Get<GridTileLookupSystem>();
|
||||
GasTileOverlaySystem = EntitySystem.Get<GasTileOverlaySystem>();
|
||||
AtmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
|
||||
RepopulateTiles();
|
||||
}
|
||||
|
||||
public override void OnAdd()
|
||||
{
|
||||
base.OnAdd();
|
||||
|
||||
if (Owner.TryGetComponent(out IMapGridComponent? mapGrid))
|
||||
_gridId = mapGrid.GridIndex;
|
||||
}
|
||||
|
||||
public virtual void RepopulateTiles()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
|
||||
foreach (var tile in mapGrid.Grid.GetAllTiles())
|
||||
{
|
||||
if(!Tiles.ContainsKey(tile.GridIndices))
|
||||
Tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1), AtmosphereSystem){Temperature = Atmospherics.T20C}));
|
||||
|
||||
Invalidate(tile.GridIndices);
|
||||
}
|
||||
|
||||
foreach (var (_, tile) in Tiles.ToArray())
|
||||
{
|
||||
tile.UpdateAdjacent();
|
||||
tile.UpdateVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Invalidate(Vector2i indices)
|
||||
{
|
||||
_invalidatedCoords.Add(indices);
|
||||
}
|
||||
|
||||
protected virtual void Revalidate()
|
||||
{
|
||||
foreach (var indices in _invalidatedCoords)
|
||||
{
|
||||
var tile = GetTile(indices);
|
||||
|
||||
if (tile == null)
|
||||
{
|
||||
tile = new TileAtmosphere(this, _gridId, indices, new GasMixture(GetVolumeForCells(1), AtmosphereSystem){Temperature = Atmospherics.T20C});
|
||||
Tiles[indices] = tile;
|
||||
}
|
||||
|
||||
var isAirBlocked = IsAirBlocked(indices);
|
||||
|
||||
if (IsSpace(indices) && !isAirBlocked)
|
||||
{
|
||||
tile.Air = new GasMixture(GetVolumeForCells(1), AtmosphereSystem);
|
||||
tile.Air.MarkImmutable();
|
||||
Tiles[indices] = tile;
|
||||
|
||||
} else if (isAirBlocked)
|
||||
{
|
||||
var nullAir = false;
|
||||
|
||||
foreach (var airtight in GetObstructingComponents(indices))
|
||||
{
|
||||
if (airtight.NoAirWhenFullyAirBlocked)
|
||||
{
|
||||
nullAir = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(nullAir)
|
||||
tile.Air = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tile.Air == null && NeedsVacuumFixing(indices))
|
||||
{
|
||||
FixVacuum(tile.GridIndices);
|
||||
}
|
||||
|
||||
// Tile used to be space, but isn't anymore.
|
||||
if (tile.Air?.Immutable ?? false)
|
||||
{
|
||||
tile.Air = null;
|
||||
}
|
||||
|
||||
tile.Air ??= new GasMixture(GetVolumeForCells(1), AtmosphereSystem){Temperature = Atmospherics.T20C};
|
||||
}
|
||||
|
||||
// By removing the active tile, we effectively remove its excited group, if any.
|
||||
RemoveActiveTile(tile);
|
||||
|
||||
// Then we activate the tile again.
|
||||
AddActiveTile(tile);
|
||||
|
||||
tile.BlockedAirflow = GetBlockedDirections(indices);
|
||||
|
||||
// TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity
|
||||
tile.ThermalConductivity = tile.Tile?.Tile.GetContentTileDefinition().ThermalConductivity ?? 0.5f;
|
||||
tile.UpdateAdjacent();
|
||||
GasTileOverlaySystem.Invalidate(_gridId, indices);
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var otherIndices = indices.Offset(direction.ToDirection());
|
||||
var otherTile = GetTile(otherIndices);
|
||||
if (otherTile != null) AddActiveTile(otherTile);
|
||||
}
|
||||
}
|
||||
|
||||
_invalidatedCoords.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UpdateAdjacentBits(Vector2i indices)
|
||||
{
|
||||
GetTile(indices)?.UpdateAdjacent();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void FixVacuum(Vector2i indices)
|
||||
{
|
||||
var tile = GetTile(indices);
|
||||
if (tile?.GridIndex != _gridId) return;
|
||||
// includeAirBlocked is false, therefore all tiles in this have Air != null.
|
||||
var adjacent = GetAdjacentTiles(indices);
|
||||
tile.Air = new GasMixture(GetVolumeForCells(1), AtmosphereSystem){Temperature = Atmospherics.T20C};
|
||||
Tiles[indices] = tile;
|
||||
|
||||
var ratio = 1f / adjacent.Count;
|
||||
|
||||
foreach (var (_, adj) in adjacent)
|
||||
{
|
||||
var mix = adj.Air!.RemoveRatio(ratio);
|
||||
tile.Air.Merge(mix);
|
||||
adj.Air.Merge(mix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void AddActiveTile(TileAtmosphere tile)
|
||||
{
|
||||
if (tile?.GridIndex != _gridId || tile.Air == null) return;
|
||||
tile.Excited = true;
|
||||
_activeTiles.Add(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void RemoveActiveTile(TileAtmosphere tile, bool disposeGroup = true)
|
||||
{
|
||||
_activeTiles.Remove(tile);
|
||||
tile.Excited = false;
|
||||
if(disposeGroup)
|
||||
tile.ExcitedGroup?.Dispose();
|
||||
else
|
||||
tile.ExcitedGroup?.RemoveTile(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void AddHotspotTile(TileAtmosphere tile)
|
||||
{
|
||||
if (tile?.GridIndex != _gridId || tile?.Air == null) return;
|
||||
_hotspotTiles.Add(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void RemoveHotspotTile(TileAtmosphere tile)
|
||||
{
|
||||
_hotspotTiles.Remove(tile);
|
||||
}
|
||||
|
||||
public virtual void AddSuperconductivityTile(TileAtmosphere tile)
|
||||
{
|
||||
if (tile?.GridIndex != _gridId || !AtmosphereSystem.Superconduction) return;
|
||||
_superconductivityTiles.Add(tile);
|
||||
}
|
||||
|
||||
public virtual void RemoveSuperconductivityTile(TileAtmosphere tile)
|
||||
{
|
||||
_superconductivityTiles.Remove(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void AddHighPressureDelta(TileAtmosphere tile)
|
||||
{
|
||||
if (tile.GridIndex != _gridId) return;
|
||||
_highPressureDelta.Add(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual bool HasHighPressureDelta(TileAtmosphere tile)
|
||||
{
|
||||
return _highPressureDelta.Contains(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void AddExcitedGroup(ExcitedGroup excitedGroup)
|
||||
{
|
||||
_excitedGroups.Add(excitedGroup);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void RemoveExcitedGroup(ExcitedGroup excitedGroup)
|
||||
{
|
||||
_excitedGroups.Remove(excitedGroup);
|
||||
}
|
||||
|
||||
public virtual void AddPipeNet(IPipeNet pipeNet)
|
||||
{
|
||||
_pipeNets.Add(pipeNet);
|
||||
}
|
||||
|
||||
public virtual void RemovePipeNet(IPipeNet pipeNet)
|
||||
{
|
||||
_pipeNets.Remove(pipeNet);
|
||||
}
|
||||
|
||||
public virtual void AddAtmosDevice(AtmosDeviceComponent atmosDevice)
|
||||
{
|
||||
_atmosDevices.Add(atmosDevice);
|
||||
}
|
||||
|
||||
public virtual void RemoveAtmosDevice(AtmosDeviceComponent atmosDevice)
|
||||
{
|
||||
_atmosDevices.Remove(atmosDevice);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual TileAtmosphere? GetTile(EntityCoordinates coordinates, bool createSpace = true)
|
||||
{
|
||||
return GetTile(coordinates.ToVector2i(_serverEntityManager, _mapManager), createSpace);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual TileAtmosphere? GetTile(Vector2i indices, bool createSpace = true)
|
||||
{
|
||||
if (Tiles.TryGetValue(indices, out var tile)) return tile;
|
||||
|
||||
// We don't have that tile!
|
||||
if (IsSpace(indices) && createSpace)
|
||||
{
|
||||
return new TileAtmosphere(this, _gridId, indices, new GasMixture(GetVolumeForCells(1), AtmosphereSystem){Temperature = Atmospherics.TCMB}, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAirBlocked(Vector2i indices, AtmosDirection direction = AtmosDirection.All)
|
||||
{
|
||||
var directions = AtmosDirection.Invalid;
|
||||
|
||||
foreach (var obstructingComponent in GetObstructingComponents(indices))
|
||||
{
|
||||
if (!obstructingComponent.AirBlocked)
|
||||
continue;
|
||||
|
||||
// We set the directions that are air-blocked so far,
|
||||
// as you could have a full obstruction with only 4 directional air blockers.
|
||||
directions |= obstructingComponent.AirBlockedDirection;
|
||||
|
||||
if (directions.IsFlagSet(direction))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool IsSpace(Vector2i indices)
|
||||
{
|
||||
if (_mapGridComponent == null) return default;
|
||||
|
||||
return _mapGridComponent.Grid.GetTileRef(indices).IsSpace();
|
||||
}
|
||||
|
||||
public Dictionary<AtmosDirection, TileAtmosphere> GetAdjacentTiles(EntityCoordinates coordinates, bool includeAirBlocked = false)
|
||||
{
|
||||
return GetAdjacentTiles(coordinates.ToVector2i(_serverEntityManager, _mapManager), includeAirBlocked);
|
||||
}
|
||||
|
||||
public Dictionary<AtmosDirection, TileAtmosphere> GetAdjacentTiles(Vector2i indices, bool includeAirBlocked = false)
|
||||
{
|
||||
var sides = new Dictionary<AtmosDirection, TileAtmosphere>();
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var side = indices.Offset(direction.ToDirection());
|
||||
var tile = GetTile(side);
|
||||
if (tile != null && (tile.Air != null || includeAirBlocked))
|
||||
sides[direction] = tile;
|
||||
}
|
||||
|
||||
return sides;
|
||||
}
|
||||
|
||||
public long EqualizationQueueCycleControl { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GetVolumeForCells(int cellCount)
|
||||
{
|
||||
if (_mapGridComponent == null) return default;
|
||||
|
||||
return _mapGridComponent.Grid.TileSize * cellCount * Atmospherics.CellVolume;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Update(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
var atmosTime = 1f/AtmosphereSystem.AtmosTickRate;
|
||||
|
||||
if (_invalidatedCoords.Count != 0)
|
||||
Revalidate();
|
||||
|
||||
if (_timer < atmosTime)
|
||||
return;
|
||||
|
||||
// We subtract it so it takes lost time into account.
|
||||
_timer -= atmosTime;
|
||||
|
||||
var maxProcessTime = AtmosphereSystem.AtmosMaxProcessTime;
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case ProcessState.TileEqualize:
|
||||
if (!ProcessTileEqualize(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
_state = ProcessState.ActiveTiles;
|
||||
return;
|
||||
case ProcessState.ActiveTiles:
|
||||
if (!ProcessActiveTiles(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
_state = ProcessState.ExcitedGroups;
|
||||
return;
|
||||
case ProcessState.ExcitedGroups:
|
||||
if (!ProcessExcitedGroups(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
_state = ProcessState.HighPressureDelta;
|
||||
return;
|
||||
case ProcessState.HighPressureDelta:
|
||||
if (!ProcessHighPressureDelta(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
_state = ProcessState.Hotspots;
|
||||
break;
|
||||
case ProcessState.Hotspots:
|
||||
if (!ProcessHotspots(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
// Next state depends on whether superconduction is enabled or not.
|
||||
// Note: We do this here instead of on the tile equalization step to prevent ending it early.
|
||||
// Therefore, a change to this CVar might only be applied after that step is over.
|
||||
_state = AtmosphereSystem.Superconduction ? ProcessState.Superconductivity : ProcessState.PipeNet;
|
||||
break;
|
||||
case ProcessState.Superconductivity:
|
||||
if (!ProcessSuperconductivity(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
_state = ProcessState.PipeNet;
|
||||
break;
|
||||
case ProcessState.PipeNet:
|
||||
if (!ProcessPipeNets(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
_state = ProcessState.AtmosDevices;
|
||||
break;
|
||||
case ProcessState.AtmosDevices:
|
||||
if (!ProcessAtmosDevices(_paused, maxProcessTime))
|
||||
{
|
||||
_paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_paused = false;
|
||||
// Next state depends on whether monstermos equalization is enabled or not.
|
||||
// Note: We do this here instead of on the tile equalization step to prevent ending it early.
|
||||
// Therefore, a change to this CVar might only be applied after that step is over.
|
||||
_state = AtmosphereSystem.MonstermosEqualization ? ProcessState.TileEqualize : ProcessState.ActiveTiles;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateCounter++;
|
||||
}
|
||||
|
||||
public virtual bool ProcessTileEqualize(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunTiles = new Queue<TileAtmosphere>(_activeTiles);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunTiles.Count > 0)
|
||||
{
|
||||
var tile = _currentRunTiles.Dequeue();
|
||||
tile.EqualizePressureInZone(UpdateCounter);
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool ProcessActiveTiles(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
var spaceWind = AtmosphereSystem.SpaceWind;
|
||||
|
||||
if(!resumed)
|
||||
_currentRunTiles = new Queue<TileAtmosphere>(_activeTiles);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunTiles.Count > 0)
|
||||
{
|
||||
var tile = _currentRunTiles.Dequeue();
|
||||
tile.ProcessCell(UpdateCounter, spaceWind);
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool ProcessExcitedGroups(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
var spaceIsAllConsuming = AtmosphereSystem.ExcitedGroupsSpaceIsAllConsuming;
|
||||
|
||||
if(!resumed)
|
||||
_currentRunExcitedGroups = new Queue<ExcitedGroup>(_excitedGroups);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunExcitedGroups.Count > 0)
|
||||
{
|
||||
var excitedGroup = _currentRunExcitedGroups.Dequeue();
|
||||
excitedGroup.BreakdownCooldown++;
|
||||
excitedGroup.DismantleCooldown++;
|
||||
|
||||
if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
|
||||
excitedGroup.SelfBreakdown(spaceIsAllConsuming);
|
||||
|
||||
else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
|
||||
excitedGroup.Dismantle();
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool ProcessHighPressureDelta(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunTiles = new Queue<TileAtmosphere>(_highPressureDelta);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunTiles.Count > 0)
|
||||
{
|
||||
var tile = _currentRunTiles.Dequeue();
|
||||
tile.HighPressureMovements();
|
||||
tile.PressureDifference = 0f;
|
||||
tile.PressureSpecificTarget = null;
|
||||
_highPressureDelta.Remove(tile);
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessHotspots(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunTiles = new Queue<TileAtmosphere>(_hotspotTiles);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunTiles.Count > 0)
|
||||
{
|
||||
var hotspot = _currentRunTiles.Dequeue();
|
||||
hotspot.ProcessHotspot();
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessSuperconductivity(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunTiles = new Queue<TileAtmosphere>(_superconductivityTiles);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunTiles.Count > 0)
|
||||
{
|
||||
var superconductivity = _currentRunTiles.Dequeue();
|
||||
superconductivity.Superconduct();
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessPipeNets(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunPipeNet = new Queue<IPipeNet>(_pipeNets);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunPipeNet.Count > 0)
|
||||
{
|
||||
var pipenet = _currentRunPipeNet.Dequeue();
|
||||
pipenet.Update();
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessAtmosDevices(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunAtmosDevices = new Queue<AtmosDeviceComponent>(_atmosDevices);
|
||||
|
||||
var time = _gameTiming.CurTime;
|
||||
var updateEvent = new AtmosDeviceUpdateEvent(this);
|
||||
var number = 0;
|
||||
while (_currentRunAtmosDevices.Count > 0)
|
||||
{
|
||||
var device = _currentRunAtmosDevices.Dequeue();
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(device.Owner.Uid, updateEvent, false);
|
||||
device.LastProcess = time;
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_atmosDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_atmosDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<AirtightComponent> GetObstructingComponents(Vector2i indices)
|
||||
{
|
||||
var gridLookup = EntitySystem.Get<GridTileLookupSystem>();
|
||||
|
||||
foreach (var v in gridLookup.GetEntitiesIntersecting(_gridId, indices))
|
||||
{
|
||||
if (v.TryGetComponent<AirtightComponent>(out var ac))
|
||||
yield return ac;
|
||||
}
|
||||
}
|
||||
|
||||
private bool NeedsVacuumFixing(Vector2i indices)
|
||||
{
|
||||
var value = false;
|
||||
|
||||
foreach (var airtightComponent in GetObstructingComponents(indices))
|
||||
{
|
||||
value |= airtightComponent.FixVacuum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private AtmosDirection GetBlockedDirections(Vector2i indices)
|
||||
{
|
||||
var value = AtmosDirection.Invalid;
|
||||
|
||||
foreach (var airtightComponent in GetObstructingComponents(indices))
|
||||
{
|
||||
if(airtightComponent.AirBlocked)
|
||||
value |= airtightComponent.AirBlockedDirection;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IEnumerator<TileAtmosphere> GetEnumerator()
|
||||
{
|
||||
return Tiles.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void BurnTile(Vector2i gridIndices)
|
||||
{
|
||||
// TODO ATMOS
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Content.Server/Atmos/Components/IGridAtmosphereComponent.cs
Normal file
184
Content.Server/Atmos/Components/IGridAtmosphereComponent.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
public interface IGridAtmosphereComponent : IComponent, IEnumerable<TileAtmosphere>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this atmosphere is simulated or not.
|
||||
/// </summary>
|
||||
bool Simulated { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of times <see cref="Update"/> has been called.
|
||||
/// </summary>
|
||||
int UpdateCounter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Control variable for equalization.
|
||||
/// </summary>
|
||||
long EqualizationQueueCycleControl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attemps to pry a tile.
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
void PryTile(Vector2i indices);
|
||||
|
||||
/// <summary>
|
||||
/// Burns a tile.
|
||||
/// </summary>
|
||||
/// <param name="gridIndices"></param>
|
||||
void BurnTile(Vector2i gridIndices);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates a coordinate to be revalidated again.
|
||||
/// Use this after changing a tile's gas contents, or when the tile becomes space, etc.
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
void Invalidate(Vector2i indices);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to fix a sudden vacuum by creating gas.
|
||||
/// </summary>
|
||||
void FixVacuum(Vector2i indices);
|
||||
|
||||
/// <summary>
|
||||
/// Revalidates indices immediately.
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
void UpdateAdjacentBits(Vector2i indices);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an active tile so it becomes processed every update until it becomes inactive.
|
||||
/// Also makes the tile excited.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void AddActiveTile(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an active tile and disposes of its <seealso cref="ExcitedGroup"/>.
|
||||
/// Use with caution.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void RemoveActiveTile(TileAtmosphere tile, bool disposeGroup = true);
|
||||
|
||||
/// <summary>
|
||||
/// Marks a tile as having a hotspot so it can be processed.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void AddHotspotTile(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a tile from the hotspot processing list.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void RemoveHotspotTile(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Marks a tile as superconductive so it can be processed.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void AddSuperconductivityTile(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a tile from the superconductivity processing list.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void RemoveSuperconductivityTile(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Marks a tile has having high pressure differences that need to be equalized.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
void AddHighPressureDelta(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the tile in question is marked as having high pressure differences or not.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
/// <returns></returns>
|
||||
bool HasHighPressureDelta(TileAtmosphere tile);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a excited group to be processed.
|
||||
/// </summary>
|
||||
/// <param name="excitedGroup"></param>
|
||||
void AddExcitedGroup(ExcitedGroup excitedGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an excited group.
|
||||
/// </summary>
|
||||
/// <param name="excitedGroup"></param>
|
||||
void RemoveExcitedGroup(ExcitedGroup excitedGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a tile.
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
/// <param name="createSpace"></param>
|
||||
/// <returns></returns>
|
||||
TileAtmosphere? GetTile(Vector2i indices, bool createSpace = true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a tile.
|
||||
/// </summary>
|
||||
/// <param name="coordinates"></param>
|
||||
/// <param name="createSpace"></param>
|
||||
/// <returns></returns>
|
||||
TileAtmosphere? GetTile(EntityCoordinates coordinates, bool createSpace = true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the tile in question is air-blocked.
|
||||
/// This could be due to a wall, an airlock, etc.
|
||||
/// <seealso cref="AirtightComponent"/>
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
/// <param name="direction"></param>
|
||||
/// <returns></returns>
|
||||
bool IsAirBlocked(Vector2i indices, AtmosDirection direction);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the tile in question is space.
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
/// <returns></returns>
|
||||
bool IsSpace(Vector2i indices);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the volume in liters for a number of cells/tiles.
|
||||
/// </summary>
|
||||
/// <param name="cellCount"></param>
|
||||
/// <returns></returns>
|
||||
float GetVolumeForCells(int cellCount);
|
||||
|
||||
void RepopulateTiles();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of adjacent TileAtmospheres.
|
||||
/// </summary>
|
||||
Dictionary<AtmosDirection, TileAtmosphere> GetAdjacentTiles(EntityCoordinates coordinates, bool includeAirBlocked = false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of adjacent TileAtmospheres.
|
||||
/// </summary>
|
||||
Dictionary<AtmosDirection, TileAtmosphere> GetAdjacentTiles(Vector2i indices, bool includeAirBlocked = false);
|
||||
|
||||
void Update(float frameTime);
|
||||
|
||||
void AddPipeNet(IPipeNet pipeNet);
|
||||
|
||||
void RemovePipeNet(IPipeNet pipeNet);
|
||||
|
||||
void AddAtmosDevice(AtmosDeviceComponent atmosDevice);
|
||||
|
||||
void RemoveAtmosDevice(AtmosDeviceComponent atmosDevice);
|
||||
}
|
||||
}
|
||||
128
Content.Server/Atmos/Components/MovedByPressureComponent.cs
Normal file
128
Content.Server/Atmos/Components/MovedByPressureComponent.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class MovedByPressureComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
public override string Name => "MovedByPressure";
|
||||
|
||||
private const float MoveForcePushRatio = 1f;
|
||||
private const float MoveForceForcePushRatio = 1f;
|
||||
private const float ProbabilityOffset = 25f;
|
||||
private const float ProbabilityBasePercent = 10f;
|
||||
private const float ThrowForce = 100f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("pressureResistance")]
|
||||
public float PressureResistance { get; set; } = 1f;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("moveResist")]
|
||||
public float MoveResist { get; set; } = 100f;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int LastHighPressureMovementAirCycle { get; set; } = 0;
|
||||
|
||||
public void ExperiencePressureDifference(int cycle, float pressureDifference, AtmosDirection direction,
|
||||
float pressureResistanceProbDelta, EntityCoordinates throwTarget)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out PhysicsComponent? physics))
|
||||
return;
|
||||
|
||||
physics.WakeBody();
|
||||
// TODO ATMOS stuns?
|
||||
|
||||
var transform = physics.Owner.Transform;
|
||||
var maxForce = MathF.Sqrt(pressureDifference) * 2.25f;
|
||||
var moveProb = 100f;
|
||||
|
||||
if (PressureResistance > 0)
|
||||
moveProb = MathF.Abs((pressureDifference / PressureResistance * ProbabilityBasePercent) -
|
||||
ProbabilityOffset);
|
||||
|
||||
if (moveProb > ProbabilityOffset && _robustRandom.Prob(MathF.Min(moveProb / 100f, 1f))
|
||||
&& !float.IsPositiveInfinity(MoveResist)
|
||||
&& (physics.BodyType != BodyType.Static
|
||||
&& (maxForce >= (MoveResist * MoveForcePushRatio)))
|
||||
|| (physics.BodyType == BodyType.Static && (maxForce >= (MoveResist * MoveForceForcePushRatio))))
|
||||
{
|
||||
|
||||
if (physics.Owner.HasComponent<IMobStateComponent>())
|
||||
{
|
||||
physics.BodyStatus = BodyStatus.InAir;
|
||||
|
||||
foreach (var fixture in physics.Fixtures)
|
||||
{
|
||||
fixture.CollisionMask &= ~(int) CollisionGroup.VaultImpassable;
|
||||
}
|
||||
|
||||
Owner.SpawnTimer(2000, () =>
|
||||
{
|
||||
if (Deleted || !Owner.TryGetComponent(out PhysicsComponent? physicsComponent)) return;
|
||||
|
||||
// Uhh if you get race conditions good luck buddy.
|
||||
if (physicsComponent.Owner.HasComponent<IMobStateComponent>())
|
||||
{
|
||||
physicsComponent.BodyStatus = BodyStatus.OnGround;
|
||||
}
|
||||
|
||||
foreach (var fixture in physics.Fixtures)
|
||||
{
|
||||
fixture.CollisionMask |= (int) CollisionGroup.VaultImpassable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (maxForce > ThrowForce)
|
||||
{
|
||||
// Vera please fix ;-;
|
||||
if (throwTarget != EntityCoordinates.Invalid)
|
||||
{
|
||||
var moveForce = maxForce * MathHelper.Clamp(moveProb, 0, 100) / 15f;
|
||||
var pos = ((throwTarget.Position - transform.Coordinates.Position).Normalized + direction.ToDirection().ToVec()).Normalized;
|
||||
physics.ApplyLinearImpulse(pos * moveForce);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var moveForce = MathF.Min(maxForce * MathHelper.Clamp(moveProb, 0, 100) / 2500f, 20f);
|
||||
physics.ApplyLinearImpulse(direction.ToDirection().ToVec() * moveForce);
|
||||
}
|
||||
|
||||
LastHighPressureMovementAirCycle = cycle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MovedByPressureExtensions
|
||||
{
|
||||
public static bool IsMovedByPressure(this IEntity entity)
|
||||
{
|
||||
return entity.IsMovedByPressure(out _);
|
||||
}
|
||||
|
||||
public static bool IsMovedByPressure(this IEntity entity, [NotNullWhen(true)] out MovedByPressureComponent? moved)
|
||||
{
|
||||
return entity.TryGetComponent(out moved) &&
|
||||
moved.Enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Content.Server.Pressure;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class PressureProtectionComponent : Component, IPressureProtection
|
||||
{
|
||||
public override string Name => "PressureProtection";
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("highPressureMultiplier")]
|
||||
public float HighPressureMultiplier { get; private set; } = 1f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("lowPressureMultiplier")]
|
||||
public float LowPressureMultiplier { get; private set; } = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
||||
public class SpaceGridAtmosphereComponent : UnsimulatedGridAtmosphereComponent
|
||||
{
|
||||
public override string Name => "SpaceGridAtmosphere";
|
||||
|
||||
public override void RepopulateTiles() { }
|
||||
|
||||
public override bool IsSpace(Vector2i indices)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override TileAtmosphere GetTile(Vector2i indices, bool createSpace = true)
|
||||
{
|
||||
return new(this, GridId.Invalid, indices, new GasMixture(2500, AtmosphereSystem), true);
|
||||
}
|
||||
|
||||
protected override IEnumerable<AirtightComponent> GetObstructingComponents(Vector2i indices)
|
||||
{
|
||||
return Enumerable.Empty<AirtightComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
||||
[ComponentReference(typeof(GridAtmosphereComponent))]
|
||||
[Serializable]
|
||||
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent, IGridAtmosphereComponent
|
||||
{
|
||||
public override string Name => "UnsimulatedGridAtmosphere";
|
||||
|
||||
public override bool Simulated => false;
|
||||
|
||||
public override void PryTile(Vector2i indices) { }
|
||||
|
||||
public override void RepopulateTiles()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
|
||||
foreach (var tile in mapGrid.Grid.GetAllTiles())
|
||||
{
|
||||
if(!Tiles.ContainsKey(tile.GridIndices))
|
||||
Tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Invalidate(Vector2i indices) { }
|
||||
|
||||
protected override void Revalidate() { }
|
||||
|
||||
public override void FixVacuum(Vector2i indices) { }
|
||||
|
||||
public override void AddActiveTile(TileAtmosphere? tile) { }
|
||||
|
||||
public override void RemoveActiveTile(TileAtmosphere? tile, bool disposeGroup = true) { }
|
||||
|
||||
public override void AddHotspotTile(TileAtmosphere? tile) { }
|
||||
|
||||
public override void RemoveHotspotTile(TileAtmosphere? tile) { }
|
||||
|
||||
public override void AddSuperconductivityTile(TileAtmosphere? tile) { }
|
||||
|
||||
public override void RemoveSuperconductivityTile(TileAtmosphere? tile) { }
|
||||
|
||||
public override void AddHighPressureDelta(TileAtmosphere? tile) { }
|
||||
|
||||
public override bool HasHighPressureDelta(TileAtmosphere tile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void AddExcitedGroup(ExcitedGroup excitedGroup) { }
|
||||
|
||||
public override void RemoveExcitedGroup(ExcitedGroup excitedGroup) { }
|
||||
|
||||
public override void AddPipeNet(IPipeNet pipeNet) { }
|
||||
|
||||
public override void RemovePipeNet(IPipeNet pipeNet) { }
|
||||
|
||||
public override void AddAtmosDevice(AtmosDeviceComponent atmosDevice) { }
|
||||
|
||||
public override void RemoveAtmosDevice(AtmosDeviceComponent atmosDevice) { }
|
||||
|
||||
public override void Update(float frameTime) { }
|
||||
|
||||
public override bool ProcessTileEqualize(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ProcessActiveTiles(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ProcessExcitedGroups(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ProcessHighPressureDelta(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool ProcessHotspots(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool ProcessSuperconductivity(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool ProcessPipeNets(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool ProcessAtmosDevices(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user