From 6ce75ccec973ca527e9bd1f26e297d7abdcb3c1a Mon Sep 17 00:00:00 2001 From: Artjom Date: Sat, 28 Oct 2023 09:54:18 +0400 Subject: [PATCH] Health analyzer UI improve (#17280) --- .../UI/HealthAnalyzerWindow.xaml | 40 +++- .../UI/HealthAnalyzerWindow.xaml.cs | 219 +++++++++++++----- .../components/health-analyzer-component.ftl | 1 + .../Devices/health_analyzer.rsi/airloss.png | Bin 0 -> 4254 bytes .../Devices/health_analyzer.rsi/brute.png | Bin 0 -> 4254 bytes .../Devices/health_analyzer.rsi/burn.png | Bin 0 -> 414 bytes .../Devices/health_analyzer.rsi/genetic.png | Bin 0 -> 4235 bytes .../Devices/health_analyzer.rsi/meta.json | 29 +++ .../Devices/health_analyzer.rsi/toxin.png | Bin 0 -> 4254 bytes .../Devices/health_analyzer.rsi/unknown.png | Bin 0 -> 4235 bytes 10 files changed, 220 insertions(+), 69 deletions(-) create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/airloss.png create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/brute.png create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/burn.png create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/genetic.png create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/toxin.png create mode 100644 Resources/Textures/Objects/Devices/health_analyzer.rsi/unknown.png 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 0000000000000000000000000000000000000000..82fa0e2c8891aa76ff1d61bad6feed2b93fbee7e GIT binary patch literal 4254 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI|jYULIsOHD6nG6g9+MX_sArYL63Ig}v162$xU_`fG zQd06ix)`7x+#FF+QHJBkk29bf|8_>~jDX#_oefaGuitWiKtF$_-wK%4k**@i`&sv&^I ze1bxw4j2sqtRe8{&mWY;14`kbEQXxD$j$dC1_L=*vmv@75Fb|ZePT!eOQRIaU=i{N zq8$J-p4_~S&uLhr0G|eG%Z)l<>&pI|jYUL2%_Y1(mw`b*+tbA{B!ZJsLE!#-po)P7jOf-& zN=p7m7o!&c`Sa%(_Mr%XEVs0@WO)7hHMQ)7n?}dQWA`*kyc0w z0RuKRh8-Ln42za5VK{#BBvl+jk^^1~3xh2N`TX|n+h8`#A>@Vz$(h}y5m9(fkGjR6 zs;UZhh^4tX!_()_Np=U(ApnYZ5YThr24fIAbD0`ABtW4-E+8ocEW56P?UA+r3Z%Ii zE*N|oE#K9su z3P8RE;l~HL!A8O8<7L`l-!FRe1}qM;90X3kVh7WN2!aj(`5u(gK?x27kR1Ut9NF^V z@JO(5`;xsdX}mPnD8Quymg-?S0TlP`moGEKiHI=3e7<(&at2~82iZf=0idh}%3>e@ z3JFk(hdIP@n>oWlU^Yw!%7Nk>6zrr}4l=5^d4;hp`ePYmS3jq5Z6!&7l;3qcqgIx_I2qr?9LS!1` z5NV(z5AgB9g&*x-0B4iJz#0O?7|;w1dQj>oCa>c&kE{@Y83;=KlmdzzK&j;*{i6=R zng~H{cxrb{a77n}13+CUQ11v=M*^2Ygv3GSV)GQDc>^_p-knoI4yU=iO4fPd#Xn@$ PfGqNK^>bP0l+XkKu?X!X literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cf65084e44e786feff1ed98094f3b0036cb00755 GIT binary patch literal 414 zcmV;P0b%}$P)Zq}6o&tnGK8g4RS#a6ER~rJiJ2Q@>4|!m-hd0Rr7Huw2hU9^MNzr{1!E%H&ndrz z1m^kuALbcjU}HV#4V?4qz09PP3%^+d=log}1(Z_3%^728nq~pUDpLO7ecH) z)B<2I|CBNqz`lnt>Cq=)0^qxhkPx7}0FwRyfHHlawN3Bc1wc=pq(`f`AONPMUmrhC z@g#s-Qe{NyPX?fh43HA10ttXR{A7SM0kS;a24MR1yUKqgoB*b#(|3QWFaHCen`U{8 z_HZ@=1_!W7?lU8-0oDp`7=GV6@Gio)_m`P*md9STVEi92=~Q3P_UF4Q8W*E1kH>Z& zSpy6Zqh9|`^ES`Wju9v>874Pq_MMNUQN zn%qX4|Bx6*KnQ_-K$EpSff0Z)2Bp-^PCjCc8FyQoZ%#MYFTDh2*4cRjjsO4v07*qo IM6N<$f(wDISpWb4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b660aed537581d821913c32d4b4d581ab8c8ba0f GIT binary patch literal 4235 zcmc&%ZA_b06n@^8Qrf27pwv*xkOC$SL?vN!EPM=8!-oq&L*ll?iAxp|Hx^^VK->?r z#V!7t#3e4WA8anv&CQsMA7qJUuGv^HnQ<9L8J|^PQad8h@6y+E2`#R?h1-R=)7;+s z-mm95_nv$1xt})FS8LUoY5>~W8q1rM=dhHLO#jx7js?n+54>J&fyjQ{-`hT?8rAzX zd#!**&ys*I&t(9c3u`S^jeAr=&gQXM>hp9(h=n&Ajbd!wMtx6DPp`sztb&1;mX^Zl zbZ!)8bRL7jfd2k|#F9vg_E;`Nz=wy2W1F7%J3l`^u2QQAFcb!!lqA{oL?qaJjD(c% zqo~zL-oGF6v^026o&*HN%6wuFM~>tpB}I;|t`W@6hF03vDRC1J($k@;sevLl7qank z{PyQO^nd&U*Or&?e3llCjpv|L`q9@nNd(xLi{WI7$(2tDxI z$6FH9uV0nW3Ro<9SgkL>WK!V94Lf>!+wfg^IeZoi0xc~t$Ycm#y$ZHnyu!T#CQ~w+ znhLOW>x&o}IfMrfnvhFJvi0FZT)TfCUl@(ZXl{m(oz08A)Q@`wa&tAs0uXRX~_-Zh>gHjR=Y< z5LsFA9~Uh(QA`~-fzi=9ghGC#rTfs*;)hx-Cv0IUx!qgnveDqITn-1>Ln6XWV0_$7 zd+$U=g&V1jOWr!k_HseH7)mgp(Sv| znKKFb07jI11tMBqyy(K6J8zQ>G{a~%!%d;qCDK}P`gFqJaS5>VEh(gJlGTbfx(5s{ z7p{@l8yY%}va-uKb*cb5om_I*T9w>ez$Qr|m%FL45ju|te;+#rE8T`UIU+hccc88= z52K?VbazjXC*}{Vl4z6 z-v$JVgcM}$i*EN4yGSI&@>b`w6bbq0ZrFtMboNr>7k;{aKY|q9hKYc?=N+g8@`N(- zdSVFfu}|>h%f+O<5_5C>0V$)&O@IX>9tuXHB6@*EO3QNe=+BX2Lb)UX7Q=7!!z3Q7 zRRq`*ioK5FJtX2yv|c@%S4yeKjT#;Qe?Ly6z)!32ab^1luXtT*w)+-L_=Fs%>~se1$jSf7&1{wdmVk&(mllfA8ojliSwI&L<~S#iM`-b#hpll= zzyxISvol!asFRZ8*jpz+5`Ykh7R9okXIkbF1Ag7I3P1sYzy{E6%sGL(kE<4r z*)jo9S5H_4(z~(d6_S080zY`HT6@ F{ck!^*joSq literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f68e2c990e59799c9cbd950445b51451cc98efda GIT binary patch literal 4235 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAjMhK|tHn#W5s;lTkt7{(GRBfd!1{)=NrC{zn(17XSJ4=NR^(2!JfN zw6tV+{rWYv?1Y;mDk{ow{P=MObVsnjRX`aq%kSK|gDyojUqwX)r>8I+0I~&SISUI` zDY6^^G7sbkY=8-yAU(xK9WWXKqaiRF0;3@?8UmvsFd71*Aut*OqalE`@egXlQ@dk= ztErFS08keS)H}k}k-%jTA#sqo*gS>OHv}0%@6IV9htphs`fe@$z^Y@tAd5U*{an^L HB{Ts5?(jp` literal 0 HcmV?d00001