Add triaging to health analyzers and HUD (#3149)

This commit is contained in:
pathetic meowmeow 2025-03-17 20:24:12 -04:00 committed by GitHub
parent acf2f05293
commit e2c63a83b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 474 additions and 1 deletions

View File

@ -1,5 +1,6 @@
using Content.Shared.MedicalScanner;
using Content.Shared._Shitmed.Targeting; // Shitmed Change
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
using JetBrains.Annotations;
using Robust.Client.UserInterface;
@ -21,6 +22,8 @@ namespace Content.Client.HealthAnalyzer.UI
_window = this.CreateWindow<HealthAnalyzerWindow>();
_window.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
_window.OnTriageStatusChanged += SendTriageStatusMessage; // DeltaV - Medical Records
_window.OnClaimPatient += SendTriageClaimMessage; // DeltaV - Medical Records
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
}
@ -51,5 +54,13 @@ namespace Content.Client.HealthAnalyzer.UI
}
// Shitmed Change End
// Begin DeltaV - Medical Records
private void SendTriageStatusMessage(TriageStatus status)
=> SendMessage(new HealthAnalyzerTriageStatusMessage(status));
private void SendTriageClaimMessage()
=> SendMessage(new HealthAnalyzerTriageClaimMessage());
// End DeltaV - Medical Records
}
}

View File

@ -198,6 +198,22 @@
<PanelContainer StyleClasses="LowDivider"/>
<!-- Begin DeltaV - Medical Records -->
<BoxContainer Name="TriageControls" Orientation="Vertical">
<BoxContainer Name="StatusBox"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0 8 0 0">
</BoxContainer>
<Button Name="ClaimButton"
Text="{Loc 'health-analyzer-window-triage-claim'}"
MinSize="60 0" />
<PanelContainer StyleClasses="LowDivider" Margin="0 8 0 0"/>
</BoxContainer>
<!-- End DeltaV - Medical Records -->
<GridContainer Margin="0 5 0 0"
Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}"/>

View File

@ -4,6 +4,7 @@ using Content.Client.Message;
using Content.Shared._DV.Traits.Assorted; // DeltaV
using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
using Content.Shared._Shitmed.Targeting; // Shitmed
using Content.Shared.Alert;
using Content.Shared.Damage;
@ -50,6 +51,14 @@ namespace Content.Client.HealthAnalyzer.UI
private EntityUid? _target;
// Shitmed Change End
// 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 HealthAnalyzerWindow()
{
RobustXamlLoader.Load(this);
@ -83,6 +92,26 @@ namespace Content.Client.HealthAnalyzer.UI
}
ReturnButton.OnPressed += _ => ResetBodyPart();
// Shitmed Change End
// 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
}
// Shitmed Change Start
@ -229,6 +258,16 @@ namespace Content.Client.HealthAnalyzer.UI
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
// Begin DeltaV - Medical Records
if (msg.MedicalRecord is not {} records)
{
TriageControls.Visible = false;
return;
}
_triageControls[records.Status].Pressed = true;
// End DeltaV - Medical Records
}
// Shitmed Change End
private static string GetStatus(MobState mobState)

View File

@ -0,0 +1,64 @@
using Content.Client.Overlays;
using Content.Shared._DV.MedicalRecords;
using Content.Shared._DV.Overlays;
using Content.Shared.Access.Systems;
using Content.Shared.Overlays;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Overlays;
public sealed class ShowTriageIconsSystem : EquipmentHudSystem<ShowTriageIconsComponent>
{
[Dependency] private readonly SharedIdCardSystem _idCard = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
private static ProtoId<HealthIconPrototype> Minor = "TriageStatusMinor";
private static ProtoId<HealthIconPrototype> Delayed = "TriageStatusDelayed";
private static ProtoId<HealthIconPrototype> Immediate = "TriageStatusImmediate";
private static ProtoId<HealthIconPrototype> Expectant = "TriageStatusExpectant";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MedicalRecordComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
}
private void OnGetStatusIconsEvent(Entity<MedicalRecordComponent> ent, ref GetStatusIconsEvent ev)
{
if (!IsActive)
return;
var triageStatusIcon = ent.Comp.Record.Status switch {
TriageStatus.None => null,
TriageStatus.Minor => _prototype.Index(Minor),
TriageStatus.Delayed => _prototype.Index(Delayed),
TriageStatus.Immediate => _prototype.Index(Immediate),
TriageStatus.Expectant => _prototype.Index(Expectant),
};
if (triageStatusIcon is not {} statusPrototype)
return;
ev.StatusIcons.Add(statusPrototype);
if (ent.Comp.Record.ClaimedName is {} claimedName)
{
if (_player.LocalEntity is {} local && _idCard.TryFindIdCard(local, out var idCard) && idCard.Comp.FullName == claimedName)
{
ev.StatusIcons.Add(_prototype.Index<HealthIconPrototype>("TriageClaimedYours"));
}
else
{
ev.StatusIcons.Add(_prototype.Index<HealthIconPrototype>("TriageClaimedOthers"));
}
}
else
{
ev.StatusIcons.Add(_prototype.Index<HealthIconPrototype>("TriageUnclaimed"));
}
}
}

