BodySystem stuff 2: overused boogaloo (#1174)

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
GlassEclipse
2020-07-02 13:51:14 -05:00
committed by GitHub
parent 7d346ede28
commit 610ab8bf50
30 changed files with 1493 additions and 688 deletions

View File

@@ -102,7 +102,8 @@ namespace Content.Client.UserInterface
{
_template = template;
_parts = parts;
_slots = new List<string>();
_slots = new List<string>();
BodyPartList.Clear();
foreach (var (key, value) in _parts)
{
_slots.Add(key); //We have to do this since ItemLists only return the index of what item is selected and dictionaries don't allow you to explicitly grab things by index.

View File

@@ -1,166 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Storage;
using Content.Client.Interfaces.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Content.Shared.BodySystem;
using System.Globalization;
namespace Content.Client.BodySystem
{
[RegisterComponent]
public class ClientSurgeryToolComponent : SharedSurgeryToolComponent
{
private SurgeryToolWindow Window;
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case OpenSurgeryUIMessage msg:
HandleOpenSurgeryUIMessage();
break;
case CloseSurgeryUIMessage msg:
HandleCloseSurgeryUIMessage();
break;
case UpdateSurgeryUIMessage msg:
HandleUpdateSurgeryUIMessage(msg);
break;
}
}
public override void OnAdd()
{
base.OnAdd();
Window = new SurgeryToolWindow() { SurgeryToolEntity = this };
}
public override void OnRemove()
{
Window.Dispose();
base.OnRemove();
}
private void HandleOpenSurgeryUIMessage()
{
Window.Open();
}
private void HandleCloseSurgeryUIMessage()
{
Window.Close();
}
private void HandleUpdateSurgeryUIMessage(UpdateSurgeryUIMessage surgeryUIState)
{
Window.BuildDisplay(surgeryUIState.Targets);
}
private class SurgeryToolWindow : SS14Window
{
private Control _VSplitContainer;
private VBoxContainer _bodyPartList;
public ClientSurgeryToolComponent SurgeryToolEntity;
protected override Vector2? CustomSize => (300, 400);
public SurgeryToolWindow()
{
Title = "Select surgery target...";
RectClipContent = true;
_VSplitContainer = new VBoxContainer();
var listScrollContainer = new ScrollContainer
{
SizeFlagsVertical = SizeFlags.FillExpand,
SizeFlagsHorizontal = SizeFlags.FillExpand,
HScrollEnabled = true,
VScrollEnabled = true
};
_bodyPartList = new VBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand
};
listScrollContainer.AddChild(_bodyPartList);
_VSplitContainer.AddChild(listScrollContainer);
Contents.AddChild(_VSplitContainer);
}
public override void Close()
{
SurgeryToolEntity.SendNetworkMessage(new CloseSurgeryUIMessage());
base.Close();
}
public void BuildDisplay(Dictionary<string, string> targets)
{
_bodyPartList.DisposeAllChildren();
foreach (var(slotName, partname) in targets)
{
var button = new BodyPartButton(slotName);
button.ActualButton.OnToggled += OnButtonPressed;
button.LimbName.Text = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(slotName + " - " + partname);
//button.SpriteView.Sprite = sprite;
_bodyPartList.AddChild(button);
}
}
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
{
var parent = (BodyPartButton) args.Button.Parent;
SurgeryToolEntity.SendNetworkMessage(new SelectSurgeryUIMessage(parent.LimbSlotName));
}
}
private class BodyPartButton : PanelContainer
{
public Button ActualButton { get; }
public SpriteView SpriteView { get; }
public Control EntityControl { get; }
public Label LimbName { get; }
public string LimbSlotName { get; }
public BodyPartButton(string slotName)
{
LimbSlotName = slotName;
ActualButton = new Button
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
ToggleMode = true,
MouseFilter = MouseFilterMode.Stop
};
AddChild(ActualButton);
var hBoxContainer = new HBoxContainer();
SpriteView = new SpriteView
{
CustomMinimumSize = new Vector2(32.0f, 32.0f)
};
LimbName = new Label
{
SizeFlagsVertical = SizeFlags.ShrinkCenter,
Text = "N/A",
};
hBoxContainer.AddChild(SpriteView);
hBoxContainer.AddChild(LimbName);
EntityControl = new Control
{
SizeFlagsHorizontal = SizeFlags.FillExpand
};
hBoxContainer.AddChild(EntityControl);
AddChild(hBoxContainer);
}
}
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Players;
using Content.Shared.BodySystem;
namespace Content.Client.BodySystem
{
//TODO : Make window close if target or surgery tool gets too far away from user.
/// <summary>
/// Generic client-side UI list popup that allows users to choose from an option of limbs or organs to operate on.
/// </summary>
public class GenericSurgeryBoundUserInterface : BoundUserInterface
{
private GenericSurgeryWindow _window;
public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
_window = new GenericSurgeryWindow();
_window.OpenCentered();
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
switch (message)
{
case RequestBodyPartSurgeryUIMessage msg:
HandleBodyPartRequest(msg);
break;
case RequestMechanismSurgeryUIMessage msg:
HandleMechanismRequest(msg);
break;
case RequestBodyPartSlotSurgeryUIMessage msg:
HandleBodyPartSlotRequest(msg);
break;
}
}
private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg)
{
_window.BuildDisplay(msg.Targets, BodyPartSelectedCallback);
}
private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg)
{
_window.BuildDisplay(msg.Targets, MechanismSelectedCallback);
}
private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg)
{
_window.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback);
}
private void BodyPartSelectedCallback(int selectedOptionData)
{
SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData));
}
private void MechanismSelectedCallback(int selectedOptionData)
{
SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData));
}
private void BodyPartSlotSelectedCallback(int selectedOptionData)
{
SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window.Dispose();
}
}
}

View File

@@ -0,0 +1,124 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Content.Client.BodySystem
{
public class GenericSurgeryWindow : SS14Window
{
public delegate void CloseCallback();
public delegate void OptionSelectedCallback(int selectedOptionData);
private Control _vSplitContainer;
private VBoxContainer _optionsBox;
private OptionSelectedCallback _optionSelectedCallback;
protected override Vector2? CustomSize => (300, 400);
public GenericSurgeryWindow()
{
Title = Loc.GetString("Select surgery target...");
RectClipContent = true;
_vSplitContainer = new VBoxContainer();
var listScrollContainer = new ScrollContainer
{
SizeFlagsVertical = SizeFlags.FillExpand,
SizeFlagsHorizontal = SizeFlags.FillExpand,
HScrollEnabled = true,
VScrollEnabled = true
};
_optionsBox = new VBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand
};
listScrollContainer.AddChild(_optionsBox);
_vSplitContainer.AddChild(listScrollContainer);
Contents.AddChild(_vSplitContainer);
}
public void BuildDisplay(Dictionary<string, int> data, OptionSelectedCallback callback)
{
_optionsBox.DisposeAllChildren();
_optionSelectedCallback = callback;
foreach (var (displayText, callbackData) in data)
{
var button = new SurgeryButton(callbackData);
button.SetOnToggleBehavior(OnButtonPressed);
button.SetDisplayText(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(displayText));
_optionsBox.AddChild(button);
}
}
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
{
var pressedButton = (SurgeryButton)args.Button.Parent;
_optionSelectedCallback(pressedButton.CallbackData);
}
}
class SurgeryButton : PanelContainer
{
public Button Button { get; }
private SpriteView SpriteView { get; }
private Control EntityControl { get; }
private Label DisplayText { get; }
public int CallbackData { get; }
public SurgeryButton(int callbackData)
{
CallbackData = callbackData;
Button = new Button
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
ToggleMode = true,
MouseFilter = MouseFilterMode.Stop
};
AddChild(Button);
var hBoxContainer = new HBoxContainer();
SpriteView = new SpriteView
{
CustomMinimumSize = new Vector2(32.0f, 32.0f)
};
DisplayText = new Label
{
SizeFlagsVertical = SizeFlags.ShrinkCenter,
Text = "N/A",
};
hBoxContainer.AddChild(SpriteView);
hBoxContainer.AddChild(DisplayText);
EntityControl = new Control
{
SizeFlagsHorizontal = SizeFlags.FillExpand
};
hBoxContainer.AddChild(EntityControl);
AddChild(hBoxContainer);
}
public void SetDisplayText(string text)
{
DisplayText.Text = text;
}
public void SetOnToggleBehavior(Action<BaseButton.ButtonToggledEventArgs> behavior)
{
Button.OnToggled += behavior;
}
public void SetSprite()
{
//button.SpriteView.Sprite = sprite;
}
}
}

View File

@@ -130,7 +130,8 @@
"PowerReceiver",
"Wire",
"StressTestMovement",
"Toys"
"Toys",
"SurgeryTool"
};
}
}

View File

@@ -452,7 +452,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head);
foreach (var head in heads)
{
var droppedHead = bodyManagerComponent.DisconnectBodyPart(head, true);
var droppedHead = bodyManagerComponent.DropBodyPart(head);
_storage.Insert(droppedHead);
headCount++;
}

View File

