Delta-v/Content.Client/Medical/Cryogenics/CryoPodWindow.xaml.cs

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;
}
}