Body scanner and new health analyzer UI (#445)

* Body scanner and new health analyzer UI

* fix

---------

Co-authored-by: XDRmix <xdrmix@mail.ru>
This commit is contained in:
rhailrake
2023-09-27 13:37:45 +06:00
committed by Aviu00
parent 4009932fb2
commit d9daee02a5
32 changed files with 2132 additions and 218 deletions

View File

@@ -1,33 +1,26 @@
<DefaultWindow xmlns="https://spacestation14.io"
SetSize="250 100">
<ScrollContainer
VerticalExpand="True">
<BoxContainer
Name="RootContainer"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<BoxContainer
Name="PatientDataContainer"
Orientation="Vertical"
Margin="0 0 5 10">
<Label
Name="PatientName"/>
<Label
Name="Temperature"
Margin="0 5 0 0"/>
<Label
Name="BloodLevel"
Margin="0 5 0 0"/>
<Label
Name="patientDamageAmount"
Margin="0 15 0 0"/>
</BoxContainer>
<BoxContainer
Name="GroupsContainer"
Orientation="Vertical">
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</DefaultWindow>
<!-- WD start -->
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc 'gravity-generator-window-title'}"
MinSize="260 300">
<BoxContainer Margin="4 0" Orientation="Vertical">
<controls:StripeBack>
<Label Name="EntityNameLabel" Text="N/A" StyleClasses="LabelBig" Align="Center"/>
</controls:StripeBack>
<GridContainer Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-current-alive-status-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="AliveStatusLabel" Text="{Loc 'health-analyzer-window-no-data'}" Margin="4 0 0 0"/>
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="TemperatureLabel" Text="{Loc 'health-analyzer-window-no-data'}" Margin="4 0 0 0"/>
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="BloodLevelLabel" Text="{Loc 'health-analyzer-window-no-data'}" Margin="4 0 0 0"/>
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="TotalDamageLabel" Text="{Loc 'health-analyzer-window-no-data'}" Margin="4 0 0 0"/>
</GridContainer>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
<!-- Filled by code -->
<GridContainer Name="DamageGroupsContainer" Columns="2"/>
</BoxContainer>
</controls:FancyWindow>
<!-- WD end -->

View File