@@ -14,15 +14,14 @@ using Content.Server.GameObjects.EntitySystems;
namespace Content.Server.BodySystem {
/// <summary>
/// Component representing the many BodyParts attached to each other.
/// Component representing a collection of <see cref="BodyPart">BodyParts</see> attached to each other.
/// </summary>
[RegisterComponent]
public class BodyManagerComponent : Component, IInteractHand {
public class BodyManagerComponent : Component, IBodyPartContainer {
public sealed override string Name => "BodyManager";
#pragma warning disable CS0649
[Dependency]
private IPrototypeManager _prototypeManager;
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore
[ViewVariables]
@@ -32,45 +31,63 @@ namespace Content.Server.BodySystem {
private Dictionary<string, BodyPart> _partDictionary = new Dictionary<string, BodyPart>();
/// <summary>
/// The BodyTemplate that this BodyManagerComponent is adhering to.
/// The <see cref="BodyTemplate"/> that this BodyManagerComponent is adhering to.
/// </summary>
public BodyTemplate Template => _template;
/// <summary>
/// Maps BodyTemplate slot name to the BodyPart object filling it (if there is one).
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="BodyPart"/> object filling it (if there is one).
/// </summary>
public Dictionary<string, BodyPart> PartDictionary => _partDictionary;
/// <summary>
/// List of all BodyParts in this body, taken from the keys of _parts.
/// List of all occupied slots in this body, taken from the values of _parts.
/// </summary>
public IEnumerable<BodyPart> Parts
public IEnumerable<string> AllSlots
{
get
{
return _template.Slots.Keys;
}
}
/// <summary>
/// List of all occupied slots in this body, taken from the values of _parts.
/// </summary>
public IEnumerable<string> OccupiedSlots
{
get
{
return _partDictionary.Keys;
}
}
/// <summary>
/// List of all <see cref="BodyPart">BodyParts</see> in this body, taken from the keys of _parts.
/// </summary>
public IEnumerable<BodyPart> Parts {
get {
return _partDictionary.Values;
}
}
/// <summary>
/// Recursive search that returns whether a given BodyPart is connected to the center BodyPart. Not efficient (O(n^2)), but most bodies don't have a ton of BodyParts.
/// Recursive search that returns whether a given <see cref="BodyPart"/> is connected to the center <see cref="BodyPart"/>.
/// Not efficient (O(n^2)), but most bodies don't have a ton of <see cref="BodyPart">BodyParts</see>.
/// </summary>
protected bool ConnectedToCenterPart(BodyPart target)
{
public bool ConnectedToCenterPart(BodyPart target) {
List<string> searchedSlots = new List<string> { };
if (TryGetSlotName(target, out string result))
return false;
return ConnectedToCenterPartRecursion(searchedSlots, result);
}
protected bool ConnectedToCenterPartRecursion(List<string> searchedSlots, string slotName)
{
private bool ConnectedToCenterPartRecursion(List<string> searchedSlots, string slotName) {
TryGetBodyPart(slotName, out BodyPart part);
if (part == GetCenterBodyPart())
if (part != null && part == GetCenterBodyPart())
return true;
searchedSlots.Add(slotName);
if (TryGetBodyPartConnections(slotName, out List<string> connections))
{
foreach (string connection in connections)
{
if (TryGetBodyPartConnections(slotName, out List<string> connections)) {
foreach (string connection in connections) {
if (!searchedSlots.Contains(connection) && ConnectedToCenterPartRecursion(searchedSlots, connection))
return true;
}
@@ -80,56 +97,92 @@ namespace Content.Server.BodySystem {
}
/// <summary>
/// Returns the central BodyPart of this body based on the BodyTemplate. For humans, this is the torso. Returns null if not found.
/// Returns the central <see cref="BodyPart"/> of this body based on the <see cref="BodyTemplate"/>. For humans, this is the torso. Returns null if not found.
/// </summary>
protected BodyPart GetCenterBodyPart()
{
public BodyPart GetCenterBodyPart() {
_partDictionary.TryGetValue(_template.CenterSlot, out BodyPart center);
return center;
}
/// <summary>
/// Grabs the BodyPart in the given slotName if there is one. Returns true if a BodyPart is found, false otherwise. If false, result will be null.
/// Returns whether the given slot name exists within the current <see cref="BodyTemplate"/>.
/// </summary>
protected bool TryGetBodyPart(string slotName, out BodyPart result)
public bool SlotExists(string slotName)
{
return _template.SlotExists(slotName);
}
/// <summary>
/// Grabs the <see cref="BodyPart"/> in the given slotName if there is one. Returns true if a <see cref="BodyPart"/> is found,
/// false otherwise. If false, result will be null.
/// </summary>
public bool TryGetBodyPart(string slotName, out BodyPart result) {
return _partDictionary.TryGetValue(slotName, out result);
}
/// <summary>
/// Grabs the slotName that the given BodyPart resides in. Returns true if the BodyPart is part of this body, false otherwise. If false, result will be null.
/// Grabs the slotName that the given <see cref="BodyPart"/> resides in. Returns true if the <see cref="BodyPart"/> is
/// part of this body and a slot is found, false otherwise. If false, result will be null.
/// </summary>
protected bool TryGetSlotName(BodyPart part, out string result)
{
public bool TryGetSlotName(BodyPart part, out string result) {
result = _partDictionary.FirstOrDefault(x => x.Value == part).Key; //We enforce that there is only one of each value in the dictionary, so we can iterate through the dictionary values to get the key from there.
return result == null;
}
/// <summary>
/// Grabs the <see cref="BodyPartType"/> of the given slotName if there is one. Returns true if the slot is found, false otherwise. If false, result will be null.
/// </summary>
public bool TryGetSlotType(string slotName, out BodyPartType result)
{
return _template.Slots.TryGetValue(slotName, out result);
}
/// <summary>
/// Grabs the names of all connected slots to the given slotName from the template. Returns true if connections are found to the slotName, false otherwise. If false, connections will be null.
/// </summary>
protected bool TryGetBodyPartConnections(string slotName, out List<string> connections)
{
public bool TryGetBodyPartConnections(string slotName, out List<string> connections) {
return _template.Connections.TryGetValue(slotName, out connections);
}
/// <summary>
/// Grabs all occupied slots connected to the given slot, regardless of whether the target slot is occupied. Returns true if successful, false if there was an error or no connected BodyParts were found.
/// </summary>
public bool TryGetBodyPartConnections(string slotName, out List<BodyPart> result)
{
result = null;
if (!_template.Connections.TryGetValue(slotName, out List<string> connections))
return false;
List<BodyPart> toReturn = new List<BodyPart>();
foreach (string connection in connections)
{
if (TryGetBodyPart(connection, out BodyPart bodyPartResult))
{
toReturn.Add(bodyPartResult);
}
}
if (toReturn.Count <= 0)
return false;
result = toReturn;
return true;
}
/////////
///////// Server-specific stuff
/////////
public bool InteractHand(InteractHandEventArgs eventArgs)
{
//TODO: remove organs?
return false;
}
public override void ExposeData(ObjectSerializer serializer) {
base.ExposeData(serializer);
string templateName = null;
serializer.DataField(ref templateName, "BaseTemplate", "bodyTemplate.Humanoid");
if (serializer.Reading)
{
if (serializer.Reading) {
if (!_prototypeManager.TryIndex(templateName, out BodyTemplatePrototype templateData))
throw new InvalidOperationException("No BodyTemplatePrototype was found with the name " + templateName + " while loading a BodyTemplate!"); //Should never happen unless you fuck up the prototype.
@@ -144,18 +197,14 @@ namespace Content.Server.BodySystem {
}
/// <summary>
/// Loads the given preset - forcefully changes all limbs found in both the preset and this template!
/// Loads the given <see cref="BodyPreset"/> - forcefully changes all limbs found in both the preset and this template!
/// </summary>
public void LoadBodyPreset(BodyPreset preset)
{
foreach (var (slotName, type) in _template.Slots)
{
if (!preset.PartIDs.TryGetValue(slotName, out string partID))
{ //For each slot in our BodyManagerComponent's template, try and grab what the ID of what the preset says should be inside it.
public void LoadBodyPreset(BodyPreset preset) {
foreach (var (slotName, type) in _template.Slots) {
if (!preset.PartIDs.TryGetValue(slotName, out string partID)) { //For each slot in our BodyManagerComponent's template, try and grab what the ID of what the preset says should be inside it.
continue; //If the preset doesn't define anything for it, continue.
}
if (!_prototypeManager.TryIndex(partID, out BodyPartPrototype newPartData))
{ //Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed.
if (!_prototypeManager.TryIndex(partID, out BodyPartPrototype newPartData)) { //Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed.
throw new InvalidOperationException("BodyPart prototype with ID " + partID + " could not be found!");
}
_partDictionary.Remove(slotName); //Try and remove an existing limb if that exists.
@@ -164,88 +213,129 @@ namespace Content.Server.BodySystem {
}
/// <summary>
/// Changes the current BodyTemplate to the new BodyTemplate. Attempts to keep previous BodyParts if there is a slot for them in both BodyTemplates.
/// Changes the current <see cref="BodyTemplate"/> to the given <see cref="BodyTemplate"/>. Attempts to keep previous <see cref="BodyPart">BodyParts</see>
/// if there is a slot for them in both <see cref="BodyTemplate"/>.
/// </summary>
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
{
foreach (KeyValuePair<string, BodyPart> part in _partDictionary)
{
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate) {
foreach (KeyValuePair<string, BodyPart> part in _partDictionary) {
//TODO: Make this work.
}
}
/// <summary>
/// Grabs all limbs of the given type in this body.
/// Grabs all <see cref="BodyPart">BodyParts</see> of the given type in this body.
/// </summary>
public List<BodyPart> GetBodyPartsOfType(BodyPartType type)
{
public List<BodyPart> GetBodyPartsOfType(BodyPartType type) {
List<BodyPart> toReturn = new List<BodyPart>();
foreach (var (slotName, bodyPart) in _partDictionary)
{
foreach (var (slotName, bodyPart) in _partDictionary) {
if (bodyPart.PartType == type)
toReturn.Add(bodyPart);
}
return toReturn;
}
/// <summary>
/// Disconnects the given BodyPart reference, potentially dropping other BodyParts if they were hanging off it.
/// Installs the given <see cref="BodyPart"/> into the given slot. Returns true if successful, false otherwise.
/// </summary>
/// <returns>Returns the dropped entity, or null if no part is dropped</returns>
public IEntity DisconnectBodyPart(BodyPart part, bool dropEntity)
public bool InstallBodyPart(BodyPart part, string slotName)
{
if (!SlotExists(slotName)) //Make sure the given slot exists
return false;
if (TryGetBodyPart(slotName, out BodyPart result)) //And that nothing is in it
return false;
_partDictionary.Add(slotName, part);
return true;
}
/// <summary>
/// Installs the given <see cref="DroppedBodyPartComponent"/> into the given slot, deleting the <see cref="IEntity"/> afterwards. Returns true if successful, false otherwise.
/// </summary>
public bool InstallDroppedBodyPart(DroppedBodyPartComponent part, string slotName)
{
if (!InstallBodyPart(part.ContainedBodyPart, slotName))
return false;
part.Owner.Delete();
return true;
}
/// <summary>
/// Disconnects the given <see cref="BodyPart"/> reference, potentially dropping other <see cref="BodyPart">BodyParts</see>
/// if they were hanging off it. Returns the IEntity representing the dropped BodyPart.
/// </summary>
public IEntity DropBodyPart(BodyPart part)
{
if (!_partDictionary.ContainsValue(part))
return null;
if (part != null)
{
string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key;
_partDictionary.Remove(slotName);
if (TryGetBodyPartConnections(slotName, out List<string> connections)) //Call disconnect on all limbs that were hanging off this limb.
{
foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit
{
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result))
{
DisconnectBodyPartByName(connectionName, dropEntity);
DisconnectBodyPartByName(connectionName, true);
}
}
}
_partDictionary.Remove(slotName);
if (dropEntity)
{
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
return partEntity;
}
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
return partEntity;
}
return null;
}
/// <summary>
/// Internal string version of DisconnectBodyPart for performance purposes.
/// Disconnects the given <see cref="BodyPart"/> reference, potentially dropping other <see cref="BodyPart">BodyParts</see> if they were hanging off it.
/// </summary>
private void DisconnectBodyPartByName(string name, bool dropEntity)
{
if (!TryGetBodyPart(name, out BodyPart part))
public void DisconnectBodyPart(BodyPart part, bool dropEntity) {
if (!_partDictionary.ContainsValue(part))
return;
if (part != null)
{
if (TryGetBodyPartConnections(name, out List<string> connections))
if (part != null) {
string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key;
_partDictionary.Remove(slotName);
if (TryGetBodyPartConnections(slotName, out List<string> connections)) //Call disconnect on all limbs that were hanging off this limb.
{
foreach (string connectionName in connections)
foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit
{
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result))
{
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result)) {
DisconnectBodyPartByName(connectionName, dropEntity);
}
}
}
_partDictionary.Remove(name);
if (dropEntity)
{
if (dropEntity) {
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
}
}
}
/// <summary>
/// Internal string version of DisconnectBodyPart for performance purposes. Yes, it is actually more performant.
/// </summary>
private void DisconnectBodyPartByName(string name, bool dropEntity) {
if (!TryGetBodyPart(name, out BodyPart part))
return;
if (part != null) {
_partDictionary.Remove(name);
if (TryGetBodyPartConnections(name, out List<string> connections)) {
foreach (string connectionName in connections) {
if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result)) {
DisconnectBodyPartByName(connectionName, dropEntity);
}
}
}
if (dropEntity) {
var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition);
partEntity.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(part);
}
}
}
}
}

View File

@@ -15,8 +15,8 @@ namespace Content.Server.BodySystem
/// <summary>
/// Data class representing a singular limb such as an arm or a leg. Typically held within a BodyManagerComponent,
/// which coordinates functions between BodyParts.
/// Data class representing a singular limb such as an arm or a leg. Typically held within either a <see cref="BodyManagerComponent"/>,
/// which coordinates functions between BodyParts, or a <see cref="DroppedBodyPartComponent"/>.
/// </summary>
public class BodyPart
{
@@ -31,13 +31,13 @@ namespace Content.Server.BodySystem
private int _sizeUsed = 0;
/// <summary>
/// Body part name.
/// The name of this BodyPart, often displayed to the user. For example, it could be named "advanced robotic arm".
/// </summary>
[ViewVariables]
public string Name { get; set; }
/// <summary>
/// Plural version of this body part's name.
/// Plural version of this BodyPart name.
/// </summary>
[ViewVariables]
public string Plural { get; set; }
@@ -55,19 +55,19 @@ namespace Content.Server.BodySystem
public string RSIState { get; set; }
/// <summary>
/// BodyPartType that this body part is considered.
/// <see cref="BodyPartType"/> that this BodyPart is considered to be. For example, BodyPartType.Arm.
/// </summary>
[ViewVariables]
public BodyPartType PartType { get; set; }
/// <summary>
/// Max HP of this body part.
/// Max HP of this BodyPart.
/// </summary>
[ViewVariables]
public int MaxDurability { get; set; }
/// <summary>
/// Current HP of this body part based on sum of all damage types.
/// Current HP of this BodyPart based on sum of all damage types.
/// </summary>
[ViewVariables]
public int CurrentDurability => MaxDurability - CurrentDamages.Damage;
@@ -79,42 +79,42 @@ namespace Content.Server.BodySystem
public AbstractDamageContainer CurrentDamages { get; set; }
/// <summary>
/// At what HP this body part is completely destroyed.
/// At what HP this BodyPartis completely destroyed.
/// </summary>
[ViewVariables]
public int DestroyThreshold { get; set; }
/// <summary>
/// Armor of the body part against attacks.
/// Armor of this BodyPart against attacks.
/// </summary>
[ViewVariables]
public float Resistance { get; set; }
/// <summary>
/// Determines many things: how many mechanisms can be fit inside a body part, fitting through tiny crevices, etc.
/// Determines many things: how many mechanisms can be fit inside this BodyPart, whether a body can fit through tiny crevices, etc.
/// </summary>
[ViewVariables]
public int Size { get; set; }
/// <summary>
/// What types of body parts this body part can attach to. For the most part, most limbs aren't universal and require extra work to attach between types.
/// What types of BodyParts this BodyPart can easily attach to. For the most part, most limbs aren't universal and require extra work to attach between types.
/// </summary>
[ViewVariables]
public BodyPartCompatibility Compatibility { get; set; }
/// <summary>
/// List of IExposeData properties, allowing for additional data classes to be attached to a limb, such as a "length" class to an arm.
/// List of <see cref="IExposeData"/> properties, allowing for additional data classes to be attached to a limb, such as a "length" class to an arm.
/// </summary>
[ViewVariables]
public List<IExposeData> Properties { get; set; }
/// <summary>
/// List of all Mechanisms currently inside this BodyPart.
/// List of all <see cref="Mechanism">Mechanisms</see> currently inside this BodyPart.
/// </summary>
[ViewVariables]
public List<Mechanism> Mechanisms => _mechanisms;
public BodyPart(){}
public BodyPart() { }
public BodyPart(BodyPartPrototype data)
{
@@ -124,32 +124,55 @@ namespace Content.Server.BodySystem
public bool CanAttachBodyPart(BodyPart toBeConnected)
{
return _surgeryData.CanAttachBodyPart(toBeConnected);
}
/// <summary>
/// Attempts to add a Mechanism. Returns true if successful, false if there was an error (e.g. not enough room in BodyPart). Use InstallDroppedMechanism if you want to easily install an IEntity with a DroppedMechanismComponent.
/// Returns whether the given <see cref="Mechanism"/> can be installed on this BodyPart.
/// </summary>
public bool InstallMechanism(Mechanism mechanism)
public bool CanInstallMechanism(Mechanism mechanism)
{
if (_sizeUsed + mechanism.Size > Size)
return false; //No space
_mechanisms.Add(mechanism);
_sizeUsed += mechanism.Size;
return true;
return _surgeryData.CanInstallMechanism(mechanism);
}
/// <summary>
/// Attempts to install a DroppedMechanismComponent into the given limb, potentially deleting the dropped IEntity. Returns true if successful, false if there was an error (e.g. not enough room in BodyPart).
/// Attempts to add a <see cref="Mechanism"/>. Returns true if successful, false if there was an error (e.g. not enough room in BodyPart). Call InstallDroppedMechanism instead if you want to easily install an IEntity with a DroppedMechanismComponent.
/// </summary>
public bool InstallDroppedMechanism(DroppedMechanismComponent droppedMechanism)
public bool TryInstallMechanism(Mechanism mechanism)
{
if (_sizeUsed + droppedMechanism.ContainedMechanism.Size > Size)
return false; //No space
InstallMechanism(droppedMechanism.ContainedMechanism);
if (CanInstallMechanism(mechanism))
{
_mechanisms.Add(mechanism);
_sizeUsed += mechanism.Size;
return true;
}
return false;
}
/// <summary>
/// Attempts to install a <see cref="DroppedMechanismComponent"/> into the given limb, potentially deleting the dropped <see cref="IEntity"/>. Returns true if successful, false if there was an error (e.g. not enough room in BodyPart).
/// </summary>
public bool TryInstallDroppedMechanism(DroppedMechanismComponent droppedMechanism)
{
if (!TryInstallMechanism(droppedMechanism.ContainedMechanism))
return false; //Installing the mechanism failed for some reason.
droppedMechanism.Owner.Delete();
return true;
}
/// <summary>
/// Tries to remove the given Mechanism reference from the given BodyPart reference. Returns null if there was an error in spawning the entity or removing the mechanism, otherwise returns a reference to the DroppedMechanismComponent on the newly spawned entity.
/// Tries to remove the given <see cref="Mechanism"/> reference from this BodyPart. Returns null if there was an error in spawning the entity or removing the mechanism, otherwise returns a reference to the <see cref="DroppedMechanismComponent"/> on the newly spawned entity.
/// </summary>
public DroppedMechanismComponent DropMechanism(IEntity dropLocation, Mechanism mechanismTarget)
{
@@ -165,7 +188,7 @@ namespace Content.Server.BodySystem
}
/// <summary>
/// Tries to destroy the given Mechanism in the given BodyPart. Returns false if there was an error, true otherwise. Does NOT spawn a dropped entity.
/// Tries to destroy the given <see cref="Mechanism"/> in the given BodyPart. Returns false if there was an error, true otherwise. Does NOT spawn a dropped entity.
/// </summary>
public bool DestroyMechanism(BodyPart bodyPartTarget, Mechanism mechanismTarget)
{
@@ -176,10 +199,14 @@ namespace Content.Server.BodySystem
return true;
}
/// <summary>
/// Returns whether the given SurgertToolType can be used on the current state of this BodyPart (e.g.
/// Returns whether the given <see cref="SurgeryType"/> can be used on the current state of this BodyPart.
/// </summary>
public bool SurgeryCheck(SurgeryToolType toolType)
public bool SurgeryCheck(SurgeryType toolType)
{
return _surgeryData.CheckSurgery(toolType);
}
@@ -187,13 +214,17 @@ namespace Content.Server.BodySystem
/// <summary>
/// Attempts to perform surgery on this BodyPart with the given tool. Returns false if there was an error, true if successful.
/// </summary>
public bool AttemptSurgery(SurgeryToolType toolType, BodyManagerComponent target, IEntity performer)
public bool AttemptSurgery(SurgeryType toolType, IBodyPartContainer target, ISurgeon surgeon, IEntity performer)
{
return _surgeryData.PerformSurgery(toolType, target, performer);
return _surgeryData.PerformSurgery(toolType, target, surgeon, performer);
}
/// <summary>
/// Loads the given BodyPartPrototype - current data on this BodyPart will be overwritten!
/// Loads the given <see cref="BodyPartPrototype"/> - current data on this <see cref="BodyPart"/> will be overwritten!
/// </summary>
public virtual void LoadFromPrototype(BodyPartPrototype data)
{

View File

@@ -8,29 +8,159 @@ using Content.Shared.BodySystem;
using Robust.Shared.ViewVariables;
using System.Globalization;
using Robust.Server.GameObjects;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.Player;
using Content.Shared.Interfaces;
using Robust.Shared.Interfaces.Random;
using System.Linq;
using Robust.Shared.Localization;
namespace Content.Server.BodySystem {
namespace Content.Server.BodySystem
{
/// <summary>
/// Component containing the data for a dropped BodyPart entity.
/// Component representing a dropped, tangible <see cref="BodyPart"/> entity.
/// </summary>
[RegisterComponent]
public class DroppedBodyPartComponent : Component {
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
public sealed override string Name => "DroppedBodyPart";
[ViewVariables]
private BodyPart _containedBodyPart;
public BodyPart ContainedBodyPart { get; set; }
private BoundUserInterface _userInterface;
private Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private IEntity _performerCache;
private BodyManagerComponent _bodyManagerComponentCache;
private int _idHash = 0;
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
public void TransferBodyPartData(BodyPart data)
{
_containedBodyPart = data;
Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_containedBodyPart.Name);
ContainedBodyPart = data;
Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(ContainedBodyPart.Name);
if (Owner.TryGetComponent<SpriteComponent>(out SpriteComponent component))
{
component.LayerSetRSI(0, data.RSIPath);
component.LayerSetState(0, data.RSIState);
}
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null)
return;
CloseAllSurgeryUIs();
_optionsCache.Clear();
_performerCache = null;
_bodyManagerComponentCache = null;
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out BodyManagerComponent bodyManager))
{
SendBodySlotListToUser(eventArgs, bodyManager);
}
}
private void SendBodySlotListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
{
var toSend = new Dictionary<string, int>(); //Create dictionary to send to client (text to be shown : data sent back if selected)
//Here we are trying to grab a list of all empty BodySlots adjancent to an existing BodyPart that can be attached to. i.e. an empty left hand slot, connected to an occupied left arm slot would be valid.
List<string> unoccupiedSlots = bodyManager.AllSlots.ToList().Except(bodyManager.OccupiedSlots.ToList()).ToList();
foreach (string slot in unoccupiedSlots)
{
if (bodyManager.TryGetSlotType(slot, out BodyPartType typeResult) && typeResult == ContainedBodyPart.PartType)
{
if (bodyManager.TryGetBodyPartConnections(slot, out List<BodyPart> bodypartResult))
{
foreach (BodyPart connectedPart in bodypartResult)
{
if (connectedPart.CanAttachBodyPart(ContainedBodyPart))
{
_optionsCache.Add(_idHash, slot);
toSend.Add(slot, _idHash++);
}
}
}
}
}
if (_optionsCache.Count > 0)
{
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
UpdateSurgeryUIBodyPartSlotRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession, toSend);
_performerCache = eventArgs.User;
_bodyManagerComponentCache = bodyManager;
}
else //If surgery cannot be performed, show message saying so.
{
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, Loc.GetString("You see no way to install the {0}.", Owner.Name));
}
}
/// <summary>
/// Called after the client chooses from a list of possible BodyPartSlots to install the limb on.
/// </summary>
private void HandleReceiveBodyPartSlot(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
//TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out object targetObject))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You see no useful way to attach the {0} anymore.", Owner.Name));
}
string target = targetObject as string;
if (!_bodyManagerComponentCache.InstallDroppedBodyPart(this, target))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You can't attach it!"));
}
else
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You attach the {0}.", ContainedBodyPart.Name));
}
}
public void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
}
public void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
}
public void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
}
public void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
switch (message.Message)
{
case ReceiveBodyPartSlotSurgeryUIMessage msg:
HandleReceiveBodyPartSlot(msg.SelectedOptionID);
break;
}
}
}
}

View File

@@ -1,15 +1,11 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using System.Collections.Generic;
using Content.Shared.BodySystem;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Shared.BodySystem {
namespace Content.Server.BodySystem {
/// <summary>
/// Stores data on what BodyPart(Prototypes) should fill a BodyTemplate. Used for loading complete body presets, like a "basic human" with all human limbs.
/// Stores data on what <see cref="BodyPartPrototype">BodyPartPrototypes</see> should fill a BodyTemplate. Used for loading complete body presets, like a "basic human" with all human limbs.
/// </summary>
public class BodyPreset {
private string _name;
@@ -19,7 +15,7 @@ namespace Content.Shared.BodySystem {
public string Name => _name;
/// <summary>
/// Maps a template slot to the ID of the BodyPart that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
/// Maps a template slot to the ID of the <see cref="BodyPart"> that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
/// </summary>
[ViewVariables]
public Dictionary<string, string> PartIDs => _partIDs;

View File

@@ -1,53 +1,45 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Content.Shared.BodySystem;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Shared.BodySystem {
namespace Content.Server.BodySystem {
/// <summary>
/// This class is a data capsule representing the standard format of a body. For instance, the "humanoid" BodyTemplate
/// defines two arms, each connected to a torso and so on. Capable of loading data from a BodyTemplatePrototype.
/// This class is a data capsule representing the standard format of a <see cref="BodyManagerComponent"/>. For instance, the "humanoid" BodyTemplate
/// defines two arms, each connected to a torso and so on. Capable of loading data from a <see cref="BodyTemplatePrototype"/>.
/// </summary>
public class BodyTemplate {
private int _hash;
private string _name;
private string _centerSlot;
private Dictionary<string, BodyPartType> _slots = new Dictionary<string, BodyPartType>();
private Dictionary<string, List<string>> _connections = new Dictionary<string, List<string>>();
[ViewVariables]
public int Hash => _hash;
[ViewVariables]
public string Name => _name;
public string Name;
/// <summary>
/// The name of the center BodyPart. For humans, this is set to "torso". Used in many calculations.
/// </summary>
[ViewVariables]
public string CenterSlot => _centerSlot;
public string CenterSlot { get; set; }
/// <summary>
/// Maps all parts on this template to its BodyPartType. For instance, "right arm" is mapped to "BodyPartType.arm" on the humanoid template.
/// </summary>
[ViewVariables]
public Dictionary<string, BodyPartType> Slots => _slots;
public Dictionary<string, BodyPartType> Slots { get; set; }
/// <summary>
/// Maps limb name to the list of their connections to other limbs. For instance, on the humanoid template "torso" is mapped to a list containing "right arm", "left arm",
/// "left leg", and "right leg". Only one of the limbs in a connection has to map it, i.e. humanoid template chooses to map "head" to "torso" and not the other way around.
/// "left leg", and "right leg". This is mapped both ways during runtime, but in the prototype only one way has to be defined, i.e., "torso" to "left arm" will automatically
/// map "left arm" to "torso".
/// </summary>
[ViewVariables]
public Dictionary<string, List<string>> Connections => _connections;
public Dictionary<string, List<string>> Connections { get; set; }
public BodyTemplate()
{
_name = "empty";
Name = "empty";
Slots = new Dictionary<string, BodyPartType>();
Connections = new Dictionary<string, List<string>>();
CenterSlot = "";
}
public BodyTemplate(BodyTemplatePrototype data)
@@ -55,34 +47,66 @@ namespace Content.Shared.BodySystem {
LoadFromPrototype(data);
}
/// <summary>
/// Somewhat costly operation. Stores an integer unique to this exact BodyTemplate in _hash when called.
/// </summary>
private void CacheHashCode()
public bool Equals(BodyTemplate other)
{
int hash = 0;
foreach (var(key, value) in _slots)
return GetHashCode() == other.GetHashCode();
}
/// <summary>
/// Returns whether the given slot exists in this BodyTemplate.
/// </summary>
public bool SlotExists(string slotName)
{
foreach (string slot in Slots.Keys)
{
hash = HashCode.Combine<int, int>(hash, key.GetHashCode());
if (slot == slotName) //string comparison xd
return true;
}
foreach (var (key, value) in _connections)
return false;
}
/// <summary>
/// Returns an integer unique to this BodyTemplate's layout. It does not matter in which order the Connections or Slots are defined.
/// </summary>
public override int GetHashCode()
{
int slotsHash = 0;
int connectionsHash = 0;
foreach (var(key, value) in Slots)
{
hash = HashCode.Combine<int, int>(hash, key.GetHashCode());
foreach (var connection in value)
int slot = key.GetHashCode();
slot = HashCode.Combine<int, int>(slot, value.GetHashCode());
slotsHash = slotsHash ^ slot;
}
List<int> connections = new List<int>();
foreach (var (key, value) in Connections)
{
foreach (var targetBodyPart in value)
{
hash = HashCode.Combine<int, int>(hash, connection.GetHashCode());
int connection = key.GetHashCode() ^ targetBodyPart.GetHashCode();
if (!connections.Contains(connection))
connections.Add(connection);
}
}
_hash = hash;
foreach (int connection in connections)
{
connectionsHash = connectionsHash ^ connection;
}
int hash = HashCode.Combine<int, int, int>(slotsHash, connectionsHash, CenterSlot.GetHashCode());
if (hash == 0) //One of the unit tests considers 0 to be an error, but it will be 0 if the BodyTemplate is empty, so let's shift that up to 1.
hash++;
return hash;
}
public virtual void LoadFromPrototype(BodyTemplatePrototype data)
{
_name = data.Name;
_centerSlot = data.CenterSlot;
_slots = data.Slots;
_connections = data.Connections;
CacheHashCode();
Name = data.Name;
CenterSlot = data.CenterSlot;
Slots = data.Slots;
Connections = data.Connections;
}
}
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Interfaces.GameObjects;
using System;
using System.Collections.Generic;
using System.Text;
namespace Content.Server.BodySystem
{
/// <summary>
/// Making a class inherit from this interface allows you to do many things with it in the <see cref="ISurgeryData"/> class. This includes passing
/// it as an argument to a <see cref="ISurgeryData.SurgeryAction"/> delegate, as to later typecast it back to the original class type. Every BodyPart also needs an
/// IBodyPartContainer to be its parent (i.e. the BodyManagerComponent holds many BodyParts, each of which have an upward reference to it).
/// </summary>
public interface IBodyPartContainer
{
}
}

View File

@@ -8,32 +8,179 @@ using Content.Shared.BodySystem;
using Robust.Shared.ViewVariables;
using System.Globalization;
using Robust.Server.GameObjects;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.Log;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.Interfaces.GameObjects;
using System.Diagnostics;
using Robust.Shared.Localization;
namespace Content.Server.BodySystem {
/// <summary>
/// Component containing the data for a dropped Mechanism entity.
/// </summary>
/// Component representing a dropped, tangible <see cref="Mechanism"/> entity.
/// </summary>
[RegisterComponent]
public class DroppedMechanismComponent : Component
public class DroppedMechanismComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore 649
public sealed override string Name => "DroppedMechanism";
[ViewVariables]
private Mechanism _containedMechanism;
public Mechanism ContainedMechanism { get; private set; }
public Mechanism ContainedMechanism => _containedMechanism;
private BoundUserInterface _userInterface;
private Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private IEntity _performerCache;
private BodyManagerComponent _bodyManagerComponentCache;
private int _idHash = 0;
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
public void InitializeDroppedMechanism(Mechanism data)
{
_containedMechanism = data;
Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_containedMechanism.Name);
ContainedMechanism = data;
Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(ContainedMechanism.Name);
if (Owner.TryGetComponent<SpriteComponent>(out SpriteComponent component))
{
component.LayerSetRSI(0, data.RSIPath);
component.LayerSetState(0, data.RSIState);
}
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null)
return;
CloseAllSurgeryUIs();
_optionsCache.Clear();
_performerCache = null;
_bodyManagerComponentCache = null;
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out BodyManagerComponent bodyManager))
{
SendBodyPartListToUser(eventArgs, bodyManager);
}
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out DroppedBodyPartComponent droppedBodyPart))
{
if (droppedBodyPart.ContainedBodyPart == null)
{
Logger.Debug("Installing a mechanism was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!");
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
}
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
{
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, Loc.GetString("You can't fit it in!"));
}
}
}
public override void ExposeData(ObjectSerializer serializer)
{
//This is a temporary way to have spawnable hard-coded DroppedMechanismComponent prototypes
//In the future (when it becomes possible) DroppedMechanismComponent should be auto-generated from the Mechanism prototypes
string debugLoadMechanismData = "";
base.ExposeData(serializer);
serializer.DataField(ref debugLoadMechanismData, "debugLoadMechanismData", "");
if (serializer.Reading && debugLoadMechanismData != "")
{
_prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data);
InitializeDroppedMechanism(new Mechanism(data));
}
}
private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
{
var toSend = new Dictionary<string, int>(); //Create dictionary to send to client (text to be shown : data sent back if selected)
foreach (var (key, value) in bodyManager.PartDictionary)
{ //For each limb in the target, add it to our cache if it is a valid option.
if (value.CanInstallMechanism(ContainedMechanism))
{
_optionsCache.Add(_idHash, value);
toSend.Add(key + ": " + value.Name, _idHash++);
}
}
if (_optionsCache.Count > 0)
{
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession, toSend);
_performerCache = eventArgs.User;
_bodyManagerComponentCache = bodyManager;
}
else //If surgery cannot be performed, show message saying so.
{
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, Loc.GetString("You see no way to install the {0}.", Owner.Name));
}
}
/// <summary>
/// Called after the client chooses from a list of possible BodyParts that can be operated on.
/// </summary>
private void HandleReceiveBodyPart(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
//TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out object targetObject))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name));
}
BodyPart target = targetObject as BodyPart;
if (!target.TryInstallDroppedMechanism(this))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You can't fit it in!"));
}
else
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You jam the {1} inside {0:them}.", _performerCache, ContainedMechanism.Name));
}
}
public void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
}
public void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
}
public void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
}
public void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
switch (message.Message)
{
case ReceiveBodyPartSurgeryUIMessage msg:
HandleReceiveBodyPart(msg.SelectedOptionID);
break;
}
}
}
}

