Change all of body system to use entities and components (#2074)
* Early commit * Early commit 2 * merging master broke my git * does anyone even read these * life is fleeting * it just works * this time passing integration tests * Remove hashset yaml serialization for now * You got a license for those nullables? * No examine, no context menu, part and mechanism parenting and visibility * Fix wrong brain sprite state * Removing layers was a mistake * just tear body system a new one and see if it still breathes * Remove redundant code * Add that comment back * Separate damage and body, component states, stomach rework * Add containers for body parts * Bring layers back pls * Fix parts magically changing color * Reimplement sprite layer visibility * Fix tests * Add leg test * Active legs is gone Crab rave * Merge fixes, rename DamageState to CurrentState * Remove IShowContextMenu and ICanExamine
This commit is contained in:
@@ -1,237 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
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.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
class AddHandCommand : IClientCommand
|
||||
{
|
||||
public string Command => "addhand";
|
||||
public string Description => "Adds a hand to your entity.";
|
||||
public string Help => $"Usage: {Command}";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
shell.SendText(player, "Only a player can run this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity == null)
|
||||
{
|
||||
shell.SendText(player, "You have no entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||
|
||||
shell.SendText(player, text);
|
||||
return;
|
||||
}
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
prototypeManager.TryIndex("bodyPart.LHand.BasicHuman", out BodyPartPrototype prototype);
|
||||
|
||||
var part = new BodyPart(prototype);
|
||||
var slot = part.GetHashCode().ToString();
|
||||
|
||||
body.Template.Slots.Add(slot, BodyPartType.Hand);
|
||||
body.TryAddPart(slot, part, true);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveHandCommand : IClientCommand
|
||||
{
|
||||
public string Command => "removehand";
|
||||
public string Description => "Removes a hand from your entity.";
|
||||
public string Help => $"Usage: {Command}";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
shell.SendText(player, "Only a player can run this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity == null)
|
||||
{
|
||||
shell.SendText(player, "You have no entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||
|
||||
shell.SendText(player, text);
|
||||
return;
|
||||
}
|
||||
|
||||
var hand = body.Parts.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand);
|
||||
if (hand.Value == null)
|
||||
{
|
||||
shell.SendText(player, "You have no hands.");
|
||||
}
|
||||
else
|
||||
{
|
||||
body.RemovePart(hand.Value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DestroyMechanismCommand : IClientCommand
|
||||
{
|
||||
public string Command => "destroymechanism";
|
||||
public string Description => "Destroys a mechanism from your entity";
|
||||
public string Help => $"Usage: {Command} <mechanism>";
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
shell.SendText(player, "Only a player can run this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.SendText(player, Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity == null)
|
||||
{
|
||||
shell.SendText(player, "You have no entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||
|
||||
shell.SendText(player, text);
|
||||
return;
|
||||
}
|
||||
|
||||
var mechanismName = string.Join(" ", args).ToLowerInvariant();
|
||||
|
||||
foreach (var part in body.Parts.Values)
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
if (mechanism.Name.ToLowerInvariant() == mechanismName)
|
||||
{
|
||||
part.DestroyMechanism(mechanism);
|
||||
shell.SendText(player, $"Mechanism with name {mechanismName} has been destroyed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shell.SendText(player, $"No mechanism was found with name {mechanismName}.");
|
||||
}
|
||||
}
|
||||
|
||||
class HurtCommand : IClientCommand
|
||||
{
|
||||
public string Command => "hurt";
|
||||
public string Description => "Ouch";
|
||||
public string Help => $"Usage: {Command} <type> <amount> (<entity uid/_>) (<ignoreResistance>)";
|
||||
|
||||
private void SendDamageTypes(IConsoleShell shell, IPlayerSession? player)
|
||||
{
|
||||
var msg = "";
|
||||
foreach (var dClass in Enum.GetNames(typeof(DamageClass)))
|
||||
{
|
||||
msg += $"\n{dClass}";
|
||||
var types = Enum.Parse<DamageClass>(dClass).ToTypes();
|
||||
foreach (var dType in types)
|
||||
{
|
||||
msg += $"\n - {dType}";
|
||||
}
|
||||
}
|
||||
shell.SendText(player, $"Damage Types:{msg}");
|
||||
}
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
// Check if we have enough for the dmg types to show
|
||||
if (args.Length > 0 && args[0] == "?")
|
||||
{
|
||||
SendDamageTypes(shell, player);
|
||||
return;
|
||||
}
|
||||
|
||||
// Not enough args
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.SendText(player, Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var ignoreResistance = false;
|
||||
var entityUid = player != null && player.AttachedEntityUid.HasValue ? player.AttachedEntityUid.Value : EntityUid.Invalid;
|
||||
if (!int.TryParse(args[1], out var amount) ||
|
||||
args.Length >= 3 && args[2] != "_" && !EntityUid.TryParse(args[2], out entityUid) ||
|
||||
args.Length >= 4 && !bool.TryParse(args[3], out ignoreResistance))
|
||||
{
|
||||
shell.SendText(player, Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entityUid == EntityUid.Invalid)
|
||||
{
|
||||
shell.SendText(player, "Not a valid entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetEntity(entityUid, out var ent))
|
||||
{
|
||||
shell.SendText(player, "Entity couldn't be found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ent.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
shell.SendText(player, "Entity can't be damaged.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Enum.TryParse<DamageClass>(args[0], true, out var dmgClass))
|
||||
{
|
||||
if (!damageable.ChangeDamage(dmgClass, amount, ignoreResistance))
|
||||
shell.SendText(player, "Something went wrong!");
|
||||
return;
|
||||
}
|
||||
// Fall back to DamageType
|
||||
else if (Enum.TryParse<DamageType>(args[0], true, out var dmgType))
|
||||
{
|
||||
if (!damageable.ChangeDamage(dmgType, amount, ignoreResistance))
|
||||
shell.SendText(player, "Something went wrong!");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SendDamageTypes(shell, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Server.Body.Surgery;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Shared.Body.Mechanism;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Part.Properties;
|
||||
using Content.Shared.Damage.DamageContainer;
|
||||
using Content.Shared.Damage.ResistanceSet;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 : IBodyPart
|
||||
{
|
||||
private IBodyManagerComponent? _body;
|
||||
|
||||
private readonly HashSet<IMechanism> _mechanisms = new HashSet<IMechanism>();
|
||||
|
||||
public BodyPart(BodyPartPrototype data)
|
||||
{
|
||||
SurgeryData = null!;
|
||||
Properties = new HashSet<IExposeData>();
|
||||
Name = null!;
|
||||
Plural = null!;
|
||||
RSIPath = null!;
|
||||
RSIState = null!;
|
||||
RSIMap = null!;
|
||||
Damage = null!;
|
||||
Resistances = null!;
|
||||
|
||||
LoadFromPrototype(data);
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public IBodyManagerComponent? Body
|
||||
{
|
||||
get => _body;
|
||||
set
|
||||
{
|
||||
var old = _body;
|
||||
_body = value;
|
||||
|
||||
if (value == null && old != null)
|
||||
{
|
||||
foreach (var mechanism in Mechanisms)
|
||||
{
|
||||
mechanism.RemovedFromBody(old);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var mechanism in Mechanisms)
|
||||
{
|
||||
mechanism.InstalledIntoBody();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Surgery.SurgeryData"/> class currently representing this BodyPart's
|
||||
/// surgery status.
|
||||
/// </summary>
|
||||
[ViewVariables] private SurgeryData SurgeryData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How much space is currently taken up by Mechanisms in this BodyPart.
|
||||
/// </summary>
|
||||
[ViewVariables] private int SizeUsed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
private HashSet<IExposeData> Properties { get; }
|
||||
|
||||
[ViewVariables] public string Name { get; private set; }
|
||||
|
||||
[ViewVariables] public string Plural { get; private set; }
|
||||
|
||||
[ViewVariables] public string RSIPath { get; private set; }
|
||||
|
||||
[ViewVariables] public string RSIState { get; private set; }
|
||||
|
||||
[ViewVariables] public Enum? RSIMap { get; set; }
|
||||
|
||||
// TODO: SpriteComponent rework
|
||||
[ViewVariables] public Color? RSIColor { get; set; }
|
||||
|
||||
[ViewVariables] public BodyPartType PartType { get; private set; }
|
||||
|
||||
[ViewVariables] public int Size { get; private set; }
|
||||
|
||||
[ViewVariables] public int MaxDurability { get; private set; }
|
||||
|
||||
[ViewVariables] public int CurrentDurability => MaxDurability - Damage.TotalDamage;
|
||||
|
||||
// TODO: Individual body part damage
|
||||
/// <summary>
|
||||
/// Current damage dealt to this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public DamageContainer Damage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Armor of this <see cref="IBodyPart"/> against damages.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ResistanceSet Resistances { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// At what HP this <see cref="IBodyPart"/> destroyed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int DestroyThreshold { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// What types of BodyParts this <see cref="IBodyPart"/> 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; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set of all <see cref="IMechanism"/> currently inside this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlyCollection<IMechanism> Mechanisms => _mechanisms;
|
||||
|
||||
/// <summary>
|
||||
/// Represents if body part is vital for creature.
|
||||
/// If the last vital body part is removed creature dies
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsVital { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by
|
||||
/// <see cref="IBodyManagerComponent.PreMetabolism"/> before
|
||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
public void PreMetabolism(float frameTime)
|
||||
{
|
||||
foreach (var mechanism in Mechanisms)
|
||||
{
|
||||
mechanism.PreMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by
|
||||
/// <see cref="IBodyManagerComponent.PostMetabolism"/> after
|
||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
public void PostMetabolism(float frameTime)
|
||||
{
|
||||
foreach (var mechanism in Mechanisms)
|
||||
{
|
||||
mechanism.PreMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add the given <see cref="BodyPartProperty"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if a <see cref="BodyPartProperty"/> of that type doesn't exist,
|
||||
/// false otherwise.
|
||||
/// </returns>
|
||||
public bool TryAddProperty(BodyPartProperty property)
|
||||
{
|
||||
if (HasProperty(property.GetType()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Properties.Add(property);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the given <see cref="BodyPartProperty"/> type.
|
||||
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||
/// </summary>
|
||||
/// <param name="property">The property if found, null otherwise.</param>
|
||||
/// <typeparam name="T">The type of the property to find.</typeparam>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool TryGetProperty<T>([NotNullWhen(true)] out T? property) where T : BodyPartProperty
|
||||
{
|
||||
property = (T?) Properties.FirstOrDefault(x => x.GetType() == typeof(T));
|
||||
|
||||
return property != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the given <see cref="BodyPartProperty"/> type.
|
||||
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool TryGetProperty(Type propertyType, [NotNullWhen(true)] out BodyPartProperty? property)
|
||||
{
|
||||
property = (BodyPartProperty?) Properties.First(x => x.GetType() == propertyType);
|
||||
|
||||
return property != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given type <see cref="T"/> is on this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// True if this <see cref="IBodyPart"/> has a property of type
|
||||
/// <see cref="T"/>, false otherwise.
|
||||
/// </returns>
|
||||
public bool HasProperty<T>() where T : BodyPartProperty
|
||||
{
|
||||
return Properties.Count(x => x.GetType() == typeof(T)) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a subtype of <see cref="BodyPartProperty"/> is on this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyType">
|
||||
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if this <see cref="IBodyPart"/> has a property of type
|
||||
/// <see cref="propertyType"/>, false otherwise.
|
||||
/// </returns>
|
||||
public bool HasProperty(Type propertyType)
|
||||
{
|
||||
return Properties.Count(x => x.GetType() == propertyType) > 0;
|
||||
}
|
||||
|
||||
public bool CanAttachPart(IBodyPart part)
|
||||
{
|
||||
return SurgeryData.CanAttachBodyPart(part);
|
||||
}
|
||||
|
||||
public bool CanInstallMechanism(IMechanism mechanism)
|
||||
{
|
||||
return SizeUsed + mechanism.Size <= Size &&
|
||||
SurgeryData.CanInstallMechanism(mechanism);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to install a mechanism onto this body part.
|
||||
/// Call <see cref="TryInstallDroppedMechanism"/> instead if you want to
|
||||
/// easily install an <see cref="IEntity"/> with a
|
||||
/// <see cref="DroppedMechanismComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism to try to install.</param>
|
||||
/// <returns>
|
||||
/// True if successful, false if there was an error
|
||||
/// (e.g. not enough room in <see cref="IBodyPart"/>).
|
||||
/// </returns>
|
||||
private bool TryInstallMechanism(IMechanism mechanism)
|
||||
{
|
||||
if (!CanInstallMechanism(mechanism))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddMechanism(mechanism);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to install a <see cref="DroppedMechanismComponent"/> into this
|
||||
/// <see cref="IBodyPart"/>, potentially deleting the dropped
|
||||
/// <see cref="IEntity"/>.
|
||||
/// </summary>
|
||||
/// <param name="droppedMechanism">The mechanism to install.</param>
|
||||
/// <returns>
|
||||
/// True if successful, false if there was an error
|
||||
/// (e.g. not enough room in <see cref="BodyPart"/>).
|
||||
/// </returns>
|
||||
public bool TryInstallDroppedMechanism(DroppedMechanismComponent droppedMechanism)
|
||||
{
|
||||
if (!TryInstallMechanism(droppedMechanism.ContainedMechanism))
|
||||
{
|
||||
return false; // Installing the mechanism failed for some reason.
|
||||
}
|
||||
|
||||
droppedMechanism.Owner.Delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryDropMechanism(IEntity dropLocation, IMechanism mechanismTarget,
|
||||
[NotNullWhen(true)] out DroppedMechanismComponent dropped)
|
||||
{
|
||||
dropped = null!;
|
||||
|
||||
if (!_mechanisms.Remove(mechanismTarget))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SizeUsed -= mechanismTarget.Size;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var position = dropLocation.Transform.Coordinates;
|
||||
var mechanismEntity = entityManager.SpawnEntity("BaseDroppedMechanism", position);
|
||||
|
||||
dropped = mechanismEntity.GetComponent<DroppedMechanismComponent>();
|
||||
dropped.InitializeDroppedMechanism(mechanismTarget);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to destroy the given <see cref="IMechanism"/> in this
|
||||
/// <see cref="IBodyPart"/>. Does NOT spawn a dropped entity.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Tries to destroy the given <see cref="IMechanism"/> in this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <param name="mechanismTarget">The mechanism to destroy.</param>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool DestroyMechanism(IMechanism mechanismTarget)
|
||||
{
|
||||
if (!RemoveMechanism(mechanismTarget))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SurgeryCheck(SurgeryType surgery)
|
||||
{
|
||||
return SurgeryData.CheckSurgery(surgery);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to perform surgery on this <see cref="IBodyPart"/> with the given
|
||||
/// tool.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false if there was an error.</returns>
|
||||
public bool AttemptSurgery(SurgeryType toolType, IBodyPartContainer target, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
return SurgeryData.PerformSurgery(toolType, target, surgeon, performer);
|
||||
}
|
||||
|
||||
private void AddMechanism(IMechanism mechanism)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
_mechanisms.Add(mechanism);
|
||||
SizeUsed += mechanism.Size;
|
||||
mechanism.Part = this;
|
||||
|
||||
mechanism.EnsureInitialize();
|
||||
|
||||
if (Body == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Body.Template.MechanismLayers.TryGetValue(mechanism.Id, out var mapString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(mapString, out var @enum))
|
||||
{
|
||||
Logger.Warning($"Template {Body.Template.Name} has an invalid RSI map key {mapString} for mechanism {mechanism.Id}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new MechanismSpriteAddedMessage(@enum);
|
||||
|
||||
Body.Owner.SendNetworkMessage(Body, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove the given <see cref="mechanism"/> from this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism to remove.</param>
|
||||
/// <returns>True if it was removed, false otherwise.</returns>
|
||||
private bool RemoveMechanism(IMechanism mechanism)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
if (!_mechanisms.Remove(mechanism))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SizeUsed -= mechanism.Size;
|
||||
mechanism.Part = null;
|
||||
|
||||
if (Body == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Body.Template.MechanismLayers.TryGetValue(mechanism.Id, out var mapString))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(mapString, out var @enum))
|
||||
{
|
||||
Logger.Warning($"Template {Body.Template.Name} has an invalid RSI map key {mapString} for mechanism {mechanism.Id}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
var message = new MechanismSpriteRemovedMessage(@enum);
|
||||
|
||||
Body.Owner.SendNetworkMessage(Body, message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the given <see cref="BodyPartPrototype"/>.
|
||||
/// Current data on this <see cref="IBodyPart"/> will be overwritten!
|
||||
/// </summary>
|
||||
protected virtual void LoadFromPrototype(BodyPartPrototype data)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
Name = data.Name;
|
||||
Plural = data.Plural;
|
||||
PartType = data.PartType;
|
||||
RSIPath = data.RSIPath;
|
||||
RSIState = data.RSIState;
|
||||
MaxDurability = data.Durability;
|
||||
IsVital = data.IsVital;
|
||||
|
||||
if (!prototypeManager.TryIndex(data.DamageContainerPresetId,
|
||||
out DamageContainerPrototype damageContainerData))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No {nameof(DamageContainerPrototype)} found with id {data.DamageContainerPresetId}");
|
||||
}
|
||||
|
||||
Damage = new DamageContainer(OnHealthChanged, damageContainerData);
|
||||
|
||||
if (!prototypeManager.TryIndex(data.ResistanceSetId, out ResistanceSetPrototype resistancesData))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No {nameof(ResistanceSetPrototype)} found with id {data.ResistanceSetId}");
|
||||
}
|
||||
|
||||
Resistances = new ResistanceSet(resistancesData);
|
||||
Size = data.Size;
|
||||
Compatibility = data.Compatibility;
|
||||
|
||||
Properties.Clear();
|
||||
Properties.UnionWith(data.Properties);
|
||||
|
||||
var surgeryDataType = Type.GetType(data.SurgeryDataName);
|
||||
|
||||
if (surgeryDataType == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No {nameof(Surgery.SurgeryData)} found with name {data.SurgeryDataName}");
|
||||
}
|
||||
|
||||
if (!surgeryDataType.IsSubclassOf(typeof(SurgeryData)))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Class {data.SurgeryDataName} is not a subtype of {nameof(Surgery.SurgeryData)} with id {data.ID}");
|
||||
}
|
||||
|
||||
SurgeryData = IoCManager.Resolve<IDynamicTypeFactory>().CreateInstance<SurgeryData>(surgeryDataType, new object[] {this});
|
||||
|
||||
foreach (var id in data.Mechanisms)
|
||||
{
|
||||
if (!prototypeManager.TryIndex(id, out MechanismPrototype mechanismData))
|
||||
{
|
||||
throw new InvalidOperationException($"No {nameof(MechanismPrototype)} found with id {id}");
|
||||
}
|
||||
|
||||
var mechanism = new Mechanism(mechanismData);
|
||||
|
||||
AddMechanism(mechanism);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHealthChanged(List<HealthChangeData> changes)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public bool SpawnDropped([NotNullWhen(true)] out IEntity dropped)
|
||||
{
|
||||
dropped = default!;
|
||||
|
||||
if (Body == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dropped = IoCManager.Resolve<IEntityManager>().SpawnEntity("BaseDroppedBodyPart", Body.Owner.Transform.Coordinates);
|
||||
|
||||
dropped.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Preset;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores data on what <see cref="BodyPartPrototype"></see> should
|
||||
/// fill a BodyTemplate.
|
||||
/// Used for loading complete body presets, like a "basic human" with all
|
||||
/// human limbs.
|
||||
/// </summary>
|
||||
public class BodyPreset
|
||||
{
|
||||
[ViewVariables] public bool Initialized { get; private set; }
|
||||
|
||||
[ViewVariables] public string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps a template slot to the ID of the <see cref="IBodyPart"/>
|
||||
/// that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> PartIDs { get; protected set; }
|
||||
|
||||
public virtual void Initialize(BodyPresetPrototype prototype)
|
||||
{
|
||||
DebugTools.Assert(!Initialized, $"{nameof(BodyPreset)} {Name} has already been initialized!");
|
||||
|
||||
Name = prototype.Name;
|
||||
PartIDs = prototype.PartIDs;
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.Body.Template;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
[ViewVariables] public bool Initialized { get; private set; }
|
||||
|
||||
[ViewVariables] public string Name { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the center BodyPart. For humans, this is set to "torso".
|
||||
/// Used in many calculations.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
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 { get; private set; } = new Dictionary<string, BodyPartType>();
|
||||
|
||||
/// <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".
|
||||
/// 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 { get; private set; } = new Dictionary<string, List<string>>();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> Layers { get; private set; } = new Dictionary<string, string>();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, string> MechanismLayers { get; private set; } = new Dictionary<string, string>();
|
||||
|
||||
public bool Equals(BodyTemplate other)
|
||||
{
|
||||
return GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given slot exists in this <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if it does, false otherwise.</returns>
|
||||
public bool HasSlot(string slotName)
|
||||
{
|
||||
return Slots.Keys.Any(slot => slot == slotName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the hash code for this instance of <see cref="BodyTemplate"/>.
|
||||
/// It does not matter in which order the Connections or Slots are defined.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An integer unique to this <see cref="BodyTemplate"/>'s layout.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var slotsHash = 0;
|
||||
var connectionsHash = 0;
|
||||
|
||||
foreach (var (key, value) in Slots)
|
||||
{
|
||||
var slot = key.GetHashCode();
|
||||
slot = HashCode.Combine(slot, value.GetHashCode());
|
||||
slotsHash ^= slot;
|
||||
}
|
||||
|
||||
var connections = new List<int>();
|
||||
foreach (var (key, value) in Connections)
|
||||
{
|
||||
foreach (var targetBodyPart in value)
|
||||
{
|
||||
var connection = key.GetHashCode() ^ targetBodyPart.GetHashCode();
|
||||
if (!connections.Contains(connection))
|
||||
{
|
||||
connections.Add(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
connectionsHash ^= connection;
|
||||
}
|
||||
|
||||
// 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.
|
||||
var hash = HashCode.Combine(
|
||||
CenterSlot.GetHashCode(),
|
||||
slotsHash,
|
||||
connectionsHash);
|
||||
|
||||
if (hash == 0)
|
||||
{
|
||||
hash++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public virtual void Initialize(BodyTemplatePrototype prototype)
|
||||
{
|
||||
DebugTools.Assert(!Initialized, $"{nameof(BodyTemplate)} {Name} has already been initialized!");
|
||||
|
||||
Name = prototype.Name;
|
||||
CenterSlot = prototype.CenterSlot;
|
||||
Slots = new Dictionary<string, BodyPartType>(prototype.Slots);
|
||||
Connections = new Dictionary<string, List<string>>(prototype.Connections);
|
||||
Layers = new Dictionary<string, string>(prototype.Layers);
|
||||
MechanismLayers = new Dictionary<string, string>(prototype.MechanismLayers);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.Body.Part.Properties;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
public interface IBodyPart
|
||||
{
|
||||
/// <summary>
|
||||
/// The body that this body part is currently in, if any.
|
||||
/// </summary>
|
||||
IBodyManagerComponent? Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="BodyPartType"/> that this <see cref="IBodyPart"/> is considered
|
||||
/// to be.
|
||||
/// For example, <see cref="BodyPartType.Arm"/>.
|
||||
/// </summary>
|
||||
BodyPartType PartType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this <see cref="IBodyPart"/>, often displayed to the user.
|
||||
/// For example, it could be named "advanced robotic arm".
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Plural version of this <see cref="IBodyPart"/> name.
|
||||
/// </summary>
|
||||
public string Plural { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines many things: how many mechanisms can be fit inside this
|
||||
/// <see cref="IBodyPart"/>, whether a body can fit through tiny crevices,
|
||||
/// etc.
|
||||
/// </summary>
|
||||
int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Max HP of this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
int MaxDurability { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current HP of this <see cref="IBodyPart"/> based on sum of all damage types.
|
||||
/// </summary>
|
||||
int CurrentDurability { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of all <see cref="IMechanism"/>s currently inside this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// To add and remove from this list see <see cref="AddMechanism"/> and
|
||||
/// <see cref="RemoveMechanism"/>
|
||||
/// </summary>
|
||||
IReadOnlyCollection<IMechanism> Mechanisms { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the RSI that represents this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
public string RSIPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI state that represents this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
public string RSIState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI map keys that this body part changes on the sprite.
|
||||
/// </summary>
|
||||
public Enum? RSIMap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI color of this body part.
|
||||
/// </summary>
|
||||
// TODO: SpriteComponent rework
|
||||
public Color? RSIColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If body part is vital
|
||||
/// </summary>
|
||||
public bool IsVital { get; }
|
||||
|
||||
bool HasProperty<T>() where T : BodyPartProperty;
|
||||
|
||||
bool HasProperty(Type type);
|
||||
|
||||
bool TryGetProperty<T>([NotNullWhen(true)] out T? property) where T : BodyPartProperty;
|
||||
|
||||
void PreMetabolism(float frameTime);
|
||||
|
||||
void PostMetabolism(float frameTime);
|
||||
|
||||
bool SpawnDropped([NotNullWhen(true)] out IEntity? dropped);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given <see cref="SurgeryType"/> can be used on
|
||||
/// the current state of this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if it can be used, false otherwise.</returns>
|
||||
bool SurgeryCheck(SurgeryType surgery);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if another <see cref="IBodyPart"/> can be connected to this one.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to connect.</param>
|
||||
/// <returns>True if it can be connected, false otherwise.</returns>
|
||||
bool CanAttachPart(IBodyPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="IMechanism"/> can be installed on this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if it can be installed, false otherwise.</returns>
|
||||
bool CanInstallMechanism(IMechanism mechanism);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove the given <see cref="IMechanism"/> reference from
|
||||
/// this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The newly spawned <see cref="DroppedMechanismComponent"/>, or null
|
||||
/// if there was an error in spawning the entity or removing the mechanism.
|
||||
/// </returns>
|
||||
bool TryDropMechanism(IEntity dropLocation, IMechanism mechanismTarget,
|
||||
[NotNullWhen(true)] out DroppedMechanismComponent dropped);
|
||||
|
||||
bool DestroyMechanism(IMechanism mechanism);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Server.Body.Surgery;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// Making a class inherit from this interface allows you to do many things with
|
||||
/// it in the <see cref="SurgeryData"/> class.
|
||||
/// This includes passing it as an argument to a
|
||||
/// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast
|
||||
/// it back to the original class type.
|
||||
/// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be
|
||||
/// its parent (i.e. the <see cref="BodyManagerComponent"/> holds many
|
||||
/// <see cref="IBodyPart"/>, each of which have an upward reference to it).
|
||||
/// </summary>
|
||||
public interface IBodyPartContainer
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// The behaviors of a brain, inhabitable by a player.
|
||||
/// </summary>
|
||||
public class BrainBehavior : MechanismBehavior
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Body.Network;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class HeartBehavior : MechanismBehavior
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
protected override Type? Network => typeof(CirculatoryNetwork);
|
||||
|
||||
public override void PreMetabolism(float frameTime)
|
||||
{
|
||||
// TODO do between pre and metabolism
|
||||
base.PreMetabolism(frameTime);
|
||||
|
||||
if (Mechanism.Body == null ||
|
||||
!Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update at most once per second
|
||||
_accumulatedFrameTime += frameTime;
|
||||
|
||||
// TODO: Move/accept/process bloodstream reagents only when the heart is pumping
|
||||
if (_accumulatedFrameTime >= 1)
|
||||
{
|
||||
// bloodstream.Update(_accumulatedFrameTime);
|
||||
_accumulatedFrameTime -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Body.Network;
|
||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class LungBehavior : MechanismBehavior
|
||||
{
|
||||
protected override Type? Network => typeof(RespiratoryNetwork);
|
||||
|
||||
public override void PreMetabolism(float frameTime)
|
||||
{
|
||||
base.PreMetabolism(frameTime);
|
||||
|
||||
if (Mechanism.Body == null ||
|
||||
!Mechanism.Body.Owner.TryGetComponent(out LungComponent? lung))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lung.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
|
||||
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// The behaviors a mechanism performs.
|
||||
/// </summary>
|
||||
public abstract class MechanismBehavior
|
||||
{
|
||||
private bool Initialized { get; set; }
|
||||
|
||||
private bool Removed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The network, if any, that this behavior forms when its mechanism is
|
||||
/// added and destroys when its mechanism is removed.
|
||||
/// </summary>
|
||||
protected virtual Type? Network { get; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Upward reference to the parent <see cref="Mechanisms.Mechanism"/> that this
|
||||
/// behavior is attached to.
|
||||
/// </summary>
|
||||
protected Mechanism Mechanism { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Called by a <see cref="Mechanism"/> to initialize this behavior.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism that owns this behavior.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// If the mechanism has already been initialized.
|
||||
/// </exception>
|
||||
public void Initialize(Mechanism mechanism)
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
throw new InvalidOperationException("This mechanism has already been initialized.");
|
||||
}
|
||||
|
||||
Mechanism = mechanism;
|
||||
|
||||
Initialize();
|
||||
|
||||
if (Mechanism.Body != null)
|
||||
{
|
||||
OnInstalledIntoBody();
|
||||
}
|
||||
|
||||
if (Mechanism.Part != null)
|
||||
{
|
||||
OnInstalledIntoPart();
|
||||
}
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a behavior is removed from a <see cref="Mechanism"/>.
|
||||
/// </summary>
|
||||
public void Remove()
|
||||
{
|
||||
OnRemove();
|
||||
TryRemoveNetwork(Mechanism.Body);
|
||||
|
||||
Mechanism = null!;
|
||||
Removed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the containing <see cref="IBodyPart"/> is attached to a
|
||||
/// <see cref="BodyManagerComponent"/>.
|
||||
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||
/// </summary>
|
||||
public void InstalledIntoBody()
|
||||
{
|
||||
TryAddNetwork();
|
||||
OnInstalledIntoBody();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||
/// installed into a <see cref="IBodyPart"/>.
|
||||
/// For instance, putting a brain into an empty head.
|
||||
/// </summary>
|
||||
public void InstalledIntoPart()
|
||||
{
|
||||
TryAddNetwork();
|
||||
OnInstalledIntoPart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the containing <see cref="IBodyPart"/> is removed from a
|
||||
/// <see cref="BodyManagerComponent"/>.
|
||||
/// For instance, cutting off ones head will call this on the brain inside.
|
||||
/// </summary>
|
||||
public void RemovedFromBody(IBodyManagerComponent old)
|
||||
{
|
||||
OnRemovedFromBody(old);
|
||||
TryRemoveNetwork(old);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||
/// removed from a <see cref="IBodyPart"/>.
|
||||
/// For instance, taking a brain out of ones head.
|
||||
/// </summary>
|
||||
public void RemovedFromPart(IBodyPart old)
|
||||
{
|
||||
OnRemovedFromPart(old);
|
||||
TryRemoveNetwork(old.Body);
|
||||
}
|
||||
|
||||
private void TryAddNetwork()
|
||||
{
|
||||
if (Network != null)
|
||||
{
|
||||
Mechanism.Body?.EnsureNetwork(Network);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryRemoveNetwork(IBodyManagerComponent? body)
|
||||
{
|
||||
if (Network != null)
|
||||
{
|
||||
body?.RemoveNetwork(Network);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by <see cref="Initialize"/> when this behavior is first initialized.
|
||||
/// </summary>
|
||||
protected virtual void Initialize() { }
|
||||
|
||||
protected virtual void OnRemove() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the containing <see cref="IBodyPart"/> is attached to a
|
||||
/// <see cref="BodyManagerComponent"/>.
|
||||
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||
/// </summary>
|
||||
protected virtual void OnInstalledIntoBody() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||
/// installed into a <see cref="IBodyPart"/>.
|
||||
/// For instance, putting a brain into an empty head.
|
||||
/// </summary>
|
||||
protected virtual void OnInstalledIntoPart() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the containing <see cref="IBodyPart"/> is removed from a
|
||||
/// <see cref="BodyManagerComponent"/>.
|
||||
/// For instance, cutting off ones head will call this on the brain inside.
|
||||
/// </summary>
|
||||
protected virtual void OnRemovedFromBody(IBodyManagerComponent old) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||
/// removed from a <see cref="IBodyPart"/>.
|
||||
/// For instance, taking a brain out of ones head.
|
||||
/// </summary>
|
||||
protected virtual void OnRemovedFromPart(IBodyPart old) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update when this behavior is connected to a
|
||||
/// <see cref="BodyManagerComponent"/>, but not while in a
|
||||
/// <see cref="DroppedMechanismComponent"/> or
|
||||
/// <see cref="DroppedBodyPartComponent"/>,
|
||||
/// before <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
public virtual void PreMetabolism(float frameTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update when this behavior is connected to a
|
||||
/// <see cref="BodyManagerComponent"/>, but not while in a
|
||||
/// <see cref="DroppedMechanismComponent"/> or
|
||||
/// <see cref="DroppedBodyPartComponent"/>,
|
||||
/// after <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
public virtual void PostMetabolism(float frameTime) { }
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Body.Network;
|
||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class StomachBehavior : MechanismBehavior
|
||||
{
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
protected override Type? Network => typeof(DigestiveNetwork);
|
||||
|
||||
public override void PreMetabolism(float frameTime)
|
||||
{
|
||||
base.PreMetabolism(frameTime);
|
||||
|
||||
if (Mechanism.Body == null ||
|
||||
!Mechanism.Body.Owner.TryGetComponent(out StomachComponent? stomach))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update at most once per second
|
||||
_accumulatedFrameTime += frameTime;
|
||||
|
||||
if (_accumulatedFrameTime >= 1)
|
||||
{
|
||||
stomach.Update(_accumulatedFrameTime);
|
||||
_accumulatedFrameTime -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body.Mechanisms.Behaviors;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
|
||||
namespace Content.Server.Body.Mechanisms
|
||||
{
|
||||
public interface IMechanism
|
||||
{
|
||||
string Id { get; }
|
||||
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Professional description of the <see cref="IMechanism"/>.
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
string ExamineMessage { get; set; }
|
||||
|
||||
// TODO: Make RSI properties sane
|
||||
/// <summary>
|
||||
/// Path to the RSI that represents this <see cref="IMechanism"/>.
|
||||
/// </summary>
|
||||
string RSIPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI state that represents this <see cref="IMechanism"/>.
|
||||
/// </summary>
|
||||
string RSIState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max HP of this <see cref="IMechanism"/>.
|
||||
/// </summary>
|
||||
int MaxDurability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current HP of this <see cref="IMechanism"/>.
|
||||
/// </summary>
|
||||
int CurrentDurability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// At what HP this <see cref="IMechanism"/> is completely destroyed.
|
||||
/// </summary>
|
||||
int DestroyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Armor of this <see cref="IMechanism"/> against attacks.
|
||||
/// </summary>
|
||||
int Resistance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines a handful of things - mostly whether this
|
||||
/// <see cref="IMechanism"/> can fit into a <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
// TODO: OnSizeChanged
|
||||
int Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// What kind of <see cref="IBodyPart"/> this <see cref="IMechanism"/> can be
|
||||
/// easily installed into.
|
||||
/// </summary>
|
||||
BodyPartCompatibility Compatibility { get; set; }
|
||||
|
||||
IReadOnlyList<MechanismBehavior> Behaviors { get; }
|
||||
|
||||
IBodyManagerComponent? Body { get; }
|
||||
|
||||
IBodyPart? Part { get; set; }
|
||||
|
||||
void EnsureInitialize();
|
||||
|
||||
void InstalledIntoBody();
|
||||
|
||||
void RemovedFromBody(IBodyManagerComponent old);
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by <see cref="IBodyPart.PreMetabolism"/> before
|
||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
void PreMetabolism(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by <see cref="IBodyPart.PostMetabolism"/> after
|
||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
void PostMetabolism(float frameTime);
|
||||
|
||||
void AddBehavior(MechanismBehavior behavior);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a behavior from this mechanism.
|
||||
/// </summary>
|
||||
/// <param name="behavior">The behavior to remove.</param>
|
||||
/// <returns>True if it was removed, false otherwise.</returns>
|
||||
bool RemoveBehavior(MechanismBehavior behavior);
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body.Mechanisms.Behaviors;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.Body.Mechanism;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body.Mechanisms
|
||||
{
|
||||
/// <summary>
|
||||
/// Data class representing a persistent item inside a <see cref="IBodyPart"/>.
|
||||
/// This includes livers, eyes, cameras, brains, explosive implants,
|
||||
/// binary communicators, and other things.
|
||||
/// </summary>
|
||||
public class Mechanism : IMechanism
|
||||
{
|
||||
private IBodyPart? _part;
|
||||
|
||||
public Mechanism(MechanismPrototype data)
|
||||
{
|
||||
Data = data;
|
||||
Id = null!;
|
||||
Name = null!;
|
||||
Description = null!;
|
||||
ExamineMessage = null!;
|
||||
RSIPath = null!;
|
||||
RSIState = null!;
|
||||
_behaviors = new List<MechanismBehavior>();
|
||||
}
|
||||
|
||||
[ViewVariables] private bool Initialized { get; set; }
|
||||
|
||||
[ViewVariables] private MechanismPrototype Data { get; set; }
|
||||
|
||||
[ViewVariables] public string Id { get; private set; }
|
||||
|
||||
[ViewVariables] public string Name { get; set; }
|
||||
|
||||
[ViewVariables] public string Description { get; set; }
|
||||
|
||||
[ViewVariables] public string ExamineMessage { get; set; }
|
||||
|
||||
[ViewVariables] public string RSIPath { get; set; }
|
||||
|
||||
[ViewVariables] public string RSIState { get; set; }
|
||||
|
||||
[ViewVariables] public int MaxDurability { get; set; }
|
||||
|
||||
[ViewVariables] public int CurrentDurability { get; set; }
|
||||
|
||||
[ViewVariables] public int DestroyThreshold { get; set; }
|
||||
|
||||
[ViewVariables] public int Resistance { get; set; }
|
||||
|
||||
[ViewVariables] public int Size { get; set; }
|
||||
|
||||
[ViewVariables] public BodyPartCompatibility Compatibility { get; set; }
|
||||
|
||||
private readonly List<MechanismBehavior> _behaviors;
|
||||
|
||||
[ViewVariables] public IReadOnlyList<MechanismBehavior> Behaviors => _behaviors;
|
||||
|
||||
public IBodyManagerComponent? Body => Part?.Body;
|
||||
|
||||
public IBodyPart? Part
|
||||
{
|
||||
get => _part;
|
||||
set
|
||||
{
|
||||
var old = _part;
|
||||
_part = value;
|
||||
|
||||
if (value == null && old != null)
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
behavior.RemovedFromPart(old);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
behavior.InstalledIntoPart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureInitialize()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadFromPrototype(Data);
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the given <see cref="MechanismPrototype"/>.
|
||||
/// Current data on this <see cref="Mechanism"/> will be overwritten!
|
||||
/// </summary>
|
||||
private void LoadFromPrototype(MechanismPrototype data)
|
||||
{
|
||||
Data = data;
|
||||
Id = data.ID;
|
||||
Name = data.Name;
|
||||
Description = data.Description;
|
||||
ExamineMessage = data.ExamineMessage;
|
||||
RSIPath = data.RSIPath;
|
||||
RSIState = data.RSIState;
|
||||
MaxDurability = data.Durability;
|
||||
CurrentDurability = MaxDurability;
|
||||
DestroyThreshold = data.DestroyThreshold;
|
||||
Resistance = data.Resistance;
|
||||
Size = data.Size;
|
||||
Compatibility = data.Compatibility;
|
||||
|
||||
foreach (var behavior in _behaviors.ToArray())
|
||||
{
|
||||
RemoveBehavior(behavior);
|
||||
}
|
||||
|
||||
foreach (var mechanismBehaviorName in data.BehaviorClasses)
|
||||
{
|
||||
var mechanismBehaviorType = Type.GetType(mechanismBehaviorName);
|
||||
|
||||
if (mechanismBehaviorType == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No {nameof(MechanismBehavior)} found with name {mechanismBehaviorName}");
|
||||
}
|
||||
|
||||
if (!mechanismBehaviorType.IsSubclassOf(typeof(MechanismBehavior)))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Class {mechanismBehaviorName} is not a subtype of {nameof(MechanismBehavior)} for mechanism prototype {data.ID}");
|
||||
}
|
||||
|
||||
var newBehavior = IoCManager.Resolve<IDynamicTypeFactory>().CreateInstance<MechanismBehavior>(mechanismBehaviorType);
|
||||
|
||||
AddBehavior(newBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
public void InstalledIntoBody()
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
behavior.InstalledIntoBody();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovedFromBody(IBodyManagerComponent old)
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
behavior.RemovedFromBody(old);
|
||||
}
|
||||
}
|
||||
|
||||
public void PreMetabolism(float frameTime)
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
behavior.PreMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void PostMetabolism(float frameTime)
|
||||
{
|
||||
foreach (var behavior in Behaviors)
|
||||
{
|
||||
behavior.PostMetabolism(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBehavior(MechanismBehavior behavior)
|
||||
{
|
||||
_behaviors.Add(behavior);
|
||||
behavior.Initialize(this);
|
||||
}
|
||||
|
||||
public bool RemoveBehavior(MechanismBehavior behavior)
|
||||
{
|
||||
if (_behaviors.Remove(behavior))
|
||||
{
|
||||
behavior.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Body.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a "network" such as a bloodstream or electrical power that
|
||||
/// is coordinated throughout an entire <see cref="BodyManagerComponent"/>.
|
||||
/// </summary>
|
||||
public abstract class BodyNetwork : IExposeData
|
||||
{
|
||||
[ViewVariables]
|
||||
public abstract string Name { get; }
|
||||
|
||||
protected IEntity Owner { get; private set; }
|
||||
|
||||
public virtual void ExposeData(ObjectSerializer serializer) { }
|
||||
|
||||
public void OnAdd(IEntity entity)
|
||||
{
|
||||
Owner = entity;
|
||||
OnAdd();
|
||||
}
|
||||
|
||||
protected virtual void OnAdd() { }
|
||||
|
||||
public virtual void OnRemove() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update by
|
||||
/// <see cref="BodyManagerComponent.PreMetabolism"/>.
|
||||
/// </summary>
|
||||
public virtual void PreMetabolism(float frameTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update by
|
||||
/// <see cref="BodyManagerComponent.PostMetabolism"/>.
|
||||
/// </summary>
|
||||
public virtual void PostMetabolism(float frameTime) { }
|
||||
}
|
||||
|
||||
public static class BodyNetworkExtensions
|
||||
{
|
||||
public static void TryAddNetwork(this IEntity entity, Type type)
|
||||
{
|
||||
if (!entity.TryGetComponent(out BodyManagerComponent body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
body.EnsureNetwork(type);
|
||||
}
|
||||
|
||||
public static void TryAddNetwork<T>(this IEntity entity) where T : BodyNetwork
|
||||
{
|
||||
if (!entity.TryGetComponent(out BodyManagerComponent body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
body.EnsureNetwork<T>();
|
||||
}
|
||||
|
||||
public static bool TryGetBodyNetwork(this IEntity entity, Type type, out BodyNetwork network)
|
||||
{
|
||||
network = null;
|
||||
|
||||
return entity.TryGetComponent(out BodyManagerComponent body) &&
|
||||
body.TryGetNetwork(type, out network);
|
||||
}
|
||||
|
||||
public static bool TryGetBodyNetwork<T>(this IEntity entity, out T network) where T : BodyNetwork
|
||||
{
|
||||
entity.TryGetBodyNetwork(typeof(T), out var unCastNetwork);
|
||||
network = (T) unCastNetwork;
|
||||
return network != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Body.Network
|
||||
{
|
||||
public class BodyNetworkFactory : IBodyNetworkFactory
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactory _typeFactory = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of body network names to their types.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Type> _names = new Dictionary<string, Type>();
|
||||
|
||||
private void Register(Type type)
|
||||
{
|
||||
if (_names.ContainsValue(type))
|
||||
{
|
||||
throw new InvalidOperationException($"Type is already registered: {type}");
|
||||
}
|
||||
|
||||
if (!type.IsSubclassOf(typeof(BodyNetwork)))
|
||||
{
|
||||
throw new InvalidOperationException($"{type} is not a subclass of {nameof(BodyNetwork)}");
|
||||
}
|
||||
|
||||
var dummy = _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||
|
||||
if (dummy == null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
var name = dummy.Name;
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new NullReferenceException($"{type}'s name cannot be null.");
|
||||
}
|
||||
|
||||
if (_names.ContainsKey(name))
|
||||
{
|
||||
throw new InvalidOperationException($"{name} is already registered.");
|
||||
}
|
||||
|
||||
_names.Add(name, type);
|
||||
}
|
||||
|
||||
public void DoAutoRegistrations()
|
||||
{
|
||||
var bodyNetwork = typeof(BodyNetwork);
|
||||
|
||||
foreach (var child in _reflectionManager.GetAllChildren(bodyNetwork))
|
||||
{
|
||||
Register(child);
|
||||
}
|
||||
}
|
||||
|
||||
public BodyNetwork GetNetwork(string name)
|
||||
{
|
||||
Type type;
|
||||
|
||||
try
|
||||
{
|
||||
type = _names[name];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
throw new ArgumentException($"No {nameof(BodyNetwork)} exists with name {name}");
|
||||
}
|
||||
|
||||
return _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||
}
|
||||
|
||||
public BodyNetwork GetNetwork(Type type)
|
||||
{
|
||||
if (!_names.ContainsValue(type))
|
||||
{
|
||||
throw new ArgumentException($"{type} is not registered.");
|
||||
}
|
||||
|
||||
return _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Body.Network
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class CirculatoryNetwork : BodyNetwork
|
||||
{
|
||||
public override string Name => "Circulatory";
|
||||
|
||||
protected override void OnAdd()
|
||||
{
|
||||
Owner.EnsureComponent<BloodstreamComponent>();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
if (Owner.HasComponent<BloodstreamComponent>())
|
||||
{
|
||||
Owner.RemoveComponent<BloodstreamComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Body.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the system that processes food, liquids, and the reagents inside them.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class DigestiveNetwork : BodyNetwork
|
||||
{
|
||||
public override string Name => "Digestive";
|
||||
|
||||
protected override void OnAdd()
|
||||
{
|
||||
Owner.EnsureComponent<StomachComponent>();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
if (Owner.HasComponent<StomachComponent>())
|
||||
{
|
||||
Owner.RemoveComponent<StomachComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Content.Server.Body.Network
|
||||
{
|
||||
public interface IBodyNetworkFactory
|
||||
{
|
||||
void DoAutoRegistrations();
|
||||
|
||||
BodyNetwork GetNetwork(string name);
|
||||
|
||||
BodyNetwork GetNetwork(Type type);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Body.Network
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class RespiratoryNetwork : BodyNetwork
|
||||
{
|
||||
public override string Name => "Respiratory";
|
||||
|
||||
protected override void OnAdd()
|
||||
{
|
||||
Owner.EnsureComponent<LungComponent>();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
if (Owner.HasComponent<LungComponent>())
|
||||
{
|
||||
Owner.RemoveComponent<LungComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.Interfaces;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.Body.Surgery
|
||||
{
|
||||
/// <summary>
|
||||
/// Data class representing the surgery state of a biological entity.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class BiologicalSurgeryData : SurgeryData
|
||||
{
|
||||
private readonly List<IMechanism> _disconnectedOrgans = new List<IMechanism>();
|
||||
|
||||
private bool _skinOpened;
|
||||
private bool _skinRetracted;
|
||||
private bool _vesselsClamped;
|
||||
|
||||
public BiologicalSurgeryData(IBodyPart parent) : base(parent) { }
|
||||
|
||||
protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType)
|
||||
{
|
||||
if (toolType == SurgeryType.Amputation)
|
||||
{
|
||||
return RemoveBodyPartSurgery;
|
||||
}
|
||||
|
||||
if (!_skinOpened)
|
||||
{
|
||||
// Case: skin is normal.
|
||||
if (toolType == SurgeryType.Incision)
|
||||
{
|
||||
return OpenSkinSurgery;
|
||||
}
|
||||
}
|
||||
else if (!_vesselsClamped)
|
||||
{
|
||||
// Case: skin is opened, but not clamped.
|
||||
switch (toolType)
|
||||
{
|
||||
case SurgeryType.VesselCompression:
|
||||
return ClampVesselsSurgery;
|
||||
case SurgeryType.Cauterization:
|
||||
return CauterizeIncisionSurgery;
|
||||
}
|
||||
}
|
||||
else if (!_skinRetracted)
|
||||
{
|
||||
// Case: skin is opened and clamped, but not retracted.
|
||||
switch (toolType)
|
||||
{
|
||||
case SurgeryType.Retraction:
|
||||
return RetractSkinSurgery;
|
||||
case SurgeryType.Cauterization:
|
||||
return CauterizeIncisionSurgery;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Case: skin is fully open.
|
||||
if (Parent.Mechanisms.Count > 0 &&
|
||||
toolType == SurgeryType.VesselCompression)
|
||||
{
|
||||
if (_disconnectedOrgans.Except(Parent.Mechanisms).Count() != 0 ||
|
||||
Parent.Mechanisms.Except(_disconnectedOrgans).Count() != 0)
|
||||
{
|
||||
return LoosenOrganSurgery;
|
||||
}
|
||||
}
|
||||
|
||||
if (_disconnectedOrgans.Count > 0 && toolType == SurgeryType.Incision)
|
||||
{
|
||||
return RemoveOrganSurgery;
|
||||
}
|
||||
|
||||
if (toolType == SurgeryType.Cauterization)
|
||||
{
|
||||
return CauterizeIncisionSurgery;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string GetDescription(IEntity target)
|
||||
{
|
||||
var 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 (var mechanism in _disconnectedOrgans)
|
||||
{
|
||||
toReturn += Loc.GetString("{0:their} {1} is loose.\n", target, mechanism.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public override bool CanInstallMechanism(IMechanism mechanism)
|
||||
{
|
||||
return _skinOpened && _vesselsClamped && _skinRetracted;
|
||||
}
|
||||
|
||||
public override bool CanAttachBodyPart(IBodyPart part)
|
||||
{
|
||||
return true;
|
||||
// TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached.
|
||||
}
|
||||
|
||||
private void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
performer.PopupMessage(Loc.GetString("Cut open the skin..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_skinOpened = true;
|
||||
}
|
||||
|
||||
private void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
performer.PopupMessage(Loc.GetString("Clamp the vessels..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_vesselsClamped = true;
|
||||
}
|
||||
|
||||
private void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
performer.PopupMessage(Loc.GetString("Retract the skin..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_skinRetracted = true;
|
||||
}
|
||||
|
||||
private void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
performer.PopupMessage(Loc.GetString("Cauterize the incision..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_skinOpened = false;
|
||||
_vesselsClamped = false;
|
||||
_skinRetracted = false;
|
||||
}
|
||||
|
||||
private void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
if (Parent.Mechanisms.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var toSend = new List<IMechanism>();
|
||||
foreach (var mechanism in Parent.Mechanisms)
|
||||
{
|
||||
if (!_disconnectedOrgans.Contains(mechanism))
|
||||
{
|
||||
toSend.Add(mechanism);
|
||||
}
|
||||
}
|
||||
|
||||
if (toSend.Count > 0)
|
||||
{
|
||||
surgeon.RequestMechanism(toSend, LoosenOrganSurgeryCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoosenOrganSurgeryCallback(IMechanism target, IBodyPartContainer container, ISurgeon surgeon,
|
||||
IEntity performer)
|
||||
{
|
||||
if (target == null || !Parent.Mechanisms.Contains(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
performer.PopupMessage(Loc.GetString("Loosen the organ..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_disconnectedOrgans.Add(target);
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveOrganSurgeryCallback(IMechanism target, IBodyPartContainer container, ISurgeon surgeon,
|
||||
IEntity performer)
|
||||
{
|
||||
if (target == null || !Parent.Mechanisms.Contains(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
performer.PopupMessage(Loc.GetString("Remove the organ..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
Parent.TryDropMechanism(performer, target, out _);
|
||||
_disconnectedOrgans.Remove(target);
|
||||
}
|
||||
|
||||
private void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||
{
|
||||
// This surgery requires a DroppedBodyPartComponent.
|
||||
if (!(container is BodyManagerComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bmTarget = (BodyManagerComponent) container;
|
||||
performer.PopupMessage(Loc.GetString("Saw off the limb!"));
|
||||
|
||||
// TODO do_after: Delay
|
||||
bmTarget.RemovePart(Parent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.Body.Surgery
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface representing an entity capable of performing surgery (performing operations on an
|
||||
/// <see cref="SurgeryData"/> class).
|
||||
/// For an example see <see cref="SurgeryToolComponent"/>, which inherits from this class.
|
||||
/// </summary>
|
||||
public interface ISurgeon
|
||||
{
|
||||
public delegate void MechanismRequestCallback(
|
||||
IMechanism target,
|
||||
IBodyPartContainer container,
|
||||
ISurgeon surgeon,
|
||||
IEntity performer);
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to perform a single surgery step (in seconds).
|
||||
/// </summary>
|
||||
public float BaseOperationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When performing a surgery, the <see cref="SurgeryData"/> 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(IEnumerable<IMechanism> options, MechanismRequestCallback callback);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.Body.Surgery
|
||||
{
|
||||
/// <summary>
|
||||
/// This data class represents the state of a <see cref="IBodyPart"/> in regards to everything surgery related -
|
||||
/// whether there's an incision on it, whether the bone is broken, etc.
|
||||
/// </summary>
|
||||
public abstract class SurgeryData
|
||||
{
|
||||
protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IBodyPart"/> this surgeryData is attached to.
|
||||
/// The <see cref="SurgeryData"/> class should not exist without a
|
||||
/// <see cref="IBodyPart"/> that it represents, and will throw errors if it
|
||||
/// is null.
|
||||
/// </summary>
|
||||
protected readonly IBodyPart Parent;
|
||||
|
||||
protected SurgeryData(IBodyPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="BodyPartType"/> of the parent <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
protected BodyPartType ParentType => Parent.PartType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the description of this current <see cref="IBodyPart"/> to be shown
|
||||
/// upon observing the given entity.
|
||||
/// </summary>
|
||||
public abstract string GetDescription(IEntity target);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a <see cref="IMechanism"/> can be installed into the
|
||||
/// <see cref="IBodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||
/// </summary>
|
||||
public abstract bool CanInstallMechanism(IMechanism mechanism);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given <see cref="IBodyPart"/> can be connected to the
|
||||
/// <see cref="IBodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||
/// </summary>
|
||||
public abstract bool CanAttachBodyPart(IBodyPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate corresponding to the surgery step using the given
|
||||
/// <see cref="SurgeryType"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The corresponding surgery action or null if no step can be performed.
|
||||
/// </returns>
|
||||
protected 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="SurgeryData"/> represents.
|
||||
/// </summary>
|
||||
public bool CheckSurgery(SurgeryType toolType)
|
||||
{
|
||||
return GetSurgeryStep(toolType) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to perform surgery of the given <see cref="SurgeryType"/>. Returns whether the operation was successful.
|
||||
/// </summary>
|
||||
/// <param name="surgeryType">The <see cref="SurgeryType"/> used for this surgery.</param>
|
||||
/// <param name="container">The container where the surgery is being done.</param>
|
||||
/// <param name="surgeon">The entity being used to perform the surgery.</param>
|
||||
/// <param name="performer">The entity performing the surgery.</param>
|
||||
public bool PerformSurgery(SurgeryType surgeryType, IBodyPartContainer container, ISurgeon surgeon,
|
||||
IEntity performer)
|
||||
{
|
||||
var step = GetSurgeryStep(surgeryType);
|
||||
|
||||
if (step == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
step(container, surgeon, performer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user