View File

@ -1,3 +1,4 @@
using Content.Shared.StationRecords; // DeltaV - triage
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
@ -67,4 +68,12 @@ public sealed partial class HealthAnalyzerComponent : Component
/// </summary>
[DataField]
public bool Silent;
// Begin DeltaV - Medical Records
/// <summary>
/// The station records key of the scanned individual, if they have one
/// </summary>
[DataField]
public StationRecordKey? StationRecordKey;
// End DeltaV - Medical Records
}

View File

@ -25,6 +25,10 @@ using Content.Shared.Body.Systems;
using Content.Shared._Shitmed.Targeting;
using System.Linq;
// DeltaV - Medical Records
using Content.Server._DV.MedicalRecords;
using Content.Shared._DV.MedicalRecords;
namespace Content.Server.Medical;
public sealed class HealthAnalyzerSystem : EntitySystem
@ -39,6 +43,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly MedicalRecordsSystem _medicalRecords = default!;
public override void Initialize()
{
@ -51,6 +56,8 @@ public sealed class HealthAnalyzerSystem : EntitySystem
Subs.BuiEvents<HealthAnalyzerComponent>(HealthAnalyzerUiKey.Key, subs =>
{
subs.Event<HealthAnalyzerPartMessage>(OnHealthAnalyzerPartSelected);
subs.Event<HealthAnalyzerTriageStatusMessage>(OnHealthAnalyzerTriageStatusSelected);
subs.Event<HealthAnalyzerTriageClaimMessage>(OnHealthAnalyzerTriageClaimSelected);
});
// Shitmed Change End
}
@ -132,6 +139,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
OpenUserInterface(args.User, uid);
BeginAnalyzingEntity(uid, args.Target.Value);
uid.Comp.StationRecordKey = _medicalRecords.GetMedicalRecordsKey(args.Target.Value); // DeltaV - Medical Records
args.Handled = true;
}
@ -226,6 +234,24 @@ public sealed class HealthAnalyzerSystem : EntitySystem
}
// Shitmed Change End
// Begin DeltaV - Medical Records
private void OnHealthAnalyzerTriageStatusSelected(Entity<HealthAnalyzerComponent> healthAnalyzer, ref HealthAnalyzerTriageStatusMessage args)
{
if (healthAnalyzer.Comp.StationRecordKey is not {} key)
return;
_medicalRecords.SetPatientStatus(key, args.TriageStatus);
}
private void OnHealthAnalyzerTriageClaimSelected(Entity<HealthAnalyzerComponent> healthAnalyzer, ref HealthAnalyzerTriageClaimMessage args)
{
if (healthAnalyzer.Comp.StationRecordKey is not {} key)
return;
_medicalRecords.ClaimPatient(key, args.Actor);
}
// End DeltaV - Medical Records
/// <summary>
/// Send an update for the target to the healthAnalyzer
/// </summary>
@ -276,6 +302,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
unrevivable,
// Shitmed Change
body,
_medicalRecords.GetMedicalRecords(target), // DeltaV - Medical Records
part != null ? GetNetEntity(part) : null
));
}

View File