@@ -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<IEntityManager>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>();
}
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
GroupsContainer.RemoveAllChildren();
var entities = IoCManager.Resolve<IEntityManager>();
var target = _entityManager.GetEntity(msg.TargetEntity);
if (target == null
|| !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
if (msg.TargetEntity != null &&
entities.TryGetComponent<DamageableComponent>(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<MobStateComponent>(entities.GetEntity(msg.TargetEntity), out var mobStateComponent);
string entityName = Loc.GetString("health-analyzer-window-entity-unknown-text");
if (_entityManager.HasComponent<MetaDataComponent>(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<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
SetHeight = AnalyzerHeight;
SetWidth = AnalyzerWidth;
}
private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups, IReadOnlyDictionary<string, FixedPoint2> damageDict)
{
HashSet<string> 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<string, FixedPoint2> damagePerGroup = damageable.DamagePerGroup;
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
GroupsContainer.AddChild(groupContainer);
DamageGroupsContainer.RemoveAllChildren();
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(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<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
{
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
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,165 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'body-scanner-console-window-title'}"
MinSize="830 670">
<BoxContainer Orientation="Vertical">
<BoxContainer Name="MainContainer"
Margin="4 0"
Orientation="Vertical"
Visible="False">
<BoxContainer Orientation="Vertical">
<!-- Entity name -->
<controls:StripeBack>
<Label Name="EntityNameLabel" Text="{Loc 'body-scanner-console-window-no-data'}" StyleClasses="LabelBig" Align="Center"/>
</controls:StripeBack>
<!-- Main box -->
<BoxContainer Orientation="Horizontal">
<!-- First column -->
<BoxContainer Orientation="Vertical" MinSize="300 490">
<GridContainer Columns="2">
<Label Text="{Loc 'body-scanner-console-window-current-alive-status-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="AliveStatusLabel" Text="{Loc 'body-scanner-console-window-no-data'}" Margin="4 0 0 0"/>
<Label Text="{Loc 'body-scanner-console-window-temperature-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="TemperatureLabel" Text="{Loc 'body-scanner-console-window-no-data'}" Margin="4 0 0 0"/>
<Label Text="{Loc 'body-scanner-console-window-entity-blood-level-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="BloodLevelLabel" Text="{Loc 'body-scanner-console-window-no-data'}" Margin="4 0 0 0"/>
<Label Text="{Loc 'body-scanner-console-window-entity-damage-total-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="TotalDamageLabel" Text="{Loc 'body-scanner-console-window-no-data'}" Margin="4 0 0 0"/>
</GridContainer>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 5"/>
<GridContainer Name="DamageGroupsContainer" Columns="2"/>
</BoxContainer>
<!-- Second column -->
<customControls:VSeparator StyleClasses="LowDivider" Margin="5 0"/>
<BoxContainer Orientation="Vertical" MinSize="255 485">
<GridContainer Columns="2">
<!-- Temperature -->
<BoxContainer Orientation="Vertical" Margin="4 0 4 4">
<Label Text="{Loc 'body-scanner-console-window-temperature-group-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="CurrentTemperature"/>
<Label Name="HeatDamageThreshold"/>
<Label Name="ColdDamageThreshold"/>
</BoxContainer>
<!-- Saturation -->
<BoxContainer Orientation="Vertical" Margin="4 0 4 4">
<Label Text="{Loc 'body-scanner-console-window-saturation-group-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="CurrentSaturation"/>
<Label Name="MinimumSaturation"/>
<Label Name="MaximumSaturation"/>
</BoxContainer>
<!-- Thirst -->
<BoxContainer Orientation="Vertical" Margin="4">
<Label Text="{Loc 'body-scanner-console-window-thirst-group-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="CurrentThirst"/>
<Label Name="CurrentThirstStatus"/>
<Label Name="CurrentThirstThreshold"/>
</BoxContainer>
<!-- Hunger -->
<BoxContainer Orientation="Vertical" Margin="4">
<Label Text="{Loc 'body-scanner-console-window-hunger-group-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="CurrentHunger"/>
<Label Name="CurrentHungerStatus"/>
<Label Name="CurrentHungerThreshold"/>
</BoxContainer>
</GridContainer>
<!-- Blood solution-->
<BoxContainer Orientation="Vertical" Margin="4">
<Label Text="{Loc 'body-scanner-console-window-blood-solutions-group-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="BloodSolutionVolume"/>
<PanelContainer VerticalExpand="True" MinSize="0 100">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer HorizontalExpand="True" MinSize="0 100">
<BoxContainer Name="BloodSolutionElements" Orientation="Vertical" Margin="4" HorizontalExpand="True"/>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
<!-- Chemical solution-->
<BoxContainer Orientation="Vertical" Margin="4">
<Label Text="{Loc 'body-scanner-console-window-chemical-solutions-group-text'}" StyleClasses="StatusFieldTitle"/>
<Label Name="ChemicalSolutionVolume"/>
<PanelContainer VerticalExpand="True" MinSize="0 100">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer HorizontalExpand="True" MinSize="0 100">
<BoxContainer Name="ChemicalSolutionElements" Orientation="Vertical" Margin="4" HorizontalExpand="True"/>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
<!-- Third column -->
<customControls:VSeparator StyleClasses="LowDivider" Margin="5 0"/>
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<ProgressBar Name="HealthBar" HorizontalAlignment="Center" VerticalAlignment="Center" MinValue="0" MaxValue="1" MinSize="200 20" Margin="10"/>
<PanelContainer Margin="10 0 0 0" StyleClasses="Inset" VerticalAlignment="Center" >
<SpriteView Name="EntityView" OverrideDirection="South" Scale="8 8" />
</PanelContainer>
</BoxContainer>
</BoxContainer>
<!-- Main panel bottom row -->
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 5"/>
<BoxContainer Orientation="Horizontal">
<Label Name="Mind" Margin="5 0 0 0"/>
<customControls:VSeparator StyleClasses="LowDivider" Margin="10 0"/>
<Label Name="DNA"/>
<customControls:VSeparator StyleClasses="LowDivider" Margin="10 0"/>
<Label Name="Fingerprint"/>
</BoxContainer>
</BoxContainer>
<!-- Bottom row -->
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 5"/>
<BoxContainer Orientation="Horizontal">
<Button Name="PrintButton" TextAlign="Center" MinSize="150 25" Margin="0 0 10 5" Text="{Loc 'body-scanner-console-window-print-button'}"/>
<Button Name="ScanButton" TextAlign="Center" MinSize="150 25" Margin="0 0 10 5" Text="{Loc 'body-scanner-console-window-scan-button'}"/>
<Label Name="StatusText"/>
</BoxContainer>
</BoxContainer>
<BoxContainer Name="NoDataContainer"
VerticalExpand="True"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="4 0"
Orientation="Vertical">
<TextureRect Stretch="KeepAspectCentered"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TexturePath="/Textures/White/Interface/BodyScanner/body_scanner_logo.svg.160dpi.png"
Margin="10"/>
<ProgressBar Name="NoDataScanProgressBar"
HorizontalAlignment="Center"
MinValue="0" MaxValue="1"
MinSize="400 75"
Margin="10"/>
<Button Name="NoDataScanButton"
HorizontalAlignment="Center"
TextAlign="Center"
MinSize="250 50"
Margin="10"
Text="{Loc 'body-scanner-console-window-scan-button'}"/>
<Label Name="NoDataStatusLabel"
Text=" "
HorizontalAlignment="Center"
StyleClasses="LabelBig"
Margin="10"/>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical" VerticalAlignment="Bottom">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'body-scanner-console-window-flavor-bottom-left'}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'body-scanner-console-window-flavor-bottom-right'}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark"
Stretch="KeepAspectCentered"
VerticalAlignment="Center"
HorizontalAlignment="Right"
SetSize="19 19"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -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<string> shownTypes = new();
var protos = IoCManager.Resolve<IPrototypeManager>();
IReadOnlyDictionary<string, FixedPoint2> damagePerGroup = state.DamagePerGroup;
IReadOnlyDictionary<string, FixedPoint2> 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}"));
}
/// <summary>
/// Smooth transition from one color to another depending on the percentage.
/// </summary>
/// <param name="startColor"></param>
/// <param name="endColor"></param>
/// <param name="percentage"></param>
/// <returns></returns>
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);
}
}
}

View File

@@ -0,0 +1,14 @@
<Control xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer MinSize="120 100" Margin="1">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderColor="#777777" BorderThickness="1"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<Label Name="DamageGroupTitle" Align="Center" Margin="5" StyleClasses="StatusFieldTitle"/>
<customControls:HSeparator Margin="5 0"/>
<BoxContainer Name="DamageLabelsContainer" Orientation="Vertical" Margin="5"/>
</BoxContainer>
</PanelContainer>
</Control>

View File

@@ -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<string, FixedPoint2> damagePerType)
{
RobustXamlLoader.Load(this);
DamageGroupTitle.Text = title;
HashSet<string> shownTypes = new();
var protos = IoCManager.Resolve<IPrototypeManager>();
var group = protos.Index<DamageGroupPrototype>(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);
}
}
}
}
/// <summary>
/// Sets the color of a label depending on the damage.
/// </summary>
/// <param name="label"></param>
/// <param name="damage"></param>
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);
}
}
/// <summary>
/// Smooth transition from one color to another depending on the percentage.
/// </summary>
/// <param name="startColor"></param>
/// <param name="endColor"></param>
/// <param name="percentage"></param>
/// <returns></returns>
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);
}
}
}

View File

@@ -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";

View File

@@ -0,0 +1,20 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations;
namespace Content.Server.White.Medical.BodyScanner
{
[RegisterComponent]
public sealed partial class ActiveBodyScannerConsoleComponent : Component
{
/// <summary>
/// When did the scanning start?
/// </summary>
[DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
public TimeSpan StartTime;
/// <summary>
/// What is being scanned?
/// </summary>
[ViewVariables]
public EntityUid PatientUid;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
/// <summary>
/// Genetic scanner. Can be null if not linked.
/// </summary>
[ViewVariables]
public EntityUid? GeneticScanner;
/// <summary>
/// Maximum distance between body scanner console and one if its machines.
/// </summary>
[DataField("maxDistance")]
public float MaxDistance = 4f;
public bool GeneticScannerInRange = true;
/// <summary>
/// How long it takes to scan.
/// </summary>
[ViewVariables, DataField("scanDuration", customTypeSerializer: typeof(TimespanSerializer))]
public TimeSpan ScanDuration = TimeSpan.FromSeconds(3);
/// <summary>
/// Sound to play on scan finished.
/// </summary>
[DataField("scanFinishedSound")]
public SoundSpecifier ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
/// <summary>
/// Sound to play when printing.
/// </summary>
[DataField("printSound")]
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/diagnoser_printing.ogg");
[ViewVariables]
public BodyScannerConsoleBoundUserInterfaceState? LastScannedState;
/// <summary>
/// The entity spawned by a report.
/// </summary>
[DataField("reportEntityId", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ReportEntityId = "Paper";
}
}

View File

@@ -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<ActiveBodyScannerConsoleComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<BodyScannerConsoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BodyScannerConsoleComponent, NewLinkEvent>(OnNewLink);
SubscribeLocalEvent<BodyScannerConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<BodyScannerConsoleComponent, BodyScannerStartScanningMessage>(OnStartScanning);
SubscribeLocalEvent<BodyScannerConsoleComponent, BodyScannerStartPrintingMessage>(OnStartPrinting);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var queryActiveBodyScanner = EntityQueryEnumerator<ActiveBodyScannerConsoleComponent, BodyScannerConsoleComponent>();
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<BodyInScannerBodyScannerConsoleComponent, BodyScannerConsoleComponent>();
while (queryBodyInScanner.MoveNext(out var uid, out var bodyInScanner, out var scan))
{
if (bodyInScanner.MedicalCannerComponent?.BodyContainer.ContainedEntity == null)
{
RemComp<BodyInScannerBodyScannerConsoleComponent>(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<MedicalScannerComponent>(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<BloodstreamComponent>(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<DnaComponent>(scanBody, out var dna))
{
state.DNA = dna.DNA;
}
if (TryComp<FingerprintComponent>(scanBody, out var fingerprint))
{
state.Fingerprint = fingerprint.Fingerprint ?? "";
}
if (TryComp<MindContainerComponent>(scanBody, out var mind))
{
state.HasMind = mind.HasMind;
}
if (TryComp<RespiratorComponent>(scanBody, out var respirator))
{
state.MaxSaturation = respirator.MaxSaturation;
state.MinSaturation = respirator.MinSaturation;
state.Saturation = respirator.Saturation;
}
if (TryComp<TemperatureComponent>(scanBody, out var temp))
{
state.HeatDamageThreshold = temp.HeatDamageThreshold;
state.ColdDamageThreshold = temp.ColdDamageThreshold;
state.CurrentTemperature = temp.CurrentTemperature;
}
if (TryComp<ThirstComponent>(scanBody, out var thirst))
{
state.CurrentThirst = thirst.CurrentThirst;
state.CurrentThirstThreshold = (byte) thirst.CurrentThirstThreshold;
}
if (TryComp<DamageableComponent>(scanBody, out var damageable))
{
state.TotalDamage = damageable.TotalDamage;
state.DamageDict = new(damageable.Damage.DamageDict);
state.DamagePerGroup = new(damageable.DamagePerGroup);
}
if (TryComp<HungerComponent>(scanBody, out var hunger))
{
state.CurrentHunger = hunger.CurrentHunger;
state.CurrentHungerThreshold = (byte) hunger.CurrentThreshold;
}
if (TryComp<MobStateComponent>(scanBody, out var mobState))
{
state.CurrentState = mobState.CurrentState;
}
if (TryComp<MobThresholdsComponent>(scanBody, out var mobThresholds))
{
foreach (var pair in mobThresholds.Thresholds)
{
if (pair.Value == Shared.Mobs.MobState.Dead)
{
state.DeadThreshold = pair.Key;
}
}
}
if (TryComp<StaminaComponent>(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<ActiveBodyScannerConsoleComponent>(uid);
if (component?.GeneticScanner != null && TryComp<MedicalScannerComponent>(component.GeneticScanner, out var scanner))
{
EnsureComp<BodyInScannerBodyScannerConsoleComponent>(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<MedicalScannerComponent>(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<MedicalScannerComponent>(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<MedicalScannerComponent>(component.GeneticScanner, out var scanner))
return;
if (scanner.BodyContainer.ContainedEntity == null)
return;
var activeComp = EnsureComp<ActiveBodyScannerConsoleComponent>(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<string> shownTypes = new();
var protos = IoCManager.Resolve<IPrototypeManager>();
IReadOnlyDictionary<string, FixedPoint2> damagePerGroup = state.DamagePerGroup;
IReadOnlyDictionary<string, FixedPoint2> 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<DamageGroupPrototype>(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<ActiveBodyScannerConsoleComponent>(uid);
}
}
}

View File

@@ -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<string, FixedPoint2> DamageDict;
public Dictionary<string, FixedPoint2> 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
{
}
}

View File

@@ -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

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -155,8 +155,14 @@
name: Раздатчик
description: Передатчик сигнала ХимМастера
# WD
- type: sourcePort
id: LightStatus
name: Статус светильника
description: Этот порт вызывается всякий раз, когда меняется статус светильника
defaultLinks: [ Toggle ]
- type: sourcePort
id: BodyScannerSender
name: Сканер тела
description: Передатчик сканера тела

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="211"
height="60"
viewBox="0 0 211 60"
fill="none"
version="1.1"
id="svg4"
sodipodi:docname="body_scanner_logo.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
inkscape:export-filename="body_scanner_logo.svg.160dpi.png"
inkscape:export-xdpi="160"
inkscape:export-ydpi="160"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1608">
<g
id="g1630"
transform="scale(0.96824607,1.0327953)">
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1610"
width="145.43811"
height="3.8830388"
x="0"
y="1.1368795e-05" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1612"
width="145.43811"
height="3.8830388"
x="0"
y="6.207139" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1614"
width="145.43811"
height="3.8830388"
x="0"
y="12.414267" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1616"
width="145.43811"
height="3.8830388"
x="0"
y="18.621393" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1618"
width="145.43811"
height="3.8830388"
x="0"
y="24.828522" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1620"
width="145.43811"
height="3.8830388"
x="0"
y="31.035648" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1622"
width="145.43811"
height="3.8830388"
x="0"
y="37.242775" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1624"
width="145.43811"
height="3.8830388"
x="0"
y="43.449905" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1626"
width="145.43811"
height="3.8830388"
x="0"
y="55.864159" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1628"
width="145.43811"
height="3.8830388"
x="0"
y="49.657032" />
</g>
</clipPath>
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview6"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:bbox-nodes="true"
inkscape:snap-object-midpoints="false"
inkscape:snap-nodes="false"
inkscape:snap-page="true"
inkscape:zoom="4.2223172"
inkscape:cx="137.48375"
inkscape:cy="0.11841839"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="true" />
<g
aria-label="NTX"
transform="scale(1.0327953,0.96824607)"
id="text1677"
clip-path="url(#clipPath1608)"
style="font-size:87.0333px;line-height:1.25;display:inline;fill:#2c3b8c;stroke-width:2.17583">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:74.7181px;line-height:1.25;font-family:'Californian FB';-inkscape-font-specification:'Californian FB, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.06434"
x="-7.0813799"
y="54.390907"
id="text2"
transform="scale(0.95869604,1.0430835)"><tspan
id="tspan2"
x="-7.0813799"
y="54.390907"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:74.7181px;font-family:Algerian;-inkscape-font-specification:'Algerian, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;opacity:1;fill:#008000;stroke-width:4.06434"
sodipodi:role="line">ВД</tspan></text>
</g>
<g
aria-label="Labs"
id="text2013"
style="font-size:41.0143px;line-height:1.25;fill:#6969e1;fill-opacity:1;stroke-width:1.02536"
transform="translate(0,-3.3157149)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:30.2197px;line-height:1.25;font-family:Candara;-inkscape-font-specification:'Candara, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;display:inline;fill:#6969e1;fill-opacity:1;stroke:none;stroke-width:0.755493"
x="72.789795"
y="50.594276"
id="text3"><tspan
sodipodi:role="line"
id="tspan3"
x="72.789795"
y="50.594276"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:30.2197px;font-family:Candara;-inkscape-font-specification:'Candara, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#6969e1;fill-opacity:1;stroke-width:0.755493">Медицина </tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@@ -0,0 +1,2 @@
sample:
filter: true

View File

@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="76"
height="32"
viewBox="0 0 76 32"
fill="none"
version="1.1"
id="svg4"
sodipodi:docname="paper_background_dotmatrix.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
inkscape:export-filename="paper_background_dotmatrix.svg.96dpi.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8">
<inkscape:path-effect
effect="powermask"
id="path-effect2519"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect2519"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<filter
id="mask-powermask-path-effect1163_inverse"
inkscape:label="filtermask-powermask-path-effect1163"
style="color-interpolation-filters:sRGB"
height="100"
width="100"
x="-50"
y="-50">
<feColorMatrix
id="mask-powermask-path-effect1163_primitive1"
values="1"
type="saturate"
result="fbSourceGraphic" />
<feColorMatrix
id="mask-powermask-path-effect1163_primitive2"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 "
in="fbSourceGraphic" />
</filter>
<mask
maskUnits="userSpaceOnUse"
id="mask2602">
<path
id="mask-powermask-path-effect2634_box"
style="fill:#ffffff;fill-opacity:1"
d="M -1,-0.96563349 H 77 V 33 H -1 Z" />
<g
id="g2632"
style="">
<g
id="g2612">
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.0923076;stroke-linejoin:round;stroke-opacity:1"
id="circle2604"
cx="16"
cy="0"
r="12" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.0923076;stroke-linejoin:round;stroke-opacity:1"
id="circle2606"
cx="60"
cy="0"
r="12" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.0923076;stroke-linejoin:round;stroke-opacity:1"
id="circle2608"
cx="16"
cy="32"
r="12" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.0923076;stroke-linejoin:round;stroke-opacity:1"
id="circle2610"
cx="60"
cy="32"
r="12" />
</g>
<g
id="g2630"
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-opacity:1">
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2614"
cx="34"
cy="4"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2616"
cx="42"
cy="4"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2618"
cx="34"
cy="28"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2620"
cx="42"
cy="28"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2622"
cx="34"
cy="20"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2624"
cx="42"
cy="20"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2626"
cx="34"
cy="12"
r="2" />
<circle
style="fill:#000000;fill-opacity:0.797456;stroke:none;stroke-width:0.133333;stroke-linejoin:round;stroke-opacity:1"
id="circle2628"
cx="42"
cy="12"
r="2" />
</g>
</g>
</mask>
<filter
id="mask-powermask-path-effect2634_inverse"
inkscape:label="filtermask-powermask-path-effect2634"
style="color-interpolation-filters:sRGB"
height="100"
width="100"
x="-50"
y="-50">
<feColorMatrix
id="mask-powermask-path-effect2634_primitive1"
values="1"
type="saturate"
result="fbSourceGraphic" />
<feColorMatrix
id="mask-powermask-path-effect2634_primitive2"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 "
in="fbSourceGraphic" />
</filter>
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1916"
inkscape:window-height="1034"
id="namedview6"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="8.4422326"
inkscape:cx="37.667761"
inkscape:cy="19.130011"
inkscape:window-x="0"
inkscape:window-y="44"
inkscape:window-maximized="0"
inkscape:current-layer="svg4"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="false">
<inkscape:grid
type="xygrid"
id="grid233"
originx="0"
originy="0" />
</sodipodi:namedview>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linejoin:round;stroke-opacity:1"
id="rect1200"
width="76"
height="31.965633"
x="0"
y="0.034366511"
mask="url(#mask2602)" />
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

View File

@@ -0,0 +1,2 @@
sample:
filter: true

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="12"
height="32"
viewBox="0 0 12 32"
fill="none"
version="1.1"
id="svg4"
sodipodi:docname="paper_content_dotmatrix_blue.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
inkscape:export-filename="paper_content_dotmatrix_blue.svg.96dpi.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview6"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:bbox-nodes="true"
inkscape:snap-object-midpoints="false"
inkscape:snap-nodes="false"
inkscape:snap-page="true"
inkscape:zoom="32"
inkscape:cx="0.28125"
inkscape:cy="6.265625"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<rect
style="mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.49485;stroke-linejoin:round;stroke-opacity:1"
id="rect983"
width="12"
height="16"
x="0"
y="0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:3.47338;stroke-linejoin:round"
id="rect231"
width="12"
height="16"
x="0"
y="0.21811378" />
<rect
style="fill:#b6b6fb;fill-opacity:0.30067104;stroke-width:3.13086;stroke-linejoin:round"
id="rect339"
width="12"
height="12.999999"
x="0"
y="19" />
<rect
style="fill:#d0d0fb;fill-opacity:0.30000001;stroke-width:1.50402;stroke-linejoin:round"
id="rect1539"
width="12"
height="3"
x="0"
y="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

View File

@@ -0,0 +1,2 @@
sample:
filter: true

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="211"
height="60"
viewBox="0 0 211 60"
fill="none"
version="1.1"
id="svg4"
sodipodi:docname="paper_heading_body_scanner.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
inkscape:export-filename="paper_heading_body_scanner.svg.96dpi.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath1608">
<g
id="g1630"
transform="scale(0.96824607,1.0327953)">
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1610"
width="145.43811"
height="3.8830388"
x="0"
y="1.1368795e-05" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1612"
width="145.43811"
height="3.8830388"
x="0"
y="6.207139" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1614"
width="145.43811"
height="3.8830388"
x="0"
y="12.414267" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1616"
width="145.43811"
height="3.8830388"
x="0"
y="18.621393" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1618"
width="145.43811"
height="3.8830388"
x="0"
y="24.828522" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1620"
width="145.43811"
height="3.8830388"
x="0"
y="31.035648" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1622"
width="145.43811"
height="3.8830388"
x="0"
y="37.242775" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1624"
width="145.43811"
height="3.8830388"
x="0"
y="43.449905" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1626"
width="145.43811"
height="3.8830388"
x="0"
y="55.864159" />
<rect
style="fill:#00a700;fill-opacity:1;stroke-width:0.0886665;stroke-linejoin:round"
id="rect1628"
width="145.43811"
height="3.8830388"
x="0"
y="49.657032" />
</g>
</clipPath>
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview6"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:bbox-nodes="true"
inkscape:snap-object-midpoints="false"
inkscape:snap-nodes="false"
inkscape:snap-page="true"
inkscape:zoom="4.2223172"
inkscape:cx="137.48375"
inkscape:cy="0.11841839"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="true" />
<g
aria-label="NTX"
transform="scale(1.0327953,0.96824607)"
id="text1677"
clip-path="url(#clipPath1608)"
style="font-size:87.0333px;line-height:1.25;display:inline;fill:#2c3b8c;stroke-width:2.17583">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:74.7181px;line-height:1.25;font-family:'Californian FB';-inkscape-font-specification:'Californian FB, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.06434"
x="-7.0813799"
y="54.390907"
id="text2"
transform="scale(0.95869604,1.0430835)"><tspan
id="tspan2"
x="-7.0813799"
y="54.390907"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:74.7181px;font-family:Algerian;-inkscape-font-specification:'Algerian, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;opacity:1;fill:#008000;stroke-width:4.06434"
sodipodi:role="line">ВД</tspan></text>
</g>
<g
aria-label="Labs"
id="text2013"
style="font-size:41.0143px;line-height:1.25;fill:#6969e1;fill-opacity:1;stroke-width:1.02536"
transform="translate(0,-3.3157149)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:30.2197px;line-height:1.25;font-family:Candara;-inkscape-font-specification:'Candara, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;display:inline;fill:#6969e1;fill-opacity:1;stroke:none;stroke-width:0.755493"
x="72.789795"
y="50.594276"
id="text3"><tspan
sodipodi:role="line"
id="tspan3"
x="72.789795"
y="50.594276"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:30.2197px;font-family:Candara;-inkscape-font-specification:'Candara, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#6969e1;fill-opacity:1;stroke-width:0.755493">Медицина </tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,2 @@
sample:
filter: true