422 lines
18 KiB
C#
422 lines
18 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.BloodOxygenationTooltip _bloodOxygenationTooltip = new();
|
|
private readonly Tooltips.HeartRateTooltip _heartRateTooltip = new();
|
|
private readonly Tooltips.HeartHealthTooltip _heartHealthTooltip = new();
|
|
private readonly Tooltips.LungHealthTooltip _lungHealthTooltip = new();
|
|
private readonly Tooltips.BloodFlowTooltip _bloodFlowTooltip = new();
|
|
private readonly Tooltips.BloodTooltip _bloodTooltip = new();
|
|
private readonly Tooltips.TemperatureTooltip _temperatureTooltip = new();
|
|
private readonly Tooltips.DamageTooltip _damageTooltip = 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;
|
|
BloodOxygenationButton.TooltipSupplier = _ => _bloodOxygenationTooltip;
|
|
HeartRateButton.TooltipSupplier = _ => _heartRateTooltip;
|
|
BloodFlowButton.TooltipSupplier = _ => _bloodFlowTooltip;
|
|
HeartHealthButton.TooltipSupplier = _ => _heartHealthTooltip;
|
|
TemperatureButton.TooltipSupplier = _ => _temperatureTooltip;
|
|
DamageButton.TooltipSupplier = _ => _damageTooltip;
|
|
BloodButton.TooltipSupplier = _ => _bloodTooltip;
|
|
LungHealthButton.TooltipSupplier = _ => _lungHealthTooltip;
|
|
// 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);
|
|
_bloodPressureTooltip.Update(msg);
|
|
_bloodOxygenationTooltip.Update(msg, (target.Value, damageable, _entityManager.GetComponentOrNull<HeartrateComponent>(target)));
|
|
_heartRateTooltip.Update(msg, (target.Value, damageable, _entityManager.GetComponentOrNull<HeartrateComponent>(target)));
|
|
_bloodFlowTooltip.Update(msg);
|
|
_heartHealthTooltip.Update(msg);
|
|
_temperatureTooltip.Update(msg, (target.Value, _entityManager.GetComponentOrNull<CryostasisFactorComponent>(target)));
|
|
// 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}"), ("rating", woundable.BrainHealthRating));
|
|
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}"), ("rating", woundable.HeartHealthRating));
|
|
HeartHealthButton.Visible = true;
|
|
|
|
HeartRateText.Visible = true;
|
|
HeartRateLabel.Visible = true;
|
|
HeartRateLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-rate-value", ("value", woundable.HeartRate), ("rating", woundable.HeartRateRating));
|
|
HeartRateButton.Visible = true;
|
|
|
|
BloodOxygenationText.Visible = true;
|
|
BloodOxygenationLabel.Visible = true;
|
|
BloodOxygenationLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-oxygenation-value", ("value", $"{woundable.BloodOxygenation * 100:F1}"), ("rating", woundable.BloodOxygenationRating));
|
|
BloodOxygenationButton.Visible = true;
|
|
|
|
BloodFlowText.Visible = true;
|
|
BloodFlowLabel.Visible = true;
|
|
BloodFlowLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-flow-value", ("value", $"{woundable.BloodFlow * 100:F1}"), ("rating", woundable.BloodFlowRating));
|
|
BloodFlowButton.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), ("rating", woundable.BloodPressureRating));
|
|
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}"), ("rating", woundable.LungHealthRating));
|
|
LungHealthButton.Visible = true;
|
|
|
|
BloodLabel.Visible = false;
|
|
BloodText.Visible = false;
|
|
BloodButton.Visible = false;
|
|
}
|
|
else
|
|
{
|
|
BrainHealthLabel.Visible = false;
|
|
BloodPressureLabel.Visible = false;
|
|
BloodOxygenationLabel.Visible = false;
|
|
HeartRateLabel.Visible = false;
|
|
HeartHealthLabel.Visible = false;
|
|
BloodFlowLabel.Visible = false;
|
|
LungHealthLabel.Visible = false;
|
|
BrainHealthText.Visible = false;
|
|
BloodPressureText.Visible = false;
|
|
BloodOxygenationText.Visible = false;
|
|
BloodFlowText.Visible = false;
|
|
HeartRateText.Visible = false;
|
|
HeartHealthText.Visible = false;
|
|
LungHealthText.Visible = false;
|
|
BrainHealthButton.Visible = false;
|
|
BloodPressureButton.Visible = false;
|
|
BloodOxygenationButton.Visible = false;
|
|
BloodFlowButton.Visible = false;
|
|
HeartRateButton.Visible = false;
|
|
HeartHealthButton.Visible = false;
|
|
LungHealthButton.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;
|
|
}
|
|
}
|
|
}
|