mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-30 08:07:28 +00:00
* Fixed some alerts not showing in the health analyzer * This is upstream now so removing comments
487 lines
19 KiB
C#
487 lines
19 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Shared.Atmos;
|
|
using Content.Shared.Damage.Components;
|
|
using Content.Shared.Damage.Prototypes;
|
|
using Content.Shared.Damage.Systems;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Humanoid;
|
|
using Content.Shared.Humanoid.Prototypes;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.MedicalScanner;
|
|
using Content.Shared.Mobs;
|
|
using Content.Shared.Mobs.Components;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
|
|
// BEGIN DeltaV
|
|
using Content.Client._DV.Traits.Assorted;
|
|
using Content.Shared._DV.Traits.Assorted;
|
|
using Content.Shared._DV.Medical; // Uncloneable
|
|
using Content.Shared._DV.MedicalRecords; // Medical Records
|
|
|
|
// Health Analyzer Plus
|
|
using Content.Shared.Chemistry.Components;
|
|
using Content.Shared.Chemistry.Reagent;
|
|
using Content.Shared.Body.Systems;
|
|
using Content.Client.Body.Systems;
|
|
using Content.Shared.Chemistry.EntitySystems;
|
|
using Content.Client.Chemistry.Containers.EntitySystems;
|
|
using Content.Shared.Body.Components;
|
|
// END DeltaV
|
|
namespace Content.Client.HealthAnalyzer.UI;
|
|
|
|
|
|
|
|
// Health analyzer UI is split from its window because it's used by both the
|
|
// health analyzer item and the cryo pod UI.
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class HealthAnalyzerControl : BoxContainer
|
|
{
|
|
private const string REAGENT_GROUP_MEDICINE = "Medicine"; // DeltaV - Health Analyzer
|
|
|
|
private readonly IEntityManager _entityManager;
|
|
private readonly SpriteSystem _spriteSystem;
|
|
private readonly IPrototypeManager _prototypes;
|
|
private readonly IResourceCache _cache;
|
|
private readonly DamageableSystem _damageable;
|
|
|
|
private readonly UnborgableSystem _unborgable; // DeltaV
|
|
private readonly RedshirtSystem _redshirt; // DeltaV
|
|
private readonly UncloneableSystem _uncloneable; // DeltaV
|
|
private readonly SharedBloodstreamSystem _bloodstream; // DeltaV
|
|
private readonly SharedSolutionContainerSystem _solutionContainer; // DeltaV
|
|
|
|
// Begin DeltaV - Medical Records
|
|
private readonly ButtonGroup _triageStatusGroup = new();
|
|
private readonly Dictionary<TriageStatus, Button> _triageControls = new();
|
|
|
|
public event Action<TriageStatus>? OnTriageStatusChanged;
|
|
public event Action? OnClaimPatient;
|
|
// End DeltaV - Medical Records
|
|
public Action? OnPrintMedTekRecord; // DeltaV - MedTek Reports
|
|
|
|
|
|
public HealthAnalyzerControl()
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
|
|
var dependencies = IoCManager.Instance!;
|
|
_entityManager = dependencies.Resolve<IEntityManager>();
|
|
_spriteSystem = _entityManager.System<SpriteSystem>();
|
|
_prototypes = dependencies.Resolve<IPrototypeManager>();
|
|
_cache = dependencies.Resolve<IResourceCache>();
|
|
_damageable = _entityManager.System<DamageableSystem>();
|
|
|
|
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
|
|
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
|
|
_uncloneable = _entityManager.System<UncloneableSystem>(); // DeltaV
|
|
_bloodstream = _entityManager.System<BloodstreamSystem>(); // DeltaV
|
|
_solutionContainer = _entityManager.System<SolutionContainerSystem>(); // DeltaV
|
|
|
|
// Begin DeltaV - Medical Records
|
|
foreach (var item in Enum.GetValues<TriageStatus>())
|
|
{
|
|
var btn = new Button();
|
|
var ftlKey = item.ToString();
|
|
btn.Group = _triageStatusGroup;
|
|
btn.Text = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}");
|
|
btn.ToolTip = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}.ToolTip");
|
|
btn.OnPressed += _ => OnTriageStatusChanged?.Invoke(item);
|
|
btn.AddStyleClass("ButtonSquare");
|
|
StatusBox.AddChild(btn);
|
|
_triageControls[item] = btn;
|
|
}
|
|
StatusBox.Children.First().AddStyleClass("OpenRight");
|
|
StatusBox.Children.First().RemoveStyleClass("ButtonSquare");
|
|
StatusBox.Children.Last().AddStyleClass("OpenLeft");
|
|
StatusBox.Children.Last().RemoveStyleClass("ButtonSquare");
|
|
ClaimButton.OnPressed += _ => OnClaimPatient?.Invoke();
|
|
// End DeltaV - Medical Records
|
|
PrintReportButton.OnPressed += (_) => OnPrintMedTekRecord?.Invoke(); // DeltaV - MedTek Reports
|
|
}
|
|
|
|
public void Populate(HealthAnalyzerUiState state)
|
|
{
|
|
var target = _entityManager.GetEntity(state.TargetEntity);
|
|
|
|
if (target == null
|
|
|| !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
|
|
{
|
|
NoPatientDataText.Visible = true;
|
|
return;
|
|
}
|
|
|
|
NoPatientDataText.Visible = false;
|
|
|
|
// Scan Mode
|
|
|
|
ScanModeLabel.Text = state.ScanMode.HasValue
|
|
? state.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 = state.ScanMode.HasValue && state.ScanMode.Value ? Color.Green : Color.Red;
|
|
|
|
PrintReportButton.Disabled = state is { ScanMode: false }; // DeltaV
|
|
|
|
// Patient Information
|
|
SpriteView.SetEntity(target.Value);
|
|
SpriteView.Visible = state.ScanMode.HasValue && state.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<HumanoidProfileComponent>(target.Value,
|
|
out var humanoidComponent)
|
|
? Loc.GetString(_prototypes.Index(humanoidComponent.Species).Name)
|
|
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
|
|
|
|
// Basic Diagnostic
|
|
|
|
TemperatureLabel.Text = !float.IsNaN(state.Temperature)
|
|
? $"{state.Temperature - Atmospherics.T0C:F1} °C ({state.Temperature:F1} K)"
|
|
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
|
|
|
// Begin DeltaV - Health Analyzer Plus
|
|
/*
|
|
BloodLabel.Text = !float.IsNaN(state.BloodLevel)
|
|
? $"{state.BloodLevel * 100:F1} %"
|
|
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
|
*/
|
|
// End DeltaV - Health Analyzer Plus
|
|
|
|
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.GetTotalDamage(target.Value).ToString(); // DeltaV - Health Analyzer Plus
|
|
|
|
// Alerts
|
|
// DeltaV traits - This is going to be horrid if we just keep adding things like this.
|
|
var unborgable = _unborgable.IsUnborgable(target.Value);
|
|
var redshirt = _redshirt.IsRedshirt(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Redshirt
|
|
var uncloneable = _uncloneable.IsUncloneable(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Unclonable
|
|
|
|
// I'm just going to lump them all into 'extraTraitAlerts' to group our modifications as a single bool in the next check
|
|
var extraTraitAlerts = unborgable || redshirt || uncloneable;
|
|
// END DeltaV
|
|
|
|
var showAlerts = state.Unrevivable == true || state.Bleeding == true || extraTraitAlerts; // DeltaV - extra alerts
|
|
|
|
AlertsDivider.Visible = showAlerts;
|
|
AlertsContainer.Visible = showAlerts;
|
|
|
|
if (showAlerts)
|
|
AlertsContainer.RemoveAllChildren();
|
|
|
|
if (state.Unrevivable == true)
|
|
AlertsContainer.AddChild(new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
|
|
Margin = new Thickness(0, 4),
|
|
MaxWidth = 300
|
|
});
|
|
|
|
if (state.Bleeding == true)
|
|
AlertsContainer.AddChild(new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
|
|
Margin = new Thickness(0, 4),
|
|
MaxWidth = 300
|
|
});
|
|
|
|
if (unborgable) // DeltaV
|
|
AlertsContainer.AddChild(new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-window-entity-unborgable-text"),
|
|
Margin = new Thickness(0, 4),
|
|
MaxWidth = 300
|
|
});
|
|
|
|
if (redshirt) // DeltaV
|
|
AlertsContainer.AddChild(new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-window-entity-redshirt-text"),
|
|
Margin = new Thickness(0, 4),
|
|
MaxWidth = 300
|
|
});
|
|
|
|
if (uncloneable) // DeltaV - Uncloneable
|
|
AlertsContainer.AddChild(new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-window-entity-uncloneable-text"),
|
|
Margin = new Thickness(0, 4),
|
|
MaxWidth = 300
|
|
});
|
|
|
|
// Damage Groups
|
|
|
|
var damageSortedGroups =
|
|
_damageable.GetDamagePerGroup(target.Value)
|
|
.OrderByDescending(damage => damage.Value)
|
|
.ToDictionary(x => x.Key, x => x.Value);
|
|
|
|
var damagePerType = _damageable.GetAllDamage(target.Value).DamageDict;
|
|
|
|
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
|
|
DrawBloodstreamInfo(target.Value, state.BloodSolution, state.BloodLevel); // End DeltaV - Health Analyzer Plus
|
|
|
|
// Begin DeltaV - Medical Records
|
|
if (state.MedicalRecord is not { } records)
|
|
{
|
|
TriageControls.Visible = false;
|
|
return;
|
|
}
|
|
|
|
TriageControls.Visible = true;
|
|
_triageControls[records.Status].Pressed = true;
|
|
|
|
// Update claim button based on claimed status
|
|
if (records.ClaimedName != null)
|
|
{
|
|
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-unclaim", ("claimedBy", records.ClaimedName));
|
|
}
|
|
else
|
|
{
|
|
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-claim");
|
|
}
|
|
// End DeltaV - Medical Records
|
|
}
|
|
|
|
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<ProtoId<DamageGroupPrototype>, FixedPoint2> groups,
|
|
IReadOnlyDictionary<ProtoId<DamageTypePrototype>, FixedPoint2> damageDict)
|
|
{
|
|
// Begin DeltaV - Health Analyzer Plus
|
|
// GroupsContainer.RemoveAllChildren();
|
|
DamageGroupsContainer.RemoveAllChildren();
|
|
|
|
// Begin DeltaV - Health Analyzer Plus
|
|
var totalDamage = damageDict.Sum(x => x.Value.Double());
|
|
TotalDamageLabel.Text = Loc.GetString("health-analyzer-plus-window-entity-total-damage-text", ("amount", Math.Round(totalDamage, 2)));
|
|
NoDamageLabel.Visible = totalDamage == 0;
|
|
// End DeltaV - Health Analyzer Plus
|
|
|
|
foreach (var (damageGroupId, damageAmount) in groups)
|
|
{
|
|
if (damageAmount == 0)
|
|
continue;
|
|
|
|
var groupTitleText = $"{Loc.GetString(
|
|
"health-analyzer-window-damage-group-text",
|
|
("damageGroup", _prototypes.Index(damageGroupId).LocalizedName),
|
|
("amount", damageAmount)
|
|
)}";
|
|
|
|
var groupContainer = new BoxContainer
|
|
{
|
|
Align = AlignMode.Begin,
|
|
Orientation = LayoutOrientation.Vertical,
|
|
};
|
|
|
|
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
|
|
|
|
// GroupsContainer.AddChild(groupContainer); // DeltaV - Health Analyzer Plus
|
|
DamageGroupsContainer.AddChild(groupContainer); // DeltaV - Health Analyzer Plus
|
|
|
|
// 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)
|
|
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 DeltaV - Health Analyzer Plus
|
|
private void DrawBloodstreamInfo(EntityUid target, Solution? bloodSolution, float bloodlevel)
|
|
{
|
|
BloodstreamReagentListContainer.RemoveAllChildren();
|
|
|
|
List<ReagentQuantity> bloodstream = new();
|
|
double totalReagentQuantity = 0;
|
|
double unknownReagents = 0;
|
|
// Make sure we can access the bloodtype and reagents before trying to use them
|
|
if (bloodSolution is not null)
|
|
{
|
|
if (!_entityManager.TryGetComponent<BloodstreamComponent>(target, out var bloodstreamComp) ||
|
|
!_solutionContainer.ResolveSolution(target, bloodstreamComp.BloodSolutionName, ref bloodstreamComp.BloodSolution, out var bloodReagentSolution))
|
|
return;
|
|
|
|
// Get the blood reagent IDs to ignore in the scan
|
|
var bloodReagentIds = bloodstreamComp.BloodReferenceSolution.Select(b => b.Reagent);
|
|
|
|
// Get all of the medicines in the bloodstream
|
|
foreach (var reagent in bloodSolution!.Contents)
|
|
{
|
|
if (bloodReagentIds.Contains(reagent.Reagent))
|
|
continue;
|
|
|
|
if (_prototypes.Index<ReagentPrototype>(reagent.Reagent.Prototype).Group == REAGENT_GROUP_MEDICINE)
|
|
{
|
|
bloodstream.Add(reagent);
|
|
totalReagentQuantity += reagent.Quantity.Double();
|
|
}
|
|
else
|
|
{
|
|
unknownReagents += reagent.Quantity.Double();
|
|
}
|
|
}
|
|
}
|
|
|
|
var bloodQuantityPercent = !float.IsNaN(bloodlevel)
|
|
? $"{bloodlevel * 100:F1} %"
|
|
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
|
|
|
// Display the percent and amount of blood in the system
|
|
// Normal health scanner is percent only, this one adds the actual units too
|
|
BloodLevelLabel.Text = Loc.GetString(
|
|
"health-analyzer-plus-window-entity-blood-level-quantity-text",
|
|
("percent", bloodQuantityPercent)
|
|
);
|
|
|
|
// Display the total amount of reagents in the bloodstream, 0 is valid
|
|
TotalReagentQuantityLabel.Text = Loc.GetString(
|
|
"health-analyzer-plus-window-entity-total-reagents-text",
|
|
("amount", Math.Round(totalReagentQuantity, 1).ToString("0.0"))
|
|
);
|
|
|
|
// If the bloodstream was empty, display a message instead to fill the space with something
|
|
NoReagentsLabel.Visible = bloodstream.Count == 0 && unknownReagents == 0;
|
|
|
|
// For each of the reagents in the bloodstream, add them to the list of reagents
|
|
// If there are none, this loop will simply do nothing
|
|
foreach (var (reagentID, reagentQuantity) in bloodstream)
|
|
{
|
|
var reagentPrototype = _prototypes.Index<ReagentPrototype>(reagentID.Prototype);
|
|
|
|
// Make a new container for each reagent we add to the list
|
|
var reagentContainer = new BoxContainer
|
|
{
|
|
Align = AlignMode.Begin,
|
|
Orientation = LayoutOrientation.Vertical
|
|
};
|
|
|
|
// Add the string "[] <reagent_name>: <reagent_amount>u" for the current reagent
|
|
// The [] is a unicode block character, and is colored with the reagent color
|
|
// to make it easier to identify
|
|
reagentContainer.AddChild(
|
|
new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-plus-window-reagent-text",
|
|
("reagentColor", reagentPrototype.SubstanceColor),
|
|
("reagentName", reagentPrototype.LocalizedName),
|
|
("amount", Math.Round(reagentQuantity.Double(), 1).ToString("0.0"))
|
|
)
|
|
}
|
|
);
|
|
|
|
BloodstreamReagentListContainer.AddChild(reagentContainer);
|
|
}
|
|
|
|
// Add unknown reagents (if any)
|
|
if (unknownReagents > 0)
|
|
{
|
|
var unknownReagentContainer = new BoxContainer
|
|
{
|
|
Align = AlignMode.Begin,
|
|
Orientation = LayoutOrientation.Vertical
|
|
};
|
|
|
|
unknownReagentContainer.AddChild(
|
|
new RichTextLabel
|
|
{
|
|
Text = Loc.GetString("health-analyzer-plus-window-unknown-reagents-text",
|
|
("amount", Math.Round(unknownReagents, 1).ToString("0.0"))
|
|
)
|
|
}
|
|
);
|
|
BloodstreamReagentListContainer.AddChild(unknownReagentContainer);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// End DeltaV - Health Analyzer Plus
|
|
|
|
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 = LayoutOrientation.Horizontal,
|
|
};
|
|
|
|
rootContainer.AddChild(new TextureRect
|
|
{
|
|
SetSize = new Vector2(30, 30),
|
|
Texture = GetTexture(id.ToLower())
|
|
});
|
|
|
|
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
|
|
|
|
return rootContainer;
|
|
}
|
|
}
|