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
@@ -1,179 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
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.GameObjects.Components.Atmos
|
||||
{
|
||||
[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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MapInit()
|
||||
{
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
}
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_airBlocked = false;
|
||||
|
||||
UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
|
||||
|
||||
if (_fixVacuum)
|
||||
{
|
||||
_atmosphereSystem.GetGridAtmosphere(_lastPosition.Item1)?.FixVacuum(_lastPosition.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnTransformMove()
|
||||
{
|
||||
UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
|
||||
UpdatePosition();
|
||||
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePosition()
|
||||
{
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
if (!Owner.Transform.GridID.IsValid())
|
||||
return;
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
UpdatePosition(Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePosition(GridId gridId, Vector2i pos)
|
||||
{
|
||||
var gridAtmos = _atmosphereSystem.GetGridAtmosphere(gridId);
|
||||
|
||||
gridAtmos?.UpdateAdjacentBits(pos);
|
||||
gridAtmos?.Invalidate(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
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.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Server.Body.Respiratory;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.GameObjects;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Doors;
|
||||
using Content.Server.Doors.Components;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Alert;
|
||||
using Content.Server.Atmos;
|
||||
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.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
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.GameObjects.Components.Atmos
|
||||
{
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification;
|
||||
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.GameObjects.Components.Atmos
|
||||
{
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Atmos.Piping;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that manages gas mixtures temperature, pressure and output.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class GasCanisterComponent : Component, IGasMixtureHolder, IActivate
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public override string Name => "GasCanister";
|
||||
|
||||
private const int MaxLabelLength = 32;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Label { get; set; } = "Gas Canister";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ValveOpened { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// What <see cref="GasMixture"/> the canister contains.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("gasMixture")]
|
||||
public GasMixture Air { get; set; } = new (DefaultVolume);
|
||||
|
||||
[ViewVariables]
|
||||
public bool Anchored => !Owner.TryGetComponent<IPhysBody>(out var physics) || physics.BodyType == BodyType.Static;
|
||||
|
||||
/// <summary>
|
||||
/// The floor connector port that the canister is attached to.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public GasCanisterPortComponent? ConnectedPort { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public bool ConnectedToPort => ConnectedPort != null;
|
||||
|
||||
public const float DefaultVolume = 10;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float ReleasePressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user interface bound to the canister.
|
||||
/// </summary>
|
||||
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SharedGasCanisterComponent.GasCanisterUiKey.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Stores the last ui state after it's been casted into <see cref="GasCanisterBoundUserInterface"/>
|
||||
/// </summary>
|
||||
private GasCanisterBoundUserInterfaceState? _lastUiState;
|
||||
|
||||
private AppearanceComponent? _appearance;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (Owner.TryGetComponent<IPhysBody>(out var physics))
|
||||
{
|
||||
AnchorUpdate();
|
||||
}
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
}
|
||||
|
||||
// Init some variables
|
||||
Label = Owner.Name;
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
|
||||
UpdateUserInterface();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
#region Connector port methods
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage -= OnUiReceiveMessage;
|
||||
}
|
||||
DisconnectFromPort();
|
||||
}
|
||||
|
||||
public void TryConnectToPort()
|
||||
{
|
||||
if (!Owner.Transform.Anchored) return;
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
var coords = Owner.Transform.Coordinates;
|
||||
var port = grid.GetLocal(coords)
|
||||
.Select(entity => Owner.EntityManager.ComponentManager.TryGetComponent<GasCanisterPortComponent>(entity, out var port) ? port : null)
|
||||
.Where(port => port != null)
|
||||
.Where(port => !port!.ConnectedToCanister)
|
||||
.FirstOrDefault();
|
||||
if (port == null) return;
|
||||
ConnectedPort = port;
|
||||
ConnectedPort.ConnectGasCanister(this);
|
||||
}
|
||||
|
||||
|
||||
public void DisconnectFromPort()
|
||||
{
|
||||
ConnectedPort?.DisconnectGasCanister();
|
||||
ConnectedPort = null;
|
||||
}
|
||||
|
||||
public void AnchorUpdate()
|
||||
{
|
||||
if (Anchored)
|
||||
{
|
||||
TryConnectToPort();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisconnectFromPort();
|
||||
}
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
if (obj.Session.AttachedEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PlayerCanUse(obj.Session.AttachedEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the label has been changed by a client
|
||||
if (obj.Message is CanisterLabelChangedMessage canLabelMessage)
|
||||
{
|
||||
var newLabel = canLabelMessage.NewLabel;
|
||||
if (newLabel.Length > MaxLabelLength)
|
||||
newLabel = newLabel.Substring(0, MaxLabelLength);
|
||||
Label = newLabel;
|
||||
Owner.Name = Label;
|
||||
UpdateUserInterface();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the release pressure has been adjusted by the client on the gas canister
|
||||
if (obj.Message is ReleasePressureButtonPressedMessage rPMessage)
|
||||
{
|
||||
ReleasePressure += rPMessage.ReleasePressure;
|
||||
ReleasePressure = Math.Clamp(ReleasePressure, 0, 1000);
|
||||
ReleasePressure = MathF.Round(ReleasePressure, 2);
|
||||
UpdateUserInterface();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (obj.Message is UiButtonPressedMessage btnPressedMessage)
|
||||
{
|
||||
switch (btnPressedMessage.Button)
|
||||
{
|
||||
case UiButton.ValveToggle:
|
||||
ToggleValve();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the user interface if relevant
|
||||
/// </summary>
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
var state = GetUserInterfaceState();
|
||||
|
||||
if (_lastUiState != null && _lastUiState.Equals(state))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastUiState = state;
|
||||
UserInterface?.SetState(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the canister's sprite
|
||||
/// </summary>
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearance?.SetData(GasCanisterVisuals.ConnectedState, ConnectedToPort);
|
||||
// The Eris canisters are being used, so best to use the Eris light logic unless someone else has a better idea.
|
||||
// https://github.com/discordia-space/CEV-Eris/blob/fdd6ee7012f46838a6711adb1737cd90c48ae448/code/game/machinery/atmoalter/canister.dm#L129
|
||||
if (Air.Pressure < 10)
|
||||
{
|
||||
_appearance?.SetData(GasCanisterVisuals.PressureState, 0);
|
||||
}
|
||||
else if (Air.Pressure < Atmospherics.OneAtmosphere)
|
||||
{
|
||||
_appearance?.SetData(GasCanisterVisuals.PressureState, 1);
|
||||
}
|
||||
else if (Air.Pressure < (15 * Atmospherics.OneAtmosphere))
|
||||
{
|
||||
_appearance?.SetData(GasCanisterVisuals.PressureState, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appearance?.SetData(GasCanisterVisuals.PressureState, 3);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current interface state from server data
|
||||
/// </summary>
|
||||
/// <returns>The state</returns>
|
||||
private GasCanisterBoundUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
// We round the pressure for ease of reading
|
||||
return new GasCanisterBoundUserInterfaceState(Label,
|
||||
MathF.Round(Air.Pressure, 2),
|
||||
ReleasePressure,
|
||||
ValveOpened);
|
||||
}
|
||||
|
||||
public void AirWasUpdated()
|
||||
{
|
||||
UpdateUserInterface();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
#region Check methods
|
||||
|
||||
private bool PlayerCanUse(IEntity? player)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
|
||||
|
||||
if (!actionBlocker.CanInteract(player) ||
|
||||
!actionBlocker.CanUse(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the canister's valve is toggled
|
||||
/// </summary>
|
||||
private void ToggleValve()
|
||||
{
|
||||
ValveOpened = !ValveOpened;
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
public void Update(in float frameTime)
|
||||
{
|
||||
if (ValveOpened)
|
||||
{
|
||||
var tileAtmosphere = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
if (tileAtmosphere != null)
|
||||
{
|
||||
// If tileAtmosphere.Air is null, then we're airblocked, so DON'T release
|
||||
if (tileAtmosphere.Air != null)
|
||||
{
|
||||
Air.ReleaseGasTo(tileAtmosphere.Air, ReleasePressure);
|
||||
tileAtmosphere.Invalidate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Air.ReleaseGasTo(null, ReleasePressure);
|
||||
}
|
||||
|
||||
AirWasUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasMixtureHolderComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
public override string Name => "GasMixtureHolder";
|
||||
|
||||
[ViewVariables] [DataField("air")] public GasMixture Air { get; set; } = new GasMixture();
|
||||
}
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
#nullable enable
|
||||
#nullable disable warnings
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Body.Respiratory;
|
||||
using Content.Server.Explosion;
|
||||
using Content.Server.Interfaces;
|
||||
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.Audio;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameObjects.Components.Atmos.GasTank;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
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.GameObjects.Components.Atmos
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class GasTankComponent : SharedGasTankComponent, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
|
||||
{
|
||||
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;
|
||||
|
||||
[DataField("air")] [ViewVariables] public GasMixture? Air { 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,944 +0,0 @@
|
||||
#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;
|
||||
using Content.Server.GameObjects.Components.Atmos.Piping;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||
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.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <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!;
|
||||
|
||||
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;
|
||||
|
||||
[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<PipeNetDeviceComponent> _pipeNetDevices = new();
|
||||
|
||||
[ViewVariables]
|
||||
private double _pipeNetDevicesLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<TileAtmosphere> _currentRunTiles = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<ExcitedGroup> _currentRunExcitedGroups = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<IPipeNet> _currentRunPipeNet = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Queue<PipeNetDeviceComponent> _currentRunPipeNetDevice = new();
|
||||
|
||||
[ViewVariables]
|
||||
private ProcessState _state = ProcessState.TileEqualize;
|
||||
|
||||
public GridAtmosphereComponent()
|
||||
{
|
||||
_paused = false;
|
||||
}
|
||||
|
||||
private enum ProcessState
|
||||
{
|
||||
TileEqualize,
|
||||
ActiveTiles,
|
||||
ExcitedGroups,
|
||||
HighPressureDelta,
|
||||
Hotspots,
|
||||
Superconductivity,
|
||||
PipeNet,
|
||||
PipeNetDevices,
|
||||
}
|
||||
|
||||
/// <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 AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
|
||||
{
|
||||
_pipeNetDevices.Add(pipeNetDevice);
|
||||
}
|
||||
|
||||
public virtual void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
|
||||
{
|
||||
_pipeNetDevices.Remove(pipeNetDevice);
|
||||
}
|
||||
|
||||
/// <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.PipeNetDevices;
|
||||
break;
|
||||
case ProcessState.PipeNetDevices:
|
||||
if (!ProcessPipeNetDevices(_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 ProcessPipeNetDevices(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
if(!resumed)
|
||||
_currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunPipeNetDevice.Count > 0)
|
||||
{
|
||||
var device = _currentRunPipeNetDevice.Dequeue();
|
||||
device.Update();
|
||||
|
||||
if (number++ < LagCheckIterations) continue;
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_stopwatch.Elapsed.TotalMilliseconds >= lagCheck)
|
||||
{
|
||||
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_pipeNetDevicesLastProcess = _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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
#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.GameObjects.Components.Atmos
|
||||
{
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasCanisterPortComponent : Component
|
||||
{
|
||||
public override string Name => "GasCanisterPort";
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public GasCanisterComponent? ConnectedCanister { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public bool ConnectedToCanister => ConnectedCanister != null;
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _gasPort;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PipeNetDeviceComponent>();
|
||||
SetGasPort();
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
var coords = Owner.Transform.Coordinates;
|
||||
var entities = grid.GetLocal(coords);
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (Owner.EntityManager.ComponentManager.TryGetComponent<GasCanisterComponent>(entity, out var canister) && canister.Anchored && !canister.ConnectedToPort)
|
||||
{
|
||||
canister.TryConnectToPort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
ConnectedCanister?.DisconnectFromPort();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_gasPort == null || ConnectedCanister == null)
|
||||
return;
|
||||
|
||||
ConnectedCanister.Air.Share(_gasPort.Air, 1);
|
||||
ConnectedCanister.AirWasUpdated();
|
||||
}
|
||||
|
||||
public void ConnectGasCanister(GasCanisterComponent gasCanister)
|
||||
{
|
||||
ConnectedCanister = gasCanister;
|
||||
}
|
||||
|
||||
public void DisconnectGasCanister()
|
||||
{
|
||||
ConnectedCanister = null;
|
||||
}
|
||||
|
||||
private void SetGasPort()
|
||||
{
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{nameof(GasCanisterPortComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
_gasPort = container.Nodes.OfType<PipeNode>().FirstOrDefault();
|
||||
if (_gasPort == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(GasCanisterPortComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasFilterComponent : Component
|
||||
{
|
||||
public override string Name => "GasFilter";
|
||||
|
||||
/// <summary>
|
||||
/// If the filter is currently filtering.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool FilterEnabled
|
||||
{
|
||||
get => _filterEnabled;
|
||||
set
|
||||
{
|
||||
_filterEnabled = value;
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
private bool _filterEnabled;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Gas GasToFilter
|
||||
{
|
||||
get => _gasToFilter;
|
||||
set
|
||||
{
|
||||
_gasToFilter = value;
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("gasToFilter")] private Gas _gasToFilter = Gas.Plasma;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int VolumeFilterRate
|
||||
{
|
||||
get => _volumeFilterRate;
|
||||
set => _volumeFilterRate = Math.Clamp(value, 0, MaxVolumeFilterRate);
|
||||
}
|
||||
|
||||
[DataField("startingVolumePumpRate")]
|
||||
private int _volumeFilterRate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int MaxVolumeFilterRate
|
||||
{
|
||||
get => _maxVolumeFilterRate;
|
||||
set => Math.Max(value, 0);
|
||||
}
|
||||
|
||||
[DataField("maxVolumePumpRate")] private int _maxVolumeFilterRate = 100;
|
||||
|
||||
[DataField("inletDirection")] [ViewVariables]
|
||||
private PipeDirection _initialInletDirection = PipeDirection.None;
|
||||
|
||||
/// <summary>
|
||||
/// The direction the filtered-out gas goes.
|
||||
/// </summary>
|
||||
[DataField("filterOutletDirection")] [ViewVariables]
|
||||
private PipeDirection _initialFilterOutletDirection = PipeDirection.None;
|
||||
|
||||
/// <summary>
|
||||
/// The direction the rest of the gas goes.
|
||||
/// </summary>
|
||||
[DataField("outletDirection")] [ViewVariables]
|
||||
private PipeDirection _initialOutletDirection = PipeDirection.None;
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _inletPipe;
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _filterOutletPipe;
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _outletPipe;
|
||||
|
||||
[ComponentDependency]
|
||||
private readonly AppearanceComponent? _appearance = default;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponent<PipeNetDeviceComponent>();
|
||||
SetPipes();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!FilterEnabled)
|
||||
return;
|
||||
|
||||
if (_inletPipe == null || _inletPipe.Air == null ||
|
||||
_filterOutletPipe == null || _filterOutletPipe.Air == null ||
|
||||
_outletPipe == null || _outletPipe.Air == null)
|
||||
return;
|
||||
|
||||
FilterGas(_inletPipe.Air, _filterOutletPipe.Air, _outletPipe.Air);
|
||||
}
|
||||
|
||||
private void FilterGas(GasMixture inletGas, GasMixture filterOutletGas, GasMixture outletGas)
|
||||
{
|
||||
var volumeRatio = Math.Clamp(VolumeFilterRate / inletGas.Volume, 0, 1);
|
||||
var gas = inletGas.RemoveRatio(volumeRatio);
|
||||
|
||||
var molesToSeperate = gas.GetMoles(GasToFilter);
|
||||
gas.SetMoles(GasToFilter, 0);
|
||||
filterOutletGas.AdjustMoles(GasToFilter, molesToSeperate);
|
||||
|
||||
outletGas.Merge(gas);
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearance?.SetData(FilterVisuals.VisualState, new FilterVisualState(FilterEnabled));
|
||||
}
|
||||
|
||||
private void SetPipes()
|
||||
{
|
||||
_inletPipe = null;
|
||||
_filterOutletPipe = null;
|
||||
_outletPipe = null;
|
||||
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{typeof(GasFilterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var pipeNodes = container.Nodes.OfType<PipeNode>();
|
||||
|
||||
_inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialInletDirection).FirstOrDefault();
|
||||
_filterOutletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialFilterOutletDirection).FirstOrDefault();
|
||||
_outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialOutletDirection).FirstOrDefault();
|
||||
|
||||
if (_inletPipe == null || _filterOutletPipe == null || _outletPipe == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(GasFilterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates gas in the attached pipe.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class GasGeneratorComponent : Component
|
||||
{
|
||||
public override string Name => "GasGenerator";
|
||||
|
||||
/// <summary>
|
||||
/// If the generator is producing gas.
|
||||
/// </summary>
|
||||
[DataField("generatorEnabled")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool GeneratorEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// What gas is being generated.
|
||||
/// </summary>
|
||||
[DataField("generatedGas")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Gas GeneratedGas { get; set; } = Gas.Oxygen;
|
||||
|
||||
/// <summary>
|
||||
/// Molar rate of gas generation.
|
||||
/// </summary>
|
||||
[DataField("gasGenerationRate")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float GasGenerationRate { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// The pipe pressure above which the generator stops producing gas.
|
||||
/// </summary>
|
||||
[DataField("generatorPressureCap")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float GeneratorPressureCap { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// The pipe to which generated gas is added.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private PipeNode? Pipe { get; set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PipeNetDeviceComponent>();
|
||||
SetPipes();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!GeneratorEnabled)
|
||||
return;
|
||||
|
||||
if (Pipe == null || Pipe.Air.Pressure > GeneratorPressureCap)
|
||||
return;
|
||||
|
||||
Pipe.Air.AdjustMoles(GeneratedGas, GasGenerationRate);
|
||||
}
|
||||
|
||||
private void SetPipes()
|
||||
{
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{nameof(GasGeneratorComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
Pipe = container.Nodes.OfType<PipeNode>().FirstOrDefault();
|
||||
if (Pipe == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(GasGeneratorComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
{
|
||||
/// <summary>
|
||||
/// Placeholder component for adjusting the temperature of gas in pipes.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PipeHeaterComponent : Component
|
||||
{
|
||||
public override string Name => "PipeHeater";
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _heaterPipe;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float TargetTemperature { get; set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PipeNetDeviceComponent>();
|
||||
SetPipe();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_heaterPipe == null)
|
||||
return;
|
||||
|
||||
_heaterPipe.Air.Temperature = TargetTemperature;
|
||||
}
|
||||
|
||||
private void SetPipe()
|
||||
{
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{nameof(PipeHeaterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
_heaterPipe = container.Nodes.OfType<PipeNode>().FirstOrDefault();
|
||||
if (_heaterPipe == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(PipeHeaterComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds itself to a <see cref="IGridAtmosphereComponent"/> to be updated by.
|
||||
/// TODO: Make compatible with unanchoring/anchoring. Currently assumes that the Owner does not move.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PipeNetDeviceComponent : Component
|
||||
{
|
||||
public override string Name => "PipeNetDevice";
|
||||
|
||||
private IGridAtmosphereComponent? JoinedGridAtmos { get; set; }
|
||||
|
||||
private PipeNetUpdateMessage _cachedUpdateMessage = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
JoinGridAtmos();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
LeaveGridAtmos();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
SendMessage(_cachedUpdateMessage);
|
||||
}
|
||||
|
||||
private void JoinGridAtmos()
|
||||
{
|
||||
var gridAtmos = EntitySystem.Get<AtmosphereSystem>()
|
||||
.GetGridAtmosphere(Owner.Transform.Coordinates);
|
||||
JoinedGridAtmos = gridAtmos;
|
||||
JoinedGridAtmos.AddPipeNetDevice(this);
|
||||
}
|
||||
|
||||
private void LeaveGridAtmos()
|
||||
{
|
||||
JoinedGridAtmos?.RemovePipeNetDevice(this);
|
||||
JoinedGridAtmos = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class PipeNetUpdateMessage : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
|
||||
{
|
||||
/// <summary>
|
||||
/// Transfer gas from one <see cref="PipeNode"/> to another.
|
||||
/// </summary>
|
||||
public abstract class BasePumpComponent : Component, IActivate
|
||||
{
|
||||
/// <summary>
|
||||
/// If the pump is currently pumping.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool PumpEnabled
|
||||
{
|
||||
get => _pumpEnabled;
|
||||
set
|
||||
{
|
||||
_pumpEnabled = value;
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("pumpEnabled")]
|
||||
private bool _pumpEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="PipeNode"/> on this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("initialInletDirection", required: true)]
|
||||
private PipeDirection _initialInletDirection = PipeDirection.None;
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="PipeNode"/> on this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("initialOutletDirection", required: true)]
|
||||
private PipeDirection _initialOutletDirection = PipeDirection.None;
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _inletPipe;
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _outletPipe;
|
||||
|
||||
private AppearanceComponent? _appearance;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PipeNetDeviceComponent>();
|
||||
SetPipes();
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!PumpEnabled)
|
||||
return;
|
||||
|
||||
if (_inletPipe == null || _outletPipe == null)
|
||||
return;
|
||||
|
||||
PumpGas(_inletPipe.Air, _outletPipe.Air);
|
||||
}
|
||||
|
||||
protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas);
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (_inletPipe == null || _outletPipe == null) return;
|
||||
_appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_initialInletDirection, _initialOutletDirection, PumpEnabled));
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
PumpEnabled = !PumpEnabled;
|
||||
}
|
||||
|
||||
private void SetPipes()
|
||||
{
|
||||
_inletPipe = null;
|
||||
_outletPipe = null;
|
||||
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{nameof(BasePumpComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
var pipeNodes = container.Nodes.OfType<PipeNode>();
|
||||
_inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialInletDirection).FirstOrDefault();
|
||||
_outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _initialOutletDirection).FirstOrDefault();
|
||||
if (_inletPipe == null || _outletPipe == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(BasePumpComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(BasePumpComponent))]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class PressurePumpComponent : BasePumpComponent
|
||||
{
|
||||
public override string Name => "PressurePump";
|
||||
|
||||
/// <summary>
|
||||
/// The pressure this pump will try to bring its oulet too.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PressurePumpTarget
|
||||
{
|
||||
get => _pressurePumpTarget;
|
||||
set => _pressurePumpTarget = Math.Clamp(value, 0, MaxPressurePumpTarget);
|
||||
}
|
||||
|
||||
[DataField("startingPressurePumpTarget")]
|
||||
private int _pressurePumpTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Max value <see cref="PressurePumpTarget"/> can be set to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int MaxPressurePumpTarget
|
||||
{
|
||||
get => _maxPressurePumpTarget;
|
||||
set => Math.Max(value, 0);
|
||||
}
|
||||
[DataField("maxPressurePumpTarget")]
|
||||
private int _maxPressurePumpTarget = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Every update, this pump will only increase the outlet pressure by this fraction of the amount needed to reach the <see cref="PressurePumpTarget"/>.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TransferRatio
|
||||
{
|
||||
get => _transferRatio;
|
||||
set => _transferRatio = Math.Clamp(value, 0, 1);
|
||||
}
|
||||
[DataField("transferRatio")]
|
||||
private float _transferRatio = 0.5f;
|
||||
|
||||
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
|
||||
{
|
||||
var goalDiff = PressurePumpTarget - outletGas.Pressure;
|
||||
var realGoalPressureDiff = goalDiff * TransferRatio;
|
||||
var realTargetPressure = outletGas.Pressure + realGoalPressureDiff;
|
||||
var realCappedTargetPressure = Math.Max(realTargetPressure, outletGas.Pressure); //no lowering the outlet's pressure
|
||||
inletGas.PumpGasTo(outletGas, realCappedTargetPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(BasePumpComponent))]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class VolumePumpComponent : BasePumpComponent
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int VolumePumpRate
|
||||
{
|
||||
get => _volumePumpRate;
|
||||
set => _volumePumpRate = Math.Clamp(value, 0, MaxVolumePumpRate);
|
||||
}
|
||||
[DataField("startingVolumePumpRate")]
|
||||
private int _volumePumpRate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int MaxVolumePumpRate
|
||||
{
|
||||
get => _maxVolumePumpRate;
|
||||
set => Math.Max(value, 0);
|
||||
}
|
||||
[DataField("maxVolumePumpRate")]
|
||||
private int _maxVolumePumpRate = 100;
|
||||
|
||||
public override string Name => "VolumePump";
|
||||
|
||||
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
|
||||
{
|
||||
var volumeRatio = Math.Clamp(VolumePumpRate / inletGas.Volume, 0, 1);
|
||||
outletGas.Merge(inletGas.RemoveRatio(volumeRatio));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Scrubbers
|
||||
{
|
||||
/// <summary>
|
||||
/// Transfers gas from the tile it is on to a <see cref="PipeNode"/>.
|
||||
/// </summary>
|
||||
public abstract class BaseSiphonComponent : Component
|
||||
{
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _scrubberOutlet;
|
||||
|
||||
private AtmosphereSystem? _atmosSystem;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool SiphonEnabled
|
||||
{
|
||||
get => _siphonEnabled;
|
||||
set
|
||||
{
|
||||
_siphonEnabled = value;
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
private bool _siphonEnabled = true;
|
||||
|
||||
private AppearanceComponent? _appearance;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PipeNetDeviceComponent>();
|
||||
_atmosSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
SetOutlet();
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!SiphonEnabled)
|
||||
return;
|
||||
|
||||
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
|
||||
if (_scrubberOutlet == null || tileAtmos == null || tileAtmos.Air == null)
|
||||
return;
|
||||
|
||||
ScrubGas(tileAtmos.Air, _scrubberOutlet.Air);
|
||||
tileAtmos.Invalidate();
|
||||
}
|
||||
|
||||
protected abstract void ScrubGas(GasMixture inletGas, GasMixture outletGas);
|
||||
|
||||
private void SetOutlet()
|
||||
{
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{nameof(BaseSiphonComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
_scrubberOutlet = container.Nodes.OfType<PipeNode>().FirstOrDefault();
|
||||
if (_scrubberOutlet == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(BaseSiphonComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearance?.SetData(SiphonVisuals.VisualState, new SiphonVisualState(SiphonEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Scrubbers
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(BaseSiphonComponent))]
|
||||
public class PressureSiphonComponent : BaseSiphonComponent
|
||||
{
|
||||
public override string Name => "PressureSiphon";
|
||||
|
||||
/// <summary>
|
||||
/// The pressure this siphon will try to bring its inlet too.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SiphonPressureTarget
|
||||
{
|
||||
get => _siphonPressureTarget;
|
||||
set => _siphonPressureTarget = Math.Max(value, 0);
|
||||
}
|
||||
[DataField("startingSiphonPressureTarget")]
|
||||
private float _siphonPressureTarget = Atmospherics.OneAtmosphere * 2;
|
||||
|
||||
/// <summary>
|
||||
/// Every update, this siphon will only decrease the inlet pressure by this fraction of the amount needed to reach the <see cref="SiphonPressureTarget"/>.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TransferRatio
|
||||
{
|
||||
get => _transferRatio;
|
||||
set => _transferRatio = Math.Clamp(value, 0, 1);
|
||||
}
|
||||
[DataField("transferRatio")]
|
||||
private float _transferRatio = 0.5f;
|
||||
|
||||
protected override void ScrubGas(GasMixture inletGas, GasMixture outletGas)
|
||||
{
|
||||
var goalDiff = SiphonPressureTarget - inletGas.Pressure;
|
||||
var realGoalPressureDiff = goalDiff * TransferRatio;
|
||||
|
||||
var moleRatioToTransfer = 1 - (inletGas.Pressure + realGoalPressureDiff) / inletGas.Pressure;
|
||||
moleRatioToTransfer = Math.Clamp(moleRatioToTransfer, 0, 1);
|
||||
|
||||
var transferedGas = inletGas.RemoveRatio(moleRatioToTransfer);
|
||||
outletGas.Merge(transferedGas);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Vents
|
||||
{
|
||||
/// <summary>
|
||||
/// Transfers gas from a <see cref="PipeNode"/> to the tile it is on.
|
||||
/// </summary>
|
||||
public abstract class BaseVentComponent : Component
|
||||
{
|
||||
|
||||
[ViewVariables]
|
||||
private PipeNode? _ventInlet;
|
||||
|
||||
private AtmosphereSystem? _atmosSystem;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool VentEnabled
|
||||
{
|
||||
get => _ventEnabled;
|
||||
set
|
||||
{
|
||||
_ventEnabled = value;
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
private bool _ventEnabled = true;
|
||||
|
||||
private AppearanceComponent? _appearance;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PipeNetDeviceComponent>();
|
||||
_atmosSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
SetInlet();
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PipeNetUpdateMessage:
|
||||
Update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!VentEnabled)
|
||||
return;
|
||||
|
||||
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
|
||||
if (_ventInlet == null || tileAtmos == null || tileAtmos.Air == null)
|
||||
return;
|
||||
|
||||
VentGas(_ventInlet.Air, tileAtmos.Air);
|
||||
tileAtmos.Invalidate();
|
||||
}
|
||||
|
||||
protected abstract void VentGas(GasMixture inletGas, GasMixture outletGas);
|
||||
|
||||
private void SetInlet()
|
||||
{
|
||||
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
Logger.Warning($"{nameof(BaseVentComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} did not have a {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
_ventInlet = container.Nodes.OfType<PipeNode>().FirstOrDefault();
|
||||
if (_ventInlet == null)
|
||||
{
|
||||
Logger.Warning($"{nameof(BaseVentComponent)} on {Owner?.Prototype?.ID}, Uid {Owner?.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearance?.SetData(VentVisuals.VisualState, new VentVisualState(VentEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos.Piping.Vents
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(BaseVentComponent))]
|
||||
public class PressureVentComponent : BaseVentComponent
|
||||
{
|
||||
public override string Name => "PressureVent";
|
||||
|
||||
/// <summary>
|
||||
/// The pressure this vent will try to bring its oulet to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float VentPressureTarget
|
||||
{
|
||||
get => _ventPressureTarget;
|
||||
set => _ventPressureTarget = Math.Clamp(value, 0, MaxVentPressureTarget);
|
||||
}
|
||||
[DataField("startingVentPressureTarget")]
|
||||
private float _ventPressureTarget = Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Max value <see cref="VentPressureTarget"/> can be set to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxVentPressureTarget
|
||||
{
|
||||
get => _maxVentPressureTarget;
|
||||
set => Math.Max(value, 0);
|
||||
}
|
||||
[DataField("maxVentPressureTarget")]
|
||||
private float _maxVentPressureTarget = Atmospherics.OneAtmosphere * 2;
|
||||
|
||||
/// <summary>
|
||||
/// Every update, this vent will only increase the outlet pressure by this fraction of the amount needed to reach the <see cref="VentPressureTarget"/>.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float TransferRatio
|
||||
{
|
||||
get => _transferRatio;
|
||||
set => _transferRatio = Math.Clamp(value, 0, 1);
|
||||
}
|
||||
[DataField("transferRatio")]
|
||||
private float _transferRatio = 0.5f;
|
||||
|
||||
protected override void VentGas(GasMixture inletGas, GasMixture outletGas)
|
||||
{
|
||||
var goalDiff = VentPressureTarget - outletGas.Pressure;
|
||||
var realGoalPressureDiff = goalDiff * TransferRatio;
|
||||
var realTargetPressure = outletGas.Pressure + realGoalPressureDiff;
|
||||
var realCappedTargetPressure = Math.Max(realTargetPressure, outletGas.Pressure); //no lowering the outlet's pressure
|
||||
inletGas.PumpGasTo(outletGas, realCappedTargetPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Server.Pressure;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
[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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Atmos.Piping;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IGridAtmosphereComponent))]
|
||||
[ComponentReference(typeof(GridAtmosphereComponent))]
|
||||
[Serializable]
|
||||
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent, IGridAtmosphereComponent
|
||||
{
|
||||
public override string Name => "UnsimulatedGridAtmosphere";
|
||||
|
||||
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 AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice) { }
|
||||
|
||||
public override void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice) { }
|
||||
|
||||
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 ProcessPipeNetDevices(bool resumed = false, float lagCheck = 5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
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.GameObjects.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.
|
||||
@@ -1,139 +0,0 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.Atmos
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Players allowed to see the atmos debug overlay.
|
||||
/// To modify it see <see cref="AddObserver"/> and
|
||||
/// <see cref="RemoveObserver"/>.
|
||||
/// </summary>
|
||||
private readonly HashSet<IPlayerSession> _playerObservers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Overlay update ticks per second.
|
||||
/// </summary>
|
||||
private float _updateCooldown;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
public bool AddObserver(IPlayerSession observer)
|
||||
{
|
||||
return _playerObservers.Add(observer);
|
||||
}
|
||||
|
||||
public bool HasObserver(IPlayerSession observer)
|
||||
{
|
||||
return _playerObservers.Contains(observer);
|
||||
}
|
||||
|
||||
public bool RemoveObserver(IPlayerSession observer)
|
||||
{
|
||||
if (!_playerObservers.Remove(observer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = new AtmosDebugOverlayDisableMessage();
|
||||
RaiseNetworkEvent(message, observer.ConnectedClient);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given observer if it doesn't exist, removes it otherwise.
|
||||
/// </summary>
|
||||
/// <param name="observer">The observer to toggle.</param>
|
||||
/// <returns>true if added, false if removed.</returns>
|
||||
public bool ToggleObserver(IPlayerSession observer)
|
||||
{
|
||||
if (HasObserver(observer))
|
||||
{
|
||||
RemoveObserver(observer);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddObserver(observer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
{
|
||||
RemoveObserver(e.Session);
|
||||
}
|
||||
}
|
||||
|
||||
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
|
||||
{
|
||||
var gases = new float[Atmospherics.TotalNumberOfGases];
|
||||
if (tile?.Air == null)
|
||||
{
|
||||
return new AtmosDebugOverlayData(0, gases, AtmosDirection.Invalid, false, tile?.BlockedAirflow ?? AtmosDirection.Invalid);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
gases[i] = tile.Air.GetMoles(i);
|
||||
}
|
||||
return new AtmosDebugOverlayData(tile.Air.Temperature, gases, tile.PressureDirectionForDebugOverlay, tile.ExcitedGroup != null, tile.BlockedAirflow);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
AccumulatedFrameTime += frameTime;
|
||||
_updateCooldown = 1 / _configManager.GetCVar(CCVars.NetAtmosDebugOverlayTickRate);
|
||||
|
||||
if (AccumulatedFrameTime < _updateCooldown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the timer from GasTileOverlaySystem
|
||||
AccumulatedFrameTime -= _updateCooldown;
|
||||
|
||||
var currentTick = _gameTiming.CurTick;
|
||||
|
||||
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
|
||||
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
|
||||
// Afterwards we reset all the chunk data for the next time we tick.
|
||||
foreach (var session in _playerObservers)
|
||||
{
|
||||
if (session.AttachedEntity == null) continue;
|
||||
|
||||
var entity = session.AttachedEntity;
|
||||
|
||||
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
|
||||
new Vector2(LocalViewRange, LocalViewRange));
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds))
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) continue;
|
||||
|
||||
if (!gridEnt.TryGetComponent<GridAtmosphereComponent>(out var gam)) continue;
|
||||
|
||||
var entityTile = grid.GetTileRef(entity.Transform.Coordinates).GridIndices;
|
||||
var baseTile = new Vector2i(entityTile.X - (LocalViewRange / 2), entityTile.Y - (LocalViewRange / 2));
|
||||
var debugOverlayContent = new AtmosDebugOverlayData[LocalViewRange * LocalViewRange];
|
||||
|
||||
var index = 0;
|
||||
for (var y = 0; y < LocalViewRange; y++)
|
||||
{
|
||||
for (var x = 0; x < LocalViewRange; x++)
|
||||
{
|
||||
var Vector2i = new Vector2i(baseTile.X + x, baseTile.Y + y);
|
||||
debugOverlayContent[index++] = ConvertTileToData(gam.GetTile(Vector2i));
|
||||
}
|
||||
}
|
||||
|
||||
RaiseNetworkEvent(new AtmosDebugOverlayMessage(grid.Index, baseTile, debugOverlayContent), session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Shared.GameTicking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
// ReSharper disable once RedundantUsingDirective
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Dependency = Robust.Shared.IoC.DependencyAttribute;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.Atmos
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem, IResettingEntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The tiles that have had their atmos data updated since last tick
|
||||
/// </summary>
|
||||
private readonly Dictionary<GridId, HashSet<Vector2i>> _invalidTiles = new();
|
||||
|
||||
private readonly Dictionary<IPlayerSession, PlayerGasOverlay> _knownPlayerChunks =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// Gas data stored in chunks to make PVS / bubbling easier.
|
||||
/// </summary>
|
||||
private readonly Dictionary<GridId, Dictionary<Vector2i, GasOverlayChunk>> _overlay =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// How far away do we update gas overlays (minimum; due to chunking further away tiles may also be updated).
|
||||
/// </summary>
|
||||
private float _updateRange;
|
||||
// Because the gas overlay updates aren't run every tick we need to avoid the pop-in that might occur with
|
||||
// the regular PVS range.
|
||||
private const float RangeOffset = 6.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay update ticks per second.
|
||||
/// </summary>
|
||||
private float _updateCooldown;
|
||||
|
||||
private AtmosphereSystem _atmosphereSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_atmosphereSystem = Get<AtmosphereSystem>();
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invalidate(GridId gridIndex, Vector2i indices)
|
||||
{
|
||||
if (!_invalidTiles.TryGetValue(gridIndex, out var existing))
|
||||
{
|
||||
existing = new HashSet<Vector2i>();
|
||||
_invalidTiles[gridIndex] = existing;
|
||||
}
|
||||
|
||||
existing.Add(indices);
|
||||
}
|
||||
|
||||
private GasOverlayChunk GetOrCreateChunk(GridId gridIndex, Vector2i indices)
|
||||
{
|
||||
if (!_overlay.TryGetValue(gridIndex, out var chunks))
|
||||
{
|
||||
chunks = new Dictionary<Vector2i, GasOverlayChunk>();
|
||||
_overlay[gridIndex] = chunks;
|
||||
}
|
||||
|
||||
var chunkIndices = GetGasChunkIndices(indices);
|
||||
|
||||
if (!chunks.TryGetValue(chunkIndices, out var chunk))
|
||||
{
|
||||
chunk = new GasOverlayChunk(gridIndex, chunkIndices);
|
||||
chunks[chunkIndices] = chunk;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private void OnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
if (_overlay.ContainsKey(gridId))
|
||||
{
|
||||
_overlay.Remove(gridId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
{
|
||||
if (_knownPlayerChunks.ContainsKey(e.Session))
|
||||
{
|
||||
_knownPlayerChunks.Remove(e.Session);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_knownPlayerChunks.ContainsKey(e.Session))
|
||||
{
|
||||
_knownPlayerChunks[e.Session] = new PlayerGasOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the overlay-relevant data for a gas tile has been updated.
|
||||
/// </summary>
|
||||
/// <param name="gam"></param>
|
||||
/// <param name="oldTile"></param>
|
||||
/// <param name="indices"></param>
|
||||
/// <param name="overlayData"></param>
|
||||
/// <returns>true if updated</returns>
|
||||
private bool TryRefreshTile(GridAtmosphereComponent gam, GasOverlayData oldTile, Vector2i indices, out GasOverlayData overlayData)
|
||||
{
|
||||
var tile = gam.GetTile(indices);
|
||||
|
||||
if (tile == null)
|
||||
{
|
||||
overlayData = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileData = new List<GasData>();
|
||||
|
||||
if(tile.Air != null)
|
||||
for (byte i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var gas = _atmosphereSystem.GetGas(i);
|
||||
var overlay = _atmosphereSystem.GetOverlay(i);
|
||||
if (overlay == null) continue;
|
||||
|
||||
var moles = tile.Air.Gases[i];
|
||||
|
||||
if (moles < gas.GasMolesVisible) continue;
|
||||
|
||||
var data = new GasData(i, (byte) (MathHelper.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
|
||||
tileData.Add(data);
|
||||
}
|
||||
|
||||
overlayData = new GasOverlayData(tile!.Hotspot.State, tile.Hotspot.Temperature, tileData.Count == 0 ? Array.Empty<GasData>() : tileData.ToArray());
|
||||
|
||||
if (overlayData.Equals(oldTile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get every chunk in range of our entity that exists, including on other grids.
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
private List<GasOverlayChunk> GetChunksInRange(IEntity entity)
|
||||
{
|
||||
var inRange = new List<GasOverlayChunk>();
|
||||
|
||||
// This is the max in any direction that we can get a chunk (e.g. max 2 chunks away of data).
|
||||
var (maxXDiff, maxYDiff) = ((int) (_updateRange / ChunkSize) + 1, (int) (_updateRange / ChunkSize) + 1);
|
||||
|
||||
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
|
||||
new Vector2(_updateRange, _updateRange));
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds))
|
||||
{
|
||||
if (!_overlay.TryGetValue(grid.Index, out var chunks))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entityTile = grid.GetTileRef(entity.Transform.Coordinates).GridIndices;
|
||||
|
||||
for (var x = -maxXDiff; x <= maxXDiff; x++)
|
||||
{
|
||||
for (var y = -maxYDiff; y <= maxYDiff; y++)
|
||||
{
|
||||
var chunkIndices = GetGasChunkIndices(new Vector2i(entityTile.X + x * ChunkSize, entityTile.Y + y * ChunkSize));
|
||||
|
||||
if (!chunks.TryGetValue(chunkIndices, out var chunk)) continue;
|
||||
|
||||
// Now we'll check if it's in range and relevant for us
|
||||
// (e.g. if we're on the very edge of a chunk we may need more chunks).
|
||||
|
||||
var (xDiff, yDiff) = (chunkIndices.X - entityTile.X, chunkIndices.Y - entityTile.Y);
|
||||
if (xDiff > 0 && xDiff > _updateRange ||
|
||||
yDiff > 0 && yDiff > _updateRange ||
|
||||
xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange ||
|
||||
yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) continue;
|
||||
|
||||
inRange.Add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inRange;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
AccumulatedFrameTime += frameTime;
|
||||
_updateCooldown = 1 / _configManager.GetCVar(CCVars.NetGasOverlayTickRate);
|
||||
|
||||
if (AccumulatedFrameTime < _updateCooldown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updateRange = _configManager.GetCVar(CVars.NetMaxUpdateRange) + RangeOffset;
|
||||
|
||||
// TODO: So in the worst case scenario we still have to send a LOT of tile data per tick if there's a fire.
|
||||
// If we go with say 15 tile radius then we have up to 900 tiles to update per tick.
|
||||
// In a saltern fire the worst you'll normally see is around 650 at the moment.
|
||||
// Need a way to fake this more because sending almost 2,000 tile updates per second to even 50 players is... yikes
|
||||
// I mean that's as big as it gets so larger maps will have the same but still, that's a lot of data.
|
||||
|
||||
// Some ways to do this are potentially: splitting fire and gas update data so they don't update at the same time
|
||||
// (gives the illusion of more updates happening), e.g. if gas updates are 3 times a second and fires are 1.6 times a second or something.
|
||||
// Could also look at updating tiles close to us more frequently (e.g. within 1 chunk every tick).
|
||||
// Stuff just out of our viewport we need so when we move it doesn't pop in but it doesn't mean we need to update it every tick.
|
||||
|
||||
AccumulatedFrameTime -= _updateCooldown;
|
||||
|
||||
var gridAtmosComponents = new Dictionary<GridId, GridAtmosphereComponent>();
|
||||
var updatedTiles = new Dictionary<GasOverlayChunk, HashSet<Vector2i>>();
|
||||
|
||||
// So up to this point we've been caching the updated tiles for multiple ticks.
|
||||
// Now we'll go through and check whether the update actually matters for the overlay or not,
|
||||
// and if not then we won't bother sending the data.
|
||||
foreach (var (gridId, indices) in _invalidTiles)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var gridEntityId = grid.GridEntityId;
|
||||
|
||||
if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent? gam))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's being invalidated it should have this right?
|
||||
// At any rate we'll cache it for here + the AddChunk
|
||||
if (!gridAtmosComponents.ContainsKey(gridId))
|
||||
{
|
||||
gridAtmosComponents[gridId] = gam;
|
||||
}
|
||||
|
||||
foreach (var invalid in indices.ToArray())
|
||||
{
|
||||
var chunk = GetOrCreateChunk(gridId, invalid);
|
||||
|
||||
if (!TryRefreshTile(gam, chunk.GetData(invalid), invalid, out var data)) continue;
|
||||
|
||||
if (!updatedTiles.TryGetValue(chunk, out var tiles))
|
||||
{
|
||||
tiles = new HashSet<Vector2i>();
|
||||
updatedTiles[chunk] = tiles;
|
||||
}
|
||||
|
||||
updatedTiles[chunk].Add(invalid);
|
||||
chunk.Update(data, invalid);
|
||||
}
|
||||
}
|
||||
|
||||
var currentTick = _gameTiming.CurTick;
|
||||
|
||||
// Set the LastUpdate for chunks.
|
||||
foreach (var (chunk, _) in updatedTiles)
|
||||
{
|
||||
chunk.Dirty(currentTick);
|
||||
}
|
||||
|
||||
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
|
||||
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
|
||||
// Afterwards we reset all the chunk data for the next time we tick.
|
||||
foreach (var (session, overlay) in _knownPlayerChunks)
|
||||
{
|
||||
if (session.AttachedEntity == null) continue;
|
||||
|
||||
// Get chunks in range and update if we've moved around or the chunks have new overlay data
|
||||
var chunksInRange = GetChunksInRange(session.AttachedEntity);
|
||||
var knownChunks = overlay.GetKnownChunks();
|
||||
var chunksToRemove = new List<GasOverlayChunk>();
|
||||
var chunksToAdd = new List<GasOverlayChunk>();
|
||||
|
||||
foreach (var chunk in chunksInRange)
|
||||
{
|
||||
if (!knownChunks.Contains(chunk))
|
||||
{
|
||||
chunksToAdd.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in knownChunks)
|
||||
{
|
||||
if (!chunksInRange.Contains(chunk))
|
||||
{
|
||||
chunksToRemove.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in chunksToAdd)
|
||||
{
|
||||
var message = overlay.AddChunk(currentTick, chunk);
|
||||
if (message != null)
|
||||
{
|
||||
RaiseNetworkEvent(message, session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in chunksToRemove)
|
||||
{
|
||||
overlay.RemoveChunk(chunk);
|
||||
}
|
||||
|
||||
var clientInvalids = new Dictionary<GridId, List<(Vector2i, GasOverlayData)>>();
|
||||
|
||||
// Check for any dirty chunks in range and bundle the data to send to the client.
|
||||
foreach (var chunk in chunksInRange)
|
||||
{
|
||||
if (!updatedTiles.TryGetValue(chunk, out var invalids)) continue;
|
||||
|
||||
if (!clientInvalids.TryGetValue(chunk.GridIndices, out var existingData))
|
||||
{
|
||||
existingData = new List<(Vector2i, GasOverlayData)>();
|
||||
clientInvalids[chunk.GridIndices] = existingData;
|
||||
}
|
||||
|
||||
chunk.GetData(existingData, invalids);
|
||||
}
|
||||
|
||||
foreach (var (grid, data) in clientInvalids)
|
||||
{
|
||||
RaiseNetworkEvent(overlay.UpdateClient(grid, data), session.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
_invalidTiles.Clear();
|
||||
}
|
||||
private sealed class PlayerGasOverlay
|
||||
{
|
||||
private readonly Dictionary<GridId, Dictionary<Vector2i, GasOverlayChunk>> _data =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<GasOverlayChunk, GameTick> _lastSent =
|
||||
new();
|
||||
|
||||
public GasOverlayMessage UpdateClient(GridId grid, List<(Vector2i, GasOverlayData)> data)
|
||||
{
|
||||
return new(grid, data);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_data.Clear();
|
||||
_lastSent.Clear();
|
||||
}
|
||||
|
||||
public List<GasOverlayChunk> GetKnownChunks()
|
||||
{
|
||||
var known = new List<GasOverlayChunk>();
|
||||
|
||||
foreach (var (_, chunks) in _data)
|
||||
{
|
||||
foreach (var (_, chunk) in chunks)
|
||||
{
|
||||
known.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return known;
|
||||
}
|
||||
|
||||
public GasOverlayMessage? AddChunk(GameTick currentTick, GasOverlayChunk chunk)
|
||||
{
|
||||
if (!_data.TryGetValue(chunk.GridIndices, out var chunks))
|
||||
{
|
||||
chunks = new Dictionary<Vector2i, GasOverlayChunk>();
|
||||
_data[chunk.GridIndices] = chunks;
|
||||
}
|
||||
|
||||
if (_lastSent.TryGetValue(chunk, out var last) && last >= chunk.LastUpdate)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_lastSent[chunk] = currentTick;
|
||||
var message = ChunkToMessage(chunk);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public void RemoveChunk(GasOverlayChunk chunk)
|
||||
{
|
||||
// Don't need to sync to client as they can manage it themself.
|
||||
if (!_data.TryGetValue(chunk.GridIndices, out var chunks))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunks.ContainsKey(chunk.Vector2i))
|
||||
{
|
||||
chunks.Remove(chunk.Vector2i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a whole chunk as a message, only getting the relevant tiles for the gas overlay.
|
||||
/// </summary>
|
||||
/// <param name="chunk"></param>
|
||||
/// <returns></returns>
|
||||
private GasOverlayMessage? ChunkToMessage(GasOverlayChunk chunk)
|
||||
{
|
||||
// Chunk data should already be up to date.
|
||||
// Only send relevant tiles to client.
|
||||
|
||||
var tileData = new List<(Vector2i, GasOverlayData)>();
|
||||
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
// TODO: Check could be more robust I think.
|
||||
var data = chunk.TileData[x, y];
|
||||
if ((data.Gas == null || data.Gas.Length == 0) && data.FireState == 0 && data.FireTemperature == 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var indices = new Vector2i(chunk.Vector2i.X + x, chunk.Vector2i.Y + y);
|
||||
tileData.Add((indices, data));
|
||||
}
|
||||
}
|
||||
|
||||
if (tileData.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GasOverlayMessage(chunk.GridIndices, tileData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_invalidTiles.Clear();
|
||||
_overlay.Clear();
|
||||
|
||||
foreach (var (_, data) in _knownPlayerChunks)
|
||||
{
|
||||
data.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AtmosExposedSystem
|
||||
: EntitySystem
|
||||
{
|
||||
|
||||
private const float UpdateDelay = 1f;
|
||||
private float _lastUpdate;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_lastUpdate += frameTime;
|
||||
if (_lastUpdate < UpdateDelay) return;
|
||||
|
||||
// creadth: everything exposable by atmos should be updated as well
|
||||
foreach (var atmosExposedComponent in EntityManager.ComponentManager.EntityQuery<AtmosExposedComponent>(true))
|
||||
{
|
||||
var tile = atmosExposedComponent.Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
if (tile == null) continue;
|
||||
atmosExposedComponent.Update(tile, _lastUpdate);
|
||||
}
|
||||
|
||||
_lastUpdate = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AtmosphereSystem : SharedAtmosphereSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
|
||||
|
||||
private GridTileLookupSystem? _gridTileLookup = null;
|
||||
|
||||
/// <summary>
|
||||
/// List of gas reactions ordered by priority.
|
||||
/// </summary>
|
||||
public IEnumerable<GasReactionPrototype> GasReactions => _gasReactions!;
|
||||
|
||||
private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
|
||||
public float[] GasSpecificHeats => _gasSpecificHeats;
|
||||
|
||||
public GridTileLookupSystem GridTileLookupSystem => _gridTileLookup ??= Get<GridTileLookupSystem>();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_gasReactions = _protoMan.EnumeratePrototypes<GasReactionPrototype>().ToArray();
|
||||
Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority));
|
||||
|
||||
_mapManager.MapCreated += OnMapCreated;
|
||||
_mapManager.TileChanged += OnTileChanged;
|
||||
|
||||
Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
|
||||
|
||||
for (var i = 0; i < GasPrototypes.Length; i++)
|
||||
{
|
||||
_gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat;
|
||||
}
|
||||
|
||||
// Required for airtight components.
|
||||
SubscribeLocalEvent<RotateEvent>(RotateEvent);
|
||||
SubscribeLocalEvent<AirtightComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.SpaceWind, OnSpaceWindChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.MonstermosEqualization, OnMonstermosEqualizationChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.Superconduction, OnSuperconductionChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.AtmosMaxProcessTime, OnAtmosMaxProcessTimeChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.AtmosTickRate, OnAtmosTickRateChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.ExcitedGroupsSpaceIsAllConsuming, OnExcitedGroupsSpaceIsAllConsumingChanged, true);
|
||||
}
|
||||
|
||||
private static void HandleSnapGridMove(EntityUid uid, AirtightComponent component, SnapGridPositionChangedEvent args)
|
||||
{
|
||||
component.OnTransformMove();
|
||||
}
|
||||
|
||||
public bool SpaceWind { get; private set; }
|
||||
public bool MonstermosEqualization { get; private set; }
|
||||
public bool Superconduction { get; private set; }
|
||||
public bool ExcitedGroupsSpaceIsAllConsuming { get; private set; }
|
||||
public float AtmosMaxProcessTime { get; private set; }
|
||||
public float AtmosTickRate { get; private set; }
|
||||
|
||||
private void OnExcitedGroupsSpaceIsAllConsumingChanged(bool obj)
|
||||
{
|
||||
ExcitedGroupsSpaceIsAllConsuming = obj;
|
||||
}
|
||||
|
||||
private void OnAtmosTickRateChanged(float obj)
|
||||
{
|
||||
AtmosTickRate = obj;
|
||||
}
|
||||
|
||||
private void OnAtmosMaxProcessTimeChanged(float obj)
|
||||
{
|
||||
AtmosMaxProcessTime = obj;
|
||||
}
|
||||
|
||||
private void OnMonstermosEqualizationChanged(bool obj)
|
||||
{
|
||||
MonstermosEqualization = obj;
|
||||
}
|
||||
|
||||
private void OnSuperconductionChanged(bool obj)
|
||||
{
|
||||
Superconduction = obj;
|
||||
}
|
||||
|
||||
private void OnSpaceWindChanged(bool obj)
|
||||
{
|
||||
SpaceWind = obj;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_mapManager.MapCreated -= OnMapCreated;
|
||||
}
|
||||
|
||||
private void RotateEvent(RotateEvent ev)
|
||||
{
|
||||
if (ev.Sender.TryGetComponent(out AirtightComponent? airtight))
|
||||
{
|
||||
airtight.RotateEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public IGridAtmosphereComponent? GetGridAtmosphere(GridId gridId)
|
||||
{
|
||||
if (!gridId.IsValid())
|
||||
return null;
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
return null;
|
||||
|
||||
return ComponentManager.TryGetComponent(grid.GridEntityId, out IGridAtmosphereComponent? gridAtmosphere)
|
||||
? gridAtmosphere : null;
|
||||
}
|
||||
|
||||
public IGridAtmosphereComponent GetGridAtmosphere(EntityCoordinates coordinates)
|
||||
{
|
||||
return GetGridAtmosphere(coordinates.ToMap(EntityManager));
|
||||
}
|
||||
|
||||
public IGridAtmosphereComponent GetGridAtmosphere(MapCoordinates coordinates)
|
||||
{
|
||||
if (coordinates.MapId == MapId.Nullspace)
|
||||
{
|
||||
throw new ArgumentException($"Coordinates cannot be in nullspace!", nameof(coordinates));
|
||||
}
|
||||
|
||||
if (_mapManager.TryFindGridAt(coordinates, out var grid))
|
||||
{
|
||||
if (ComponentManager.TryGetComponent(grid.GridEntityId, out IGridAtmosphereComponent? atmos))
|
||||
{
|
||||
return atmos;
|
||||
}
|
||||
}
|
||||
|
||||
return _mapManager.GetMapEntity(coordinates.MapId).GetComponent<IGridAtmosphereComponent>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var (mapGridComponent, gridAtmosphereComponent) in EntityManager.ComponentManager.EntityQuery<IMapGridComponent, IGridAtmosphereComponent>(true))
|
||||
{
|
||||
if (_pauseManager.IsGridPaused(mapGridComponent.GridIndex)) continue;
|
||||
|
||||
gridAtmosphereComponent.Update(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTileChanged(object? sender, TileChangedEventArgs eventArgs)
|
||||
{
|
||||
// When a tile changes, we want to update it only if it's gone from
|
||||
// space -> not space or vice versa. So if the old tile is the
|
||||
// same as the new tile in terms of space-ness, ignore the change
|
||||
|
||||
if (eventArgs.NewTile.IsSpace() == eventArgs.OldTile.IsSpace())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetGridAtmosphere(eventArgs.NewTile.GridPosition(_mapManager))?.Invalidate(eventArgs.NewTile.GridIndices);
|
||||
}
|
||||
|
||||
private void OnMapCreated(object? sender, MapEventArgs e)
|
||||
{
|
||||
if (e.Map == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var map = _mapManager.GetMapEntity(e.Map);
|
||||
|
||||
if (!map.HasComponent<IGridAtmosphereComponent>())
|
||||
map.AddComponent<SpaceGridAtmosphereComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ComputerUIActivatorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BaseComputerUserInterfaceComponent, ActivateInWorldEvent>(HandleActivate);
|
||||
}
|
||||
|
||||
private void HandleActivate(EntityUid uid, BaseComputerUserInterfaceComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
component.ActivateThunk(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasAnalyzerSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var analyzer in ComponentManager.EntityQuery<GasAnalyzerComponent>(true))
|
||||
{
|
||||
analyzer.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class GasCanisterSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasCanisterComponent, PhysicsBodyTypeChangedEvent>(OnBodyTypeChanged);
|
||||
}
|
||||
|
||||
private static void OnBodyTypeChanged(
|
||||
EntityUid uid,
|
||||
GasCanisterComponent component,
|
||||
PhysicsBodyTypeChangedEvent args)
|
||||
{
|
||||
component.AnchorUpdate();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var component in ComponentManager.EntityQuery<GasCanisterComponent>(true))
|
||||
{
|
||||
component.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasTankSystem : EntitySystem
|
||||
{
|
||||
private float _timer = 0f;
|
||||
private const float Interval = 0.5f;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
_timer += frameTime;
|
||||
|
||||
if (_timer < Interval) return;
|
||||
_timer = 0f;
|
||||
|
||||
foreach (var gasTank in EntityManager.ComponentManager.EntityQuery<GasTankComponent>(true))
|
||||
{
|
||||
gasTank.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user