feat: Medbay cryo pods (#11349)
Fixes https://github.com/space-wizards/space-station-14/issues/11245
This commit is contained in:
7
Content.Client/Medical/Cryogenics/CryoPodComponent.cs
Normal file
7
Content.Client/Medical/Cryogenics/CryoPodComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using Content.Shared.DragDrop;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
namespace Content.Client.Medical.Cryogenics;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CryoPodComponent : SharedCryoPodComponent { }
|
||||||
83
Content.Client/Medical/Cryogenics/CryoPodSystem.cs
Normal file
83
Content.Client/Medical/Cryogenics/CryoPodSystem.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using Content.Shared.Destructible;
|
||||||
|
using Content.Shared.Emag.Systems;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||||
|
|
||||||
|
namespace Content.Client.Medical.Cryogenics;
|
||||||
|
|
||||||
|
public sealed class CryoPodSystem: SharedCryoPodSystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, DoInsertCryoPodEvent>(DoInsertCryoPod);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, DoInsertCancelledCryoPodEvent>(DoInsertCancelCryoPod);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, ComponentStartup>(OnCryoPodInsertion);
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, ComponentRemove>(OnCryoPodRemoval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCryoPodInsertion(EntityUid uid, InsideCryoPodComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
if (!TryComp<SpriteComponent>(uid, out var spriteComponent))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.PreviousOffset = spriteComponent.Offset;
|
||||||
|
spriteComponent.Offset = new Vector2(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCryoPodRemoval(EntityUid uid, InsideCryoPodComponent component, ComponentRemove args)
|
||||||
|
{
|
||||||
|
if (!TryComp<SpriteComponent>(uid, out var spriteComponent))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteComponent.Offset = component.PreviousOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppearanceChange(EntityUid uid, SharedCryoPodComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.Component.TryGetData(SharedCryoPodComponent.CryoPodVisuals.ContainsEntity, out bool isOpen)
|
||||||
|
|| !args.Component.TryGetData(SharedCryoPodComponent.CryoPodVisuals.IsOn, out bool isOn))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpen)
|
||||||
|
{
|
||||||
|
args.Sprite.LayerSetState(CryoPodVisualLayers.Base, "pod-open");
|
||||||
|
args.Sprite.LayerSetVisible(CryoPodVisualLayers.Cover, false);
|
||||||
|
args.Sprite.DrawDepth = (int) DrawDepth.Objects;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.Sprite.DrawDepth = (int) DrawDepth.Mobs;
|
||||||
|
args.Sprite.LayerSetState(CryoPodVisualLayers.Base, isOn ? "pod-on" : "pod-off");
|
||||||
|
args.Sprite.LayerSetState(CryoPodVisualLayers.Cover, isOn ? "cover-on" : "cover-off");
|
||||||
|
args.Sprite.LayerSetVisible(CryoPodVisualLayers.Cover, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CryoPodVisualLayers : byte
|
||||||
|
{
|
||||||
|
Base,
|
||||||
|
Cover,
|
||||||
|
}
|
||||||
16
Content.Server/Medical/Components/CryoPodComponent.cs
Normal file
16
Content.Server/Medical/Components/CryoPodComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Atmos;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
namespace Content.Server.Medical.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CryoPodComponent: SharedCryoPodComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Local air buffer that will be mixed with the pipenet, if one exists, per tick.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("gasMixture")]
|
||||||
|
public GasMixture Air { get; set; } = new(Atmospherics.OneAtmosphere);
|
||||||
|
}
|
||||||
59
Content.Server/Medical/CryoPodEjectLockWireAction.cs
Normal file
59
Content.Server/Medical/CryoPodEjectLockWireAction.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using Content.Server.Medical.Components;
|
||||||
|
using Content.Server.Wires;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
using Content.Shared.Wires;
|
||||||
|
|
||||||
|
namespace Content.Server.Medical;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes a failure in the cryo pod ejection system when cut. A crowbar will be needed to pry open the pod.
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition]
|
||||||
|
public sealed class CryoPodEjectLockWireAction: BaseWireAction
|
||||||
|
{
|
||||||
|
[DataField("color")]
|
||||||
|
private Color _statusColor = Color.Red;
|
||||||
|
|
||||||
|
[DataField("name")]
|
||||||
|
private string _text = "LOCK";
|
||||||
|
|
||||||
|
public override object? StatusKey { get; } = CryoPodWireActionKey.Key;
|
||||||
|
public override bool Cut(EntityUid user, Wire wire)
|
||||||
|
{
|
||||||
|
if (EntityManager.TryGetComponent<CryoPodComponent>(wire.Owner, out var cryoPodComponent) && !cryoPodComponent.PermaLocked)
|
||||||
|
{
|
||||||
|
cryoPodComponent.Locked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Mend(EntityUid user, Wire wire)
|
||||||
|
{
|
||||||
|
if (EntityManager.TryGetComponent<CryoPodComponent>(wire.Owner, out var cryoPodComponent) && !cryoPodComponent.PermaLocked)
|
||||||
|
{
|
||||||
|
cryoPodComponent.Locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Pulse(EntityUid user, Wire wire)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StatusLightData? GetStatusLightData(Wire wire)
|
||||||
|
{
|
||||||
|
StatusLightState lightState = StatusLightState.Off;
|
||||||
|
if (EntityManager.TryGetComponent<CryoPodComponent>(wire.Owner, out var cryoPodComponent) && cryoPodComponent.Locked)
|
||||||
|
{
|
||||||
|
lightState = StatusLightState.On; //TODO figure out why this doesn't get updated when the pod is emagged
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StatusLightData(
|
||||||
|
_statusColor,
|
||||||
|
lightState,
|
||||||
|
_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
267
Content.Server/Medical/CryoPodSystem.cs
Normal file
267
Content.Server/Medical/CryoPodSystem.cs
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Content.Server.Atmos;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Atmos.Piping.Components;
|
||||||
|
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||||
|
using Content.Server.Body.Components;
|
||||||
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Server.Chemistry.Components.SolutionManager;
|
||||||
|
using Content.Server.Chemistry.EntitySystems;
|
||||||
|
using Content.Server.Climbing;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Server.Medical.Components;
|
||||||
|
using Content.Server.NodeContainer;
|
||||||
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
|
using Content.Server.NodeContainer.Nodes;
|
||||||
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Server.Tools;
|
||||||
|
using Content.Server.UserInterface;
|
||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Containers.ItemSlots;
|
||||||
|
using Content.Shared.Destructible;
|
||||||
|
using Content.Shared.DragDrop;
|
||||||
|
using Content.Shared.Emag.Systems;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
using Content.Shared.MedicalScanner;
|
||||||
|
using Content.Shared.Tools.Components;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.Medical;
|
||||||
|
|
||||||
|
public sealed partial class CryoPodSystem: SharedCryoPodSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||||
|
[Dependency] private readonly GasCanisterSystem _gasCanisterSystem = default!;
|
||||||
|
[Dependency] private readonly ClimbSystem _climbSystem = default!;
|
||||||
|
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||||
|
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
|
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||||
|
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
|
[Dependency] private readonly ToolSystem _toolSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
|
||||||
|
[Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, DoInsertCryoPodEvent>(DoInsertCryoPod);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, DoInsertCancelledCryoPodEvent>(DoInsertCancelCryoPod);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, AtmosDeviceUpdateEvent>(OnCryoPodUpdateAtmosphere);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, DragDropEvent>(HandleDragDropOn);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, ExaminedEvent>(OnExamined);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, GasAnalyzerScanEvent>(OnGasAnalyzed);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, ActivatableUIOpenAttemptEvent>(OnActivateUIAttempt);
|
||||||
|
SubscribeLocalEvent<CryoPodComponent, AfterActivatableUIOpenEvent>(OnActivateUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var curTime = _gameTiming.CurTime;
|
||||||
|
var bloodStreamQuery = GetEntityQuery<BloodstreamComponent>();
|
||||||
|
var metaDataQuery = GetEntityQuery<MetaDataComponent>();
|
||||||
|
var itemSlotsQuery = GetEntityQuery<ItemSlotsComponent>();
|
||||||
|
var fitsInDispenserQuery = GetEntityQuery<FitsInDispenserComponent>();
|
||||||
|
var solutionContainerManagerQuery = GetEntityQuery<SolutionContainerManagerComponent>();
|
||||||
|
foreach (var (_, cryoPod) in EntityQuery<ActiveCryoPodComponent, CryoPodComponent>())
|
||||||
|
{
|
||||||
|
metaDataQuery.TryGetComponent(cryoPod.Owner, out var metaDataComponent);
|
||||||
|
if (curTime < cryoPod.NextInjectionTime + _metaDataSystem.GetPauseTime(cryoPod.Owner, metaDataComponent))
|
||||||
|
continue;
|
||||||
|
cryoPod.NextInjectionTime = curTime + TimeSpan.FromSeconds(cryoPod.BeakerTransferTime);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!itemSlotsQuery.TryGetComponent(cryoPod.Owner, out var itemSlotsComponent)
|
||||||
|
|| !fitsInDispenserQuery.TryGetComponent(cryoPod.Owner, out var fitsInDispenserComponent)
|
||||||
|
|| !solutionContainerManagerQuery.TryGetComponent(cryoPod.Owner, out var solutionContainerManagerComponent))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var container = _itemSlotsSystem.GetItemOrNull(cryoPod.Owner, cryoPod.SolutionContainerName, itemSlotsComponent);
|
||||||
|
var patient = cryoPod.BodyContainer.ContainedEntity;
|
||||||
|
if (container != null
|
||||||
|
&& container.Value.Valid
|
||||||
|
&& patient != null
|
||||||
|
&& _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution, dispenserFits: fitsInDispenserComponent, solutionManager: solutionContainerManagerComponent))
|
||||||
|
{
|
||||||
|
if (!bloodStreamQuery.TryGetComponent(patient, out var bloodstream))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var solutionToInject = _solutionContainerSystem.SplitSolution(container.Value, containerSolution, cryoPod.BeakerTransferAmount);
|
||||||
|
_bloodstreamSystem.TryAddToChemicals(patient.Value, solutionToInject, bloodstream);
|
||||||
|
_reactiveSystem.DoEntityReaction(patient.Value, solutionToInject, ReactionMethod.Injection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EjectBody(EntityUid uid, SharedCryoPodComponent? cryoPodComponent)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref cryoPodComponent))
|
||||||
|
return;
|
||||||
|
if (cryoPodComponent.BodyContainer.ContainedEntity is not {Valid: true} contained)
|
||||||
|
return;
|
||||||
|
base.EjectBody(uid, cryoPodComponent);
|
||||||
|
_climbSystem.ForciblySetClimbing(contained, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Interaction
|
||||||
|
|
||||||
|
private void HandleDragDropOn(EntityUid uid, CryoPodComponent cryoPodComponent, DragDropEvent args)
|
||||||
|
{
|
||||||
|
if (cryoPodComponent.BodyContainer.ContainedEntity != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cryoPodComponent.DragDropCancelToken != null)
|
||||||
|
{
|
||||||
|
cryoPodComponent.DragDropCancelToken.Cancel();
|
||||||
|
cryoPodComponent.DragDropCancelToken = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cryoPodComponent.DragDropCancelToken = new CancellationTokenSource();
|
||||||
|
var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, cryoPodComponent.DragDropCancelToken.Token, uid, args.Dragged)
|
||||||
|
{
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnStun = true,
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
NeedHand = false,
|
||||||
|
TargetFinishedEvent = new DoInsertCryoPodEvent(args.Dragged),
|
||||||
|
TargetCancelledEvent = new DoInsertCancelledCryoPodEvent()
|
||||||
|
};
|
||||||
|
_doAfterSystem.DoAfter(doAfterArgs);
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnActivateUIAttempt(EntityUid uid, CryoPodComponent cryoPodComponent, ActivatableUIOpenAttemptEvent args)
|
||||||
|
{
|
||||||
|
if (args.Cancelled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var containedEntity = cryoPodComponent.BodyContainer.ContainedEntity;
|
||||||
|
if (containedEntity == null || containedEntity == args.User || !HasComp<ActiveCryoPodComponent>(uid))
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnActivateUI(EntityUid uid, CryoPodComponent cryoPodComponent, AfterActivatableUIOpenEvent args)
|
||||||
|
{
|
||||||
|
_userInterfaceSystem.TrySendUiMessage(
|
||||||
|
uid,
|
||||||
|
SharedHealthAnalyzerComponent.HealthAnalyzerUiKey.Key,
|
||||||
|
new SharedHealthAnalyzerComponent.HealthAnalyzerScannedUserMessage(cryoPodComponent.BodyContainer.ContainedEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractUsing(EntityUid uid, CryoPodComponent cryoPodComponent, InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || !cryoPodComponent.Locked || cryoPodComponent.BodyContainer.ContainedEntity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TryComp(args.Used, out ToolComponent? tool)
|
||||||
|
&& tool.Qualities.Contains("Prying")) // Why aren't those enums?
|
||||||
|
{
|
||||||
|
if (cryoPodComponent.IsPrying)
|
||||||
|
return;
|
||||||
|
cryoPodComponent.IsPrying = true;
|
||||||
|
|
||||||
|
_toolSystem.UseTool(args.Used, args.User, uid, 0f,
|
||||||
|
cryoPodComponent.PryDelay, "Prying",
|
||||||
|
new CryoPodPryFinished(), new CryoPodPryInterrupted(), uid);
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamined(EntityUid uid, CryoPodComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
var container = _itemSlotsSystem.GetItemOrNull(component.Owner, component.SolutionContainerName);
|
||||||
|
if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution))
|
||||||
|
{
|
||||||
|
args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));
|
||||||
|
if (containerSolution.CurrentVolume == 0)
|
||||||
|
{
|
||||||
|
args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPowerChanged(EntityUid uid, CryoPodComponent component, ref PowerChangedEvent args)
|
||||||
|
{
|
||||||
|
// Needed to avoid adding/removing components on a deleted entity
|
||||||
|
if (Terminating(uid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Powered)
|
||||||
|
{
|
||||||
|
EnsureComp<ActiveCryoPodComponent>(uid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemComp<ActiveCryoPodComponent>(uid);
|
||||||
|
_uiSystem.TryCloseAll(uid, SharedHealthAnalyzerComponent.HealthAnalyzerUiKey.Key);
|
||||||
|
}
|
||||||
|
UpdateAppearance(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Atmos handler
|
||||||
|
|
||||||
|
private void OnCryoPodUpdateAtmosphere(EntityUid uid, CryoPodComponent cryoPod, AtmosDeviceUpdateEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp(uid, out NodeContainerComponent? nodeContainer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!nodeContainer.TryGetNode(cryoPod.PortName, out PortablePipeNode? portNode))
|
||||||
|
return;
|
||||||
|
_atmosphereSystem.React(cryoPod.Air, portNode);
|
||||||
|
|
||||||
|
if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
||||||
|
{
|
||||||
|
_gasCanisterSystem.MixContainerWithPipeNet(cryoPod.Air, net.Air);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGasAnalyzed(EntityUid uid, CryoPodComponent component, GasAnalyzerScanEvent args)
|
||||||
|
{
|
||||||
|
var gasMixDict = new Dictionary<string, GasMixture?> { { Name(uid), component.Air } };
|
||||||
|
// If it's connected to a port, include the port side
|
||||||
|
if (TryComp(uid, out NodeContainerComponent? nodeContainer))
|
||||||
|
{
|
||||||
|
if(nodeContainer.TryGetNode(component.PortName, out PipeNode? port))
|
||||||
|
gasMixDict.Add(component.PortName, port.Air);
|
||||||
|
}
|
||||||
|
args.GasMixtures = gasMixDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
47
Content.Server/Medical/InsideCryoPodSystem.cs
Normal file
47
Content.Server/Medical/InsideCryoPodSystem.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.Medical.Components;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
namespace Content.Server.Medical
|
||||||
|
{
|
||||||
|
public sealed partial class CryoPodSystem
|
||||||
|
{
|
||||||
|
public override void InitializeInsideCryoPod()
|
||||||
|
{
|
||||||
|
base.InitializeInsideCryoPod();
|
||||||
|
// Atmos overrides
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, InhaleLocationEvent>(OnInhaleLocation);
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, ExhaleLocationEvent>(OnExhaleLocation);
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, AtmosExposedGetAirEvent>(OnGetAir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Atmos handlers
|
||||||
|
|
||||||
|
private void OnGetAir(EntityUid uid, InsideCryoPodComponent component, ref AtmosExposedGetAirEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<CryoPodComponent>(Transform(uid).ParentUid, out var cryoPodComponent))
|
||||||
|
{
|
||||||
|
args.Gas = cryoPodComponent.Air;
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInhaleLocation(EntityUid uid, InsideCryoPodComponent component, InhaleLocationEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<CryoPodComponent>(Transform(uid).ParentUid, out var cryoPodComponent))
|
||||||
|
{
|
||||||
|
args.Gas = cryoPodComponent.Air;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExhaleLocation(EntityUid uid, InsideCryoPodComponent component, ExhaleLocationEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<CryoPodComponent>(Transform(uid).ParentUid, out var cryoPodComponent))
|
||||||
|
{
|
||||||
|
args.Gas = cryoPodComponent.Air;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Content.Server.Temperature.Components;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ContainerTemperatureDamageThresholdsComponent: Component
|
||||||
|
{
|
||||||
|
[DataField("heatDamageThreshold")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float? HeatDamageThreshold;
|
||||||
|
|
||||||
|
[DataField("coldDamageThreshold")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float? ColdDamageThreshold;
|
||||||
|
}
|
||||||
@@ -24,6 +24,18 @@ namespace Content.Server.Temperature.Components
|
|||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float ColdDamageThreshold = 260f;
|
public float ColdDamageThreshold = 260f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides HeatDamageThreshold if the entity's within a parent with the TemperatureDamageThresholdsComponent component.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float? ParentHeatDamageThreshold;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides ColdDamageThreshold if the entity's within a parent with the TemperatureDamageThresholdsComponent component.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float? ParentColdDamageThreshold;
|
||||||
|
|
||||||
[DataField("specificHeat")]
|
[DataField("specificHeat")]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float SpecificHeat = 50f;
|
public float SpecificHeat = 50f;
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ using Content.Shared.Database;
|
|||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Temperature;
|
using Content.Shared.Temperature;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
|
|
||||||
namespace Content.Server.Temperature.Systems
|
namespace Content.Server.Temperature.Systems
|
||||||
{
|
{
|
||||||
@@ -22,7 +20,7 @@ namespace Content.Server.Temperature.Systems
|
|||||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger= default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All the components that will have their damage updated at the end of the tick.
|
/// All the components that will have their damage updated at the end of the tick.
|
||||||
@@ -40,7 +38,15 @@ namespace Content.Server.Temperature.Systems
|
|||||||
SubscribeLocalEvent<TemperatureComponent, OnTemperatureChangeEvent>(EnqueueDamage);
|
SubscribeLocalEvent<TemperatureComponent, OnTemperatureChangeEvent>(EnqueueDamage);
|
||||||
SubscribeLocalEvent<TemperatureComponent, AtmosExposedUpdateEvent>(OnAtmosExposedUpdate);
|
SubscribeLocalEvent<TemperatureComponent, AtmosExposedUpdateEvent>(OnAtmosExposedUpdate);
|
||||||
SubscribeLocalEvent<AlertsComponent, OnTemperatureChangeEvent>(ServerAlert);
|
SubscribeLocalEvent<AlertsComponent, OnTemperatureChangeEvent>(ServerAlert);
|
||||||
SubscribeLocalEvent<TemperatureProtectionComponent, InventoryRelayedEvent<ModifyChangedTemperatureEvent>>(OnTemperatureChangeAttempt);
|
SubscribeLocalEvent<TemperatureProtectionComponent, InventoryRelayedEvent<ModifyChangedTemperatureEvent>>(
|
||||||
|
OnTemperatureChangeAttempt);
|
||||||
|
|
||||||
|
// Allows overriding thresholds based on the parent's thresholds.
|
||||||
|
SubscribeLocalEvent<TemperatureComponent, EntParentChangedMessage>(OnParentChange);
|
||||||
|
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentStartup>(
|
||||||
|
OnParentThresholdStartup);
|
||||||
|
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentShutdown>(
|
||||||
|
OnParentThresholdShutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -76,11 +82,13 @@ namespace Content.Server.Temperature.Systems
|
|||||||
float lastTemp = temperature.CurrentTemperature;
|
float lastTemp = temperature.CurrentTemperature;
|
||||||
float delta = temperature.CurrentTemperature - temp;
|
float delta = temperature.CurrentTemperature - temp;
|
||||||
temperature.CurrentTemperature = temp;
|
temperature.CurrentTemperature = temp;
|
||||||
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), true);
|
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance=false, TemperatureComponent? temperature = null)
|
public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false,
|
||||||
|
TemperatureComponent? temperature = null)
|
||||||
{
|
{
|
||||||
if (Resolve(uid, ref temperature))
|
if (Resolve(uid, ref temperature))
|
||||||
{
|
{
|
||||||
@@ -95,11 +103,13 @@ namespace Content.Server.Temperature.Systems
|
|||||||
temperature.CurrentTemperature += heatAmount / temperature.HeatCapacity;
|
temperature.CurrentTemperature += heatAmount / temperature.HeatCapacity;
|
||||||
float delta = temperature.CurrentTemperature - lastTemp;
|
float delta = temperature.CurrentTemperature - lastTemp;
|
||||||
|
|
||||||
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta), true);
|
RaiseLocalEvent(uid, new OnTemperatureChangeEvent(temperature.CurrentTemperature, lastTemp, delta),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAtmosExposedUpdate(EntityUid uid, TemperatureComponent temperature, ref AtmosExposedUpdateEvent args)
|
private void OnAtmosExposedUpdate(EntityUid uid, TemperatureComponent temperature,
|
||||||
|
ref AtmosExposedUpdateEvent args)
|
||||||
{
|
{
|
||||||
var transform = args.Transform;
|
var transform = args.Transform;
|
||||||
|
|
||||||
@@ -109,9 +119,11 @@ namespace Content.Server.Temperature.Systems
|
|||||||
var position = _transformSystem.GetGridOrMapTilePosition(uid, transform);
|
var position = _transformSystem.GetGridOrMapTilePosition(uid, transform);
|
||||||
|
|
||||||
var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature;
|
var temperatureDelta = args.GasMixture.Temperature - temperature.CurrentTemperature;
|
||||||
var tileHeatCapacity = _atmosphereSystem.GetTileHeatCapacity(transform.GridUid, transform.MapUid.Value, position);
|
var tileHeatCapacity =
|
||||||
var heat = temperatureDelta * (tileHeatCapacity * temperature.HeatCapacity / (tileHeatCapacity + temperature.HeatCapacity));
|
_atmosphereSystem.GetTileHeatCapacity(transform.GridUid, transform.MapUid.Value, position);
|
||||||
ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature );
|
var heat = temperatureDelta * (tileHeatCapacity * temperature.HeatCapacity /
|
||||||
|
(tileHeatCapacity + temperature.HeatCapacity));
|
||||||
|
ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args)
|
private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args)
|
||||||
@@ -173,42 +185,160 @@ namespace Content.Server.Temperature.Systems
|
|||||||
var y = temperature.DamageCap;
|
var y = temperature.DamageCap;
|
||||||
var c = y * 2;
|
var c = y * 2;
|
||||||
|
|
||||||
if (temperature.CurrentTemperature >= temperature.HeatDamageThreshold)
|
var heatDamageThreshold = temperature.ParentHeatDamageThreshold ?? temperature.HeatDamageThreshold;
|
||||||
|
var coldDamageThreshold = temperature.ParentColdDamageThreshold ?? temperature.ColdDamageThreshold;
|
||||||
|
|
||||||
|
if (temperature.CurrentTemperature >= heatDamageThreshold)
|
||||||
{
|
{
|
||||||
if (!temperature.TakingDamage)
|
if (!temperature.TakingDamage)
|
||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.Temperature, $"{ToPrettyString(temperature.Owner):entity} started taking high temperature damage");
|
_adminLogger.Add(LogType.Temperature,
|
||||||
|
$"{ToPrettyString(temperature.Owner):entity} started taking high temperature damage");
|
||||||
temperature.TakingDamage = true;
|
temperature.TakingDamage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var diff = Math.Abs(temperature.CurrentTemperature - temperature.HeatDamageThreshold);
|
var diff = Math.Abs(temperature.CurrentTemperature - heatDamageThreshold);
|
||||||
var tempDamage = c / (1 + a * Math.Pow(Math.E, -heatK * diff)) - y;
|
var tempDamage = c / (1 + a * Math.Pow(Math.E, -heatK * diff)) - y;
|
||||||
_damageableSystem.TryChangeDamage(uid, temperature.HeatDamage * tempDamage, interruptsDoAfters: false);
|
_damageableSystem.TryChangeDamage(uid, temperature.HeatDamage * tempDamage, interruptsDoAfters: false);
|
||||||
}
|
}
|
||||||
else if (temperature.CurrentTemperature <= temperature.ColdDamageThreshold)
|
else if (temperature.CurrentTemperature <= coldDamageThreshold)
|
||||||
{
|
{
|
||||||
if (!temperature.TakingDamage)
|
if (!temperature.TakingDamage)
|
||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.Temperature, $"{ToPrettyString(temperature.Owner):entity} started taking low temperature damage");
|
_adminLogger.Add(LogType.Temperature,
|
||||||
|
$"{ToPrettyString(temperature.Owner):entity} started taking low temperature damage");
|
||||||
temperature.TakingDamage = true;
|
temperature.TakingDamage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var diff = Math.Abs(temperature.CurrentTemperature - temperature.ColdDamageThreshold);
|
var diff = Math.Abs(temperature.CurrentTemperature - coldDamageThreshold);
|
||||||
var tempDamage =
|
var tempDamage =
|
||||||
Math.Sqrt(diff * (Math.Pow(temperature.DamageCap.Double(), 2) / temperature.ColdDamageThreshold));
|
Math.Sqrt(diff * (Math.Pow(temperature.DamageCap.Double(), 2) / coldDamageThreshold));
|
||||||
_damageableSystem.TryChangeDamage(uid, temperature.ColdDamage * tempDamage, interruptsDoAfters: false);
|
_damageableSystem.TryChangeDamage(uid, temperature.ColdDamage * tempDamage, interruptsDoAfters: false);
|
||||||
}
|
}
|
||||||
else if (temperature.TakingDamage)
|
else if (temperature.TakingDamage)
|
||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.Temperature, $"{ToPrettyString(temperature.Owner):entity} stopped taking temperature damage");
|
_adminLogger.Add(LogType.Temperature,
|
||||||
|
$"{ToPrettyString(temperature.Owner):entity} stopped taking temperature damage");
|
||||||
temperature.TakingDamage = false;
|
temperature.TakingDamage = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComponent component, InventoryRelayedEvent<ModifyChangedTemperatureEvent> args)
|
private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComponent component,
|
||||||
|
InventoryRelayedEvent<ModifyChangedTemperatureEvent> args)
|
||||||
{
|
{
|
||||||
args.Args.TemperatureDelta *= component.Coefficient;
|
args.Args.TemperatureDelta *= component.Coefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnParentChange(EntityUid uid, TemperatureComponent component,
|
||||||
|
ref EntParentChangedMessage args)
|
||||||
|
{
|
||||||
|
var temperatureQuery = GetEntityQuery<TemperatureComponent>();
|
||||||
|
var transformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
var thresholdsQuery = GetEntityQuery<ContainerTemperatureDamageThresholdsComponent>();
|
||||||
|
// We only need to update thresholds if the thresholds changed for the entity's ancestors.
|
||||||
|
var oldThresholds = args.OldParent != null
|
||||||
|
? RecalculateParentThresholds(args.OldParent.Value, transformQuery, thresholdsQuery)
|
||||||
|
: (null, null);
|
||||||
|
var newThresholds = RecalculateParentThresholds(transformQuery.GetComponent(uid).ParentUid, transformQuery, thresholdsQuery);
|
||||||
|
|
||||||
|
if (oldThresholds != newThresholds)
|
||||||
|
{
|
||||||
|
RecursiveThresholdUpdate(uid, temperatureQuery, transformQuery, thresholdsQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnParentThresholdStartup(EntityUid uid, ContainerTemperatureDamageThresholdsComponent component,
|
||||||
|
ComponentStartup args)
|
||||||
|
{
|
||||||
|
RecursiveThresholdUpdate(uid, GetEntityQuery<TemperatureComponent>(), GetEntityQuery<TransformComponent>(),
|
||||||
|
GetEntityQuery<ContainerTemperatureDamageThresholdsComponent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnParentThresholdShutdown(EntityUid uid, ContainerTemperatureDamageThresholdsComponent component,
|
||||||
|
ComponentShutdown args)
|
||||||
|
{
|
||||||
|
RecursiveThresholdUpdate(uid, GetEntityQuery<TemperatureComponent>(), GetEntityQuery<TransformComponent>(),
|
||||||
|
GetEntityQuery<ContainerTemperatureDamageThresholdsComponent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculate and apply parent thresholds for the root entity and all its descendant.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="root"></param>
|
||||||
|
/// <param name="temperatureQuery"></param>
|
||||||
|
/// <param name="transformQuery"></param>
|
||||||
|
/// <param name="tempThresholdsQuery"></param>
|
||||||
|
private void RecursiveThresholdUpdate(EntityUid root, EntityQuery<TemperatureComponent> temperatureQuery,
|
||||||
|
EntityQuery<TransformComponent> transformQuery,
|
||||||
|
EntityQuery<ContainerTemperatureDamageThresholdsComponent> tempThresholdsQuery)
|
||||||
|
{
|
||||||
|
RecalculateAndApplyParentThresholds(root, temperatureQuery, transformQuery, tempThresholdsQuery);
|
||||||
|
|
||||||
|
foreach (var child in Transform(root).ChildEntities)
|
||||||
|
{
|
||||||
|
RecursiveThresholdUpdate(child, temperatureQuery, transformQuery, tempThresholdsQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculate parent thresholds and apply them on the uid temperature component.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uid"></param>
|
||||||
|
/// <param name="temperatureQuery"></param>
|
||||||
|
/// <param name="transformQuery"></param>
|
||||||
|
/// <param name="tempThresholdsQuery"></param>
|
||||||
|
private void RecalculateAndApplyParentThresholds(EntityUid uid,
|
||||||
|
EntityQuery<TemperatureComponent> temperatureQuery, EntityQuery<TransformComponent> transformQuery,
|
||||||
|
EntityQuery<ContainerTemperatureDamageThresholdsComponent> tempThresholdsQuery)
|
||||||
|
{
|
||||||
|
if (!temperatureQuery.TryGetComponent(uid, out var temperature))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newThresholds = RecalculateParentThresholds(transformQuery.GetComponent(uid).ParentUid, transformQuery, tempThresholdsQuery);
|
||||||
|
temperature.ParentHeatDamageThreshold = newThresholds.Item1;
|
||||||
|
temperature.ParentColdDamageThreshold = newThresholds.Item2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculate Parent Heat/Cold DamageThreshold by recursively checking each ancestor and fetching the
|
||||||
|
/// maximum HeatDamageThreshold and the minimum ColdDamageThreshold if any exists (aka the best value for each).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initialParentUid"></param>
|
||||||
|
/// <param name="transformQuery"></param>
|
||||||
|
/// <param name="tempThresholdsQuery"></param>
|
||||||
|
private (float?, float?) RecalculateParentThresholds(
|
||||||
|
EntityUid initialParentUid,
|
||||||
|
EntityQuery<TransformComponent> transformQuery,
|
||||||
|
EntityQuery<ContainerTemperatureDamageThresholdsComponent> tempThresholdsQuery)
|
||||||
|
{
|
||||||
|
// Recursively check parents for the best threshold available
|
||||||
|
var parentUid = initialParentUid;
|
||||||
|
float? newHeatThreshold = null;
|
||||||
|
float? newColdThreshold = null;
|
||||||
|
while (parentUid.IsValid())
|
||||||
|
{
|
||||||
|
if (tempThresholdsQuery.TryGetComponent(parentUid, out var newThresholds))
|
||||||
|
{
|
||||||
|
if (newThresholds.HeatDamageThreshold != null)
|
||||||
|
{
|
||||||
|
newHeatThreshold = Math.Max(newThresholds.HeatDamageThreshold.Value,
|
||||||
|
newHeatThreshold ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newThresholds.ColdDamageThreshold != null)
|
||||||
|
{
|
||||||
|
newColdThreshold = Math.Min(newThresholds.ColdDamageThreshold.Value,
|
||||||
|
newColdThreshold ?? float.MaxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentUid = transformQuery.GetComponent(parentUid).ParentUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (newHeatThreshold, newColdThreshold);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class OnTemperatureChangeEvent : EntityEventArgs
|
public sealed class OnTemperatureChangeEvent : EntityEventArgs
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.Medical.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracking component for an enabled cryo pod (which periodically tries to inject chemicals in the occupant, if one exists)
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ActiveCryoPodComponent : Component
|
||||||
|
{
|
||||||
|
}
|
||||||
10
Content.Shared/Medical/Cryogenics/CryoPodWireStatus.cs
Normal file
10
Content.Shared/Medical/Cryogenics/CryoPodWireStatus.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Medical.Cryogenics
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum CryoPodWireActionKey: byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Content.Shared/Medical/Cryogenics/InsideCryoPodComponent.cs
Normal file
12
Content.Shared/Medical/Cryogenics/InsideCryoPodComponent.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[NetworkedComponent]
|
||||||
|
public sealed class InsideCryoPodComponent: Component
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
[DataField("previousOffset")]
|
||||||
|
public Vector2 PreviousOffset { get; set; } = new(0, 0);
|
||||||
|
}
|
||||||
105
Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs
Normal file
105
Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.DragDrop;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
[NetworkedComponent]
|
||||||
|
public abstract class SharedCryoPodComponent: Component, IDragDropOn
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the name of the atmospherics port to draw gas from.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("port")]
|
||||||
|
public string PortName { get; set; } = "port";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the name of the atmospherics port to draw gas from.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("solutionContainerName")]
|
||||||
|
public string SolutionContainerName { get; set; } = "beakerSlot";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How often (seconds) are chemicals transferred from the beaker to the body?
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("beakerTransferTime")]
|
||||||
|
public float BeakerTransferTime = 1f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("nextInjectionTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan? NextInjectionTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many units to transfer per tick from the beaker to the mob?
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("beakerTransferAmount")]
|
||||||
|
public float BeakerTransferAmount = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delay applied when inserting a mob in the pod.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("entryDelay")]
|
||||||
|
public float EntryDelay = 2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delay applied when trying to pry open a locked pod.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("pryDelay")]
|
||||||
|
public float PryDelay = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container for mobs inserted in the pod.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public ContainerSlot BodyContainer = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the eject verb will not work on the pod and the user must use a crowbar to pry the pod open.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("locked")]
|
||||||
|
public bool Locked { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes the pod to be locked without being fixable by messing with wires.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField("permaLocked")]
|
||||||
|
public bool PermaLocked { get; set; }
|
||||||
|
|
||||||
|
public bool IsPrying { get; set; }
|
||||||
|
|
||||||
|
public CancellationTokenSource? DragDropCancelToken;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum CryoPodVisuals : byte
|
||||||
|
{
|
||||||
|
ContainsEntity,
|
||||||
|
IsOn
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanInsert(EntityUid entity)
|
||||||
|
{
|
||||||
|
return IoCManager.Resolve<IEntityManager>().HasComponent<BodyComponent>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IDragDropOn.CanDragDropOn(DragDropEvent eventArgs)
|
||||||
|
{
|
||||||
|
return CanInsert(eventArgs.Dragged);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IDragDropOn.DragDropOn(DragDropEvent eventArgs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
169
Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs
Normal file
169
Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
using Content.Server.Medical.Components;
|
||||||
|
using Content.Shared.Destructible;
|
||||||
|
using Content.Shared.Emag.Systems;
|
||||||
|
using Content.Shared.MobState.Components;
|
||||||
|
using Content.Shared.MobState.EntitySystems;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Standing;
|
||||||
|
using Content.Shared.Stunnable;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
public abstract partial class SharedCryoPodSystem: EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||||
|
[Dependency] private readonly StandingStateSystem _standingStateSystem = default!;
|
||||||
|
[Dependency] private readonly SharedMobStateSystem _mobStateSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
InitializeInsideCryoPod();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnComponentInit(EntityUid uid, SharedCryoPodComponent cryoPodComponent, ComponentInit args)
|
||||||
|
{
|
||||||
|
cryoPodComponent.BodyContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, "scanner-body");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateAppearance(EntityUid uid, SharedCryoPodComponent? cryoPod = null, AppearanceComponent? appearance = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref cryoPod))
|
||||||
|
return;
|
||||||
|
var cryoPodEnabled = HasComp<ActiveCryoPodComponent>(uid);
|
||||||
|
if (TryComp<SharedPointLightComponent>(uid, out var light))
|
||||||
|
{
|
||||||
|
light.Enabled = cryoPodEnabled && cryoPod.BodyContainer.ContainedEntity != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Resolve(uid, ref appearance))
|
||||||
|
return;
|
||||||
|
_appearanceSystem.SetData(uid, SharedCryoPodComponent.CryoPodVisuals.ContainsEntity, cryoPod.BodyContainer.ContainedEntity == null, appearance);
|
||||||
|
_appearanceSystem.SetData(uid, SharedCryoPodComponent.CryoPodVisuals.IsOn, cryoPodEnabled, appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertBody(EntityUid uid, EntityUid target, SharedCryoPodComponent cryoPodComponent)
|
||||||
|
{
|
||||||
|
if (cryoPodComponent.BodyContainer.ContainedEntity != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!HasComp<MobStateComponent>(target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var xform = Transform(target);
|
||||||
|
cryoPodComponent.BodyContainer.Insert(target, transform: xform);
|
||||||
|
|
||||||
|
EnsureComp<InsideCryoPodComponent>(target);
|
||||||
|
_standingStateSystem.Stand(target, force: true); // Force-stand the mob so that the cryo pod sprite overlays it fully
|
||||||
|
|
||||||
|
UpdateAppearance(uid, cryoPodComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TryEjectBody(EntityUid uid, EntityUid userId, SharedCryoPodComponent? cryoPodComponent)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref cryoPodComponent))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cryoPodComponent.Locked)
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("cryo-pod-locked"), uid, userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EjectBody(uid, cryoPodComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void EjectBody(EntityUid uid, SharedCryoPodComponent? cryoPodComponent)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref cryoPodComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cryoPodComponent.BodyContainer.ContainedEntity is not {Valid: true} contained)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cryoPodComponent.BodyContainer.Remove(contained);
|
||||||
|
// InsideCryoPodComponent is removed automatically in its EntGotRemovedFromContainerMessage listener
|
||||||
|
// RemComp<InsideCryoPodComponent>(contained);
|
||||||
|
|
||||||
|
// Restore the correct position of the patient. Checking the components manually feels hacky, but I did not find a better way for now.
|
||||||
|
if (HasComp<KnockedDownComponent>(contained) || _mobStateSystem.IsIncapacitated(contained))
|
||||||
|
{
|
||||||
|
_standingStateSystem.Down(contained);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_standingStateSystem.Stand(contained);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance(uid, cryoPodComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddAlternativeVerbs(EntityUid uid, SharedCryoPodComponent cryoPodComponent, GetVerbsEvent<AlternativeVerb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess || !args.CanInteract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Eject verb
|
||||||
|
if (cryoPodComponent.BodyContainer.ContainedEntity != null)
|
||||||
|
{
|
||||||
|
args.Verbs.Add(new AlternativeVerb
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("cryo-pod-verb-noun-occupant"),
|
||||||
|
Category = VerbCategory.Eject,
|
||||||
|
Priority = 1, // Promote to top to make ejecting the ALT-click action
|
||||||
|
Act = () => TryEjectBody(uid, args.User, cryoPodComponent)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnEmagged(EntityUid uid, SharedCryoPodComponent? cryoPodComponent, GotEmaggedEvent args)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref cryoPodComponent))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cryoPodComponent.PermaLocked = true;
|
||||||
|
cryoPodComponent.Locked = true;
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DoInsertCryoPod(EntityUid uid, SharedCryoPodComponent cryoPodComponent, DoInsertCryoPodEvent args)
|
||||||
|
{
|
||||||
|
cryoPodComponent.DragDropCancelToken = null;
|
||||||
|
InsertBody(uid, args.ToInsert, cryoPodComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DoInsertCancelCryoPod(EntityUid uid, SharedCryoPodComponent cryoPodComponent, DoInsertCancelledCryoPodEvent args)
|
||||||
|
{
|
||||||
|
cryoPodComponent.DragDropCancelToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnCryoPodPryFinished(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryFinished args)
|
||||||
|
{
|
||||||
|
cryoPodComponent.IsPrying = false;
|
||||||
|
EjectBody(uid, cryoPodComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnCryoPodPryInterrupted(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryInterrupted args)
|
||||||
|
{
|
||||||
|
cryoPodComponent.IsPrying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Event records
|
||||||
|
|
||||||
|
protected record DoInsertCryoPodEvent(EntityUid ToInsert);
|
||||||
|
protected record DoInsertCancelledCryoPodEvent;
|
||||||
|
protected record CryoPodPryFinished;
|
||||||
|
protected record CryoPodPryInterrupted;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Shared.Standing;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
namespace Content.Shared.Medical.Cryogenics;
|
||||||
|
|
||||||
|
public abstract partial class SharedCryoPodSystem
|
||||||
|
{
|
||||||
|
public virtual void InitializeInsideCryoPod()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, DownAttemptEvent>(HandleDown);
|
||||||
|
SubscribeLocalEvent<InsideCryoPodComponent, EntGotRemovedFromContainerMessage>(OnEntGotRemovedFromContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must stand in the cryo pod
|
||||||
|
private void HandleDown(EntityUid uid, InsideCryoPodComponent component, DownAttemptEvent args)
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEntGotRemovedFromContainer(EntityUid uid, InsideCryoPodComponent component, EntGotRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
if (Terminating(uid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemComp<InsideCryoPodComponent>(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,7 +116,8 @@ namespace Content.Shared.Standing
|
|||||||
|
|
||||||
public bool Stand(EntityUid uid,
|
public bool Stand(EntityUid uid,
|
||||||
StandingStateComponent? standingState = null,
|
StandingStateComponent? standingState = null,
|
||||||
AppearanceComponent? appearance = null)
|
AppearanceComponent? appearance = null,
|
||||||
|
bool force = false)
|
||||||
{
|
{
|
||||||
// TODO: This should actually log missing comps...
|
// TODO: This should actually log missing comps...
|
||||||
if (!Resolve(uid, ref standingState, false))
|
if (!Resolve(uid, ref standingState, false))
|
||||||
@@ -128,11 +129,14 @@ namespace Content.Shared.Standing
|
|||||||
if (standingState.Standing)
|
if (standingState.Standing)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (!force)
|
||||||
|
{
|
||||||
var msg = new StandAttemptEvent();
|
var msg = new StandAttemptEvent();
|
||||||
RaiseLocalEvent(uid, msg, false);
|
RaiseLocalEvent(uid, msg, false);
|
||||||
|
|
||||||
if (msg.Cancelled)
|
if (msg.Cancelled)
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
standingState.Standing = true;
|
standingState.Standing = true;
|
||||||
Dirty(standingState);
|
Dirty(standingState);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Ejection verb label.
|
||||||
|
cryo-pod-verb-noun-occupant = Patient
|
||||||
|
# Examine text showing whether there's a beaker in the pod and if it is empty.
|
||||||
|
cryo-pod-examine = There's {INDEFINITE($beaker)} {$beaker} in here.
|
||||||
|
cryo-pod-empty-beaker = It is empty!
|
||||||
|
# Shown when a normal ejection through the eject verb is attempted on a locked pod.
|
||||||
|
cryo-pod-locked = The ejection mechanism is unresponsive!
|
||||||
@@ -160,6 +160,7 @@
|
|||||||
- MedicalScannerMachineCircuitboard
|
- MedicalScannerMachineCircuitboard
|
||||||
- StasisBedMachineCircuitboard
|
- StasisBedMachineCircuitboard
|
||||||
- CloningConsoleComputerCircuitboard
|
- CloningConsoleComputerCircuitboard
|
||||||
|
- CryoPodMachineCircuitboard
|
||||||
|
|
||||||
# Security Technology Tree
|
# Security Technology Tree
|
||||||
|
|
||||||
|
|||||||
@@ -268,6 +268,24 @@
|
|||||||
Glass: 5
|
Glass: 5
|
||||||
Cable: 1
|
Cable: 1
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: CryoPodMachineCircuitboard
|
||||||
|
parent: BaseMachineCircuitboard
|
||||||
|
name: cryo pod machine board
|
||||||
|
description: A machine printed circuit board for a cryo pod
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: medical
|
||||||
|
- type: MachineBoard
|
||||||
|
prototype: CryoPod
|
||||||
|
requirements:
|
||||||
|
ScanningModule: 1
|
||||||
|
Manipulator: 1
|
||||||
|
MatterBin: 2
|
||||||
|
materialRequirements:
|
||||||
|
Glass: 1
|
||||||
|
Cable: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: ChemMasterMachineCircuitboard
|
id: ChemMasterMachineCircuitboard
|
||||||
parent: BaseMachineCircuitboard
|
parent: BaseMachineCircuitboard
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
- type: entity
|
||||||
|
parent: [BaseStructure, ConstructibleMachine] # Not a BaseMachinePowered since we don't want the anchorable component
|
||||||
|
id: CryoPod
|
||||||
|
name: cryo pod
|
||||||
|
description: A special machine intended to create a safe environment for the use of chemicals that react in cold environments.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
sprite: Structures/Machines/cryogenics.rsi
|
||||||
|
drawdepth: Mobs
|
||||||
|
noRot: true
|
||||||
|
offset: 0, 0.5
|
||||||
|
layers:
|
||||||
|
- sprite: Structures/Piping/Atmospherics/pipe.rsi
|
||||||
|
state: pipeHalf
|
||||||
|
offset: 0, -0.5
|
||||||
|
map: [ "enum.PipeVisualLayers.Pipe" ]
|
||||||
|
- state: pod-open
|
||||||
|
map: [ "enum.CryoPodVisualLayers.Base" ]
|
||||||
|
- state: cover-on
|
||||||
|
map: [ "enum.CryoPodVisualLayers.Cover" ]
|
||||||
|
visible: false
|
||||||
|
- state: pod-panel
|
||||||
|
map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
|
||||||
|
visible: false
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Transform
|
||||||
|
noRot: true
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.5,0.5,0.90"
|
||||||
|
density: 200
|
||||||
|
mask:
|
||||||
|
- MachineMask
|
||||||
|
layer:
|
||||||
|
- MachineLayer
|
||||||
|
- type: ContainerContainer
|
||||||
|
containers:
|
||||||
|
scanner-body:
|
||||||
|
!type:ContainerSlot
|
||||||
|
showEnts: true
|
||||||
|
beakerSlot: !type:ContainerSlot {}
|
||||||
|
machine_board: !type:Container
|
||||||
|
machine_parts: !type:Container
|
||||||
|
- type: AtmosDevice
|
||||||
|
- type: Appearance
|
||||||
|
- type: Machine
|
||||||
|
board: CryoPodMachineCircuitboard
|
||||||
|
- type: WiresVisuals
|
||||||
|
- type: Wires
|
||||||
|
BoardName: "Cryo pod"
|
||||||
|
LayoutId: CryoPod
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
damageModifierSet: Metallic
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 100
|
||||||
|
behaviors:
|
||||||
|
- !type:EmptyAllContainersBehaviour
|
||||||
|
- !type:ChangeConstructionNodeBehavior
|
||||||
|
node: machineFrame
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: [ "Destruction" ]
|
||||||
|
- type: ApcPowerReceiver
|
||||||
|
powerLoad: 3000
|
||||||
|
- type: ExtensionCableReceiver
|
||||||
|
- type: NodeContainer
|
||||||
|
nodes:
|
||||||
|
port:
|
||||||
|
!type:PortablePipeNode
|
||||||
|
nodeGroupID: Pipe
|
||||||
|
pipeDirection: South
|
||||||
|
- type: ItemSlots
|
||||||
|
slots:
|
||||||
|
beakerSlot:
|
||||||
|
whitelist:
|
||||||
|
components:
|
||||||
|
- FitsInDispenser
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.HealthAnalyzerUiKey.Key
|
||||||
|
type: HealthAnalyzerBoundUserInterface
|
||||||
|
- key: enum.WiresUiKey.Key
|
||||||
|
type: WiresBoundUserInterface
|
||||||
|
- type: ActivatableUI
|
||||||
|
key: enum.HealthAnalyzerUiKey.Key
|
||||||
|
requireHands: false
|
||||||
|
- type: ActivatableUIRequiresPower
|
||||||
|
- type: PointLight
|
||||||
|
color: "#3a807f"
|
||||||
|
radius: 2
|
||||||
|
energy: 10
|
||||||
|
enabled: false
|
||||||
|
- type: EmptyOnMachineDeconstruct
|
||||||
|
containers:
|
||||||
|
- scanner-body
|
||||||
|
- type: CryoPod
|
||||||
|
- type: ContainerTemperatureDamageThresholds
|
||||||
|
coldDamageThreshold: 10
|
||||||
@@ -268,6 +268,7 @@
|
|||||||
- PortableScrubberMachineCircuitBoard
|
- PortableScrubberMachineCircuitBoard
|
||||||
- CloningPodMachineCircuitboard
|
- CloningPodMachineCircuitboard
|
||||||
- MedicalScannerMachineCircuitboard
|
- MedicalScannerMachineCircuitboard
|
||||||
|
- CryoPodMachineCircuitboard
|
||||||
- CrewMonitoringComputerCircuitboard
|
- CrewMonitoringComputerCircuitboard
|
||||||
- VaccinatorMachineCircuitboard
|
- VaccinatorMachineCircuitboard
|
||||||
- DiagnoserMachineCircuitboard
|
- DiagnoserMachineCircuitboard
|
||||||
|
|||||||
@@ -91,6 +91,16 @@
|
|||||||
Steel: 100
|
Steel: 100
|
||||||
Glass: 900
|
Glass: 900
|
||||||
|
|
||||||
|
- type: latheRecipe
|
||||||
|
id: CryoPodMachineCircuitboard
|
||||||
|
icon: Objects/Misc/module.rsi/id_mod.png
|
||||||
|
result: CryoPodMachineCircuitboard
|
||||||
|
completetime: 4
|
||||||
|
materials:
|
||||||
|
Steel: 100
|
||||||
|
Glass: 900
|
||||||
|
Gold: 100
|
||||||
|
|
||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: ChemMasterMachineCircuitboard
|
id: ChemMasterMachineCircuitboard
|
||||||
icon: { sprite: Objects/Misc/module.rsi, state: id_mod }
|
icon: { sprite: Objects/Misc/module.rsi, state: id_mod }
|
||||||
|
|||||||
@@ -68,3 +68,10 @@
|
|||||||
dummyWires: 4
|
dummyWires: 4
|
||||||
wires:
|
wires:
|
||||||
- !type:PowerWireAction
|
- !type:PowerWireAction
|
||||||
|
|
||||||
|
- type: wireLayout
|
||||||
|
id: CryoPod
|
||||||
|
dummyWires: 2
|
||||||
|
wires:
|
||||||
|
- !type:PowerWireAction
|
||||||
|
- !type:CryoPodEjectLockWireAction
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 751 B |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "https://github.com/tgstation/tgstation/commit/033d025f53051dc53ece230f486578be6f05f88f",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 64
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "pod-panel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cover-off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cover-on",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pod-open"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pod-on"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pod-off"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Resources/Textures/Structures/Machines/cryogenics.rsi/pod-on.png
Normal file
BIN
Resources/Textures/Structures/Machines/cryogenics.rsi/pod-on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 296 B |
Reference in New Issue
Block a user