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