* Update RobustToolbox * Transition direct type usages * More updates * Fix invalid use of to map * Update RobustToolbox * Fix dropping items * Rename name usages of "GridCoordinates" to "EntityCoordinates" * Revert "Update RobustToolbox" This reverts commit 9f334a17c5908ded0043a63158bb671e4aa3f346. * Revert "Update RobustToolbox" This reverts commit 3a9c8cfa3606fa501aa84407796d2ad920853a09. # Conflicts: # RobustToolbox * Fix cursed IMapGrid method usage. * GridTileLookupTest now uses EntityCoordinates Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com> Co-authored-by: Víctor Aguilera Puerto <zddm@outlook.es>
535 lines
18 KiB
C#
535 lines
18 KiB
C#
#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;
|
|
}
|
|
}
|
|
}
|