@@ -108,6 +108,7 @@ namespace Content.Client
|
||||
"AccessReader",
|
||||
"IdCardConsole",
|
||||
"Airlock",
|
||||
"MedicalScanner",
|
||||
};
|
||||
|
||||
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>
|
||||
/// Holds the damage template which controls the threshold and resistance settings for this species type
|
||||
/// </summary>
|
||||
private DamageTemplates DamageTemplate;
|
||||
public DamageTemplates DamageTemplate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 |