@@ -108,6 +108,7 @@ namespace Content.Client
|
|||||||
"AccessReader",
|
"AccessReader",
|
||||||
"IdCardConsole",
|
"IdCardConsole",
|
||||||
"Airlock",
|
"Airlock",
|
||||||
|
"MedicalScanner",
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var ignoreName in registerIgnore)
|
foreach (var ignoreName in registerIgnore)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
|
{
|
||||||
|
public class MedicalScannerBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private MedicalScannerWindow _window;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
_window = new MedicalScannerWindow
|
||||||
|
{
|
||||||
|
Title = Owner.Owner.Name,
|
||||||
|
Size = (485, 90),
|
||||||
|
};
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
_window.Populate((MedicalScannerBoundUserInterfaceState) state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent.MedicalScannerStatus;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
|
{
|
||||||
|
public class MedicalScannerVisualizer2D : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return;
|
||||||
|
sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status));
|
||||||
|
sprite.LayerSetState(MedicalScannerVisualLayers.Terminal, StatusToTerminalStateId(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string StatusToMachineStateId(MedicalScannerStatus status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case Off: return "scanner_off";
|
||||||
|
case Open: return "scanner_open";
|
||||||
|
case Red: return "scanner_red";
|
||||||
|
case Death: return "scanner_death";
|
||||||
|
case Green: return "scanner_green";
|
||||||
|
case Yellow: return "scanner_yellow";
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string StatusToTerminalStateId(MedicalScannerStatus status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case Off: return "scanner_terminal_off";
|
||||||
|
case Open: return "scanner_terminal_blue";
|
||||||
|
case Red: return "scanner_terminal_red";
|
||||||
|
case Death: return "scanner_terminal_dead";
|
||||||
|
case Green: return "scanner_terminal_green";
|
||||||
|
case Yellow: return "scanner_terminal_blue";
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MedicalScannerVisualLayers
|
||||||
|
{
|
||||||
|
Machine,
|
||||||
|
Terminal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
|
{
|
||||||
|
public class MedicalScannerWindow : SS14Window
|
||||||
|
{
|
||||||
|
public MedicalScannerWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(MedicalScannerBoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
Contents.RemoveAllChildren();
|
||||||
|
var text = new StringBuilder();
|
||||||
|
if (state.MaxHealth == 0)
|
||||||
|
{
|
||||||
|
text.Append("No patient data.");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
text.Append($"Patient's health: {state.CurrentHealth}/{state.MaxHealth}\n");
|
||||||
|
|
||||||
|
if (state.DamageDictionary != null)
|
||||||
|
{
|
||||||
|
foreach (var (dmgType, amount) in state.DamageDictionary)
|
||||||
|
{
|
||||||
|
text.Append($"\n{dmgType}: {amount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Contents.AddChild(new Label(){Text = text.ToString()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Medical;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Medical
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IActivate))]
|
||||||
|
public class MedicalScannerComponent : SharedMedicalScannerComponent, IActivate
|
||||||
|
{
|
||||||
|
private AppearanceComponent _appearance;
|
||||||
|
private BoundUserInterface _userInterface;
|
||||||
|
private ContainerSlot _bodyContainer;
|
||||||
|
public bool IsOccupied => _bodyContainer.ContainedEntity != null;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_appearance = Owner.GetComponent<AppearanceComponent>();
|
||||||
|
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||||
|
.GetBoundUserInterface(MedicalScannerUiKey.Key);
|
||||||
|
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
|
||||||
|
UpdateUserInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState =
|
||||||
|
new MedicalScannerBoundUserInterfaceState(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null);
|
||||||
|
|
||||||
|
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
|
||||||
|
{
|
||||||
|
var body = _bodyContainer.ContainedEntity;
|
||||||
|
if (body == null)
|
||||||
|
{
|
||||||
|
_appearance.SetData(MedicalScannerVisuals.Status, MedicalScannerStatus.Open);
|
||||||
|
return EmptyUIState;
|
||||||
|
}
|
||||||
|
|
||||||
|
var damageable = body.GetComponent<DamageableComponent>();
|
||||||
|
var species = body.GetComponent<SpeciesComponent>();
|
||||||
|
var deathThreshold =
|
||||||
|
species.DamageTemplate.DamageThresholds.FirstOrNull(x => x.ThresholdType == ThresholdType.Death);
|
||||||
|
if (!deathThreshold.HasValue)
|
||||||
|
{
|
||||||
|
return EmptyUIState;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deathThresholdValue = deathThreshold.Value.Value;
|
||||||
|
var currentHealth = damageable.CurrentDamage[DamageType.Total];
|
||||||
|
|
||||||
|
var dmgDict = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
foreach (var dmgType in (DamageType[])Enum.GetValues(typeof(DamageType)))
|
||||||
|
{
|
||||||
|
if (damageable.CurrentDamage.TryGetValue(dmgType, out var amount))
|
||||||
|
{
|
||||||
|
dmgDict[dmgType.ToString()] = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MedicalScannerBoundUserInterfaceState(
|
||||||
|
deathThresholdValue-currentHealth,
|
||||||
|
deathThresholdValue,
|
||||||
|
dmgDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUserInterface()
|
||||||
|
{
|
||||||
|
var newState = GetUserInterfaceState();
|
||||||
|
_userInterface.SetState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState)
|
||||||
|
{
|
||||||
|
switch (damageState)
|
||||||
|
{
|
||||||
|
case NormalState _: return MedicalScannerStatus.Green;
|
||||||
|
case CriticalState _: return MedicalScannerStatus.Red;
|
||||||
|
case DeadState _: return MedicalScannerStatus.Death;
|
||||||
|
default: throw new ArgumentException(nameof(damageState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private MedicalScannerStatus GetStatus()
|
||||||
|
{
|
||||||
|
var body = _bodyContainer.ContainedEntity;
|
||||||
|
return body == null
|
||||||
|
? MedicalScannerStatus.Open
|
||||||
|
: GetStatusFromDamageState(body.GetComponent<SpeciesComponent>().CurrentDamageState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
_appearance.SetData(MedicalScannerVisuals.Status, GetStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Activate(ActivateEventArgs args)
|
||||||
|
{
|
||||||
|
if (!args.User.TryGetComponent(out IActorComponent actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_userInterface.Open(actor.playerSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
public sealed class EnterVerb : Verb<MedicalScannerComponent>
|
||||||
|
{
|
||||||
|
protected override string GetText(IEntity user, MedicalScannerComponent component)
|
||||||
|
{
|
||||||
|
return "Enter";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override VerbVisibility GetVisibility(IEntity user, MedicalScannerComponent component)
|
||||||
|
{
|
||||||
|
return component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, MedicalScannerComponent component)
|
||||||
|
{
|
||||||
|
component.InsertBody(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
public sealed class EjectVerb : Verb<MedicalScannerComponent>
|
||||||
|
{
|
||||||
|
protected override string GetText(IEntity user, MedicalScannerComponent component)
|
||||||
|
{
|
||||||
|
return "Eject";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override VerbVisibility GetVisibility(IEntity user, MedicalScannerComponent component)
|
||||||
|
{
|
||||||
|
return component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, MedicalScannerComponent component)
|
||||||
|
{
|
||||||
|
component.EjectBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertBody(IEntity user)
|
||||||
|
{
|
||||||
|
_bodyContainer.Insert(user);
|
||||||
|
UpdateUserInterface();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EjectBody()
|
||||||
|
{
|
||||||
|
_bodyContainer.Remove(_bodyContainer.ContainedEntity);
|
||||||
|
UpdateUserInterface();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float frameTime)
|
||||||
|
{
|
||||||
|
if (_bodyContainer.ContainedEntity == null)
|
||||||
|
{
|
||||||
|
// There's no need to update if there's no one inside
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateUserInterface();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ namespace Content.Server.GameObjects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the damage template which controls the threshold and resistance settings for this species type
|
/// Holds the damage template which controls the threshold and resistance settings for this species type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private DamageTemplates DamageTemplate;
|
public DamageTemplates DamageTemplate { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Variable for serialization
|
/// Variable for serialization
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Medical;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
|
{
|
||||||
|
public class MedicalScannerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
EntityQuery = new TypeEntityQuery(typeof(MedicalScannerComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var entity in RelevantEntities)
|
||||||
|
{
|
||||||
|
var comp = entity.GetComponent<MedicalScannerComponent>();
|
||||||
|
comp.Update(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Medical
|
||||||
|
{
|
||||||
|
public class SharedMedicalScannerComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "MedicalScanner";
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class MedicalScannerBoundUserInterfaceState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public readonly int CurrentHealth;
|
||||||
|
public readonly int MaxHealth;
|
||||||
|
public readonly Dictionary<string, int> DamageDictionary;
|
||||||
|
|
||||||
|
public MedicalScannerBoundUserInterfaceState(
|
||||||
|
int currentHealth,
|
||||||
|
int maxHealth,
|
||||||
|
Dictionary<string, int> damageDictionary)
|
||||||
|
{
|
||||||
|
CurrentHealth = currentHealth;
|
||||||
|
MaxHealth = maxHealth;
|
||||||
|
DamageDictionary = damageDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum MedicalScannerUiKey
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum MedicalScannerVisuals
|
||||||
|
{
|
||||||
|
Status
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum MedicalScannerStatus
|
||||||
|
{
|
||||||
|
Off,
|
||||||
|
Open,
|
||||||
|
Red,
|
||||||
|
Death,
|
||||||
|
Green,
|
||||||
|
Yellow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Resources/Prototypes/Entities/buildings/medical_scanner.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
- type: entity
|
||||||
|
id: medical_scanner
|
||||||
|
name: Medical Scanner
|
||||||
|
description: A bulky medical scanner.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
sprite: Buildings/medical_scanner.rsi
|
||||||
|
layers:
|
||||||
|
- state: scanner_open
|
||||||
|
map: ["enum.MedicalScannerVisualLayers.Machine"]
|
||||||
|
- state: scanner_terminal_blue
|
||||||
|
map: ["enum.MedicalScannerVisualLayers.Terminal"]
|
||||||
|
|
||||||
|
- type: Icon
|
||||||
|
sprite: Buildings/medical_scanner.rsi
|
||||||
|
state: scanner_open
|
||||||
|
|
||||||
|
- type: Clickable
|
||||||
|
- type: Collidable
|
||||||
|
shapes:
|
||||||
|
- !type:PhysShapeAabb
|
||||||
|
bounds: "-0.5,-0.25,0.5,0.25"
|
||||||
|
mask: 19
|
||||||
|
layer: 16
|
||||||
|
IsScrapingFloor: true
|
||||||
|
- type: Physics
|
||||||
|
mass: 25
|
||||||
|
Anchored: true
|
||||||
|
- type: SnapGrid
|
||||||
|
offset: Center
|
||||||
|
- type: MedicalScanner
|
||||||
|
- type: Damageable
|
||||||
|
- type: Destructible
|
||||||
|
thresholdvalue: 100
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: MedicalScannerVisualizer2D
|
||||||
|
- type: PowerDevice
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.MedicalScannerUiKey.Key
|
||||||
|
type: MedicalScannerBoundUserInterface
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version": 1, "size": {"x": 64, "y": 32}, "states": [{"name": "scanner_death", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3]]}, {"name": "scanner_green", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}, {"name": "scanner_off", "directions": 1, "delays": [[1.0]]}, {"name": "scanner_open", "directions": 1, "delays": [[1.0]]}, {"name": "scanner_red", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}, {"name": "scanner_terminal_blue", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3]]}, {"name": "scanner_terminal_dead", "directions": 1, "delays": [[0.2, 0.2, 0.2, 0.2]]}, {"name": "scanner_terminal_green", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3]]}, {"name": "scanner_terminal_off", "directions": 1, "delays": [[1.0]]}, {"name": "scanner_terminal_red", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3]]}, {"name": "scanner_yellow", "directions": 1, "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3]]}]}
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Resources/Textures/Buildings/medical_scanner.rsi/scanner_off.png
Normal file
|
After Width: | Height: | Size: 867 B |
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Resources/Textures/Buildings/medical_scanner.rsi/scanner_red.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 883 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |