diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index 1f64feec2b..b78c3c6a56 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -1,9 +1,33 @@
-
-
-
-
+ SetSize="250 100">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index 1c5a3373e1..36f7a08b96 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -1,5 +1,5 @@
+using System.Linq;
using System.Numerics;
-using System.Text;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
@@ -8,85 +8,182 @@ using Content.Shared.MedicalScanner;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.ResourceManagement;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI
{
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : DefaultWindow
{
+ private readonly IEntityManager _entityManager;
+ private readonly SpriteSystem _spriteSystem;
+ private readonly IPrototypeManager _prototypes;
+ private readonly IResourceCache _cache;
+
+ private const int AnalyzerHeight = 430;
+ private const int AnalyzerWidth = 300;
+
public HealthAnalyzerWindow()
{
RobustXamlLoader.Load(this);
+
+ var dependencies = IoCManager.Instance!;
+ _entityManager = dependencies.Resolve();
+ _spriteSystem = _entityManager.System();
+ _prototypes = dependencies.Resolve();
+ _cache = dependencies.Resolve();
}
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
- var text = new StringBuilder();
- var entities = IoCManager.Resolve();
- var target = entities.GetEntity(msg.TargetEntity);
+ GroupsContainer.RemoveAllChildren();
- if (msg.TargetEntity != null && entities.TryGetComponent(target, out var damageable))
+ var target = _entityManager.GetEntity(msg.TargetEntity);
+
+ if (target == null
+ || !_entityManager.TryGetComponent(target, out var damageable))
{
- string entityName = "Unknown";
- if (msg.TargetEntity != null &&
- entities.HasComponent(target.Value))
- {
- entityName = Identity.Name(target.Value, entities);
- }
-
- IReadOnlyDictionary damagePerGroup = damageable.DamagePerGroup;
- IReadOnlyDictionary damagePerType = damageable.Damage.DamageDict;
-
- text.Append($"{Loc.GetString("health-analyzer-window-entity-health-text", ("entityName", entityName))}\n\n");
-
-
- text.Append($"{Loc.GetString("health-analyzer-window-entity-temperature-text", ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C"))}\n");
-
-
- text.Append($"{Loc.GetString("health-analyzer-window-entity-blood-level-text", ("bloodLevel", float.IsNaN(msg.BloodLevel) ? "N/A" : $"{msg.BloodLevel * 100:F1} %"))}\n\n");
-
-
- // Damage
- text.Append($"{Loc.GetString("health-analyzer-window-entity-damage-total-text", ("amount", damageable.TotalDamage))}\n");
-
- HashSet shownTypes = new();
-
- var protos = IoCManager.Resolve();
-
- // Show the total damage and type breakdown for each damage group.
- foreach (var (damageGroupId, damageAmount) in damagePerGroup)
- {
- if (damageAmount == 0)
- {
- continue;
- }
- text.Append($"\n{Loc.GetString("health-analyzer-window-damage-group-text", ("damageGroup", Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId)), ("amount", damageAmount))}");
-
- // Show the damage for each type in that group.
- var group = protos.Index(damageGroupId);
- foreach (var type in group.DamageTypes)
- {
- if (damagePerType.TryGetValue(type, out var typeAmount) )
- {
- // If damage types are allowed to belong to more than one damage group, they may appear twice here. Mark them as duplicate.
- if (!shownTypes.Contains(type) && typeAmount > 0)
- {
- shownTypes.Add(type);
- text.Append($"\n- {Loc.GetString("health-analyzer-window-damage-type-text", ("damageType", Loc.GetString("health-analyzer-window-damage-type-" + type)), ("amount", typeAmount))}");
- }
- }
- }
- text.AppendLine();
- }
- Diagnostics.Text = text.ToString();
- SetSize = new Vector2(250, 600);
+ NoPatientDataText.Visible = true;
+ return;
}
- else
+
+ NoPatientDataText.Visible = false;
+
+ string entityName = Loc.GetString("health-analyzer-window-entity-unknown-text");
+ if (_entityManager.HasComponent(target.Value))
{
- Diagnostics.Text = Loc.GetString("health-analyzer-window-no-patient-data-text");
- SetSize = new Vector2(250, 100);
+ entityName = Identity.Name(target.Value, _entityManager);
}
+
+ PatientName.Text = Loc.GetString(
+ "health-analyzer-window-entity-health-text",
+ ("entityName", entityName)
+ );
+
+ Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
+ ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C")
+ );
+
+ BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
+ ("bloodLevel", float.IsNaN(msg.BloodLevel) ? "N/A" : $"{msg.BloodLevel * 100:F1} %")
+ );
+
+ patientDamageAmount.Text = Loc.GetString(
+ "health-analyzer-window-entity-damage-total-text",
+ ("amount", damageable.TotalDamage)
+ );
+
+ var damageSortedGroups =
+ damageable.DamagePerGroup.OrderBy(damage => damage.Value)
+ .ToDictionary(x => x.Key, x => x.Value);
+ IReadOnlyDictionary damagePerType = damageable.Damage.DamageDict;
+
+ DrawDiagnosticGroups(damageSortedGroups, damagePerType);
+
+ SetHeight = AnalyzerHeight;
+ SetWidth = AnalyzerWidth;
+ }
+
+ private void DrawDiagnosticGroups(
+ Dictionary groups, IReadOnlyDictionary damageDict)
+ {
+ HashSet shownTypes = new();
+
+ // Show the total damage and type breakdown for each damage group.
+ foreach (var (damageGroupId, damageAmount) in groups.Reverse())
+ {
+ if (damageAmount == 0)
+ continue;
+
+ var groupTitleText = $"{Loc.GetString(
+ "health-analyzer-window-damage-group-text",
+ ("damageGroup", Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId)),
+ ("amount", damageAmount)
+ )}";
+
+ var groupContainer = new BoxContainer
+ {
+ Margin = new Thickness(0, 0, 0, 15),
+ Align = BoxContainer.AlignMode.Begin,
+ Orientation = BoxContainer.LayoutOrientation.Vertical,
+ };
+
+ groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId, damageAmount.Int()));
+
+ GroupsContainer.AddChild(groupContainer);
+
+ // Show the damage for each type in that group.
+ var group = _prototypes.Index(damageGroupId);
+
+ foreach (var type in group.DamageTypes)
+ {
+ if (damageDict.TryGetValue(type, out var typeAmount) && typeAmount > 0)
+ {
+ // If damage types are allowed to belong to more than one damage group,
+ // they may appear twice here. Mark them as duplicate.
+ if (shownTypes.Contains(type))
+ continue;
+
+ shownTypes.Add(type);
+
+ var damageString = Loc.GetString(
+ "health-analyzer-window-damage-type-text",
+ ("damageType", Loc.GetString("health-analyzer-window-damage-type-" + type)),
+ ("amount", typeAmount)
+ );
+
+ groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, "- ")));
+ }
+ }
+ }
+ }
+
+ 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(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
+ {
+ Margin = new Thickness(2, 2),
+ Text = text,
+ };
+ }
+
+ private BoxContainer CreateDiagnosticGroupTitle(string text, string id, int damageAmount)
+ {
+ var rootContainer = new BoxContainer
+ {
+ VerticalAlignment = VAlignment.Bottom,
+ Orientation = BoxContainer.LayoutOrientation.Horizontal
+ };
+
+ rootContainer.AddChild(new TextureRect
+ {
+ Margin = new Thickness(0, 3),
+ SetSize = new Vector2(30, 30),
+ Texture = GetTexture(id.ToLower())
+ });
+
+ rootContainer.AddChild(CreateDiagnosticItemLabel(text));
+
+ return rootContainer;
}
}
}
diff --git a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
index 99513df591..453bbdbb52 100644
--- a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
+++ b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
@@ -1,4 +1,5 @@
health-analyzer-window-no-patient-data-text = No patient data.
+health-analyzer-window-entity-unknown-text = unknown
health-analyzer-window-entity-health-text = {$entityName}'s health:
health-analyzer-window-entity-temperature-text = Temperature: {$temperature}
health-analyzer-window-entity-blood-level-text = Blood Level: {$bloodLevel}
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/airloss.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/airloss.png
new file mode 100644
index 0000000000..82fa0e2c88
Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/airloss.png differ
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/brute.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/brute.png
new file mode 100644
index 0000000000..bd5d80da30
Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/brute.png differ
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/burn.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/burn.png
new file mode 100644
index 0000000000..cf65084e44
Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/burn.png differ
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/genetic.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/genetic.png
new file mode 100644
index 0000000000..b660aed537
Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/genetic.png differ
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json b/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json
new file mode 100644
index 0000000000..3fccf5c46b
--- /dev/null
+++ b/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json
@@ -0,0 +1,29 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "airloss, brute, toxin and burn edited from /tg/station https://github.com/tgstation/tgstation/tree/master genetic edited from https://iconscout.com/free-icon/dna-2130814 with license CC BY 4.0",
+ "states": [
+ {
+ "name": "airloss"
+ },
+ {
+ "name": "genetic"
+ },
+ {
+ "name": "brute"
+ },
+ {
+ "name": "toxin"
+ },
+ {
+ "name": "burn"
+ },
+ {
+ "name": "unknown"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/toxin.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/toxin.png
new file mode 100644
index 0000000000..b0aafddca3
Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/toxin.png differ
diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/unknown.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/unknown.png
new file mode 100644
index 0000000000..f68e2c990e
Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/unknown.png differ