#nullable enable using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Body.Behavior; using Content.Shared.Body.Components; using Content.Shared.Body.Mechanism; using Content.Shared.Body.Part.Property; using Content.Shared.Body.Surgery; using Content.Shared.NetIDs; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Shared.Body.Part { public abstract class SharedBodyPartComponent : Component, IBodyPartContainer { public override string Name => "BodyPart"; public override uint? NetID => ContentNetIDs.BODY_PART; private SharedBodyComponent? _body; // TODO BODY Remove [DataField("mechanisms")] private readonly List _mechanismIds = new(); public IReadOnlyList MechanismIds => _mechanismIds; [ViewVariables] private readonly HashSet _mechanisms = new(); [ViewVariables] public SharedBodyComponent? Body { get => _body; set { if (_body == value) { return; } var old = _body; _body = value; if (old != null) { RemovedFromBody(old); } if (value != null) { AddedToBody(value); } } } /// /// The string to show when displaying this part's name to players. /// [ViewVariables] public string DisplayName => Name; /// /// that this is considered /// to be. /// For example, . /// [ViewVariables] [DataField("partType")] public BodyPartType PartType { get; private set; } = BodyPartType.Other; /// /// Determines how many mechanisms can be fit inside this /// . /// [ViewVariables] [DataField("size")] public int Size { get; private set; } = 1; [ViewVariables] public int SizeUsed { get; private set; } // TODO BODY size used // TODO BODY surgerydata /// /// What types of BodyParts this can easily attach to. /// For the most part, most limbs aren't universal and require extra work to /// attach between types. /// [ViewVariables] [DataField("compatibility")] public BodyPartCompatibility Compatibility { get; private set; } = BodyPartCompatibility.Universal; // TODO BODY Mechanisms occupying different parts at the body level [ViewVariables] public IReadOnlyCollection Mechanisms => _mechanisms; // TODO BODY Replace with a simulation of organs /// /// Whether or not the owning will die if all /// s of this type are removed from it. /// [ViewVariables] [DataField("vital")] public bool IsVital { get; private set; } = false; [ViewVariables] [DataField("symmetry")] public BodyPartSymmetry Symmetry { get; private set; } = BodyPartSymmetry.None; [ViewVariables] public ISurgeryData? SurgeryDataComponent => Owner.GetComponentOrNull(); protected virtual void OnAddMechanism(SharedMechanismComponent mechanism) { var prototypeId = mechanism.Owner.Prototype!.ID; if (!_mechanismIds.Contains(prototypeId)) { _mechanismIds.Add(prototypeId); } mechanism.Part = this; SizeUsed += mechanism.Size; Dirty(); } protected virtual void OnRemoveMechanism(SharedMechanismComponent mechanism) { _mechanismIds.Remove(mechanism.Owner.Prototype!.ID); mechanism.Part = null; SizeUsed -= mechanism.Size; Dirty(); } public override ComponentState GetComponentState(ICommonSession player) { var mechanismIds = new EntityUid[_mechanisms.Count]; var i = 0; foreach (var mechanism in _mechanisms) { mechanismIds[i] = mechanism.Owner.Uid; i++; } return new BodyPartComponentState(mechanismIds); } public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { base.HandleComponentState(curState, nextState); if (curState is not BodyPartComponentState state) { return; } var newMechanisms = state.Mechanisms(); foreach (var mechanism in _mechanisms.ToArray()) { if (!newMechanisms.Contains(mechanism)) { RemoveMechanism(mechanism); } } foreach (var mechanism in newMechanisms) { if (!_mechanisms.Contains(mechanism)) { TryAddMechanism(mechanism, true); } } } public bool SurgeryCheck(SurgeryType surgery) { return SurgeryDataComponent?.CheckSurgery(surgery) ?? false; } public bool AttemptSurgery(SurgeryType toolType, IBodyPartContainer target, ISurgeon surgeon, IEntity performer) { DebugTools.AssertNotNull(toolType); DebugTools.AssertNotNull(target); DebugTools.AssertNotNull(surgeon); DebugTools.AssertNotNull(performer); return SurgeryDataComponent?.PerformSurgery(toolType, target, surgeon, performer) ?? false; } public bool CanAttachPart(SharedBodyPartComponent part) { DebugTools.AssertNotNull(part); return SurgeryDataComponent?.CanAttachBodyPart(part) ?? false; } public virtual bool CanAddMechanism(SharedMechanismComponent mechanism) { DebugTools.AssertNotNull(mechanism); return SurgeryDataComponent != null && SizeUsed + mechanism.Size <= Size && SurgeryDataComponent.CanAddMechanism(mechanism); } /// /// Tries to add a to this part. /// /// The mechanism to add. /// /// Whether or not to check if the mechanism is compatible. /// Passing true does not guarantee it to be added, for example if /// it was already added before. /// /// true if added, false otherwise even if it was already added. public bool TryAddMechanism(SharedMechanismComponent mechanism, bool force = false) { DebugTools.AssertNotNull(mechanism); if (!force && !CanAddMechanism(mechanism)) { return false; } if (!_mechanisms.Add(mechanism)) { return false; } OnAddMechanism(mechanism); return true; } /// /// Tries to remove the given from this part. /// /// The mechanism to remove. /// True if it was removed, false otherwise. public bool RemoveMechanism(SharedMechanismComponent mechanism) { DebugTools.AssertNotNull(mechanism); if (!_mechanisms.Remove(mechanism)) { return false; } OnRemoveMechanism(mechanism); return true; } /// /// Tries to remove the given from this /// part and drops it at the specified coordinates. /// /// The mechanism to remove. /// The coordinates to drop it at. /// True if it was removed, false otherwise. public bool RemoveMechanism(SharedMechanismComponent mechanism, EntityCoordinates coordinates) { if (RemoveMechanism(mechanism)) { mechanism.Owner.Transform.Coordinates = coordinates; return true; } return false; } /// /// Tries to destroy the given from /// this part. /// The mechanism won't be deleted if it is not in this body part. /// /// /// True if the mechanism was in this body part and destroyed, /// false otherwise. /// public bool DeleteMechanism(SharedMechanismComponent mechanism) { DebugTools.AssertNotNull(mechanism); if (!RemoveMechanism(mechanism)) { return false; } mechanism.Owner.Delete(); return true; } private void AddedToBody(SharedBodyComponent body) { Owner.Transform.LocalRotation = 0; Owner.Transform.AttachParent(body.Owner); OnAddedToBody(body); foreach (var mechanism in _mechanisms) { mechanism.AddedToBody(body); } } private void RemovedFromBody(SharedBodyComponent old) { if (!Owner.Transform.Deleted) { Owner.Transform.AttachToGridOrMap(); } OnRemovedFromBody(old); foreach (var mechanism in _mechanisms) { mechanism.RemovedFromBody(old); } } protected virtual void OnAddedToBody(SharedBodyComponent body) { } protected virtual void OnRemovedFromBody(SharedBodyComponent old) { } /// /// Gibs the body part. /// public virtual void Gib() { foreach (var mechanism in _mechanisms) { RemoveMechanism(mechanism); } } public bool HasProperty(Type type) { return Owner.HasComponent(type); } public bool HasProperty() where T : class, IBodyPartProperty { return HasProperty(typeof(T)); } public bool TryGetProperty(Type type, [NotNullWhen(true)] out IBodyPartProperty? property) { if (!Owner.TryGetComponent(type, out var component)) { property = null; return false; } return (property = component as IBodyPartProperty) != null; } public bool TryGetProperty([NotNullWhen(true)] out T? property) where T : class, IBodyPartProperty { return Owner.TryGetComponent(out property); } public bool HasMechanismBehavior() where T : SharedMechanismBehavior { return Mechanisms.Any(m => m.HasBehavior()); } } [Serializable, NetSerializable] public class BodyPartComponentState : ComponentState { [NonSerialized] private List? _mechanisms; public readonly EntityUid[] MechanismIds; public BodyPartComponentState(EntityUid[] mechanismIds) : base(ContentNetIDs.BODY_PART) { MechanismIds = mechanismIds; } public List Mechanisms(IEntityManager? entityManager = null) { if (_mechanisms != null) { return _mechanisms; } entityManager ??= IoCManager.Resolve(); var mechanisms = new List(MechanismIds.Length); foreach (var id in MechanismIds) { if (!entityManager.TryGetEntity(id, out var entity)) { continue; } if (!entity.TryGetComponent(out SharedMechanismComponent? mechanism)) { continue; } mechanisms.Add(mechanism); } return _mechanisms = mechanisms; } } }