Files
tbd-station-14/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
Janet Blackquill 173f24590f resimmed offmed
2025-10-08 20:49:29 -04:00

434 lines
19 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Client.Message;
using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared._Offbrand.Wounds; // Offbrand
using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI
{
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : FancyWindow
{
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache;
// Begin Offbrand
private readonly Tooltips.StatusTooltip _statusTooltip = new();
private readonly Tooltips.BrainHealthTooltip _brainHealthTooltip = new();
private readonly Tooltips.BloodPressureTooltip _bloodPressureTooltip = new();
private readonly Tooltips.HeartRateTooltip _heartRateTooltip = new();
private readonly Tooltips.HeartHealthTooltip _heartHealthTooltip = new();
private readonly Tooltips.LungHealthTooltip _lungHealthTooltip = new();
private readonly Tooltips.BloodTooltip _bloodTooltip = new();
private readonly Tooltips.TemperatureTooltip _temperatureTooltip = new();
private readonly Tooltips.DamageTooltip _damageTooltip = new();
private readonly Tooltips.SpO2Tooltip _spo2Tooltip = new();
private readonly Tooltips.EtCO2Tooltip _etco2Tooltip = new();
private readonly Tooltips.RespiratoryRateTooltip _respiratoryRateTooltip = new();
// End Offbrand
public HealthAnalyzerWindow()
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_entityManager = dependencies.Resolve<IEntityManager>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>();
// Begin Offbrand
StatusButton.TooltipSupplier = _ => _statusTooltip;
BrainHealthButton.TooltipSupplier = _ => _brainHealthTooltip;
BloodPressureButton.TooltipSupplier = _ => _bloodPressureTooltip;
HeartRateButton.TooltipSupplier = _ => _heartRateTooltip;
HeartHealthButton.TooltipSupplier = _ => _heartHealthTooltip;
TemperatureButton.TooltipSupplier = _ => _temperatureTooltip;
DamageButton.TooltipSupplier = _ => _damageTooltip;
BloodButton.TooltipSupplier = _ => _bloodTooltip;
LungHealthButton.TooltipSupplier = _ => _lungHealthTooltip;
SpO2Button.TooltipSupplier = _ => _spo2Tooltip;
EtCO2Button.TooltipSupplier = _ => _etco2Tooltip;
RespiratoryRateButton.TooltipSupplier = _ => _respiratoryRateTooltip;
// End Offbrand
}
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
var target = _entityManager.GetEntity(msg.TargetEntity);
if (target == null
|| !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
{
NoPatientDataText.Visible = true;
return;
}
NoPatientDataText.Visible = false;
// Begin Offbrand Tooltips
_brainHealthTooltip.Update(msg);
_heartRateTooltip.Update(msg);
_heartHealthTooltip.Update(msg);
_temperatureTooltip.Update(msg, (target.Value, _entityManager.GetComponentOrNull<CryostasisFactorComponent>(target)));
_spo2Tooltip.Update(msg);
_etco2Tooltip.Update(msg);
_respiratoryRateTooltip.Update(msg);
// End Offbrand Tooltips
// Scan Mode
ScanModeLabel.Text = msg.ScanMode.HasValue
? msg.ScanMode.Value
? Loc.GetString("health-analyzer-window-scan-mode-active")
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
: Loc.GetString("health-analyzer-window-entity-unknown-text");
ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
// Patient Information
SpriteView.SetEntity(target.Value);
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
NoDataTex.Visible = !SpriteView.Visible;
var name = new FormattedMessage();
name.PushColor(Color.White);
name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
? Identity.Name(target.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
NameLabel.SetMessage(name);
SpeciesLabel.Text =
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
out var humanoidAppearanceComponent)
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
// Basic Diagnostic
TemperatureLabel.Text = !float.IsNaN(msg.Temperature)
? $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
? $"{msg.BloodLevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
StatusLabel.Text =
_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
? GetStatus(mobStateComponent.CurrentState)
: Loc.GetString("health-analyzer-window-entity-unknown-text");
// Total Damage
DamageLabel.Text = damageable.TotalDamage.ToString();
// Alerts
var showAlerts = msg.Unrevivable == true || msg.Bleeding == true || msg.WoundableData?.NonMedicalReagents == true || msg.WoundableData?.Wounds != null; // Offbrand
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.RemoveAllChildren();
if (msg.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (msg.Bleeding == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
// Begin Offbrand
var showReagents = msg.WoundableData?.Reagents?.Count is { } count && count > 0;
ReagentsDivider.Visible = showReagents;
ReagentsContainer.Visible = showReagents;
if (msg.WoundableData is { } woundable)
{
if (woundable.Wounds is not null)
{
foreach (var wound in woundable.Wounds)
{
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString(wound),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
}
}
if (woundable.NonMedicalReagents)
{
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-non-medical-reagents"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
}
if (woundable.Reagents is { } reagents)
{
ReagentsContainer.DisposeAllChildren();
foreach (var (reagent, amounts) in reagents.OrderBy(kvp => _prototypes.Index(kvp.Key).LocalizedName))
{
var (quantity, metabolites) = amounts;
var proto = _prototypes.Index(reagent);
ReagentsContainer.AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
Children =
{
new PanelContainer
{
VerticalExpand = true,
MinWidth = 4,
PanelOverride = new StyleBoxFlat
{
BackgroundColor = proto.SubstanceColor
},
Margin = new Thickness(4, 1),
},
new Label { Text = proto.LocalizedName, HorizontalExpand = true, SizeFlagsStretchRatio = 3 },
new Label { Text = $"{metabolites}u", StyleClasses = { Content.Client.Stylesheets.StyleNano.StyleClassLabelSecondaryColor }, HorizontalExpand = true, SizeFlagsStretchRatio = 1 },
new Label { Text = $"{quantity}u", HorizontalExpand = true, SizeFlagsStretchRatio = 1 },
}
});
}
}
BrainHealthText.Visible = true;
BrainHealthLabel.Visible = true;
BrainHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-brain-health-value", ("value", $"{woundable.BrainHealth * 100:F1}"));
BrainHealthButton.Visible = true;
HeartHealthText.Visible = true;
HeartHealthLabel.Visible = true;
HeartHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-health-value", ("value", $"{woundable.HeartHealth * 100:F1}"));
HeartHealthButton.Visible = true;
HeartRateText.Visible = true;
HeartRateLabel.Visible = true;
HeartRateLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-rate-value", ("value", woundable.HeartRate));
HeartRateButton.Visible = true;
var (systolic, diastolic) = woundable.BloodPressure;
BloodPressureText.Visible = true;
BloodPressureLabel.Visible = true;
BloodPressureLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-pressure-value", ("systolic", systolic), ("diastolic", diastolic));
BloodPressureButton.Visible = true;
LungHealthText.Visible = true;
LungHealthLabel.Visible = true;
LungHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-lung-health-value", ("value", $"{woundable.LungHealth * 100:F1}"));
LungHealthButton.Visible = true;
SpO2Text.Visible = true;
SpO2Text.Text = Loc.GetString("health-analyzer-window-entity-spo2-text", ("spo2", woundable.Spo2Name));
SpO2Label.Visible = true;
SpO2Label.Text = Loc.GetString("health-analyzer-window-entity-spo2-value", ("value", $"{woundable.Spo2 * 100:F1}"));
SpO2Button.Visible = true;
EtCO2Text.Visible = true;
EtCO2Text.Text = Loc.GetString("health-analyzer-window-entity-etco2-text", ("etco2", woundable.Etco2Name));
EtCO2Label.Visible = true;
EtCO2Label.Text = Loc.GetString("health-analyzer-window-entity-etco2-value", ("value", $"{woundable.Etco2}"));
EtCO2Button.Visible = true;
RespiratoryRateText.Visible = true;
RespiratoryRateLabel.Visible = true;
RespiratoryRateLabel.Text = Loc.GetString("health-analyzer-window-entity-respiratory-rate-value", ("value", $"{woundable.RespiratoryRate}"));
RespiratoryRateButton.Visible = true;
BloodLabel.Visible = false;
BloodText.Visible = false;
BloodButton.Visible = false;
}
else
{
BrainHealthLabel.Visible = false;
BloodPressureLabel.Visible = false;
HeartRateLabel.Visible = false;
HeartHealthLabel.Visible = false;
LungHealthLabel.Visible = false;
BrainHealthText.Visible = false;
BloodPressureText.Visible = false;
HeartRateText.Visible = false;
HeartHealthText.Visible = false;
LungHealthText.Visible = false;
BrainHealthButton.Visible = false;
BloodPressureButton.Visible = false;
HeartRateButton.Visible = false;
HeartHealthButton.Visible = false;
LungHealthButton.Visible = false;
SpO2Text.Visible = false;
SpO2Label.Visible = false;
SpO2Button.Visible = false;
EtCO2Text.Visible = false;
EtCO2Label.Visible = false;
EtCO2Button.Visible = false;
RespiratoryRateText.Visible = false;
RespiratoryRateLabel.Visible = false;
RespiratoryRateButton.Visible = false;
BloodLabel.Visible = true;
BloodText.Visible = true;
BloodButton.Visible = true;
}
// End Offbrand
// Damage Groups
var damageSortedGroups =
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value);
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
}
private static string GetStatus(MobState mobState)
{
return mobState switch
{
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
};
}
private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups,
IReadOnlyDictionary<string, FixedPoint2> damageDict)
{
GroupsContainer.RemoveAllChildren();
foreach (var (damageGroupId, damageAmount) in groups)
{
if (damageAmount == 0)
continue;
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
var groupContainer = new BoxContainer
{
Align = BoxContainer.AlignMode.Begin,
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
foreach (var type in group.DamageTypes)
{
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
continue;
var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
// Begin Offbrand
NoDamagesText.Visible = GroupsContainer.ChildCount == 0;
// End Offbrand
}
private Texture GetTexture(string texture)
{
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
{
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
}
return _spriteSystem.Frame0(rsiSprite);
}
private static Label CreateDiagnosticItemLabel(string text)
{
return new Label
{
Text = text,
};
}
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{
Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom,
Orientation = BoxContainer.LayoutOrientation.Horizontal,
};
rootContainer.AddChild(new TextureRect
{
SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower())
});
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
return rootContainer;
}
}
}