From 4e0f52bbafc9ad980103a4c02a85ed57bb197608 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 17 Dec 2020 14:21:43 +0100 Subject: [PATCH] Add do after to surgery (#2756) * Cleanup surgery classes * Add DoAfter to surgery * Consolidate doafter, break on user and target move * Ignore biological surgery data component on the client * Apply suggestions from code review Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Use a stringbuilder for surgery descriptions Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Content.Client/IgnoredComponents.cs | 3 +- .../Surgery/BiologicalSurgeryDataComponent.cs | 373 ++++++++++++++++++ .../{ => Surgery}/SurgeryToolComponent.cs | 2 +- .../Body/Part/SharedBodyPartComponent.cs | 3 +- .../Surgery/BiologicalSurgeryDataComponent.cs | 266 ------------- .../Body/Surgery/SurgeryDataComponent.cs | 38 +- 6 files changed, 390 insertions(+), 295 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs rename Content.Server/GameObjects/Components/Body/{ => Surgery}/SurgeryToolComponent.cs (99%) delete mode 100644 Content.Shared/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index cbdc8e2017..9df1ab57ce 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -224,7 +224,8 @@ "MachinePart", "MachineFrame", "MachineBoard", - "ChemicalAmmo" + "ChemicalAmmo", + "BiologicalSurgeryData" }; } } diff --git a/Content.Server/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs b/Content.Server/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs new file mode 100644 index 0000000000..76faca3c32 --- /dev/null +++ b/Content.Server/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs @@ -0,0 +1,373 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Content.Server.GameObjects.EntitySystems.DoAfter; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Body.Mechanism; +using Content.Shared.GameObjects.Components.Body.Part; +using Content.Shared.GameObjects.Components.Body.Surgery; +using Content.Shared.Interfaces; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; +using static Content.Shared.GameObjects.Components.Body.Surgery.ISurgeryData; + +namespace Content.Server.GameObjects.Components.Body.Surgery +{ + /// + /// Data class representing the surgery state of a biological entity. + /// + [RegisterComponent] + [ComponentReference(typeof(ISurgeryData))] + public class BiologicalSurgeryDataComponent : Component, ISurgeryData + { + public override string Name => "BiologicalSurgeryData"; + + private readonly HashSet _disconnectedOrgans = new(); + + private bool SkinOpened { get; set; } + + private bool SkinRetracted { get; set; } + + private bool VesselsClamped { get; set; } + + public IBodyPart? Parent => Owner.GetComponentOrNull(); + + public BodyPartType? ParentType => Parent?.PartType; + + private void AddDisconnectedOrgan(IMechanism mechanism) + { + if (_disconnectedOrgans.Add(mechanism)) + { + Dirty(); + } + } + + private void RemoveDisconnectedOrgan(IMechanism mechanism) + { + if (_disconnectedOrgans.Remove(mechanism)) + { + Dirty(); + } + } + + private async Task SurgeryDoAfter(IEntity performer) + { + if (!performer.HasComponent()) + { + return true; + } + + var doAfterSystem = EntitySystem.Get(); + var target = Parent?.Body?.Owner ?? Owner; + var args = new DoAfterEventArgs(performer, 3, target: target) + { + BreakOnUserMove = true, + BreakOnTargetMove = true + }; + + return await doAfterSystem.DoAfter(args) == DoAfterStatus.Finished; + } + + private bool HasIncisionNotClamped() + { + return SkinOpened && !VesselsClamped; + } + + private bool HasClampedIncisionNotRetracted() + { + return SkinOpened && VesselsClamped && !SkinRetracted; + } + + private bool HasFullyOpenIncision() + { + return SkinOpened && VesselsClamped && SkinRetracted; + } + + public string GetDescription() + { + if (Parent == null) + { + return string.Empty; + } + + var toReturn = new StringBuilder(); + + if (HasIncisionNotClamped()) + { + toReturn.Append(Loc.GetString("The skin on {0:their} {1} has an incision, but it is prone to bleeding.", + Owner, Parent.Name)); + } + else if (HasClampedIncisionNotRetracted()) + { + toReturn.AppendLine(Loc.GetString("The skin on {0:their} {1} has an incision, but it is not retracted.", + Owner, Parent.Name)); + } + else if (HasFullyOpenIncision()) + { + toReturn.AppendLine(Loc.GetString("There is an incision on {0:their} {1}.\n", Owner, Parent.Name)); + foreach (var mechanism in _disconnectedOrgans) + { + toReturn.AppendLine(Loc.GetString("{0:their} {1} is loose.", Owner, mechanism.Name)); + } + } + + return toReturn.ToString(); + } + + public bool CanAddMechanism(IMechanism mechanism) + { + return Parent != null && + SkinOpened && + VesselsClamped && + SkinRetracted; + } + + public bool CanAttachBodyPart(IBodyPart part) + { + return Parent != null; + // TODO BODY if a part is disconnected, you should have to do some surgery to allow another body part to be attached. + } + + public SurgeryAction? GetSurgeryStep(SurgeryType toolType) + { + if (Parent == null) + { + return null; + } + + 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 bool CheckSurgery(SurgeryType toolType) + { + return GetSurgeryStep(toolType) != null; + } + + 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; + } + + private async void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) + { + return; + } + + performer.PopupMessage(Loc.GetString("Cut open the skin...")); + + if (await SurgeryDoAfter(performer)) + { + SkinOpened = true; + } + } + + private async void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) return; + + performer.PopupMessage(Loc.GetString("Clamp the vessels...")); + + if (await SurgeryDoAfter(performer)) + { + VesselsClamped = true; + } + } + + private async void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) return; + + performer.PopupMessage(Loc.GetString("Retracting the skin...")); + + if (await SurgeryDoAfter(performer)) + { + SkinRetracted = true; + } + } + + private async void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) return; + + performer.PopupMessage(Loc.GetString("Cauterizing the incision...")); + + if (await SurgeryDoAfter(performer)) + { + SkinOpened = false; + VesselsClamped = false; + SkinRetracted = false; + } + } + + private void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) return; + if (Parent.Mechanisms.Count <= 0) return; + + var toSend = new List(); + foreach (var mechanism in Parent.Mechanisms) + { + if (!_disconnectedOrgans.Contains(mechanism)) + { + toSend.Add(mechanism); + } + } + + if (toSend.Count > 0) + { + surgeon.RequestMechanism(toSend, LoosenOrganSurgeryCallback); + } + } + + private async void LoosenOrganSurgeryCallback(IMechanism? target, IBodyPartContainer container, ISurgeon surgeon, + IEntity performer) + { + if (Parent == null || target == null || !Parent.Mechanisms.Contains(target)) + { + return; + } + + performer.PopupMessage(Loc.GetString("Loosening the organ...")); + + if (!performer.HasComponent()) + { + AddDisconnectedOrgan(target); + return; + } + + if (await SurgeryDoAfter(performer)) + { + AddDisconnectedOrgan(target); + } + } + + private void RemoveOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) return; + + if (_disconnectedOrgans.Count <= 0) + { + return; + } + + if (_disconnectedOrgans.Count == 1) + { + RemoveOrganSurgeryCallback(_disconnectedOrgans.First(), container, surgeon, performer); + } + else + { + surgeon.RequestMechanism(_disconnectedOrgans, RemoveOrganSurgeryCallback); + } + } + + private async void RemoveOrganSurgeryCallback(IMechanism? target, IBodyPartContainer container, ISurgeon surgeon, + IEntity performer) + { + if (Parent == null || target == null || !Parent.Mechanisms.Contains(target)) + { + return; + } + + performer.PopupMessage(Loc.GetString("Removing the organ...")); + + if (!performer.HasComponent()) + { + Parent.RemoveMechanism(target, performer.Transform.Coordinates); + RemoveDisconnectedOrgan(target); + return; + } + + if (await SurgeryDoAfter(performer)) + { + Parent.RemoveMechanism(target, performer.Transform.Coordinates); + RemoveDisconnectedOrgan(target); + } + } + + private async void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent == null) return; + if (container is not IBody body) return; + + performer.PopupMessage(Loc.GetString("Sawing off the limb!")); + + if (await SurgeryDoAfter(performer)) + { + body.RemovePart(Parent); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs b/Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs similarity index 99% rename from Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs rename to Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs index 9f350893ac..7173bde58f 100644 --- a/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Surgery/SurgeryToolComponent.cs @@ -21,7 +21,7 @@ using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -namespace Content.Server.GameObjects.Components.Body +namespace Content.Server.GameObjects.Components.Body.Surgery { /// /// Server-side component representing a generic tool capable of performing surgery. diff --git a/Content.Shared/GameObjects/Components/Body/Part/SharedBodyPartComponent.cs b/Content.Shared/GameObjects/Components/Body/Part/SharedBodyPartComponent.cs index 6f1f9547fe..97d05c161f 100644 --- a/Content.Shared/GameObjects/Components/Body/Part/SharedBodyPartComponent.cs +++ b/Content.Shared/GameObjects/Components/Body/Part/SharedBodyPartComponent.cs @@ -89,9 +89,8 @@ namespace Content.Shared.GameObjects.Components.Body.Part [ViewVariables] public BodyPartSymmetry Symmetry { get; private set; } - // TODO BODY [ViewVariables] - public SurgeryDataComponent? SurgeryDataComponent => Owner.GetComponentOrNull(); + public ISurgeryData? SurgeryDataComponent => Owner.GetComponentOrNull(); protected virtual void OnAddMechanism(IMechanism mechanism) { diff --git a/Content.Shared/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs b/Content.Shared/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs deleted file mode 100644 index 1fd276f781..0000000000 --- a/Content.Shared/GameObjects/Components/Body/Surgery/BiologicalSurgeryDataComponent.cs +++ /dev/null @@ -1,266 +0,0 @@ -#nullable enable -using System.Collections.Generic; -using System.Linq; -using Content.Shared.GameObjects.Components.Body.Mechanism; -using Content.Shared.GameObjects.Components.Body.Part; -using Content.Shared.Interfaces; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Shared.GameObjects.Components.Body.Surgery -{ - /// - /// Data class representing the surgery state of a biological entity. - /// - [RegisterComponent] - [ComponentReference(typeof(SurgeryDataComponent))] - public class BiologicalSurgeryDataComponent : SurgeryDataComponent - { - public override string Name => "BiologicalSurgeryData"; - - private readonly List _disconnectedOrgans = new(); - - private bool _skinOpened; - private bool _skinRetracted; - private bool _vesselsClamped; - - protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType) - { - if (Parent == null) - { - return null; - } - - 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() - { - if (Parent == null) - { - return ""; - } - - 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", - Owner, 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", - Owner, Parent.Name); - } - else if (_skinOpened && _vesselsClamped && _skinRetracted) - { - // Case: skin is fully open. - toReturn += Loc.GetString("There is an incision on {0:their} {1}.\n", Owner, Parent.Name); - foreach (var mechanism in _disconnectedOrgans) - { - toReturn += Loc.GetString("{0:their} {1} is loose.\n", Owner, mechanism.Name); - } - } - - return toReturn; - } - - public override bool CanAddMechanism(IMechanism mechanism) - { - return Parent != null && - _skinOpened && - _vesselsClamped && - _skinRetracted; - } - - public override bool CanAttachBodyPart(IBodyPart part) - { - return Parent != null; - // TODO BODY if a part 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) - { - if (Parent == null) return; - - performer.PopupMessage(Loc.GetString("Cut open the skin...")); - - // TODO BODY do_after: Delay - _skinOpened = true; - } - - private void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (Parent == null) return; - - performer.PopupMessage(Loc.GetString("Clamp the vessels...")); - - // TODO BODY do_after: Delay - _vesselsClamped = true; - } - - private void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (Parent == null) return; - - performer.PopupMessage(Loc.GetString("Retract the skin...")); - - // TODO BODY do_after: Delay - _skinRetracted = true; - } - - private void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (Parent == null) return; - - performer.PopupMessage(Loc.GetString("Cauterize the incision...")); - - // TODO BODY do_after: Delay - _skinOpened = false; - _vesselsClamped = false; - _skinRetracted = false; - } - - private void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (Parent == null) return; - if (Parent.Mechanisms.Count <= 0) return; - - var toSend = new List(); - 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 (Parent == null || target == null || !Parent.Mechanisms.Contains(target)) - { - return; - } - - performer.PopupMessage(Loc.GetString("Loosen the organ...")); - - // TODO BODY do_after: Delay - _disconnectedOrgans.Add(target); - } - - private void RemoveOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (Parent == null) return; - - 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 (Parent == null || target == null || !Parent.Mechanisms.Contains(target)) - { - return; - } - - performer.PopupMessage(Loc.GetString("Remove the organ...")); - - // TODO BODY do_after: Delay - Parent.RemoveMechanism(target, performer.Transform.Coordinates); - _disconnectedOrgans.Remove(target); - } - - private void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (Parent == null) return; - if (container is not IBody body) return; - - performer.PopupMessage(Loc.GetString("Saw off the limb!")); - - // TODO BODY do_after: Delay - body.RemovePart(Parent); - } - } -} diff --git a/Content.Shared/GameObjects/Components/Body/Surgery/SurgeryDataComponent.cs b/Content.Shared/GameObjects/Components/Body/Surgery/SurgeryDataComponent.cs index c778232c1b..4ebdab29a7 100644 --- a/Content.Shared/GameObjects/Components/Body/Surgery/SurgeryDataComponent.cs +++ b/Content.Shared/GameObjects/Components/Body/Surgery/SurgeryDataComponent.cs @@ -1,7 +1,6 @@ #nullable enable using Content.Shared.GameObjects.Components.Body.Mechanism; using Content.Shared.GameObjects.Components.Body.Part; -using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; namespace Content.Shared.GameObjects.Components.Body.Surgery @@ -9,41 +8,41 @@ namespace Content.Shared.GameObjects.Components.Body.Surgery /// /// Represents the current surgery state of a . /// - public abstract class SurgeryDataComponent : Component + public interface ISurgeryData : IComponent { - protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer); + public delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer); /// /// The this - /// is attached to. + /// is attached to. /// - protected IBodyPart? Parent => Owner.GetComponentOrNull(); + public IBodyPart? Parent { get; } /// /// The of the parent /// . /// - protected BodyPartType? ParentType => Parent?.PartType; + public BodyPartType? ParentType { get; } /// /// Returns a description of this entity. /// /// The description shown upon observing this entity. - public abstract string GetDescription(); + public string GetDescription(); /// /// Returns whether a can be added into the - /// this + /// this /// represents. /// - public abstract bool CanAddMechanism(IMechanism mechanism); + public bool CanAddMechanism(IMechanism mechanism); /// /// Returns whether the given can be connected - /// to the this + /// to the this /// represents. /// - public abstract bool CanAttachBodyPart(IBodyPart part); + public bool CanAttachBodyPart(IBodyPart part); /// /// Gets the delegate corresponding to the surgery step using the given @@ -53,12 +52,12 @@ namespace Content.Shared.GameObjects.Components.Body.Surgery /// The corresponding surgery action or null if no step can be /// performed. /// - protected abstract SurgeryAction? GetSurgeryStep(SurgeryType toolType); + public SurgeryAction? GetSurgeryStep(SurgeryType toolType); /// /// Returns whether the given can be used to /// perform a surgery on the this - /// represents. + /// represents. /// public bool CheckSurgery(SurgeryType toolType) { @@ -80,17 +79,6 @@ namespace Content.Shared.GameObjects.Components.Body.Surgery /// The entity performing the surgery. /// True if successful, false otherwise. 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; - } + IEntity performer); } }