261 lines
11 KiB
C#
261 lines
11 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Client.UserInterface.Controls;
|
|
using Content.Shared.Atmos;
|
|
using Content.Shared.Chemistry.Reagent;
|
|
using Content.Shared.Damage.Components;
|
|
using Content.Shared.EntityConditions.Conditions;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Medical.Cryogenics;
|
|
using Content.Shared.Temperature;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Prototypes;
|
|
namespace Content.Client.Medical.Cryogenics;
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class CryoPodWindow : FancyWindow
|
|
{
|
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
|
|
public event Action? OnEjectPatientPressed;
|
|
public event Action? OnEjectBeakerPressed;
|
|
public event Action<FixedPoint2>? OnInjectPressed;
|
|
|
|
public CryoPodWindow()
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
RobustXamlLoader.Load(this);
|
|
EjectPatientButton.OnPressed += _ => OnEjectPatientPressed?.Invoke();
|
|
EjectBeakerButton.OnPressed += _ => OnEjectBeakerPressed?.Invoke();
|
|
Inject1.OnPressed += _ => OnInjectPressed?.Invoke(1);
|
|
Inject5.OnPressed += _ => OnInjectPressed?.Invoke(5);
|
|
Inject10.OnPressed += _ => OnInjectPressed?.Invoke(10);
|
|
Inject20.OnPressed += _ => OnInjectPressed?.Invoke(20);
|
|
}
|
|
|
|
public void Populate(CryoPodUserMessage msg)
|
|
{
|
|
// Loading screen
|
|
if (LoadingPlaceHolder.Visible)
|
|
{
|
|
LoadingPlaceHolder.Visible = false;
|
|
Sections.Visible = true;
|
|
}
|
|
|
|
// Atmosphere
|
|
var hasCorrectPressure = (msg.GasMix.Pressure > Atmospherics.WarningLowPressure);
|
|
var hasGas = (msg.GasMix.Pressure > Atmospherics.GasMinMoles);
|
|
var showsPressureWarning = !hasCorrectPressure;
|
|
LowPressureWarning.Visible = showsPressureWarning;
|
|
Pressure.Text = Loc.GetString("gas-analyzer-window-pressure-val-text",
|
|
("pressure", $"{msg.GasMix.Pressure:0.00}"));
|
|
Temperature.Text = Loc.GetString("generic-not-available-shorthand");
|
|
|
|
if (hasGas)
|
|
{
|
|
var celsius = TemperatureHelpers.KelvinToCelsius(msg.GasMix.Temperature);
|
|
Temperature.Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
|
|
("tempK", $"{msg.GasMix.Temperature:0.0}"),
|
|
("tempC", $"{celsius:0.0}"));
|
|
}
|
|
|
|
// Gas mix segmented bar chart
|
|
GasMixChart.Clear();
|
|
GasMixChart.Visible = hasGas;
|
|
|
|
if (msg.GasMix.Gases != null)
|
|
{
|
|
var totalGasAmount = msg.GasMix.Gases.Sum(gas => gas.Amount);
|
|
|
|
foreach (var gas in msg.GasMix.Gases)
|
|
{
|
|
var color = Color.FromHex($"#{gas.Color}", Color.White);
|
|
var percent = gas.Amount / totalGasAmount * 100;
|
|
var localizedName = Loc.GetString(gas.Name);
|
|
var tooltip = Loc.GetString("gas-analyzer-window-molarity-percentage-text",
|
|
("gasName", localizedName),
|
|
("amount", $"{gas.Amount:0.##}"),
|
|
("percentage", $"{percent:0.#}"));
|
|
GasMixChart.AddEntry(gas.Amount, color, tooltip: tooltip);
|
|
}
|
|
}
|
|
|
|
// Health analyzer
|
|
var maybePatient = _entityManager.GetEntity(msg.Health.TargetEntity);
|
|
var hasPatient = msg.Health.TargetEntity.HasValue;
|
|
var hasDamage = (hasPatient
|
|
&& _entityManager.TryGetComponent(maybePatient, out DamageableComponent? damageable)
|
|
&& damageable.TotalDamage > 0);
|
|
|
|
NoDamageText.Visible = (hasPatient && !hasDamage);
|
|
HealthSection.Visible = hasPatient;
|
|
EjectPatientButton.Disabled = !hasPatient;
|
|
|
|
if (hasPatient)
|
|
HealthAnalyzer.Populate(msg.Health);
|
|
|
|
// Reagents
|
|
float? lowestTempRequirement = null;
|
|
ReagentId? lowestTempReagent = null;
|
|
var totalBeakerCapacity = msg.BeakerCapacity ?? 0;
|
|
var availableQuantity = new FixedPoint2();
|
|
var injectingQuantity =
|
|
msg.Injecting?.Aggregate(new FixedPoint2(), (sum, r) => sum + r.Quantity)
|
|
?? new FixedPoint2(); // Either the sum of the reagent quantities in `msg.Injecting` or zero.
|
|
var hasBeaker = (msg.Beaker != null);
|
|
|
|
ChemicalsChart.Clear();
|
|
ChemicalsChart.Capacity = (totalBeakerCapacity < 1 ? 50 : (int)totalBeakerCapacity);
|
|
|
|
var chartMaxChemsQuantity = ChemicalsChart.Capacity - injectingQuantity; // Ensure space for injection buffer
|
|
|
|
if (hasBeaker)
|
|
{
|
|
foreach (var (reagent, quantity) in msg.Beaker!)
|
|
{
|
|
availableQuantity += quantity;
|
|
|
|
// Make sure we don't add too many chemicals to the chart, so that there's still enough space to
|
|
// visualize the injection buffer.
|
|
var chemsQuantityOvershoot = FixedPoint2.Max(0, availableQuantity - chartMaxChemsQuantity);
|
|
var chartQuantity = FixedPoint2.Max(0, quantity - chemsQuantityOvershoot);
|
|
|
|
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
|
|
ChemicalsChart.SetEntry(
|
|
reagent.Prototype,
|
|
reagentProto.LocalizedName,
|
|
(float)chartQuantity,
|
|
reagentProto.SubstanceColor,
|
|
tooltip: $"{quantity}u {reagentProto.LocalizedName}"
|
|
);
|
|
|
|
var temp = TryFindMaxTemperatureRequirement(reagent);
|
|
if (lowestTempRequirement == null
|
|
|| temp < lowestTempRequirement)
|
|
{
|
|
lowestTempRequirement = temp;
|
|
lowestTempReagent = reagent;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (injectingQuantity != 0)
|
|
{
|
|
var injectingText = (injectingQuantity > 1 ? $"{injectingQuantity}u" : "");
|
|
ChemicalsChart.SetEntry(
|
|
"injecting",
|
|
injectingText,
|
|
(float)injectingQuantity,
|
|
Color.MediumSpringGreen,
|
|
tooltip: Loc.GetString("cryo-pod-window-chems-injecting-tooltip",
|
|
("quantity", injectingQuantity))
|
|
);
|
|
}
|
|
|
|
var isBeakerEmpty = (injectingQuantity + availableQuantity == 0);
|
|
var isChemicalsChartVisible = (hasBeaker || injectingQuantity != 0);
|
|
NoBeakerText.Visible = !isChemicalsChartVisible;
|
|
ChemicalsChart.Visible = isChemicalsChartVisible;
|
|
Inject1.Disabled = (!hasPatient || availableQuantity < 0.1f);
|
|
Inject5.Disabled = (!hasPatient || availableQuantity <= 1);
|
|
Inject10.Disabled = (!hasPatient || availableQuantity <= 5);
|
|
Inject20.Disabled = (!hasPatient || availableQuantity <= 10);
|
|
EjectBeakerButton.Disabled = !hasBeaker;
|
|
|
|
// Temperature warning
|
|
var hasCorrectTemperature = (lowestTempRequirement == null || lowestTempRequirement > msg.GasMix.Temperature);
|
|
var showsTemperatureWarning = (!showsPressureWarning && !hasCorrectTemperature);
|
|
|
|
HighTemperatureWarning.Visible = showsTemperatureWarning;
|
|
|
|
if (showsTemperatureWarning)
|
|
{
|
|
var reagentName = _prototypeManager.Index<ReagentPrototype>(lowestTempReagent!.Value.Prototype)
|
|
.LocalizedName;
|
|
HighTemperatureWarningText.Text = Loc.GetString("cryo-pod-window-high-temperature-warning",
|
|
("reagent", reagentName),
|
|
("temperature", lowestTempRequirement!));
|
|
}
|
|
|
|
// Status checklist
|
|
const float fallbackTemperatureRequirement = 213;
|
|
var hasTemperatureCheck = (hasGas && hasCorrectTemperature
|
|
&& (lowestTempRequirement != null || msg.GasMix.Temperature < fallbackTemperatureRequirement));
|
|
var hasChemicals = (hasBeaker && !isBeakerEmpty);
|
|
|
|
UpdateChecklistItem(PressureCheck, Loc.GetString("cryo-pod-window-checklist-pressure"), hasCorrectPressure);
|
|
UpdateChecklistItem(ChemicalsCheck, Loc.GetString("cryo-pod-window-checklist-chemicals"), hasChemicals);
|
|
UpdateChecklistItem(TemperatureCheck, Loc.GetString("cryo-pod-window-checklist-temperature"), hasTemperatureCheck);
|
|
|
|
var isReady = (hasCorrectPressure && hasChemicals && hasTemperatureCheck);
|
|
var isCooling = (lowestTempRequirement != null && hasPatient
|
|
&& msg.Health.Temperature > lowestTempRequirement);
|
|
var isInjecting = (injectingQuantity > 0);
|
|
StatusLabel.Text = (!isReady ? Loc.GetString("cryo-pod-window-status-not-ready") :
|
|
isCooling ? Loc.GetString("cryo-pod-window-status-cooling") :
|
|
isInjecting ? Loc.GetString("cryo-pod-window-status-injecting") :
|
|
hasPatient ? Loc.GetString("cryo-pod-window-status-ready-to-inject") :
|
|
Loc.GetString("cryo-pod-window-status-ready-for-patient"));
|
|
StatusLabel.FontColorOverride = (isReady ? Color.DeepSkyBlue : Color.Orange);
|
|
}
|
|
|
|
private void UpdateChecklistItem(Label label, string text, bool isOkay)
|
|
{
|
|
label.Text = (isOkay ? text : Loc.GetString("cryo-pod-window-checklist-fail", ("item", text)));
|
|
label.FontColorOverride = (isOkay ? null : Color.Orange);
|
|
}
|
|
|
|
private float? TryFindMaxTemperatureRequirement(ReagentId reagent)
|
|
{
|
|
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
|
|
if (reagentProto.Metabolisms == null)
|
|
return null;
|
|
|
|
float? result = null;
|
|
|
|
foreach (var (_, metabolism) in reagentProto.Metabolisms)
|
|
{
|
|
foreach (var effect in metabolism.Effects)
|
|
{
|
|
if (effect.Conditions == null)
|
|
continue;
|
|
|
|
foreach (var condition in effect.Conditions)
|
|
{
|
|
// If there are multiple temperature conditions in the same reagent (which could hypothetically
|
|
// happen, although it currently doesn't), we return the lowest max temperature.
|
|
if (condition is TemperatureCondition tempCondition
|
|
&& float.IsFinite(tempCondition.Max)
|
|
&& (result == null || tempCondition.Max < result))
|
|
{
|
|
result = tempCondition.Max;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void SetEjectErrorVisible(bool isVisible)
|
|
{
|
|
EjectError.Visible = isVisible;
|
|
}
|
|
|
|
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
|
{
|
|
const float antiJiggleSlackSpace = 80;
|
|
var oldSize = DesiredSize;
|
|
var newSize = base.MeasureOverride(availableSize);
|
|
|
|
// Reduce how often the height of the window jiggles
|
|
if (newSize.Y < oldSize.Y && newSize.Y + antiJiggleSlackSpace > oldSize.Y)
|
|
newSize.Y = oldSize.Y;
|
|
|
|
return newSize;
|
|
}
|
|
}
|