View File

@@ -12,7 +12,7 @@ using YamlDotNet.RepresentationModel;
namespace Content.Server.BodySystem {
/// <summary>
/// Data class representing a persistent item inside a BodyPart. This includes livers, eyes, cameras, brains, explosive implants, binary communicators, etc.
/// Data class representing a persistent item inside a <see cref="BodyPart"/>. This includes livers, eyes, cameras, brains, explosive implants, binary communicators, and other things.
/// </summary>
public class Mechanism {
@@ -20,13 +20,13 @@ namespace Content.Server.BodySystem {
public string Name { get; set; }
/// <summary>
/// Description shown in a mechanism installation console or when examining an uninstalled mechanism.
/// Professional description of the Mechanism.
/// </summary>
[ViewVariables]
public string Description { get; set; }
/// <summary>
/// The message to display upon examining a mob with this mechanism installed. If the string is empty (""), no message will be displayed.
/// The message to display upon examining a mob with this Mechanism installed. If the string is empty (""), no message will be displayed.
/// </summary>
[ViewVariables]
public string ExamineMessage { get; set; }
@@ -44,37 +44,37 @@ namespace Content.Server.BodySystem {
public string RSIState { get; set; }
/// <summary>
/// Max HP of this mechanism.
/// Max HP of this Mechanism.
/// </summary>
[ViewVariables]
public int MaxDurability { get; set; }
/// <summary>
/// Current HP of this mechanism.
/// Current HP of this Mechanism.
/// </summary>
[ViewVariables]
public int CurrentDurability { get; set; }
/// <summary>
/// At what HP this mechanism is completely destroyed.
/// At what HP this Mechanism is completely destroyed.
/// </summary>
[ViewVariables]
public int DestroyThreshold { get; set; }
/// <summary>
/// Armor of this mechanism against attacks.
/// Armor of this Mechanism against attacks.
/// </summary>
[ViewVariables]
public int Resistance { get; set; }
/// <summary>
/// Determines a handful of things - mostly whether this mechanism can fit into a BodyPart.
/// Determines a handful of things - mostly whether this Mechanism can fit into a BodyPart.
/// </summary>
[ViewVariables]
public int Size { get; set; }
/// <summary>
/// What kind of BodyParts this mechanism can be installed into.
/// What kind of BodyParts this Mechanism can be easily installed into.
/// </summary>
[ViewVariables]
public BodyPartCompatibility Compatibility { get; set; }
@@ -84,8 +84,13 @@ namespace Content.Server.BodySystem {
LoadFromPrototype(data);
}
/// <summary>
/// Loads the given MechanismPrototype - current data on this Mechanism will be overwritten!
/// Loads the given <see cref="MechanismPrototype"/> - current data on this Mechanism will be overwritten!
/// </summary>
public void LoadFromPrototype(MechanismPrototype data)
{

View File

@@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Server.BodySystem;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.BodySystem;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Items;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Weapon.Melee
{
[RegisterComponent]
public class ServerSurgeryToolComponent : SharedSurgeryToolComponent, IAfterInteract
{
public HashSet<IPlayerSession> SubscribedSessions = new HashSet<IPlayerSession>();
private Dictionary<string, BodyPart> _surgeryOptionsCache = new Dictionary<string, BodyPart>();
private BodyManagerComponent _targetCache;
private IEntity _performerCache;
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (eventArgs.Target == null)
return;
if (eventArgs.Target.TryGetComponent<BodySystem.BodyManagerComponent>(out BodySystem.BodyManagerComponent bodyManager))
{
_surgeryOptionsCache.Clear();
var toSend = new Dictionary<string, string>();
foreach (var(key, value) in bodyManager.PartDictionary) {
if (value.SurgeryCheck(_surgeryToolClass))
{
_surgeryOptionsCache.Add(key, value);
toSend.Add(key, value.Name);
}
}
if (_surgeryOptionsCache.Count > 0)
{
OpenSurgeryUI(eventArgs.User);
UpdateSurgeryUI(eventArgs.User, toSend);
_performerCache = eventArgs.User;
_targetCache = bodyManager;
}
}
}
/// <summary>
/// Called after the user selects a surgery target.
/// </summary>
void PerformSurgery(SelectSurgeryUIMessage msg)
{
//TODO: sanity checks to see whether user is in range, body is still same, etc etc
if (!_surgeryOptionsCache.TryGetValue(msg.TargetSlot, out BodyPart target))
{
Logger.Debug("Error when trying to perform surgery on bodypart in slot " + msg.TargetSlot + ": it was not found!");
throw new InvalidOperationException();
}
if (!target.AttemptSurgery(_surgeryToolClass, _targetCache, _performerCache))
{
Logger.Debug("Error when trying to perform surgery on bodypart " + target.Name + "!");
throw new InvalidOperationException();
}
CloseSurgeryUI(_performerCache);
}
public void OpenSurgeryUI(IEntity character)
{
var user_session = character.GetComponent<BasicActorComponent>().playerSession;
SubscribeSession(user_session);
SendNetworkMessage(new OpenSurgeryUIMessage(), user_session.ConnectedClient);
}
public void UpdateSurgeryUI(IEntity character, Dictionary<string, string> options)
{
var user_session = character.GetComponent<BasicActorComponent>().playerSession;
if (user_session.AttachedEntity == null)
{
UnsubscribeSession(user_session);
return;
}
SendNetworkMessage(new UpdateSurgeryUIMessage(options), user_session.ConnectedClient);
}
public void CloseSurgeryUI(IEntity character)
{
var user_session = character.GetComponent<BasicActorComponent>().playerSession;
SubscribeSession(user_session);
SendNetworkMessage(new CloseSurgeryUIMessage(), user_session.ConnectedClient);
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
{
base.HandleNetworkMessage(message, channel, session);
if (session == null)
{
throw new ArgumentException(nameof(session));
}
switch (message)
{
case CloseSurgeryUIMessage msg:
UnsubscribeSession(session as IPlayerSession);
break;
case SelectSurgeryUIMessage msg:
PerformSurgery(msg);
break;
}
}
public void SubscribeSession(IPlayerSession session)
{
if (!SubscribedSessions.Contains(session))
{
session.PlayerStatusChanged += HandlePlayerSessionChangeEvent;
SubscribedSessions.Add(session);
}
}
public void UnsubscribeSession(IPlayerSession session)
{
if (SubscribedSessions.Contains(session))
{
SubscribedSessions.Remove(session);
SendNetworkMessage(new CloseSurgeryUIMessage(), session.ConnectedClient);
}
}
public void HandlePlayerSessionChangeEvent(object obj, SessionStatusEventArgs SSEA)
{
if (SSEA.NewStatus != SessionStatus.InGame)
{
UnsubscribeSession(SSEA.Session);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.BodySystem;
using Robust.Shared.Interfaces.GameObjects;
using System;
using System.Collections.Generic;
namespace Content.Server.BodySystem
{
/// <summary>
/// Interface representing an entity capable of performing surgery (performing operations on an <see cref="ISurgeryData"/> class).
/// For an example see <see cref="SurgeryToolComponent"/>, which inherits from this class.
/// </summary>
public interface ISurgeon
{
/// <summary>
/// How long it takes to perform a single surgery step (in seconds).
/// </summary>
public float BaseOperationTime { get; set; }
public delegate void MechanismRequestCallback(Mechanism target, IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
/// <summary>
/// When performing a surgery, the <see cref="ISurgeryData"/> may sometimes require selecting from a set of Mechanisms to operate on.
/// This function is called in that scenario, and it is expected that you call the callback with one mechanism from the provided list.
/// </summary>
public void RequestMechanism(List<Mechanism> options, MechanismRequestCallback callback);
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.BodySystem;
using Content.Shared.GameObjects;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Content.Server.BodySystem
{
//TODO: add checks to close UI if user walks too far away from tool or target.
/// <summary>
/// Server-side component representing a generic tool capable of performing surgery. For instance, the scalpel.
/// </summary>
[RegisterComponent]
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
{
public override string Name => "SurgeryTool";
public override uint? NetID => ContentNetIDs.SURGERY;
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; }
private float _baseOperateTime;
private SurgeryType _surgeryType;
private HashSet<IPlayerSession> _subscribedSessions = new HashSet<IPlayerSession>();
private BoundUserInterface _userInterface;
private Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private IEntity _performerCache;
private BodyManagerComponent _bodyManagerComponentCache;
private ISurgeon.MechanismRequestCallback _callbackCache;
private int _idHash = 0;
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null)
return;
CloseAllSurgeryUIs();
_optionsCache.Clear();
_performerCache = null;
_bodyManagerComponentCache = null;
_callbackCache = null;
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out BodyManagerComponent bodyManager)) //Attempt surgery on a BodyManagerComponent by sending a list of operatable BodyParts to the client to choose from
{
var toSend = new Dictionary<string, int>(); //Create dictionary to send to client (text to be shown : data sent back if selected)
foreach (var(key, value) in bodyManager.PartDictionary) { //For each limb in the target, add it to our cache if it is a valid option.
if (value.SurgeryCheck(_surgeryType))
{
_optionsCache.Add(_idHash, value);
toSend.Add(key + ": " + value.Name, _idHash++);
}
}
if (_optionsCache.Count > 0)
{
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession, toSend);
_performerCache = eventArgs.User; //Also, cache the data.
_bodyManagerComponentCache = bodyManager;
}
else //If surgery cannot be performed, show message saying so.
{
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, "You see no useful way to use the " + Owner.Name + ".");
}
}
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out DroppedBodyPartComponent droppedBodyPart)) //Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI
{
_performerCache = eventArgs.User;
if (droppedBodyPart.ContainedBodyPart == null) //Throw error if the DroppedBodyPart has no data in it.
{
Logger.Debug("Surgery was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!");
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
}
if (droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType)) //If surgery can be performed...
{
if (!droppedBodyPart.ContainedBodyPart.AttemptSurgery(_surgeryType, droppedBodyPart, this, eventArgs.User)) //...do the surgery.
{
Logger.Debug("Error when trying to perform surgery on bodypart " + eventArgs.User.Name + "!"); //Log error if the surgery fails somehow.
throw new InvalidOperationException();
}
}
else //If surgery cannot be performed, show message saying so.
{
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User,"You see no useful way to use the " + Owner.Name + ".");
}
}
}
public void RequestMechanism(List<Mechanism> options, ISurgeon.MechanismRequestCallback callback)
{
var toSend = new Dictionary<string, int> ();
foreach (Mechanism mechanism in options)
{
_optionsCache.Add(_idHash, mechanism);
toSend.Add(mechanism.Name, _idHash++);
}
if (_optionsCache.Count > 0)
{
OpenSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent<BasicActorComponent>().playerSession, toSend);
_callbackCache = callback;
}
else
{
Logger.Debug("Error on callback from mechanisms: there were no viable options to choose from!");
throw new InvalidOperationException();
}
}
public void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
}
public void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
}
public void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
}
public void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
}
public void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
switch (message.Message)
{
case ReceiveBodyPartSurgeryUIMessage msg:
HandleReceiveBodyPart(msg.SelectedOptionID);
break;
case ReceiveMechanismSurgeryUIMessage msg:
HandleReceiveMechanism(msg.SelectedOptionID);
break;
}
}
/// <summary>
/// Called after the client chooses from a list of possible <see cref="BodyPart">BodyParts</see> that can be operated on.
/// </summary>
private void HandleReceiveBodyPart(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
//TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out object targetObject))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, "You see no useful way to use the " + Owner.Name + " anymore.");
}
BodyPart target = targetObject as BodyPart;
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, "You see no useful way to use the " + Owner.Name + " anymore.");
}
}
/// <summary>
/// Called after the client chooses from a list of possible <see cref="Mechanism">Mechanisms</see> to choose from.
/// </summary>
private void HandleReceiveMechanism(int key)
{
//TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
if (!_optionsCache.TryGetValue(key, out object targetObject))
{
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, "You see no useful way to use the " + Owner.Name + " anymore.");
}
Mechanism target = targetObject as Mechanism;
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
_callbackCache(target, _bodyManagerComponentCache, this, _performerCache);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _surgeryType, "surgeryType", SurgeryType.Incision);
serializer.DataField(ref _baseOperateTime, "baseOperateTime", 5);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.BodySystem;
using Content.Shared.Interfaces;
using Robust.Shared.Interfaces.GameObjects;
@@ -11,113 +12,188 @@ using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Server.BodySystem {
namespace Content.Server.BodySystem
{
/// <summary>
/// Data class representing the surgery state of a biological entity.
/// </summary>
public class BiologicalSurgeryData : ISurgeryData {
public class BiologicalSurgeryData : ISurgeryData
{
protected bool _skinOpened = false;
protected bool _vesselsClamped = false;
protected bool _skinRetracted = false;
protected Mechanism _targetOrgan;
protected List<Mechanism> _disconnectedOrgans = new List<Mechanism>();
public BiologicalSurgeryData(BodyPart parent) : base(parent) { }
public override SurgeryAction GetSurgeryStep(SurgeryToolType toolType)
public override SurgeryAction GetSurgeryStep(SurgeryType toolType)
{
if (_skinOpened)
if (toolType == SurgeryType.Amputation)
{
if (_vesselsClamped)
{
if (_skinRetracted)
{
if (_targetOrgan != null && toolType == SurgeryToolType.VesselCompression)
return RemoveOrganSurgery;
if (toolType == SurgeryToolType.Incision) //_targetOrgan is potentially given a value by DisconnectOrganSurgery.
return DisconnectOrganSurgery;
else if (toolType == SurgeryToolType.Cauterization)
return CautizerizeIncisionSurgery;
}
else
{
if (toolType == SurgeryToolType.Retraction)
return RetractSkinSurgery;
else if (toolType == SurgeryToolType.Cauterization)
return CautizerizeIncisionSurgery;
}
}
else
{
if (toolType == SurgeryToolType.VesselCompression)
return ClampVesselsSurgery;
else if (toolType == SurgeryToolType.Cauterization)
return CautizerizeIncisionSurgery;
}
return RemoveBodyPartSurgery;
}
else
if (!_skinOpened) //Case: skin is normal.
{
if (toolType == SurgeryToolType.Incision)
if (toolType == SurgeryType.Incision)
return OpenSkinSurgery;
}
else if (!_vesselsClamped) //Case: skin is opened, but not clamped.
{
if (toolType == SurgeryType.VesselCompression)
return ClampVesselsSurgery;
else if (toolType == SurgeryType.Cauterization)
return CautizerizeIncisionSurgery;
}
else if (!_skinRetracted) //Case: skin is opened and clamped, but not retracted.
{
if (toolType == SurgeryType.Retraction)
return RetractSkinSurgery;
else if (toolType == SurgeryType.Cauterization)
return CautizerizeIncisionSurgery;
}
else //Case: skin is fully open.
{
if (_parent.Mechanisms.Count > 0 && toolType == SurgeryType.VesselCompression && (_disconnectedOrgans.Except(_parent.Mechanisms).Count() != 0 || _parent.Mechanisms.Except(_disconnectedOrgans).Count() != 0))
return LoosenOrganSurgery;
else if (_disconnectedOrgans.Count > 0 && toolType == SurgeryType.Incision)
return RemoveOrganSurgery;
else if (toolType == SurgeryType.Cauterization)
return CautizerizeIncisionSurgery;
}
return null;
}
protected void OpenSkinSurgery(BodyManagerComponent target, IEntity performer)
public override string GetDescription(IEntity target)
{
ILocalizationManager localizationManager = IoCManager.Resolve<ILocalizationManager>();
performer.PopupMessage(performer, localizationManager.GetString("Cut open the skin..."));
string toReturn = "";
if (_skinOpened && !_vesselsClamped) //Case: skin is opened, but not clamped.
{
toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is prone to bleeding.\n", target, _parent.Name);
}
else if (_skinOpened && _vesselsClamped && !_skinRetracted) //Case: skin is opened and clamped, but not retracted.
{
toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is not retracted.\n", target, _parent.Name);
}
else if (_skinOpened && _vesselsClamped && _skinRetracted) //Case: skin is fully open.
{
toReturn += Loc.GetString("There is an incision on {0:their} {1}.\n", target, _parent.Name);
foreach (Mechanism mechanism in _disconnectedOrgans)
{
toReturn += Loc.GetString("{0:their} {1} is loose.\n", target, mechanism.Name);
}
}
return toReturn;
}
public override bool CanInstallMechanism(Mechanism toBeInstalled)
{
return _skinOpened && _vesselsClamped && _skinRetracted;
}
public override bool CanAttachBodyPart(BodyPart toBeConnected)
{
return true;
//TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached.
}
protected void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
performer.PopupMessage(performer, Loc.GetString("Cut open the skin..."));
//Delay?
_skinOpened = true;
}
protected void ClampVesselsSurgery(BodyManagerComponent target, IEntity performer)
protected void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
ILocalizationManager localizationManager = IoCManager.Resolve<ILocalizationManager>();
performer.PopupMessage(performer, localizationManager.GetString("Clamp the vessels..."));
performer.PopupMessage(performer, Loc.GetString("Clamp the vessels..."));
//Delay?
_vesselsClamped = true;
}
protected void RetractSkinSurgery(BodyManagerComponent target, IEntity performer)
protected void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
ILocalizationManager localizationManager = IoCManager.Resolve<ILocalizationManager>();
performer.PopupMessage(performer, localizationManager.GetString("Retract the skin..."));
performer.PopupMessage(performer, Loc.GetString("Retract the skin..."));
//Delay?
_skinRetracted = true;
}
protected void CautizerizeIncisionSurgery(BodyManagerComponent target, IEntity performer)
protected void CautizerizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
ILocalizationManager localizationManager = IoCManager.Resolve<ILocalizationManager>();
performer.PopupMessage(performer, localizationManager.GetString("Cauterize the incision..."));
performer.PopupMessage(performer, Loc.GetString("Cauterize the incision..."));
//Delay?
_skinOpened = false;
_vesselsClamped = false;
_skinRetracted = false;
}
protected void DisconnectOrganSurgery(BodyManagerComponent target, IEntity performer)
protected void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
Mechanism mechanismTarget = null;
//TODO: figureout popup, right now it just takes the first organ available if there is one
if (_parent.Mechanisms.Count > 0)
mechanismTarget = _parent.Mechanisms[0];
if (mechanismTarget != null)
if (_parent.Mechanisms.Count <= 0)
return;
List<Mechanism> toSend = new List<Mechanism>();
foreach (Mechanism mechanism in _parent.Mechanisms)
{
ILocalizationManager localizationManager = IoCManager.Resolve<ILocalizationManager>();
performer.PopupMessage(performer, localizationManager.GetString("Detach the organ..."));
//Delay?
_targetOrgan = mechanismTarget;
if (!_disconnectedOrgans.Contains(mechanism))
toSend.Add(mechanism);
}
if (toSend.Count > 0)
surgeon.RequestMechanism(toSend, LoosenOrganSurgeryCallback);
}
public void LoosenOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
if (target != null && _parent.Mechanisms.Contains(target))
{
performer.PopupMessage(performer, Loc.GetString("Loosen the organ..."));
//Delay?
_disconnectedOrgans.Add(target);
}
}
protected void RemoveOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
if (_disconnectedOrgans.Count <= 0)
return;
if (_disconnectedOrgans.Count == 1)
RemoveOrganSurgeryCallback(_disconnectedOrgans[0], container, surgeon, performer);
else
surgeon.RequestMechanism(_disconnectedOrgans, RemoveOrganSurgeryCallback);
}
protected void RemoveOrganSurgery(BodyManagerComponent target, IEntity performer)
public void RemoveOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
if (_targetOrgan != null)
if (target != null && _parent.Mechanisms.Contains(target))
{
ILocalizationManager localizationManager = IoCManager.Resolve<ILocalizationManager>();
performer.PopupMessage(performer, localizationManager.GetString("Remove the organ..."));
performer.PopupMessage(performer, Loc.GetString("Remove the organ..."));
//Delay?
_parent.DropMechanism(performer, _targetOrgan);
_parent.DropMechanism(performer, target);
_disconnectedOrgans.Remove(target);
}
}
protected void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
if (!(container is BodyManagerComponent)) //This surgery requires a DroppedBodyPartComponent.
return;
BodyManagerComponent bmTarget = (BodyManagerComponent) container;
performer.PopupMessage(performer, Loc.GetString("Saw off the limb!"));
//Delay?
bmTarget.DisconnectBodyPart(_parent, true);
}
}
}