@ -0,0 +1,97 @@
using Content.Shared._DV.MedicalRecords;
using Content.Shared.Access.Systems;
using Content.Shared.StationRecords;
using Content.Server.StationRecords.Systems;
namespace Content.Server._DV.MedicalRecords;
public sealed class MedicalRecordsSystem : SharedMedicalRecordsSystem
{
[Dependency] private readonly StationRecordsSystem _records = default!;
[Dependency] private readonly AccessReaderSystem _access = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(OnGeneralRecordCreated);
}
private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent evt)
{
_records.AddRecordEntry(evt.Key, new MedicalRecord());
_records.Synchronize(evt.Key);
}
public void SetStatus(StationRecordKey key, MedicalRecord record)
{
var name = _records.RecordName(key);
if (name != string.Empty)
UpdateMedicalRecords(name, record);
_records.AddRecordEntry(key, record);
_records.Synchronize(key);
}
public MedicalRecord? GetMedicalRecords(EntityUid patient)
{
_access.FindStationRecordKeys(patient, out var keys);
foreach (var key in keys)
{
if (_records.TryGetRecord<MedicalRecord>(key, out var record))
return record;
}
foreach (var key in keys)
{
var medicalRecord = new MedicalRecord();
SetStatus(key, medicalRecord);
return medicalRecord;
}
return null;
}
public StationRecordKey? GetMedicalRecordsKey(EntityUid patient)
{
_access.FindStationRecordKeys(patient, out var keys);
foreach (var key in keys)
{
if (_records.TryGetRecord<MedicalRecord>(key, out var record))
return key;
}
foreach (var key in keys)
{
SetStatus(key, new MedicalRecord());
return key;
}
return null;
}
public void SetPatientStatus(StationRecordKey patient, TriageStatus status)
{
if (_records.TryGetRecord<MedicalRecord>(patient, out var record) && status != TriageStatus.None)
{
SetStatus(patient, record with { Status = status });
}
else
{
SetStatus(patient, new MedicalRecord());
}
}
public void ClaimPatient(StationRecordKey patient, EntityUid claimer)
{
_access.FindStationRecordKeys(claimer, out var keys);
foreach (var key in keys)
{
var name = _records.RecordName(key);
if (name == string.Empty)
continue;
if (!_records.TryGetRecord<MedicalRecord>(patient, out var record) || record.ClaimedName == name)
continue;
SetStatus(patient, record with { ClaimedName = name });
break;
}
}
}

View File

@ -65,6 +65,7 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowMindShieldIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSyndicateIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RefRelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<Content.Shared._DV.Overlays.ShowTriageIconsComponent>>(RefRelayInventoryEvent); // DeltaV - Medical Records
SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
}

View File

@ -1,3 +1,4 @@
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
using Content.Shared._Shitmed.Targeting; // Shitmed Change
using Robust.Shared.Serialization;
@ -17,8 +18,9 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage
public bool? Unrevivable;
public Dictionary<TargetBodyPart, TargetIntegrity>? Body; // Shitmed Change
public NetEntity? Part; // Shitmed Change
public MedicalRecord? MedicalRecord; // DeltaV - Medical Records
public HealthAnalyzerScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable, Dictionary<TargetBodyPart, TargetIntegrity>? body, NetEntity? part = null) // Shitmed Change
public HealthAnalyzerScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable, Dictionary<TargetBodyPart, TargetIntegrity>? body, MedicalRecord? medicalRecord = null, NetEntity? part = null) // Shitmed Change // DeltaV - Medical Records
{
TargetEntity = targetEntity;
Temperature = temperature;
@ -28,6 +30,7 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage
Unrevivable = unrevivable;
Body = body; // Shitmed Change
Part = part; // Shitmed Change
MedicalRecord = medicalRecord; // DeltaV - Medical Records
}
}

View File

@ -0,0 +1,38 @@
using Robust.Shared.Serialization;
namespace Content.Shared._DV.MedicalRecords;
/// <summary>
/// Status used in Medical Records.
///
/// None - the default value
/// Minor - minor injuries, may be able to assist in own care
/// Delayed - serious injuries, but not expected to deteroriate within a few minutes
/// Immediate - can be helped with immediate intervention and transport
/// Expectant - unlikely to survive or already dead
/// </summary>
public enum TriageStatus : byte
{
None,
Minor,
Delayed,
Immediate,
Expectant,
}
/// <summary>
/// Medical record for a crewmember.
/// </summary>
[Serializable, NetSerializable, DataRecord]
public sealed partial record MedicalRecord
{
/// <summary>
/// Status of the person
/// </summary>
public TriageStatus Status = TriageStatus.None;
/// <summary>
/// The name of the doctor who has claimed care of this patient
/// </summary>
public string? ClaimedName;
}

View File

