diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index b78c3c6a56..670140f0a0 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -1,33 +1,26 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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 36f7a08b96..2e07120827 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -1,189 +1,67 @@
-using System.Linq;
using System.Numerics;
+using System.Text;
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Client.White.Medical.BodyScanner;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
+using Content.Shared.Mobs.Components;
using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
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
{
+ // WD start
[GenerateTypedNameReferences]
- public sealed partial class HealthAnalyzerWindow : DefaultWindow
+ public sealed partial class HealthAnalyzerWindow : FancyWindow
{
- 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)
{
- GroupsContainer.RemoveAllChildren();
+ var entities = IoCManager.Resolve();
- var target = _entityManager.GetEntity(msg.TargetEntity);
-
- if (target == null
- || !_entityManager.TryGetComponent(target, out var damageable))
+ if (msg.TargetEntity != null &&
+ entities.TryGetComponent(entities.GetEntity(msg.TargetEntity), out var damageable))
{
- NoPatientDataText.Visible = true;
- return;
- }
+ EntityNameLabel.Text = Identity.Name(entities.GetEntity(msg.TargetEntity.Value), entities);
+ TemperatureLabel.Text = float.IsNaN(msg.Temperature) ? Loc.GetString("health-analyzer-window-no-data") : $"{msg.Temperature - 273f:F1} \u00B0C";
+ BloodLevelLabel.Text = float.IsNaN(msg.BloodLevel) ? Loc.GetString("health-analyzer-window-no-data") : $"{msg.BloodLevel * 100:F1} %";
+ TotalDamageLabel.Text = damageable.TotalDamage.ToString();
- NoPatientDataText.Visible = false;
+ entities.TryGetComponent(entities.GetEntity(msg.TargetEntity), out var mobStateComponent);
- string entityName = Loc.GetString("health-analyzer-window-entity-unknown-text");
- if (_entityManager.HasComponent(target.Value))
- {
- 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
+ AliveStatusLabel.Text = mobStateComponent?.CurrentState switch
{
- Margin = new Thickness(0, 0, 0, 15),
- Align = BoxContainer.AlignMode.Begin,
- Orientation = BoxContainer.LayoutOrientation.Vertical,
+ Shared.Mobs.MobState.Alive => Loc.GetString("health-analyzer-window-entity-current-alive-status-alive-text"),
+ Shared.Mobs.MobState.Critical => Loc.GetString("health-analyzer-window-entity-current-alive-status-critical-text"),
+ Shared.Mobs.MobState.Dead => Loc.GetString("health-analyzer-window-entity-current-alive-status-dead-text"),
+ _ => Loc.GetString("health-analyzer-window-no-data"),
};
- groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId, damageAmount.Int()));
+ IReadOnlyDictionary damagePerGroup = damageable.DamagePerGroup;
+ IReadOnlyDictionary damagePerType = damageable.Damage.DamageDict;
- GroupsContainer.AddChild(groupContainer);
+ DamageGroupsContainer.RemoveAllChildren();
- // Show the damage for each type in that group.
- var group = _prototypes.Index(damageGroupId);
-
- foreach (var type in group.DamageTypes)
+ // Show the total damage and type breakdown for each damage group.
+ foreach (var (damageGroupId, damageAmount) in damagePerGroup)
{
- 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;
+ var damageGroupTitle = Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId, ("amount", damageAmount));
- 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, "- ")));
- }
+ DamageGroupsContainer.AddChild(new GroupDamageCardComponent(damageGroupTitle, damageGroupId, damagePerType));
}
}
}
-
- 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;
- }
}
+ // WD end
}
diff --git a/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleBoundUserInterface.cs b/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleBoundUserInterface.cs
new file mode 100644
index 0000000000..012777362b
--- /dev/null
+++ b/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleBoundUserInterface.cs
@@ -0,0 +1,64 @@
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Content.Shared.White.Medical.BodyScanner;
+
+namespace Content.Client.White.Medical.BodyScanner
+{
+ [UsedImplicitly]
+ public sealed class BodyScannerConsoleBoundUserInterface : BoundUserInterface
+ {
+ [ViewVariables]
+ private BodyScannerConsoleWindow? _window;
+
+ public BodyScannerConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window = new BodyScannerConsoleWindow();
+
+ _window.OnClose += Close;
+ _window.OpenCentered();
+
+ _window.OnScanButtonPressed += () => StartScanning();
+ _window.OnPrintButtonPressed += () => StartPrinting();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ switch(state)
+ {
+ case BodyScannerConsoleBoundUserInterfaceState msg:
+ _window?.UpdateUserInterface(msg);
+ break;
+ }
+ }
+
+ public void StartScanning()
+ {
+ SendMessage(new BodyScannerStartScanningMessage());
+ }
+
+ public void StartPrinting()
+ {
+ SendMessage(new BodyScannerStartPrintingMessage());
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ if (_window != null)
+ _window.OnClose -= Close;
+
+ _window?.Dispose();
+ }
+ }
+}
diff --git a/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleWindow.xaml b/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleWindow.xaml
new file mode 100644
index 0000000000..da4b96f1cb
--- /dev/null
+++ b/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleWindow.xaml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleWindow.xaml.cs b/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleWindow.xaml.cs
new file mode 100644
index 0000000000..75e721c03b
--- /dev/null
+++ b/Content.Client/White/Medical/BodyScanner/BodyScannerConsoleWindow.xaml.cs
@@ -0,0 +1,169 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.White.Medical.BodyScanner;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+using Robust.Client.Graphics;
+using Content.Shared.Chemistry.Reagent;
+
+namespace Content.Client.White.Medical.BodyScanner
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class BodyScannerConsoleWindow : FancyWindow
+ {
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ public event Action? OnScanButtonPressed;
+ public event Action? OnPrintButtonPressed;
+
+ public BodyScannerConsoleWindow()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
+ NoDataScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
+ PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke();
+ }
+
+ public void UpdateUserInterface(BodyScannerConsoleBoundUserInterfaceState state)
+ {
+ MainContainer.Visible = state.TargetEntityUid != null && !state.Scanning;
+ NoDataContainer.Visible = !MainContainer.Visible;
+ NoDataStatusLabel.Text = state.Scanning ? Loc.GetString("body-scanner-console-window-status-scanning") : " ";
+
+ ScanButton.Disabled = !state.CanScan;
+ PrintButton.Disabled = !state.CanPrint;
+ NoDataScanButton.Disabled = !state.CanScan;
+
+ NoDataScanProgressBar.Value = 1 - (float) (state.ScanTimeRemaining / state.ScanTotalTime);
+
+ if (state.Scanning)
+ return;
+
+ EntityNameLabel.Text = state.EntityName;
+
+ // First column
+ TemperatureLabel.Text = $"{state.CurrentTemperature - 273f:F1} \u00B0C";
+ BloodLevelLabel.Text = $"{state.BloodSolution.FillFraction * 100:F1} %";
+ TotalDamageLabel.Text = state.TotalDamage.ToString();
+
+ AliveStatusLabel.Text = state.CurrentState switch
+ {
+ Shared.Mobs.MobState.Alive => Loc.GetString("body-scanner-console-window-current-alive-status-alive-text"),
+ Shared.Mobs.MobState.Critical => Loc.GetString("body-scanner-console-window-current-alive-status-critical-text"),
+ Shared.Mobs.MobState.Dead => Loc.GetString("body-scanner-console-window-current-alive-status-dead-text"),
+ _ => Loc.GetString("body-scanner-console-window-no-data")
+ };
+
+ HashSet shownTypes = new();
+
+ var protos = IoCManager.Resolve();
+
+ IReadOnlyDictionary damagePerGroup = state.DamagePerGroup;
+ IReadOnlyDictionary damagePerType = state.DamageDict;
+
+ DamageGroupsContainer.RemoveAllChildren();
+
+ // Show the total damage and type breakdown for each damage group.
+ foreach (var (damageGroupId, damageAmount) in damagePerGroup)
+ {
+ var damageGroupTitle = Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId, ("amount", damageAmount));
+
+ DamageGroupsContainer.AddChild(new GroupDamageCardComponent(damageGroupTitle, damageGroupId, damagePerType));
+ }
+
+ // Second column.
+ CurrentTemperature.Text = Loc.GetString("body-scanner-console-window-temperature-current-temperature-text",
+ ("amount", $"{state.CurrentTemperature - 273:f1}"));
+ HeatDamageThreshold.Text = Loc.GetString("body-scanner-console-window-temperature-heat-damage-threshold-temperature-text",
+ ("amount", $"{state.HeatDamageThreshold - 273:f1}"));
+ ColdDamageThreshold.Text = Loc.GetString("body-scanner-console-window-temperature-cold-damage-threshold-temperature-text",
+ ("amount", $"{state.ColdDamageThreshold - 273:f1}"));
+
+ CurrentSaturation.Text = Loc.GetString("body-scanner-console-window-saturation-current-saturation-text",
+ ("amount", $"{state.Saturation:f1}"));
+ MinimumSaturation.Text = Loc.GetString("body-scanner-console-window-saturation-maximum-saturation-text",
+ ("amount", $"{state.MinSaturation:f1}"));
+ MaximumSaturation.Text = Loc.GetString("body-scanner-console-window-saturation-minimum-saturation-text",
+ ("amount", $"{state.MaxSaturation:f1}"));
+
+ CurrentThirst.Text = Loc.GetString("body-scanner-console-window-thirst-current-thirst-text",
+ ("amount", $"{state.CurrentThirst:f1}"));
+ CurrentThirstStatus.Text = Loc.GetString("body-scanner-console-window-thirst-current-thirst-status-text",
+ ("status", Loc.GetString("body-scanner-console-window-hunger-current-hunger-status-" + state.CurrentThirstThreshold)));
+
+ CurrentHunger.Text = Loc.GetString("body-scanner-console-window-hunger-current-hunger-text",
+ ("amount", $"{state.CurrentHunger:f1}"));
+ CurrentHungerStatus.Text = Loc.GetString("body-scanner-console-window-hunger-current-hunger-status-text",
+ ("status", Loc.GetString("body-scanner-console-window-thirst-current-thirst-status-" + state.CurrentHungerThreshold)));
+
+ BloodSolutionVolume.Text = Loc.GetString("body-scanner-console-window-blood-solutions-volume-group-text",
+ ("amount", $"{state.BloodSolution.Volume.Float():f1}"),
+ ("maxAmount", $"{state.BloodSolution.MaxVolume.Float():f1}"),
+ ("temperature", $"{state.BloodSolution.Temperature - 271:f1}"));
+
+ BloodSolutionElements.RemoveAllChildren();
+ state.BloodSolution.Contents.ForEach(x =>
+ {
+ _prototypeManager.TryIndex(x.Reagent.Prototype, out ReagentPrototype? proto);
+ var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text");
+ BloodSolutionElements.AddChild(new Label() { Text = $"{name}: {x.Quantity}", FontColorOverride = Color.LightGray });
+ });
+
+ ChemicalSolutionVolume.Text = Loc.GetString("body-scanner-console-window-chemical-solutions-volume-group-text",
+ ("amount", $"{state.ChemicalSolution.Volume.Float():f1}"),
+ ("maxAmount", $"{state.ChemicalSolution.MaxVolume.Float():f1}"),
+ ("temperature", $"{state.ChemicalSolution.Temperature - 271:f1}"));
+
+ ChemicalSolutionElements.RemoveAllChildren();
+ state.ChemicalSolution.Contents.ForEach(x =>
+ {
+ _prototypeManager.TryIndex(x.Reagent.Prototype, out ReagentPrototype? proto);
+ var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text");
+ ChemicalSolutionElements.AddChild(new Label() { Text = $"{name}: {x.Quantity}", FontColorOverride = Color.LightGray });
+ });
+
+ // Third column.
+ HealthBar.Value = 1 - (state.TotalDamage / state.DeadThreshold).Float();
+
+ HealthBar.ForegroundStyleBoxOverride = new StyleBoxFlat()
+ {
+ BackgroundColor = HealthBar.Value >= 0.5f ?
+ GetColorLerp(Color.Yellow, Color.Green, (HealthBar.Value - 0.5f) * 2) :
+ GetColorLerp(Color.Red, Color.Yellow, HealthBar.Value * 2)
+ };
+
+ if (state.TargetEntityUid != null)
+ EntityView.SetEntity(state.TargetEntityUid.Value);
+
+ // Bottom row.
+ Mind.Text = Loc.GetString("body-scanner-console-window-mind-text",
+ ("value", state.HasMind ? Loc.GetString("body-scanner-console-window-mind-present-text") :
+ Loc.GetString("body-scanner-console-window-mind-absent-text")));
+ DNA.Text = Loc.GetString("body-scanner-console-window-dna-text",
+ ("value", $"{state.DNA}"));
+ Fingerprint.Text = Loc.GetString("body-scanner-console-window-fingerprint-text",
+ ("value", $"{state.Fingerprint}"));
+ }
+
+ ///
+ /// Smooth transition from one color to another depending on the percentage.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private Color GetColorLerp(Color startColor, Color endColor, float percentage)
+ {
+ var r = MathHelper.Lerp(startColor.R, endColor.R, percentage);
+ var g = MathHelper.Lerp(startColor.G, endColor.G, percentage);
+ var b = MathHelper.Lerp(startColor.B, endColor.B, percentage);
+ var a = MathHelper.Lerp(startColor.A, endColor.A, percentage);
+
+ return new Color(r, g, b, a);
+ }
+ }
+}
diff --git a/Content.Client/White/Medical/BodyScanner/GroupDamageCardComponent.xaml b/Content.Client/White/Medical/BodyScanner/GroupDamageCardComponent.xaml
new file mode 100644
index 0000000000..495d30facf
--- /dev/null
+++ b/Content.Client/White/Medical/BodyScanner/GroupDamageCardComponent.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/White/Medical/BodyScanner/GroupDamageCardComponent.xaml.cs b/Content.Client/White/Medical/BodyScanner/GroupDamageCardComponent.xaml.cs
new file mode 100644
index 0000000000..551743b80a
--- /dev/null
+++ b/Content.Client/White/Medical/BodyScanner/GroupDamageCardComponent.xaml.cs
@@ -0,0 +1,102 @@
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.White.Medical.BodyScanner
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class GroupDamageCardComponent : Control
+ {
+ public GroupDamageCardComponent(string title, string damageGroupID, IReadOnlyDictionary damagePerType)
+ {
+ RobustXamlLoader.Load(this);
+
+ DamageGroupTitle.Text = title;
+
+ HashSet shownTypes = new();
+ var protos = IoCManager.Resolve();
+ var group = protos.Index(damageGroupID);
+
+ // Show the damage for each type in that group.
+ 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))
+ {
+ shownTypes.Add(type);
+
+ Label damagePerTypeLabel = new()
+ {
+ Text = Loc.GetString("health-analyzer-window-damage-type-" + type, ("amount", typeAmount)),
+ };
+
+ SetColorLabel(damagePerTypeLabel, typeAmount.Float());
+
+ DamageLabelsContainer.AddChild(damagePerTypeLabel);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Sets the color of a label depending on the damage.
+ ///
+ ///
+ ///
+ private void SetColorLabel(Label label, float damage)
+ {
+ var startColor = Color.White;
+ var critColor = Color.Yellow;
+ var endColor = Color.Red;
+
+ var startDamage = 0f;
+ var critDamage = 30f;
+ var endDamage = 100f;
+
+ if (damage <= startDamage)
+ {
+ label.FontColorOverride = startColor;
+ }
+ else if (damage >= endDamage)
+ {
+ label.FontColorOverride = endColor;
+ }
+ else if (damage >= startDamage && damage <= critDamage)
+ {
+ // We need a number from 0 to 100.
+ damage *= 100f / (critDamage - startDamage);
+ label.FontColorOverride = GetColorLerp(startColor, critColor, damage);
+ }
+ else if (damage >= critDamage && damage <= endDamage)
+ {
+ // We need a number from 0 to 100.
+ damage *= 100f / (endDamage - critDamage);
+ label.FontColorOverride = GetColorLerp(critColor, endColor, damage);
+ }
+ }
+
+ ///
+ /// Smooth transition from one color to another depending on the percentage.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private Color GetColorLerp(Color startColor, Color endColor, float percentage)
+ {
+ var t = percentage / 100f;
+ var r = MathHelper.Lerp(startColor.R, endColor.R, t);
+ var g = MathHelper.Lerp(startColor.G, endColor.G, t);
+ var b = MathHelper.Lerp(startColor.B, endColor.B, t);
+ var a = MathHelper.Lerp(startColor.A, endColor.A, t);
+
+ return new Color(r, g, b, a);
+ }
+ }
+}
diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs
index e99c955b6d..bb809b64eb 100644
--- a/Content.Server/Body/Components/BloodstreamComponent.cs
+++ b/Content.Server/Body/Components/BloodstreamComponent.cs
@@ -1,5 +1,6 @@
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
+using Content.Server.White.Medical.BodyScanner;
using Content.Shared.Chemistry.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -9,7 +10,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Body.Components
{
- [RegisterComponent, Access(typeof(BloodstreamSystem), (typeof(ChemistrySystem)))]
+ [RegisterComponent, Access(typeof(BloodstreamSystem), typeof(ChemistrySystem), typeof(BodyScannerConsoleSystem))] // WD EDIT
public sealed partial class BloodstreamComponent : Component
{
public static string DefaultChemicalsSolutionName = "chemicals";
diff --git a/Content.Server/White/Medical/BodyScanner/ActiveBodyScannerConsoleComponent.cs b/Content.Server/White/Medical/BodyScanner/ActiveBodyScannerConsoleComponent.cs
new file mode 100644
index 0000000000..a08109d3e3
--- /dev/null
+++ b/Content.Server/White/Medical/BodyScanner/ActiveBodyScannerConsoleComponent.cs
@@ -0,0 +1,20 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations;
+
+namespace Content.Server.White.Medical.BodyScanner
+{
+ [RegisterComponent]
+ public sealed partial class ActiveBodyScannerConsoleComponent : Component
+ {
+ ///
+ /// When did the scanning start?
+ ///
+ [DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
+ public TimeSpan StartTime;
+
+ ///
+ /// What is being scanned?
+ ///
+ [ViewVariables]
+ public EntityUid PatientUid;
+ }
+}
diff --git a/Content.Server/White/Medical/BodyScanner/BodyInScannerBodyScannerConsoleComponent.cs b/Content.Server/White/Medical/BodyScanner/BodyInScannerBodyScannerConsoleComponent.cs
new file mode 100644
index 0000000000..aaba78906f
--- /dev/null
+++ b/Content.Server/White/Medical/BodyScanner/BodyInScannerBodyScannerConsoleComponent.cs
@@ -0,0 +1,11 @@
+using Content.Server.Medical.Components;
+
+namespace Content.Server.White.Medical.BodyScanner
+{
+ [RegisterComponent]
+ public sealed partial class BodyInScannerBodyScannerConsoleComponent : Component
+ {
+ [ViewVariables]
+ public MedicalScannerComponent? MedicalCannerComponent;
+ }
+}
diff --git a/Content.Server/White/Medical/BodyScanner/BodyScannerConsoleComponent.cs b/Content.Server/White/Medical/BodyScanner/BodyScannerConsoleComponent.cs
new file mode 100644
index 0000000000..5db2b1aa7c
--- /dev/null
+++ b/Content.Server/White/Medical/BodyScanner/BodyScannerConsoleComponent.cs
@@ -0,0 +1,55 @@
+using Content.Shared.White.Medical.BodyScanner;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.White.Medical.BodyScanner
+{
+ [RegisterComponent]
+ public sealed partial class BodyScannerConsoleComponent : Component
+ {
+ public const string ScannerPort = "MedicalScannerReceiver";
+
+ ///
+ /// Genetic scanner. Can be null if not linked.
+ ///
+ [ViewVariables]
+ public EntityUid? GeneticScanner;
+
+ ///
+ /// Maximum distance between body scanner console and one if its machines.
+ ///
+ [DataField("maxDistance")]
+ public float MaxDistance = 4f;
+
+ public bool GeneticScannerInRange = true;
+
+ ///
+ /// How long it takes to scan.
+ ///
+ [ViewVariables, DataField("scanDuration", customTypeSerializer: typeof(TimespanSerializer))]
+ public TimeSpan ScanDuration = TimeSpan.FromSeconds(3);
+
+ ///
+ /// Sound to play on scan finished.
+ ///
+ [DataField("scanFinishedSound")]
+ public SoundSpecifier ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
+
+ ///
+ /// Sound to play when printing.
+ ///
+ [DataField("printSound")]
+ public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/diagnoser_printing.ogg");
+
+ [ViewVariables]
+ public BodyScannerConsoleBoundUserInterfaceState? LastScannedState;
+
+ ///
+ /// The entity spawned by a report.
+ ///
+ [DataField("reportEntityId", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string ReportEntityId = "Paper";
+ }
+}
diff --git a/Content.Server/White/Medical/BodyScanner/BodyScannerConsoleSystem.cs b/Content.Server/White/Medical/BodyScanner/BodyScannerConsoleSystem.cs
new file mode 100644
index 0000000000..0684cfd2ca
--- /dev/null
+++ b/Content.Server/White/Medical/BodyScanner/BodyScannerConsoleSystem.cs
@@ -0,0 +1,420 @@
+using Content.Server.Power.Components;
+using Content.Server.Medical.Components;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.White.Medical.BodyScanner;
+using Robust.Server.GameObjects;
+using Robust.Shared.Timing;
+using Content.Shared.Mobs.Components;
+using Content.Server.Temperature.Components;
+using Content.Server.Body.Components;
+using Content.Server.Forensics;
+using Content.Server.Paper;
+using Content.Shared.Popups;
+using Content.Shared.Damage;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Damage.Components;
+using System.Text;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.Mind.Components;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Server.White.Medical.BodyScanner
+{
+ public sealed class BodyScannerConsoleSystem : EntitySystem
+ {
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly PaperSystem _paper = default!;
+ [Dependency] private readonly MetaDataSystem _metaSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPowerChanged);
+
+ SubscribeLocalEvent(OnMapInit);
+
+ SubscribeLocalEvent(OnNewLink);
+ SubscribeLocalEvent(OnPortDisconnected);
+
+ SubscribeLocalEvent(OnStartScanning);
+ SubscribeLocalEvent(OnStartPrinting);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var queryActiveBodyScanner = EntityQueryEnumerator();
+ while (queryActiveBodyScanner.MoveNext(out var uid, out var active, out var scan))
+ {
+ UpdateUserInterface(scan.Owner, scan, active);
+
+ if (_timing.CurTime - active.StartTime < scan.ScanDuration)
+ continue;
+
+ FinishScan(uid, scan, active);
+ }
+
+ var queryBodyInScanner = EntityQueryEnumerator();
+ while (queryBodyInScanner.MoveNext(out var uid, out var bodyInScanner, out var scan))
+ {
+ if (bodyInScanner.MedicalCannerComponent?.BodyContainer.ContainedEntity == null)
+ {
+ RemComp(uid);
+ UpdateUserInterface(scan.Owner, scan);
+ }
+ }
+ }
+
+ private void UpdateUserInterface(
+ EntityUid uid,
+ BodyScannerConsoleComponent? scanComponent = null,
+ ActiveBodyScannerConsoleComponent? activeScanComponent = null)
+ {
+ if (!Resolve(uid, ref scanComponent))
+ return;
+
+ if (!_uiSystem.TryGetUi(uid, BodyScannerConsoleUIKey.Key, out var bui))
+ return;
+
+ var state = GetUserInterfaceState(scanComponent, activeScanComponent);
+ scanComponent.LastScannedState = state;
+
+ _uiSystem.SetUiState(bui, state);
+ }
+
+ private BodyScannerConsoleBoundUserInterfaceState GetUserInterfaceState(
+ BodyScannerConsoleComponent consoleComponent,
+ ActiveBodyScannerConsoleComponent? activeScanComponent = null)
+ {
+ BodyScannerConsoleBoundUserInterfaceState state = new();
+
+ if (consoleComponent?.GeneticScanner != null && TryComp(consoleComponent.GeneticScanner, out var scanner))
+ {
+ state.ScannerConnected = consoleComponent.GeneticScanner != null;
+ state.GeneticScannerInRange = consoleComponent.GeneticScannerInRange;
+
+ state.CanScan = true;
+
+ if (state.ScannerConnected && state.GeneticScannerInRange && activeScanComponent != null)
+ {
+ state.CanScan = false;
+ state.CanPrint = false;
+
+ state.Scanning = true;
+ state.ScanTotalTime = consoleComponent.ScanDuration;
+ state.ScanTimeRemaining = consoleComponent.ScanDuration - (_timing.CurTime - activeScanComponent.StartTime);
+
+ return state;
+ }
+
+ var scanBody = scanner.BodyContainer.ContainedEntity;
+
+ if (scanBody != null)
+ {
+ state.CanPrint = true;
+
+ state.TargetEntityUid = GetNetEntity(scanBody);
+ state.EntityName = MetaData(scanBody.Value).EntityName;
+
+ if (TryComp(scanBody, out var bloodstream))
+ {
+ state.BleedAmount = bloodstream.BleedAmount;
+ state.BloodMaxVolume = bloodstream.BloodMaxVolume;
+ state.BloodReagent = bloodstream.BloodReagent;
+ state.BloodSolution.MaxVolume = bloodstream.BloodMaxVolume;
+ state.ChemicalMaxVolume = bloodstream.ChemicalMaxVolume;
+ state.ChemicalSolution.MaxVolume = bloodstream.ChemicalMaxVolume;
+ if (bloodstream.BloodSolution != null)
+ state.BloodSolution = bloodstream.BloodSolution.Value.Comp.Solution.Clone();
+ if (bloodstream.ChemicalSolution != null)
+ state.ChemicalSolution = bloodstream.ChemicalSolution.Value.Comp.Solution.Clone();
+ }
+
+ if (TryComp(scanBody, out var dna))
+ {
+ state.DNA = dna.DNA;
+ }
+
+ if (TryComp(scanBody, out var fingerprint))
+ {
+ state.Fingerprint = fingerprint.Fingerprint ?? "";
+ }
+
+ if (TryComp(scanBody, out var mind))
+ {
+ state.HasMind = mind.HasMind;
+ }
+
+ if (TryComp(scanBody, out var respirator))
+ {
+ state.MaxSaturation = respirator.MaxSaturation;
+ state.MinSaturation = respirator.MinSaturation;
+ state.Saturation = respirator.Saturation;
+ }
+
+ if (TryComp(scanBody, out var temp))
+ {
+ state.HeatDamageThreshold = temp.HeatDamageThreshold;
+ state.ColdDamageThreshold = temp.ColdDamageThreshold;
+ state.CurrentTemperature = temp.CurrentTemperature;
+ }
+
+ if (TryComp(scanBody, out var thirst))
+ {
+ state.CurrentThirst = thirst.CurrentThirst;
+ state.CurrentThirstThreshold = (byte) thirst.CurrentThirstThreshold;
+ }
+
+ if (TryComp(scanBody, out var damageable))
+ {
+ state.TotalDamage = damageable.TotalDamage;
+ state.DamageDict = new(damageable.Damage.DamageDict);
+ state.DamagePerGroup = new(damageable.DamagePerGroup);
+ }
+
+ if (TryComp(scanBody, out var hunger))
+ {
+ state.CurrentHunger = hunger.CurrentHunger;
+ state.CurrentHungerThreshold = (byte) hunger.CurrentThreshold;
+ }
+
+ if (TryComp(scanBody, out var mobState))
+ {
+ state.CurrentState = mobState.CurrentState;
+ }
+
+ if (TryComp(scanBody, out var mobThresholds))
+ {
+ foreach (var pair in mobThresholds.Thresholds)
+ {
+ if (pair.Value == Shared.Mobs.MobState.Dead)
+ {
+ state.DeadThreshold = pair.Key;
+ }
+ }
+ }
+
+ if (TryComp(scanBody, out var stamina))
+ {
+ state.StaminaCritThreshold = stamina.CritThreshold;
+ state.StaminaDamage = stamina.StaminaDamage;
+ }
+ }
+ }
+
+ return state;
+ }
+
+ public void FinishScan(EntityUid uid, BodyScannerConsoleComponent? component = null, ActiveBodyScannerConsoleComponent? active = null)
+ {
+ if (!Resolve(uid, ref component, ref active))
+ return;
+
+ _audio.PlayPvs(component.ScanFinishedSound, uid);
+
+ RemComp(uid);
+
+ if (component?.GeneticScanner != null && TryComp(component.GeneticScanner, out var scanner))
+ {
+ EnsureComp(uid).MedicalCannerComponent = scanner;
+ }
+
+ UpdateUserInterface(uid, component);
+ }
+
+ public void OnMapInit(EntityUid uid, BodyScannerConsoleComponent component, MapInitEvent args)
+ {
+ if (!_uiSystem.TryGetUi(uid, BodyScannerConsoleUIKey.Key, out var bui))
+ return;
+
+ UpdateUserInterface(uid, component);
+ }
+
+ private void OnNewLink(EntityUid uid, BodyScannerConsoleComponent component, NewLinkEvent args)
+ {
+ if (TryComp(args.Sink, out var scanner) && args.SinkPort == BodyScannerConsoleComponent.ScannerPort)
+ {
+ component.GeneticScanner = args.Sink;
+ scanner.ConnectedConsole = uid;
+ }
+ }
+
+ private void OnPortDisconnected(EntityUid uid, BodyScannerConsoleComponent component, PortDisconnectedEvent args)
+ {
+ if (args.Port != BodyScannerConsoleComponent.ScannerPort)
+ return;
+
+ if (TryComp(component.GeneticScanner, out var scanner))
+ scanner.ConnectedConsole = null;
+
+ component.GeneticScanner = null;
+
+ FinishScan(uid, component);
+ }
+
+ private void OnStartScanning(EntityUid uid, BodyScannerConsoleComponent component, BodyScannerStartScanningMessage msg)
+ {
+ if (component.GeneticScanner == null)
+ return;
+
+ if (!TryComp(component.GeneticScanner, out var scanner))
+ return;
+
+ if (scanner.BodyContainer.ContainedEntity == null)
+ return;
+
+ var activeComp = EnsureComp(uid);
+ activeComp.StartTime = _timing.CurTime;
+ }
+
+ private void OnStartPrinting(EntityUid uid, BodyScannerConsoleComponent component, BodyScannerStartPrintingMessage args)
+ {
+ if (!_uiSystem.TryGetUi(uid, BodyScannerConsoleUIKey.Key, out var bui))
+ return;
+
+ var state = component.LastScannedState;
+ if (state == null)
+ return;
+
+ state.CanPrint = false;
+
+ _uiSystem.SetUiState(bui, state);
+
+ var report = Spawn(component.ReportEntityId, Transform(uid).Coordinates);
+ _metaSystem.SetEntityName(report,
+ Loc.GetString("body-scanner-console-report-title", ("name", state.EntityName)));
+
+ StringBuilder text = new();
+ text.AppendLine(state.EntityName);
+ text.AppendLine();
+ text.AppendLine(Loc.GetString("body-scanner-console-report-temperature",
+ ("amount", $"{state.CurrentTemperature - 273f:F1}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-report-blood-level",
+ ("amount", $"{state.BloodSolution.FillFraction * 100:F1}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-report-total-damage",
+ ("amount", state.TotalDamage.ToString())));
+ text.AppendLine();
+
+ HashSet shownTypes = new();
+
+ var protos = IoCManager.Resolve();
+
+ IReadOnlyDictionary damagePerGroup = state.DamagePerGroup;
+ IReadOnlyDictionary damagePerType = state.DamageDict;
+
+ foreach (var (damageGroupId, damageAmount) in damagePerGroup)
+ {
+ text.AppendLine(Loc.GetString("health-analyzer-window-damage-group-" + damageGroupId,
+ ("amount", damageAmount)));
+
+ 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))
+ {
+ shownTypes.Add(type);
+
+ text.Append(" - ");
+ text.AppendLine(Loc.GetString("health-analyzer-window-damage-type-" + type,
+ ("amount", typeAmount)));
+ }
+ }
+ }
+
+ text.AppendLine();
+ }
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-temperature-group-text"));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-temperature-current-temperature-text",
+ ("amount", $"{state.CurrentTemperature - 273:f1}")));
+ text.AppendLine(Loc.GetString(
+ "body-scanner-console-window-temperature-heat-damage-threshold-temperature-text",
+ ("amount", $"{state.HeatDamageThreshold - 273:f1}")));
+ text.AppendLine(Loc.GetString(
+ "body-scanner-console-window-temperature-cold-damage-threshold-temperature-text",
+ ("amount", $"{state.ColdDamageThreshold - 273:f1}")));
+ text.AppendLine();
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-group-text"));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-current-saturation-text",
+ ("amount", $"{state.Saturation:f1}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-maximum-saturation-text",
+ ("amount", $"{state.MinSaturation:f1}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-saturation-minimum-saturation-text",
+ ("amount", $"{state.MaxSaturation:f1}")));
+ text.AppendLine();
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-thirst-group-text"));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-thirst-current-thirst-text",
+ ("amount", $"{state.CurrentThirst:f1}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-thirst-current-thirst-status-text",
+ ("status",
+ Loc.GetString("body-scanner-console-window-hunger-current-hunger-status-" +
+ state.CurrentThirstThreshold))));
+ text.AppendLine();
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-hunger-group-text"));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-hunger-current-hunger-text",
+ ("amount", $"{state.CurrentHunger:f1}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-hunger-current-hunger-status-text",
+ ("status",
+ Loc.GetString("body-scanner-console-window-thirst-current-thirst-status-" +
+ state.CurrentHungerThreshold))));
+ text.AppendLine();
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-blood-solutions-group-text"));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-blood-solutions-volume-group-text",
+ ("amount", $"{state.BloodSolution.Volume.Float():f1}"),
+ ("maxAmount", $"{state.BloodSolution.MaxVolume.Float():f1}"),
+ ("temperature", $"{state.BloodSolution.Temperature - 271:f1}")));
+ state.BloodSolution.Contents.ForEach(x =>
+ {
+ text.Append(" - ");
+ text.AppendLine($"{x.Reagent.Prototype}: {x.Quantity}");
+ });
+ text.AppendLine();
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-chemical-solutions-group-text"));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-chemical-solutions-volume-group-text",
+ ("amount", $"{state.ChemicalSolution.Volume.Float():f1}"),
+ ("maxAmount", $"{state.ChemicalSolution.MaxVolume.Float():f1}"),
+ ("temperature", $"{state.ChemicalSolution.Temperature - 271:f1}")));
+ state.ChemicalSolution.Contents.ForEach(x =>
+ {
+ text.Append(" - ");
+ text.AppendLine($"{x.Reagent.Prototype}: {x.Quantity}");
+ });
+ text.AppendLine();
+
+ text.AppendLine(Loc.GetString("body-scanner-console-window-dna-text",
+ ("value", state.DNA)));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-fingerprint-text",
+ ("value", $"{state.Fingerprint}")));
+ text.AppendLine(Loc.GetString("body-scanner-console-window-mind-text",
+ ("value", $"{state.HasMind}")));
+
+ _audio.PlayPvs(component.PrintSound, uid);
+ _popup.PopupEntity(Loc.GetString("body-scanner-console-print-popup"), uid);
+ _paper.SetContent(report, text.ToString());
+ }
+
+ private void OnPowerChanged(EntityUid uid, ActiveBodyScannerConsoleComponent component, ref PowerChangedEvent args)
+ {
+ if (args.Powered)
+ return;
+
+ RemComp(uid);
+ }
+ }
+}
diff --git a/Content.Shared/White/Medical/BodyScanner/BodyScannerConsoleShared.cs b/Content.Shared/White/Medical/BodyScanner/BodyScannerConsoleShared.cs
new file mode 100644
index 0000000000..f68c2c0870
--- /dev/null
+++ b/Content.Shared/White/Medical/BodyScanner/BodyScannerConsoleShared.cs
@@ -0,0 +1,96 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Content.Shared.Mobs;
+using Content.Shared.Tiles;
+using Content.Shared.Wieldable;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.White.Medical.BodyScanner
+{
+ [Serializable, NetSerializable]
+ public enum BodyScannerConsoleUIKey : byte
+ {
+ Key
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class BodyScannerConsoleBoundUserInterfaceState : BoundUserInterfaceState
+ {
+ public bool ScannerConnected;
+ public bool GeneticScannerInRange;
+ public bool CanScan;
+ public bool CanPrint;
+
+ public bool Scanning;
+ public TimeSpan ScanTimeRemaining;
+ public TimeSpan ScanTotalTime;
+
+ public NetEntity? TargetEntityUid;
+ public string EntityName;
+
+ public float BleedAmount;
+ public FixedPoint2 BloodMaxVolume;
+ public string BloodReagent;
+ public Solution BloodSolution;
+ public FixedPoint2 ChemicalMaxVolume;
+ public Solution ChemicalSolution;
+
+ public string DNA;
+ public string Fingerprint;
+
+ public bool HasMind;
+
+ public float MaxSaturation;
+ public float MinSaturation;
+ public float Saturation;
+
+ public float HeatDamageThreshold;
+ public float ColdDamageThreshold;
+ public float CurrentTemperature;
+
+ public float CurrentThirst;
+ public byte CurrentThirstThreshold;
+
+ public FixedPoint2 TotalDamage;
+ public Dictionary DamageDict;
+ public Dictionary DamagePerGroup;
+ public FixedPoint2 DeadThreshold;
+
+ public float CurrentHunger;
+ public byte CurrentHungerThreshold;
+
+ public MobState CurrentState;
+
+ public float StaminaCritThreshold;
+ public float StaminaDamage;
+
+ public BodyScannerConsoleBoundUserInterfaceState()
+ {
+ CanScan = true;
+ EntityName = "N/A";
+ BloodMaxVolume = FixedPoint2.Zero;
+ BloodReagent = "N/A";
+ ChemicalMaxVolume = FixedPoint2.Zero;
+ BloodSolution = new();
+ ChemicalSolution = new();
+ DNA = "";
+ Fingerprint = "";
+ TotalDamage = FixedPoint2.Zero;
+ DamageDict = new();
+ DamagePerGroup = new();
+ DeadThreshold = FixedPoint2.Zero;
+ CurrentState = MobState.Invalid;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class BodyScannerStartScanningMessage : BoundUserInterfaceMessage
+ {
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class BodyScannerStartPrintingMessage : BoundUserInterfaceMessage
+ {
+ }
+}
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 453bbdbb52..d5ee0e0328 100644
--- a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
+++ b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
@@ -1,9 +1,12 @@
-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}
-health-analyzer-window-entity-damage-total-text = Total Damage: {$amount}
+health-analyzer-window-no-patient-data-text = No patient
+health-analyzer-window-no-data = N/A
+health-analyzer-window-entity-current-alive-status-text = Status
+health-analyzer-window-entity-current-alive-status-alive-text = Alive
+health-analyzer-window-entity-current-alive-status-critical-text = Critical
+health-analyzer-window-entity-current-alive-status-dead-text = Dead
+health-analyzer-window-entity-temperature-text = Temperature
+health-analyzer-window-entity-blood-level-text = Blood Level
+health-analyzer-window-entity-damage-total-text = Total Damage
health-analyzer-window-damage-group-text = {$damageGroup}: {$amount}
health-analyzer-window-damage-type-text = {$damageType}: {$amount}
health-analyzer-window-damage-type-duplicate-text = {$damageType}: {$amount} (duplicate)
@@ -15,6 +18,7 @@ health-analyzer-window-damage-type-Piercing = Piercing
health-analyzer-window-damage-group-Burn = Burn
health-analyzer-window-damage-type-Heat = Heat
+health-analyzer-window-damage-type-Laser = Laser
health-analyzer-window-damage-type-Shock = Shock
health-analyzer-window-damage-type-Cold = Cold
health-analyzer-window-damage-type-Caustic = Caustic
diff --git a/Resources/Locale/en-US/white/medical/bodyscanner/body-scanner-console-component.ftl b/Resources/Locale/en-US/white/medical/bodyscanner/body-scanner-console-component.ftl
new file mode 100644
index 0000000000..0f53f1abca
--- /dev/null
+++ b/Resources/Locale/en-US/white/medical/bodyscanner/body-scanner-console-component.ftl
@@ -0,0 +1,94 @@
+## EnterVerb
+
+body-scanner-console-window-no-data = N/A
+
+body-scanner-console-window-title = Body scanner console
+body-scanner-console-window-scan-button = Scan
+body-scanner-console-window-print-button = Print
+
+body-scanner-console-window-status-scanning = Scanning in progress
+
+body-scanner-console-window-current-alive-status-text = Status
+body-scanner-console-window-current-alive-status-alive-text = Alive
+body-scanner-console-window-current-alive-status-critical-text = Critical
+body-scanner-console-window-current-alive-status-dead-text = Dead
+body-scanner-console-window-temperature-text = Temperature
+body-scanner-console-window-entity-blood-level-text = Blood Level
+body-scanner-console-window-entity-damage-total-text = Total Damage
+body-scanner-console-window-damage-group-text = {$damageGroup}: {$amount}
+body-scanner-console-window-damage-type-text = {$damageType}: {$amount}
+body-scanner-console-window-damage-type-duplicate-text = {$damageType}: {$amount} (duplicate)
+
+body-scanner-console-window-damage-group-Brute = Brute: {$amount}
+body-scanner-console-window-damage-type-Blunt = Blunt: {$amount}
+body-scanner-console-window-damage-type-Slash = Slash: {$amount}
+body-scanner-console-window-damage-type-Piercing = Piercing: {$amount}
+
+body-scanner-console-window-damage-group-Burn = Burn: {$amount}
+body-scanner-console-window-damage-type-Heat = Heat: {$amount}
+body-scanner-console-window-damage-type-Laser = Laser: {$amount}
+body-scanner-console-window-damage-type-Shock = Shock: {$amount}
+body-scanner-console-window-damage-type-Cold = Cold: {$amount}
+body-scanner-console-window-damage-type-Caustic = Caustic: {$amount}
+
+body-scanner-console-window-damage-group-Airloss = Airloss: {$amount}
+body-scanner-console-window-damage-type-Asphyxiation = Asphyxiation: {$amount}
+body-scanner-console-window-damage-type-Bloodloss = Bloodloss: {$amount}
+
+body-scanner-console-window-damage-group-Toxin = Toxin: {$amount}
+body-scanner-console-window-damage-type-Poison = Poison: {$amount}
+body-scanner-console-window-damage-type-Radiation = Radiation: {$amount}
+
+body-scanner-console-window-damage-group-Genetic = Genetic: {$amount}
+body-scanner-console-window-damage-type-Cellular = Cellular: {$amount}
+
+body-scanner-console-window-temperature-group-text = Temperature
+body-scanner-console-window-temperature-current-temperature-text = Current: {$amount} °C
+body-scanner-console-window-temperature-heat-damage-threshold-temperature-text = Maximum: {$amount} °C
+body-scanner-console-window-temperature-cold-damage-threshold-temperature-text = Minimum: {$amount} °C
+
+body-scanner-console-window-saturation-group-text = Saturation
+body-scanner-console-window-saturation-current-saturation-text = Current: {$amount}
+body-scanner-console-window-saturation-maximum-saturation-text = Maximum: {$amount}
+body-scanner-console-window-saturation-minimum-saturation-text = Minimum: {$amount}
+
+body-scanner-console-window-thirst-group-text = Thirst
+body-scanner-console-window-thirst-current-thirst-text = Current: {$amount}
+body-scanner-console-window-thirst-current-thirst-status-text = Status: {$status}
+body-scanner-console-window-thirst-current-thirst-status-8 = Overhydrated
+body-scanner-console-window-thirst-current-thirst-status-4 = Okey
+body-scanner-console-window-thirst-current-thirst-status-2 = Thirsty
+body-scanner-console-window-thirst-current-thirst-status-1 = Parched
+body-scanner-console-window-thirst-current-thirst-status-0 = Dead
+
+body-scanner-console-window-hunger-group-text = Hunger
+body-scanner-console-window-hunger-current-hunger-text = Current: {$amount}
+body-scanner-console-window-hunger-current-hunger-status-text = Status: {$status}
+body-scanner-console-window-hunger-current-hunger-status-8 = Overfeed
+body-scanner-console-window-hunger-current-hunger-status-4 = Okey
+body-scanner-console-window-hunger-current-hunger-status-2 = Peckish
+body-scanner-console-window-hunger-current-hunger-status-1 = Starving
+body-scanner-console-window-hunger-current-hunger-status-0 = Dead
+
+body-scanner-console-window-blood-solutions-group-text = Blood solutions
+body-scanner-console-window-blood-solutions-volume-group-text = Volume: {$amount}/{$maxAmount} ({$temperature} °C)
+
+body-scanner-console-window-chemical-solutions-group-text = Chemical solutions
+body-scanner-console-window-chemical-solutions-volume-group-text = Volume: {$amount}/{$maxAmount} ({$temperature} °C)
+
+body-scanner-console-window-dna-text = DNA: {$value}
+body-scanner-console-window-fingerprint-text = Fingerprint: {$value}
+body-scanner-console-window-mind-text = Mind: {$value}
+
+body-scanner-console-window-connection-status-text-connected = Ready for work
+body-scanner-console-window-connection-status-text-no-connection = No connection
+body-scanner-console-window-connection-status-text-not-in-range = Not in range
+
+body-scanner-console-window-flavor-bottom-left = Medical technologies sourced from local unscrupulous researchers.
+body-scanner-console-window-flavor-bottom-right = v1.3
+
+body-scanner-console-print-popup = The console printed out a report.
+body-scanner-console-report-title = Health Report: Patient - {$name}
+body-scanner-console-report-temperature = Temperature: {$amount} °C
+body-scanner-console-report-blood-level = Blood Level: {$amount} %
+body-scanner-console-report-total-damage = Total Damage: {$amount}
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl b/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl
index a5f670fbc0..e28b7bee25 100644
--- a/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl
+++ b/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl
@@ -1,26 +1,35 @@
-health-analyzer-window-no-patient-data-text = Нет данных о пациенте.
-health-analyzer-window-entity-health-text = Состояние { $entityName }:
-health-analyzer-window-entity-temperature-text = Температура: { $temperature }
-health-analyzer-window-entity-blood-level-text = Уровень крови: { $bloodLevel }
-health-analyzer-window-entity-damage-total-text = Общие повреждения: { $amount }
-health-analyzer-window-damage-group-text = { $damageGroup }: { $amount }
-health-analyzer-window-damage-type-text = { $damageType }: { $amount }
-health-analyzer-window-damage-type-duplicate-text = { $damageType }: { $amount } (повтор)
-health-analyzer-window-damage-group-Brute = Механические
-health-analyzer-window-damage-type-Blunt = Удары
-health-analyzer-window-damage-type-Slash = Разрезы
-health-analyzer-window-damage-type-Piercing = Уколы
-health-analyzer-window-damage-group-Burn = Ожоги
-health-analyzer-window-damage-type-Heat = Термические
-health-analyzer-window-damage-type-Shock = Электрические
-health-analyzer-window-damage-type-Cold = Обморожение
-health-analyzer-window-damage-group-Airloss = Нехватка воздуха
-health-analyzer-window-damage-type-Asphyxiation = Удушение
-health-analyzer-window-damage-type-Bloodloss = Кровопотеря
-health-analyzer-window-damage-group-Toxin = Токсины
-health-analyzer-window-damage-type-Poison = Яды
-health-analyzer-window-damage-type-Radiation = Радиация
-health-analyzer-window-damage-group-Genetic = Генетические
-health-analyzer-window-damage-type-Cellular = Клеточные
-health-analyzer-window-damage-group-Caustic = Кислотные
-health-analyzer-window-damage-type-Caustic = Кислотные
+health-analyzer-window-no-patient-data-text = Пациент отсутствует
+health-analyzer-window-no-data = Н/Д
+health-analyzer-window-entity-current-alive-status-text = Статус
+health-analyzer-window-entity-current-alive-status-alive-text = Жив
+health-analyzer-window-entity-current-alive-status-critical-text = Критическое
+health-analyzer-window-entity-current-alive-status-dead-text = Мёртв
+health-analyzer-window-entity-temperature-text = Температура
+health-analyzer-window-entity-blood-level-text = Уровень крови
+health-analyzer-window-entity-damage-total-text = Общие повреждения
+health-analyzer-window-damage-group-text = {$damageGroup}: {$amount}
+health-analyzer-window-damage-type-text = {$damageType}: {$amount}
+health-analyzer-window-damage-type-duplicate-text = {$damageType}: {$amount} (дубликат)
+
+health-analyzer-window-damage-group-Brute = Механические: {$amount}
+health-analyzer-window-damage-type-Blunt = Удары: {$amount}
+health-analyzer-window-damage-type-Slash = Разрезы: {$amount}
+health-analyzer-window-damage-type-Piercing = Уколы: {$amount}
+
+health-analyzer-window-damage-group-Burn = Ожоги: {$amount}
+health-analyzer-window-damage-type-Heat = Термические: {$amount}
+health-analyzer-window-damage-type-Shock = Электрические: {$amount}
+health-analyzer-window-damage-type-Laser = Лазерный: {$amount}
+health-analyzer-window-damage-type-Cold = Обморожение: {$amount}
+health-analyzer-window-damage-type-Caustic = Кислотные: {$amount}
+
+health-analyzer-window-damage-group-Airloss = Нехватка воздуха: {$amount}
+health-analyzer-window-damage-type-Asphyxiation = Удушение: {$amount}
+health-analyzer-window-damage-type-Bloodloss = Кровопотеря: {$amount}
+
+health-analyzer-window-damage-group-Toxin = Токсины: {$amount}
+health-analyzer-window-damage-type-Poison = Яды: {$amount}
+health-analyzer-window-damage-type-Radiation = Радиация: {$amount}
+
+health-analyzer-window-damage-group-Genetic = Генетические: {$amount}
+health-analyzer-window-damage-type-Cellular = Клеточные: {$amount}
diff --git a/Resources/Locale/ru-RU/white/medical/bodyscanner/body-scanner-console-component.ftl b/Resources/Locale/ru-RU/white/medical/bodyscanner/body-scanner-console-component.ftl
new file mode 100644
index 0000000000..3224dfa770
--- /dev/null
+++ b/Resources/Locale/ru-RU/white/medical/bodyscanner/body-scanner-console-component.ftl
@@ -0,0 +1,96 @@
+## EnterVerb
+
+body-scanner-console-window-no-data = Н/Д
+
+body-scanner-console-window-title = Консоль сканера тела
+body-scanner-console-window-scan-button = Сканировать
+body-scanner-console-window-print-button = Печать
+
+body-scanner-console-window-status-scanning = Идет сканирование
+
+body-scanner-console-window-current-alive-status-text = Статус
+body-scanner-console-window-current-alive-status-alive-text = Жив
+body-scanner-console-window-current-alive-status-critical-text = Критический
+body-scanner-console-window-current-alive-status-dead-text = Мёртв
+body-scanner-console-window-temperature-text = Температура
+body-scanner-console-window-entity-blood-level-text = Уровень крови
+body-scanner-console-window-entity-damage-total-text = Общие повреждения
+body-scanner-console-window-damage-group-text = {$damageGroup}: {$amount}
+body-scanner-console-window-damage-type-text = {$damageType}: {$amount}
+body-scanner-console-window-damage-type-duplicate-text = {$damageType}: {$amount} (дубликат)
+
+body-scanner-console-window-damage-group-Brute = Механические: {$amount}
+body-scanner-console-window-damage-type-Blunt = Удары: {$amount}
+body-scanner-console-window-damage-type-Slash = Разрезы: {$amount}
+body-scanner-console-window-damage-type-Piercing = Уколы: {$amount}
+
+body-scanner-console-window-damage-group-Burn = Ожоги: {$amount}
+body-scanner-console-window-damage-type-Heat = Термические: {$amount}
+body-scanner-console-window-damage-type-Laser = Лазерный: {$amount}
+body-scanner-console-window-damage-type-Shock = Электрические: {$amount}
+body-scanner-console-window-damage-type-Cold = Обморожение: {$amount}
+body-scanner-console-window-damage-type-Caustic = Кислотные: {$amount}
+
+body-scanner-console-window-damage-group-Airloss = Нехватка воздуха: {$amount}
+body-scanner-console-window-damage-type-Asphyxiation = Удушение: {$amount}
+body-scanner-console-window-damage-type-Bloodloss = Кровопотеря: {$amount}
+
+body-scanner-console-window-damage-group-Toxin = Токсины: {$amount}
+body-scanner-console-window-damage-type-Poison = Яды: {$amount}
+body-scanner-console-window-damage-type-Radiation = Радиация: {$amount}
+
+body-scanner-console-window-damage-group-Genetic = Генетические: {$amount}
+body-scanner-console-window-damage-type-Cellular = Клеточные: {$amount}
+
+body-scanner-console-window-temperature-group-text = Температура
+body-scanner-console-window-temperature-current-temperature-text = Текущая: {$amount} °C
+body-scanner-console-window-temperature-heat-damage-threshold-temperature-text = Максимум: {$amount} °C
+body-scanner-console-window-temperature-cold-damage-threshold-temperature-text = Минимум: {$amount} °C
+
+body-scanner-console-window-saturation-group-text = Насыщенность
+body-scanner-console-window-saturation-current-saturation-text = Текущая: {$amount}
+body-scanner-console-window-saturation-maximum-saturation-text = Максимум: {$amount}
+body-scanner-console-window-saturation-minimum-saturation-text = Минимум: {$amount}
+
+body-scanner-console-window-thirst-group-text = Жажда
+body-scanner-console-window-thirst-current-thirst-text = Текущая: {$amount}
+body-scanner-console-window-thirst-current-thirst-status-text = Статус: {$status}
+body-scanner-console-window-thirst-current-thirst-status-8 = Переувлажнение
+body-scanner-console-window-thirst-current-thirst-status-4 = Хорошо
+body-scanner-console-window-thirst-current-thirst-status-2 = Жажда
+body-scanner-console-window-thirst-current-thirst-status-1 = Пересохший
+body-scanner-console-window-thirst-current-thirst-status-0 = Мёртв
+
+body-scanner-console-window-hunger-group-text = Голод
+body-scanner-console-window-hunger-current-hunger-text = Текущий: {$amount}
+body-scanner-console-window-hunger-current-hunger-status-text = Статус: {$status}
+body-scanner-console-window-hunger-current-hunger-status-8 = Перекармливание
+body-scanner-console-window-hunger-current-hunger-status-4 = Хорошо
+body-scanner-console-window-hunger-current-hunger-status-2 = Проголодавшийся
+body-scanner-console-window-hunger-current-hunger-status-1 = Голодающий
+body-scanner-console-window-hunger-current-hunger-status-0 = Мёртв
+
+body-scanner-console-window-blood-solutions-group-text = Состав крови
+body-scanner-console-window-blood-solutions-volume-group-text = Объём: {$amount}/{$maxAmount} ({$temperature} °C)
+
+body-scanner-console-window-chemical-solutions-group-text = Состав элементов в крови
+body-scanner-console-window-chemical-solutions-volume-group-text = Объём: {$amount}/{$maxAmount} ({$temperature} °C)
+
+body-scanner-console-window-dna-text = ДНК: {$value}
+body-scanner-console-window-fingerprint-text = Отпечатки: {$value}
+body-scanner-console-window-mind-text = Душа: {$value}
+body-scanner-console-window-mind-present-text = Присутствует
+body-scanner-console-window-mind-absent-text = Отсутствует
+
+body-scanner-console-window-connection-status-text-connected = Готова к работе
+body-scanner-console-window-connection-status-text-no-connection = Нет соединения
+body-scanner-console-window-connection-status-text-not-in-range = Не в радиусе
+
+body-scanner-console-window-flavor-bottom-left = Медицинские технологии, полученные от местных недобросовестных исследователей.
+body-scanner-console-window-flavor-bottom-right = v1.3
+
+body-scanner-console-print-popup = Консоль распечатала отчет.
+body-scanner-console-report-title = Отчет о состоянии здоровья: Пациент - {$name}
+body-scanner-console-report-temperature = Температура: {$amount} °C
+body-scanner-console-report-blood-level = Уровень крови: {$amount} %
+body-scanner-console-report-total-damage = Общие повреждения: {$amount}
\ No newline at end of file
diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml
index fdbea673e9..fee5dcd4cc 100644
--- a/Resources/Prototypes/DeviceLinking/source_ports.yml
+++ b/Resources/Prototypes/DeviceLinking/source_ports.yml
@@ -155,8 +155,14 @@
name: Раздатчик
description: Передатчик сигнала ХимМастера
+# WD
- type: sourcePort
id: LightStatus
name: Статус светильника
description: Этот порт вызывается всякий раз, когда меняется статус светильника
defaultLinks: [ Toggle ]
+
+- type: sourcePort
+ id: BodyScannerSender
+ name: Сканер тела
+ description: Передатчик сканера тела
diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
index fd7f720068..d157733afb 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml
@@ -539,12 +539,18 @@
IdCardConsole-privilegedId: !type:ContainerSlot
IdCardConsole-targetId: !type:ContainerSlot
+# WD start
- type: entity
parent: BaseComputer
id: computerBodyScanner
name: body scanner computer
- description: A body scanner.
+ description: Used to scan patients in body scanner.
components:
+ - type: BodyScannerConsole
+ reportEntityId: PaperBodyScannerReport
+ - type: DeviceList
+ - type: DeviceNetwork
+ deviceNetId: Wired
- type: ApcPowerReceiver
powerLoad: 500
- type: Computer
@@ -553,6 +559,20 @@
radius: 1.5
energy: 1.6
color: "#1f8c28"
+ - type: DeviceLinkSource
+ range: 4
+ ports:
+ - BodyScannerSender
+ - type: ActivatableUI
+ key: enum.BodyScannerConsoleUIKey.Key
+ - type: UserInterface
+ interfaces:
+ - key: enum.BodyScannerConsoleUIKey.Key
+ type: BodyScannerConsoleBoundUserInterface
+ - type: Damageable
+ damageContainer: Inorganic
+ damageModifierSet: StrongMetallic
+# WD end
- type: entity
parent: BaseComputer
diff --git a/Resources/Prototypes/White/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/White/Entities/Objects/Misc/paper.yml
new file mode 100644
index 0000000000..83cc43ec40
--- /dev/null
+++ b/Resources/Prototypes/White/Entities/Objects/Misc/paper.yml
@@ -0,0 +1,26 @@
+- type: entity
+ name: body scanner printout
+ parent: Paper
+ id: PaperBodyScannerReport
+ description: 'The readout of a body scanner'
+ components:
+ - type: Sprite
+ sprite: Objects/Misc/bureaucracy.rsi
+ layers:
+ - state: paper_dotmatrix
+ - state: paper_dotmatrix_words
+ map: ["enum.PaperVisualLayers.Writing"]
+ visible: false
+ - state: paper_stamp-generic
+ map: ["enum.PaperVisualLayers.Stamp"]
+ visible: false
+ - type: PaperVisuals
+ headerImagePath: "/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png"
+ headerMargin: 0.0, 0.0, 0.0, 16.0
+ backgroundImagePath: "/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png"
+ backgroundImageTile: true
+ backgroundPatchMargin: 37.0, 0.0, 37.0, 0.0
+ contentImagePath: "/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png"
+ contentImageNumLines: 2
+ contentMargin: 16.0, 16.0, 16.0, 0.0
+ maxWritableArea: 400.0, 0.0
\ No newline at end of file
diff --git a/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg b/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg
new file mode 100644
index 0000000000..56322efc32
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg
@@ -0,0 +1,179 @@
+
+
diff --git a/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png b/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png
new file mode 100644
index 0000000000..0418918d02
Binary files /dev/null and b/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png differ
diff --git a/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png.yml b/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png.yml
new file mode 100644
index 0000000000..5c43e23305
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png.yml
@@ -0,0 +1,2 @@
+sample:
+ filter: true
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg b/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg
new file mode 100644
index 0000000000..79ef1d6a7b
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg
@@ -0,0 +1,214 @@
+
+
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png b/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png
new file mode 100644
index 0000000000..c1bee72561
Binary files /dev/null and b/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png differ
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png.yml b/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png.yml
new file mode 100644
index 0000000000..5c43e23305
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/paper_background_dotmatrix.svg.96dpi.png.yml
@@ -0,0 +1,2 @@
+sample:
+ filter: true
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg b/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg
new file mode 100644
index 0000000000..80cf58b24c
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg
@@ -0,0 +1,91 @@
+
+
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png b/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png
new file mode 100644
index 0000000000..5d5227f34a
Binary files /dev/null and b/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png differ
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png.yml b/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png.yml
new file mode 100644
index 0000000000..5c43e23305
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/paper_content_dotmatrix_blue.svg.96dpi.png.yml
@@ -0,0 +1,2 @@
+sample:
+ filter: true
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg b/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg
new file mode 100644
index 0000000000..2285b77187
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg
@@ -0,0 +1,179 @@
+
+
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png b/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png
new file mode 100644
index 0000000000..bcf2a7bce7
Binary files /dev/null and b/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png differ
diff --git a/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png.yml b/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png.yml
new file mode 100644
index 0000000000..5c43e23305
--- /dev/null
+++ b/Resources/Textures/White/Interface/BodyScanner/paper_heading_body_scanner.svg.96dpi.png.yml
@@ -0,0 +1,2 @@
+sample:
+ filter: true