View File

@@ -8,24 +8,31 @@ using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Server.BodySystem {
namespace Content.Server.BodySystem
{
/// <summary>
/// This data class represents the state of a BodyPart in regards to everything surgery related - whether there's an incision on it, whether the bone is broken, etc.
/// This data class represents the state of a <see cref="BodyPart"/> in regards to everything surgery related - whether there's an incision on it, whether the bone is broken, etc.
/// </summary>
public abstract class ISurgeryData {
public abstract class ISurgeryData
{
/// <summary>
/// The BodyPart this surgeryData is attached to. The ISurgeryData class should not exist without a BodyPart that it represents, and will not work correctly without it.
/// The <see cref="BodyPart"/> this surgeryData is attached to. The ISurgeryData class should not exist without a <see cref="BodyPart"/> that it
/// represents, and will throw errors if it is null.
/// </summary>
protected BodyPart _parent;
/// <summary>
/// The BodyPartType of the parent PartType.
/// The <see cref="BodyPartType"/> of the parent <see cref="BodyPart"/>.
/// </summary>
protected BodyPartType _parentType => _parent.PartType;
public delegate void SurgeryAction(BodyManagerComponent target, IEntity performer);
public delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
public ISurgeryData(BodyPart parent)
{
@@ -33,29 +40,44 @@ namespace Content.Server.BodySystem {
}
/// <summary>
/// Gets the delegate corresponding to the surgery step using the given SurgeryToolType. Returns null if no surgery step can be performed.
/// Returns the description of this current <see cref="BodyPart"/> to be shown upon observing the given entity.
/// </summary>
public abstract SurgeryAction GetSurgeryStep(SurgeryToolType toolType);
public abstract string GetDescription(IEntity target);
/// <summary>
/// Returns whether the given SurgeryToolType can be used to perform a surgery.
/// Returns whether a <see cref="Mechanism"/> can be installed into the <see cref="BodyPart"/> this ISurgeryData represents.
/// </summary>
public bool CheckSurgery(SurgeryToolType toolType)
public abstract bool CanInstallMechanism(Mechanism toBeInstalled);
/// <summary>
/// Returns whether the given <see cref="BodyPart"/> can be connected to the <see cref="BodyPart"/> this ISurgeryData represents.
/// </summary>
public abstract bool CanAttachBodyPart(BodyPart toBeConnected);
/// <summary>
/// Gets the delegate corresponding to the surgery step using the given <see cref="SurgeryType"/>. Returns null if no surgery step can be performed.
/// </summary>
public abstract SurgeryAction GetSurgeryStep(SurgeryType toolType);
/// <summary>
/// Returns whether the given <see cref="SurgeryType"/> can be used to perform a surgery on the BodyPart this <see cref="ISurgeryData"/> represents.
/// </summary>
public bool CheckSurgery(SurgeryType toolType)
{
return GetSurgeryStep(toolType) != null;
}
/// <summary>
/// Attempts to perform surgery with the given tooltype. Returns whether the operation was successful.
/// Attempts to perform surgery of the given <see cref="SurgeryType"/>. Returns whether the operation was successful.
/// </summary>
/// /// <param name="toolType">The SurgeryToolType used for this surgery.</param>
/// /// <param name="performer">The entity performing the surgery.</param>
public bool PerformSurgery(SurgeryToolType toolType, BodyManagerComponent target, IEntity performer)
/// <param name="surgeryType">The <see cref="SurgeryType"/> used for this surgery.</param>
/// <param name="performer">The entity performing the surgery.</param>
public bool PerformSurgery(SurgeryType surgeryType, IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{
SurgeryAction step = GetSurgeryStep(toolType);
SurgeryAction step = GetSurgeryStep(surgeryType);
if (step == null)
return false;
step(target, performer);
step(container, surgeon, performer);
return true;
}

View File

@@ -58,7 +58,7 @@ namespace Content.Shared.BodySystem {
tempConnections.Add(connection);
}
}
else if (slotConnections.Contains(slotName))
else if (slotConnections.Contains(targetSlotName))
{
tempConnections.Add(slotName);
}

View File

@@ -1,9 +1,20 @@

namespace Content.Shared.BodySystem
{
public enum BodyPartCompatibility { Mechanical, Biological, Universal };
/// <summary>
/// Used to determine whether a BodyPart can connect to another BodyPart.
/// </summary>
public enum BodyPartCompatibility { Universal, Biological, Mechanical };
/// <summary>
/// Each BodyPart has a BodyPartType used to determine a variety of things - for instance, what slots it can fit into.
/// </summary>
public enum BodyPartType { Other, Torso, Head, Arm, Hand, Leg, Foot };
public enum SurgeryToolType { Incision, Retraction, Cauterization, VesselCompression, Drilling, BoneSawing, Amputation }
/// <summary>
/// Defines a surgery operation that can be performed.
/// </summary>
public enum SurgeryType { Incision, Retraction, Cauterization, VesselCompression, Drilling, Amputation }
}

View File

@@ -8,24 +8,27 @@ using YamlDotNet.RepresentationModel;
namespace Content.Shared.BodySystem {
namespace Content.Shared.BodySystem
{
/// <summary>
/// Prototype for the Mechanism class.
/// </summary>
/// </summary>
[Prototype("mechanism")]
[NetSerializable, Serializable]
public class MechanismPrototype : IPrototype, IIndexedPrototype {
public class MechanismPrototype : IPrototype, IIndexedPrototype
{
private string _id;
private string _name;
private string _description;
private string _examineMessage;
private string _description;
private string _examineMessage;
private string _spritePath;
private string _rsiPath;
private string _rsiState;
private int _durability;
private int _destroyThreshold;
private int _resistance;
private int _size;
private int _destroyThreshold;
private int _resistance;
private int _size;
private BodyPartCompatibility _compatibility;
[ViewVariables]
@@ -34,11 +37,11 @@ namespace Content.Shared.BodySystem {
[ViewVariables]
public string Name => _name;
[ViewVariables]
[ViewVariables]
public string Description => _description;
[ViewVariables]
public string ExamineMessage => _examineMessage;
[ViewVariables]
public string ExamineMessage => _examineMessage;
[ViewVariables]
public string RSIPath => _rsiPath;
@@ -47,21 +50,22 @@ namespace Content.Shared.BodySystem {
public string RSIState => _rsiState;
[ViewVariables]
public int Durability => _durability;
public int Durability => _durability;
[ViewVariables]
public int DestroyThreshold => _destroyThreshold;
[ViewVariables]
public int DestroyThreshold => _destroyThreshold;
[ViewVariables]
public int Resistance => _resistance;
[ViewVariables]
public int Resistance => _resistance;
[ViewVariables]
public int Size => _size;
[ViewVariables]
public int Size => _size;
[ViewVariables]
public BodyPartCompatibility Compatibility => _compatibility;
public virtual void LoadFrom(YamlMappingNode mapping){
public virtual void LoadFrom(YamlMappingNode mapping)
{
var serializer = YamlObjectSerializer.NewReader(mapping);
serializer.DataField(ref _id, "id", string.Empty);
@@ -76,6 +80,6 @@ namespace Content.Shared.BodySystem {
serializer.DataField(ref _size, "size", 2);
serializer.DataField(ref _compatibility, "compatibility", BodyPartCompatibility.Universal);
}
}
}
}

View File

@@ -0,0 +1,79 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
using System;
using System.Collections.Generic;
using System.Text;
namespace Content.Shared.BodySystem
{
[Serializable, NetSerializable]
public class RequestBodyPartSurgeryUIMessage : BoundUserInterfaceMessage
{
public Dictionary<string, int> Targets;
public RequestBodyPartSurgeryUIMessage(Dictionary<string, int> targets)
{
Targets = targets;
}
}
[Serializable, NetSerializable]
public class RequestMechanismSurgeryUIMessage : BoundUserInterfaceMessage
{
public Dictionary<string, int> Targets;
public RequestMechanismSurgeryUIMessage(Dictionary<string, int> targets)
{
Targets = targets;
}
}
[Serializable, NetSerializable]
public class RequestBodyPartSlotSurgeryUIMessage : BoundUserInterfaceMessage
{
public Dictionary<string, int> Targets;
public RequestBodyPartSlotSurgeryUIMessage(Dictionary<string, int> targets)
{
Targets = targets;
}
}
[Serializable, NetSerializable]
public class ReceiveBodyPartSurgeryUIMessage : BoundUserInterfaceMessage
{
public int SelectedOptionID;
public ReceiveBodyPartSurgeryUIMessage(int selectedOptionID)
{
SelectedOptionID = selectedOptionID;
}
}
[Serializable, NetSerializable]
public class ReceiveMechanismSurgeryUIMessage : BoundUserInterfaceMessage
{
public int SelectedOptionID;
public ReceiveMechanismSurgeryUIMessage(int selectedOptionID)
{
SelectedOptionID = selectedOptionID;
}
}
[Serializable, NetSerializable]
public class ReceiveBodyPartSlotSurgeryUIMessage : BoundUserInterfaceMessage
{
public int SelectedOptionID;
public ReceiveBodyPartSlotSurgeryUIMessage(int selectedOptionID)
{
SelectedOptionID = selectedOptionID;
}
}
[NetSerializable, Serializable]
public enum GenericSurgeryUiKey
{
Key,
}
}

View File

@@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Shared.BodySystem {
public class SharedSurgeryToolComponent : Component {
protected SurgeryToolType _surgeryToolClass;
protected float _baseOperateTime;
public override string Name => "SurgeryTool";
public override uint? NetID => ContentNetIDs.SURGERY;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _surgeryToolClass, "surgeryToolClass", SurgeryToolType.Incision);
serializer.DataField(ref _baseOperateTime, "baseOperateTime", 5);
}
}
[Serializable, NetSerializable]
public class OpenSurgeryUIMessage : ComponentMessage
{
public OpenSurgeryUIMessage()
{
Directed = true;
}
}
[Serializable, NetSerializable]
public class CloseSurgeryUIMessage : ComponentMessage
{
public CloseSurgeryUIMessage()
{
Directed = true;
}
}
[Serializable, NetSerializable]
public class UpdateSurgeryUIMessage : ComponentMessage
{
public Dictionary<string, string> Targets;
public UpdateSurgeryUIMessage(Dictionary<string, string> targets)
{
Targets = targets;
Directed = true;
}
}
[Serializable, NetSerializable]
public class SelectSurgeryUIMessage : ComponentMessage
{
public string TargetSlot;
public SelectSurgeryUIMessage(string target)
{
TargetSlot = target;
}
}
}

View File

@@ -18,6 +18,8 @@ namespace Content.Shared.BodySystem
{ DamageClass.Toxin, new List<DamageType>{ DamageType.Cellular, DamageType.DNA}},
{ DamageClass.Airloss, new List<DamageType>{ DamageType.Airloss }}
};
//TODO: autogenerate this lol
public static readonly Dictionary<DamageType, DamageClass> DamageTypeToClass = new Dictionary<DamageType, DamageClass>
{
{ DamageType.Blunt, DamageClass.Brute },

View File

@@ -0,0 +1,56 @@

using Content.Server.BodySystem;
using Content.Shared.BodySystem;
using NUnit.Framework;
using Robust.UnitTesting;
using System;
using System.Collections.Generic;
namespace Content.Tests.Shared
{
[TestFixture, Parallelizable, TestOf(typeof(BodyTemplate))]
public class BodyTemplateTest : RobustUnitTest
{
[Test]
public void CheckBodyTemplateHashingWorks()
{
BodyTemplate a = new BodyTemplate();
BodyTemplate b = new BodyTemplate();
BodyTemplate c = new BodyTemplate();
BodyTemplate d = new BodyTemplate();
BodyTemplate e = new BodyTemplate();
a.Slots.Add("torso", BodyPartType.Torso);
a.Slots.Add("left arm", BodyPartType.Arm);
a.Connections.Add("torso", new List<string>() { "left arm" });
a.CenterSlot = "torso";
b.Slots.Add("left arm", BodyPartType.Arm);
b.Slots.Add("torso", BodyPartType.Torso);
b.Connections.Add("left arm", new List<string>() { "torso" });
b.CenterSlot = "torso";
c.Slots.Add("torso", BodyPartType.Head);
c.Slots.Add("left arm", BodyPartType.Arm);
c.Connections.Add("torso", new List<string>() { "left arm" });
a.CenterSlot = "torso";
d.Slots.Add("torso", BodyPartType.Torso);
d.Slots.Add("left arm", BodyPartType.Arm);
d.Slots.Add("left hand", BodyPartType.Hand);
d.Connections.Add("left arm", new List<string>() { "left hand" });
d.CenterSlot = "torso";
e.Slots.Add("torso", BodyPartType.Torso);
e.Slots.Add("left arm", BodyPartType.Arm);
e.Slots.Add("left hand", BodyPartType.Hand);
e.Connections.Add("left arm", new List<string>() { "torso" });
e.CenterSlot = "left hand";
Assert.That(a.Equals(b) && a.GetHashCode() != 0 && b.GetHashCode() != 0);
Assert.That(!a.Equals(c) && a.GetHashCode() != 0 && c.GetHashCode() != 0);
Assert.That(!d.Equals(e) && d.GetHashCode() != 0 && e.GetHashCode() != 0);
}
}
}

View File

@@ -1,3 +1,20 @@
- type: entity
parent: BaseDroppedMechanism
id: HeartMechanismDebug
name: "human heart (debug)"
components:
- type: Sprite
sprite: Objects/BodySystem/Organs/basic_human.rsi
state: heart_human
- type: Icon
sprite: Objects/BodySystem/Organs/basic_human.rsi
state: heart_human
- type: DroppedMechanism
debugLoadMechanismData: mechanism.Heart.BasicHuman
- type: mechanism
id: mechanism.Brain.BasicHuman
name: "human brain"

View File

@@ -1,11 +1,27 @@
- type: entity
parent: BaseItem
id: BaseSurgeryTool
abstract: true
components:
- type: SurgeryTool
surgeryType: Incision
baseOperateTime: 5
- type: UserInterface
interfaces:
- key: enum.GenericSurgeryUiKey.Key
type: GenericSurgeryBoundUserInterface
- type: entity
name: scalpel
parent: BaseItem
parent: BaseSurgeryTool
id: scalpel
desc: A surgical tool used to make incisions into flesh.
components:
- type: SurgeryTool
surgeryToolClass: Incision
surgeryType: Incision
baseOperateTime: 3
- type: Sprite
@@ -22,12 +38,12 @@
- type: entity
name: retractor
parent: BaseItem
parent: BaseSurgeryTool
id: retractor
desc: A surgical tool used to hold open incisions.
components:
- type: SurgeryTool
surgeryToolClass: Retraction
surgeryType: Retraction
baseOperateTime: 3
- type: Sprite
@@ -44,12 +60,12 @@
- type: entity
name: cautery
parent: BaseItem
parent: BaseSurgeryTool
id: cautery
desc: A surgical tool used to cauterize open wounds.
components:
- type: SurgeryTool
surgeryToolClass: Cauterization
surgeryType: Cauterization
baseOperateTime: 3
- type: Sprite
@@ -66,12 +82,12 @@
- type: entity
name: drill
parent: BaseItem
parent: BaseSurgeryTool
id: drill
desc: A surgical drill for making holes into hard material.
components:
- type: SurgeryTool
surgeryToolClass: Drilling
surgeryType: Drilling
baseOperateTime: 3
- type: Sprite
@@ -88,12 +104,12 @@
- type: entity
name: bone saw
parent: BaseItem
parent: BaseSurgeryTool
id: bone_saw
desc: A surgical tool used to cauterize open wounds.
components:
- type: SurgeryTool
surgeryToolClass: BoneSawing
surgeryType: Amputation
baseOperateTime: 3
- type: Sprite
@@ -106,14 +122,16 @@
- type: ItemCooldown
- type: MeleeWeapon
- type: entity
name: hemostat
parent: BaseItem
parent: BaseSurgeryTool
id: hemostat
desc: A surgical tool used to compress blood vessels to prevent bleeding.
components:
- type: SurgeryTool
surgeryToolClass: VesselCompression
surgeryType: VesselCompression
baseOperateTime: 3
- type: Sprite

View File

@@ -8,6 +8,10 @@
- type: Sprite
texture: Objects\BodySystem\Organs\eyes_grey.png
- type: Icon
- type: UserInterface
interfaces:
- key: enum.GenericSurgeryUiKey.Key
type: GenericSurgeryBoundUserInterface
- type: entity
name: "basedroppedmechanism"
@@ -19,3 +23,7 @@
- type: Sprite
texture: Objects\BodySystem\Organs\eyes_grey.png
- type: Icon
- type: UserInterface
interfaces:
- key: enum.GenericSurgeryUiKey.Key
type: GenericSurgeryBoundUserInterface

View File

@@ -254,3 +254,4 @@
- type: SpeciesVisualizer2D
- type: HumanoidAppearance