@ -0,0 +1,11 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.MedicalRecords;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MedicalRecordComponent : Component
{
[DataField, AutoNetworkedField]
public MedicalRecord Record;
}

View File

@ -0,0 +1,29 @@
using Content.Shared.IdentityManagement;
using Content.Shared.IdentityManagement.Components;
namespace Content.Shared._DV.MedicalRecords;
public abstract class SharedMedicalRecordsSystem : EntitySystem
{
public void UpdateMedicalRecords(string name, MedicalRecord status)
{
var query = EntityQueryEnumerator<IdentityComponent>();
while (query.MoveNext(out var uid, out var identity))
{
if (!Identity.Name(uid, EntityManager).Equals(name))
continue;
if (status.Status == TriageStatus.None)
{
RemComp<MedicalRecordComponent>(uid);
}
else
{
EnsureComp<MedicalRecordComponent>(uid, out var record);
record.Record = status;
Dirty(uid, record);
}
}
}
}

View File

@ -0,0 +1,12 @@
using Robust.Shared.Serialization;
namespace Content.Shared._DV.MedicalRecords;
[Serializable, NetSerializable]
public sealed class HealthAnalyzerTriageStatusMessage(TriageStatus triageStatus) : BoundUserInterfaceMessage
{
public readonly TriageStatus TriageStatus = triageStatus;
}
[Serializable, NetSerializable]
public sealed class HealthAnalyzerTriageClaimMessage : BoundUserInterfaceMessage;

View File

@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Overlays;
/// <summary>
/// This component allows you to see triage icons above mobs.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ShowTriageIconsComponent : Component;

View File

@ -1 +1,12 @@
health-analyzer-window-entity-unborgable-text = [color=red]Patient's brain signatures are incompatible with MMI technology![/color]
health-analyzer-window-triage-claim = Claim Patient
health-analyzer-window-triage-status-None = None
.ToolTip = Patient with no injuries
health-analyzer-window-triage-status-Minor = Minor
.ToolTip = Victim with relatively minor injuries
health-analyzer-window-triage-status-Delayed = Delayed
.ToolTip = Victim's transport can be delayed. Potentially life threatening, but not immediately.
health-analyzer-window-triage-status-Immediate = Immediate
.ToolTip = Victim can be helped by immediate intervention and transport. Requires medical attention for survival.
health-analyzer-window-triage-status-Expectant = Expectant
.ToolTip = Victim unlikely to survive, or is already dead.

View File

@ -14,6 +14,7 @@
components:
- type: ShowHealthBars
- type: ShowHealthIcons
- type: ShowTriageIcons # DeltaV - Medical Records
- type: entity
parent: [ ClothingEyesBase, BaseToggleClothing ] # DeltaV - Added BaseToggleClothing

View File

@ -0,0 +1,63 @@
- type: healthIcon
id: TriageStatusIcon
abstract: true
priority: 4
locationPreference: Left
isShaded: true
- type: healthIcon
id: TriageClaimIcon
abstract: true
priority: 5
offset: 1
locationPreference: Left
isShaded: true
- type: healthIcon
parent: TriageStatusIcon
id: TriageStatusMinor
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: minor
- type: healthIcon
parent: TriageStatusIcon
id: TriageStatusDelayed
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: delayed
- type: healthIcon
parent: TriageStatusIcon
id: TriageStatusImmediate
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: immediate
- type: healthIcon
parent: TriageStatusIcon
id: TriageStatusExpectant
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: expectant
- type: healthIcon
parent: TriageClaimIcon
id: TriageUnclaimed
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: unclaimed
- type: healthIcon
parent: TriageClaimIcon
id: TriageClaimedYours
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: claimed_yours
- type: healthIcon
parent: TriageClaimIcon
id: TriageClaimedOthers
icon:
sprite: /Textures/_DV/Interface/Misc/triage_icons.rsi
state: claimed_others

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

View File

@ -0,0 +1,32 @@
{
"version": 1,
"license": "CC0-1.0",
"copyright": "claimed_others, claimed_yours, delayed, expectant, immediate, minor, unclaimed by Janet Blackquill <uhhadd@gmail.com> 2025",
"size": {
"x": 8,
"y": 8
},
"states": [
{
"name": "claimed_others"
},
{
"name": "claimed_yours"
},
{
"name": "unclaimed"
},
{
"name": "minor"
},
{
"name": "delayed"
},
{
"name": "immediate"
},
{
"name": "expectant"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B