From 4e91c4cdb31d987acb51359535ab15cf68acfeca Mon Sep 17 00:00:00 2001 From: Anri Date: Tue, 24 Dec 2024 07:40:50 +0300 Subject: [PATCH 1/6] init-commit --- .../SS220/Surgery/Ui/SurgeryDrapeBUI.cs | 49 ++++ .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml | 54 +++++ .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs | 80 +++++++ .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml | 9 + .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs | 213 ++++++++++++++++++ .../Surgery/Systems/SurgeryDrapeSystem.cs | 25 ++ .../SS220/Surgery/Graph/SurgeryGraph.cs | 10 + .../SS220/Surgery/SurgerySharedData.cs | 20 ++ .../SS220/Surgery/Ui/SurgeryDrapeUI.cs | 11 + .../Surgery/Ui/SurgeryDrapeUIMessages.cs | 18 ++ .../UserInterface/OnInteractUIComponent.cs | 24 ++ .../SS220/UserInterface/OnInteractUIEvents.cs | 32 +++ .../SS220/UserInterface/OnInteractUISystem.cs | 65 ++++++ .../Objects/Specific/Medical/surgery.yml | 6 + .../SS220/Interface/Surgery/puppet/box.png | Bin 0 -> 3536 bytes .../SS220/Interface/Surgery/puppet/butt.png | Bin 0 -> 2313 bytes .../Surgery/puppet/butt_selected.png | Bin 0 -> 2265 bytes .../Surgery/puppet/eyes_selected.png | Bin 0 -> 2250 bytes .../SS220/Interface/Surgery/puppet/head.png | Bin 0 -> 2335 bytes .../Surgery/puppet/head_selected.png | Bin 0 -> 2294 bytes .../Interface/Surgery/puppet/left_arm.png | Bin 0 -> 2272 bytes .../Surgery/puppet/left_arm_selected.png | Bin 0 -> 2250 bytes .../Interface/Surgery/puppet/left_foot.png | Bin 0 -> 2267 bytes .../Surgery/puppet/left_foot_selected.png | Bin 0 -> 2254 bytes .../Interface/Surgery/puppet/left_hand.png | Bin 0 -> 2268 bytes .../Surgery/puppet/left_hand_selected.png | Bin 0 -> 2252 bytes .../Interface/Surgery/puppet/left_leg.png | Bin 0 -> 2260 bytes .../Surgery/puppet/left_leg_selected.png | Bin 0 -> 2232 bytes .../Interface/Surgery/puppet/license.txt | 1 + .../Surgery/puppet/mouth_selected.png | Bin 0 -> 2256 bytes .../Interface/Surgery/puppet/right_arm.png | Bin 0 -> 2264 bytes .../Surgery/puppet/right_arm_selected.png | Bin 0 -> 2254 bytes .../Interface/Surgery/puppet/right_foot.png | Bin 0 -> 2264 bytes .../Surgery/puppet/right_foot_selected.png | Bin 0 -> 2253 bytes .../Interface/Surgery/puppet/right_hand.png | Bin 0 -> 2272 bytes .../Surgery/puppet/right_hand_selected.png | Bin 0 -> 2254 bytes .../Interface/Surgery/puppet/right_leg.png | Bin 0 -> 2274 bytes .../Surgery/puppet/right_leg_selected.png | Bin 0 -> 2237 bytes .../SS220/Interface/Surgery/puppet/torso.png | Bin 0 -> 2350 bytes .../Surgery/puppet/torso_selected.png | Bin 0 -> 2299 bytes 40 files changed, 617 insertions(+) create mode 100644 Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs create mode 100644 Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml create mode 100644 Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs create mode 100644 Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml create mode 100644 Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs create mode 100644 Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs create mode 100644 Content.Shared/SS220/Surgery/SurgerySharedData.cs create mode 100644 Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUI.cs create mode 100644 Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs create mode 100644 Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs create mode 100644 Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs create mode 100644 Content.Shared/SS220/UserInterface/OnInteractUISystem.cs create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/box.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/butt.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/butt_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/eyes_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/head.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/head_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_arm.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_arm_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_foot.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_foot_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_hand.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_hand_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_leg.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/left_leg_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/license.txt create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/mouth_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_arm.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_arm_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_foot.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_foot_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_hand.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_hand_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_leg.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/right_leg_selected.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/torso.png create mode 100644 Resources/Textures/SS220/Interface/Surgery/puppet/torso_selected.png diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs new file mode 100644 index 00000000000000..913be852d7a941 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs @@ -0,0 +1,49 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using System.Linq; +using Content.Shared.SS220.Surgery.Graph; +using Content.Shared.SS220.Surgery.Ui; +using Content.Shared.Whitelist; +using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; + +namespace Content.Client.SS220.Surgery.Ui; + +public sealed class SurgeryDrapeBUI : BoundUserInterface +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; + + [ViewVariables] + private SurgeryDrapeMenu? _menu; + + public SurgeryDrapeBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) { } + + protected override void Open() + { + base.Open(); + _menu = this.CreateWindow(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + switch (state) + { + case SurgeryDrapeUpdate update: + _menu?.UpdateOperations(GetAvailableOperations(update.User, update.Target)); + break; + } + } + + private List GetAvailableOperations(EntityUid user, EntityUid target) + { + var result = _prototypeManager.EnumeratePrototypes() + .Where(proto => _entityWhitelist.IsWhitelistPass(proto.TargetWhitelist, target) + && _entityWhitelist.IsWhitelistPass(proto.PerformerWhitelist, user)) + .ToList(); + + return result; + } +} diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml new file mode 100644 index 00000000000000..f98e112ef57ce7 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs new file mode 100644 index 00000000000000..bcc6536fd53cbe --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs @@ -0,0 +1,80 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using System.Linq; +using Content.Client.UserInterface.Controls; +using Content.Shared.SS220.Surgery; +using Content.Shared.SS220.Surgery.Graph; +using Content.Shared.SS220.Surgery.Ui; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.SS220.Surgery.Ui; + +[GenerateTypedNameReferences] +public sealed partial class SurgeryDrapeMenu : FancyWindow +{ + public EntityUid Target; + public EntityUid Performer; + + public SurgeryDrapeMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + + Puppet.Initialize(); + + // SS220_TODO: Add by alphabet + foreach (var part in Enum.GetValues().OrderBy(variant => LocPuppetPartPath(variant))) + { + var textPart = MakePuppetPartButton(part); + PuppetPartContainer.AddChild(textPart); + } + } + + public void UpdateOperations(List graphPrototypes) + { + + } + + private PuppetPartButton MakePuppetPartButton(PuppetParts part) + { + var partButton = new PuppetPartButton + { + Text = Loc.GetString(LocPuppetPartPath(part)), + Part = part, + Pressed = part == Puppet.SelectedPart, + ToggleMode = true, + StyleClasses = { "OpenBoth" } + }; + + partButton.OnPressed += (_) => + { + Puppet.SelectedPart = part; + UpdatePuppetPartButtons(); + }; + + return partButton; + } + + private string LocPuppetPartPath(PuppetParts part) + { + return Enum.GetName(typeof(PuppetParts), part)!; + } + + private void UpdatePuppetPartButtons() + { + foreach (var child in PuppetPartContainer.Children) + { + if (child is not PuppetPartButton button) + continue; + + button.Pressed = button.Part == Puppet.SelectedPart; + } + } + + private sealed class PuppetPartButton : Button + { + public PuppetParts Part; + } +} diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml new file mode 100644 index 00000000000000..c4766170a0c738 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml @@ -0,0 +1,9 @@ + + + + diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs new file mode 100644 index 00000000000000..0a4547edc0b143 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs @@ -0,0 +1,213 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using System.Numerics; +using Content.Shared.SS220.Surgery; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.SS220.Surgery.Ui; + +[GenerateTypedNameReferences] +public sealed partial class SurgeryPuppetBox : Control +{ + // SS220_TODO: if i had time add some properties not to hardcode in xaml + + public Vector2 Scale + { + get => _scale; + set + { + _scale = value; + ResizeAll(); + } + } + + private Vector2 _scale = new Vector2(1f); + + public PuppetParts? SelectedPart + { + get => _selectedPart; + set + { + _selectedPart = HighlightPuppetPart(value); + } + } + + private PuppetParts? _selectedPart; + + private SortedDictionary _parts = new(); + private SortedDictionary _highlightedPart = new(); + + private const string TexturePath = "/Textures/SS220/Interface/Surgery/puppet"; + + public SurgeryPuppetBox() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + // SS220_TODO: make it working. Add normal buttons to work... God help me... + // fuck buttons! + public void Initialize() + { + foreach (var part in Enum.GetValues()) + { + var textPart = MakePuppetPartButton(part, _parts, GetPuppetPartTexturePath); + + if (textPart != null) + this.AddChild(textPart); + + textPart = MakePuppetPartButton(part, _highlightedPart, GetPuppetPartSelectedTexturePath); + + if (textPart == null) + continue; + + this.AddChild(textPart); + textPart.Visible = false; + } + + ResizeAll(); + } + + private Control? MakePuppetPartButton(PuppetParts part, SortedDictionary bodyPartTexture, Func pathSpecifier) + { + var partTexturePath = pathSpecifier(part); + + if (partTexturePath == null) + return null; + + var texture = new TextureRect + { + Stretch = TextureRect.StretchMode.Keep, + TexturePath = partTexturePath + }; + + bodyPartTexture.Add(part, texture); + + return texture; + } + + private void ResizeAll() + { + foreach (var texture in _parts.Values) + { + texture.TextureScale = _scale; + } + foreach (var texture in _highlightedPart.Values) + { + texture.TextureScale = _scale; + } + Background.TextureScale = _scale; + } + + /// + /// I hope no one else will need to give a fck what happens here. God bless new med. + /// + private PuppetParts? HighlightPuppetPart(PuppetParts? puppetPartClicked) + { + var newHighlightPart = puppetPartClicked; + switch (puppetPartClicked) + { + case PuppetParts.Head: + switch (_selectedPart) + { + case PuppetParts.Head: + newHighlightPart = PuppetParts.Eyes; + break; + case PuppetParts.Eyes: + newHighlightPart = PuppetParts.Mouth; + break; + case PuppetParts.Mouth: + newHighlightPart = PuppetParts.Head; + break; + default: + break; + } + break; + default: + break; + } + + if (newHighlightPart == _selectedPart) + return _selectedPart; + + if (_selectedPart != null) + RemoveHighlight(_selectedPart.Value); + + if (newHighlightPart != null) + MakeHighlighted(newHighlightPart.Value); + + return newHighlightPart; + } + + private void MakeHighlighted(PuppetParts part) + { + if (_highlightedPart.TryGetValue(part, out var newTextureControl)) + newTextureControl.Visible = true; + if (_parts.TryGetValue(part, out var oldTextureControl)) + oldTextureControl.Visible = false; + } + + private void RemoveHighlight(PuppetParts part) + { + if (_highlightedPart.TryGetValue(part, out var newTextureControl)) + newTextureControl.Visible = false; + if (_parts.TryGetValue(part, out var oldTextureControl)) + oldTextureControl.Visible = true; + } + + + /// If you still think why we need new med to make surgery just look into this method. + private string? GetPuppetPartTexturePath(PuppetParts puppetPart) + { + var state = puppetPart switch + { + PuppetParts.Head => "head.png", + PuppetParts.Torso => "torso.png", + PuppetParts.LeftArm => "left_arm.png", + PuppetParts.RightArm => "right_arm.png", + PuppetParts.LeftHand => "left_hand.png", + PuppetParts.RightHand => "right_hand.png", + PuppetParts.LeftLeg => "left_leg.png", + PuppetParts.RightLeg => "right_leg.png", + PuppetParts.LeftFoot => "left_foot.png", + PuppetParts.RightFoot => "right_foot.png", + PuppetParts.LowerTorso => "butt.png", + _ => null + }; + + if (state == null) + return null; + return string.Join('/', [TexturePath, state]); + } + + /// If you still think why we need newMed to make surgery just look into this method too. + private string? GetPuppetPartSelectedTexturePath(PuppetParts highlightedPuppetPart) + { + var state = highlightedPuppetPart switch + { + PuppetParts.Head => "head_selected.png", + PuppetParts.Eyes => "eyes_selected.png", + PuppetParts.Mouth => "mouth_selected.png", + PuppetParts.Torso => "torso_selected.png", + PuppetParts.LeftArm => "left_arm_selected.png", + PuppetParts.RightArm => "right_arm_selected.png", + PuppetParts.LeftHand => "left_hand_selected.png", + PuppetParts.RightHand => "right_hand_selected.png", + PuppetParts.LeftLeg => "left_leg_selected.png", + PuppetParts.RightLeg => "right_leg_selected.png", + PuppetParts.LeftFoot => "left_foot_selected.png", + PuppetParts.RightFoot => "right_foot_selected.png", + PuppetParts.LowerTorso => "butt_selected.png", + _ => null + }; + + if (state == null) + return null; + return string.Join('/', [TexturePath, state]); + } +} + + diff --git a/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs b/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs new file mode 100644 index 00000000000000..eeec7e3f0f0146 --- /dev/null +++ b/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs @@ -0,0 +1,25 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.SS220.Surgery.Ui; +using Robust.Server.GameObjects; + +namespace Content.Server.SS220.Surgery.Systems; + +public sealed class SurgeryDrapeSystem : EntitySystem +{ + [Dependency] private readonly UserInterfaceSystem _userInterface = default!; + + public override void Initialize() + { + base.Initialize(); + + + } + + public void UpdateUserInterface(EntityUid drape, EntityUid user, EntityUid target) + { + + var state = new SurgeryDrapeUpdate(user, target); + _userInterface.SetUiState(drape, SurgeryDrapeUiKey.Key, state); + } +} diff --git a/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs b/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs index 6d6145e443a18a..cea02d4072d3ae 100644 --- a/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs +++ b/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs @@ -1,6 +1,7 @@ // Original code from construction graph all edits under © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt using System.Diagnostics.CodeAnalysis; +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -19,6 +20,15 @@ public sealed partial class SurgeryGraphPrototype : IPrototype, ISerializationHo [DataField(required: true)] public string End { get; private set; } = default!; + [DataField(required: true)] + public PuppetParts TargetPuppetPart; + + [DataField] + public EntityWhitelist? PerformerWhitelist; + + [DataField] + public EntityWhitelist? TargetWhitelist; + [DataField("graph", priority: 0)] private List _graph = new(); diff --git a/Content.Shared/SS220/Surgery/SurgerySharedData.cs b/Content.Shared/SS220/Surgery/SurgerySharedData.cs new file mode 100644 index 00000000000000..0cb8aa00feb4a0 --- /dev/null +++ b/Content.Shared/SS220/Surgery/SurgerySharedData.cs @@ -0,0 +1,20 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +namespace Content.Shared.SS220.Surgery; + +public enum PuppetParts +{ + Head, + Eyes, + Mouth, + Torso, + LeftArm, + RightArm, + LeftLeg, + RightHand, + LeftHand, + LowerTorso, + RightLeg, + LeftFoot, + RightFoot +} diff --git a/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUI.cs b/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUI.cs new file mode 100644 index 00000000000000..be6ded5e216e97 --- /dev/null +++ b/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUI.cs @@ -0,0 +1,11 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Robust.Shared.Serialization; + +namespace Content.Shared.SS220.Surgery.Ui; + +[Serializable, NetSerializable] +public enum SurgeryDrapeUiKey : byte +{ + Key +} diff --git a/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs b/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs new file mode 100644 index 00000000000000..3a0d460e1f4cb0 --- /dev/null +++ b/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs @@ -0,0 +1,18 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Robust.Shared.Serialization; + +namespace Content.Shared.SS220.Surgery.Ui; + +[Serializable, NetSerializable] +public sealed class SurgeryStarted : BoundUserInterfaceMessage +{ + +} + +[Serializable, NetSerializable] +public sealed class SurgeryDrapeUpdate(EntityUid user, EntityUid target) : BoundUserInterfaceState +{ + public EntityUid User { get; } = user; + public EntityUid Target { get; } = target; +} diff --git a/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs b/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs new file mode 100644 index 00000000000000..f1045b7cd83cf4 --- /dev/null +++ b/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs @@ -0,0 +1,24 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Robust.Shared.Serialization.TypeSerializers.Implementations; + +namespace Content.Shared.SS220.UserInterface; + +[RegisterComponent] +public sealed partial class OnInteractUIComponent : Component +{ + [DataField(required: true, customTypeSerializer: typeof(EnumSerializer))] + public Enum? Key; + + /// + /// Whether the item must be held in one of the user's hands to work. + /// This is ignored unless is true. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool InHandsOnly; + + + [DataField] + public LocId VerbText = "ui-verb-toggle-open"; +} diff --git a/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs b/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs new file mode 100644 index 00000000000000..9202a82e86f464 --- /dev/null +++ b/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs @@ -0,0 +1,32 @@ +// Highly inspired by ActivatableUIEvents all edits under © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +namespace Content.Shared.SS220.UserInterface; + +public sealed class InteractUIOpenAttemptEvent(EntityUid who) : CancellableEntityEventArgs +{ + public EntityUid User { get; } = who; +} + +public sealed class UserOpenInteractUIAttemptEvent(EntityUid who, EntityUid target) : CancellableEntityEventArgs //have to one-up the already stroke-inducing name +{ + public EntityUid User { get; } = who; + public EntityUid Target { get; } = target; +} + +public sealed class BeforeInteractUIOpenEvent(EntityUid who, EntityUid target) : EntityEventArgs +{ + public EntityUid User { get; } = who; + public EntityUid Target { get; } = target; +} + +public sealed class AfterInteractUIOpenEvent(EntityUid who, EntityUid target, EntityUid actor) : EntityEventArgs +{ + public EntityUid User { get; } = who; + public EntityUid Target { get; } = target; + public readonly EntityUid Actor = actor; +} + + +public sealed class InteractUIPlayerChangedEvent : EntityEventArgs +{ +} diff --git a/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs b/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs new file mode 100644 index 00000000000000..3cf26c7dfcf64e --- /dev/null +++ b/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs @@ -0,0 +1,65 @@ +// Highly inspired by ActivatableUISystem all edits under © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.ActionBlocker; +using Content.Shared.Interaction; + +namespace Content.Shared.SS220.UserInterface; + +public sealed class OnInteractUsingSystem : EntitySystem +{ + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(InteractUsing); + } + + private void InteractUsing(Entity entity, ref AfterInteractEvent args) + { + if (args.Handled || args.Target == null) + return; + + args.Handled = InteractUI(args.User, entity, args.Target.Value); + } + + private bool InteractUI(EntityUid user, Entity uiEntity, EntityUid target) + { + if (uiEntity.Comp.Key == null || !_userInterface.HasUi(uiEntity.Owner, uiEntity.Comp.Key)) + return false; + + if (_userInterface.IsUiOpen(uiEntity.Owner, uiEntity.Comp.Key, user)) + { + _userInterface.CloseUi(uiEntity.Owner, uiEntity.Comp.Key, user); + return true; + } + + if (!_actionBlocker.CanInteract(user, uiEntity.Owner)) + return false; + + // Soo. Cant do anything better - good job! + // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. + // This is so that stuff can require further conditions (like power). + var oie = new InteractUIOpenAttemptEvent(user); + var uie = new UserOpenInteractUIAttemptEvent(user, uiEntity); + RaiseLocalEvent(user, uie); + RaiseLocalEvent(uiEntity, oie); + if (oie.Cancelled || uie.Cancelled) + return false; + + // Give the UI an opportunity to prepare itself if it needs to do anything + // before opening + var bie = new BeforeInteractUIOpenEvent(user, target); + RaiseLocalEvent(uiEntity, bie); + + _userInterface.OpenUi(uiEntity.Owner, uiEntity.Comp.Key, user); + + //Let the component know a user opened it so it can do whatever it needs to do + var aie = new AfterInteractUIOpenEvent(user, target, user); + RaiseLocalEvent(uiEntity, aie); + + return true; + } +} diff --git a/Resources/Prototypes/SS220/Entities/Objects/Specific/Medical/surgery.yml b/Resources/Prototypes/SS220/Entities/Objects/Specific/Medical/surgery.yml index e82b42302e1343..6550b49bce70fe 100644 --- a/Resources/Prototypes/SS220/Entities/Objects/Specific/Medical/surgery.yml +++ b/Resources/Prototypes/SS220/Entities/Objects/Specific/Medical/surgery.yml @@ -15,6 +15,12 @@ tags: - SurgeryTool - type: SurgeryDrape + - type: OnInteractUI + key: enum.SurgeryDrapeUiKey.Key + - type: UserInterface + interfaces: + enum.SurgeryDrapeUiKey.Key: + type: SurgeryDrapeBUI - type: entity parent: BaseItem diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/box.png b/Resources/Textures/SS220/Interface/Surgery/puppet/box.png new file mode 100644 index 0000000000000000000000000000000000000000..8dc58c46567a9627f1317f4f9d3268460b22e8bd GIT binary patch literal 3536 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/butt_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/butt_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..a9812de9758487584934cf60c0364936cd465e76 GIT binary patch literal 2265 zcmcIld2AF_9G+6awp>*VrB>2$P$|mnoO{f`QntIbN7%BI1zOO+oZGRxGsDcZ-BwI1 zryN2+Q6w~o21rF}G$J9jDGi4x3JM|?O=|qdD^e8*Q6l=Ldw>S8|G3HQzM1{r_xs-W z`{up1p{lacIrry45Hz}?JWvgOAGNQn5#amSkq;L^P=;Ia`$Ila<0ZJdtOPD17{)UN zf{He8-PBWgBy``Jo`TY2yXI$o`;9(%PAGb=cSFUZb#To-|Cap~{;mAIYG>;oxf|7q zPkjB%u*qe;zF)JKWghET;a@pw`1=0K?fuLHr!rpW&$q9hv-(oazQ(hgJHI>GmnUx< z)>gG`cjLYWD0lMGqZ9fEvf9oEyDrklueFkednz+0E$J$d9(*q6)1EUOBZ#{`es$+l zxntWpsxCNlXV2_-`C36J$M_4X{OHQE+{JBurnhhdb9j`oIAhktC+pi5ZW;4=?LheC z8&d6=i;wso+I06T8&6S(3fHWy9Cz-sIe8}rGM5mAQTN0{^AEh)eWmrf_nz~YBz0$5 z*37Biyjov;m!oywglRAQ#DAdYj~U=v!eWF&&^GXnJv6=Y)F6f_W*T zs9t2Cs|SG<(Tgl_2JxWom+F-A7DJlbQWX|j>IFtbif6#n<18SENfrmkV^P&)<6dNt zmjz?{F^0f{5UbvcBpO;148eZQkYK07g$e}igx!pTa8WeFOod6D@L;$HBPf(`u{6$- zB%J&ppqe4dY<1xAWG!&#Md~a|XECg)smakqIW!}Jfv_+bPGTg90t9L{s}>hWRdZ5` zAt0H8q3D*Psj$t+@mhoBMS#-85@LFqRyC700mHC3r(=Wzx0jRx27`Bo#$su-X_YpD zDoMS!BsRm%x`b6rrq*BxQfZ^4T9Z=6^g7Mb%sTBh+R}gjL!l_73+fF4YCN>zmYe!DRYT(m$@MXkx4Vv9=7kpi40c@YvPDHM02 zq%%z5EahS;>M@*RaXbMHf+&cb#SIOtiHh7j6lgHWR;Z@Mse)7y@FGBzLs3MQlU=ft zq-B)jNgQ?Jv=imsIEUgSFHn?+phZ$1)CJ}TG@-#Z!=9f=uBZusBW);4P!vNGf`}3n z=|pLU=1~v9$fzWdE|Ow+hH{fBw%LXPULG!bGpnsiv{iX{Qgk{cl#yu>6?mYJXPiz{ za8rVZAs7#kjlehR`n9NL1T_)3OChi*u(D>vI1nKv#ziDdS0i?y0XtYlxnhFuH78$@ zxzf_)D5}7NxnnuOj=L8T>^@5(l6BgO}3giL-Iv{(?iJ@ zc02th*Z3dmht&K3SAEi4p^j4{64*g8#9j|(?=^|mVE^vgjjpGX;{%ztduk?~DB&V$ zPiEXYs1)v^NDnQbJVm=vHw{w85u}XL1kcGhFS^_!l~55?Zg8dINq7iBA_OPO2#gD5 zYzht+c~qciisyORBQwHHvjNBHu=T|-*{JvyrU*+2FFP80itGlc~*xvu_$~~9Y zzt-5^TXJ>b-o6Xd4qQL2zWe;l$osy1a#oa9 K1v*QXEdLwSr~aG( literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/eyes_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/eyes_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..2737df2891e91b9e2e4a9883972b6a2a576cdd28 GIT binary patch literal 2250 zcmcImYitx%7@bmqwm_{yq=2O3qO>Tp^V-ME$b#G5+HS+POIe`>wVlV^cI@uXFf;9L zE2fR|5G5ce5sVcYK~pRBj|$k3LXd|-5RitT7-M2&CEj(WQwoCTLXU|G>@DK@cZbIPgWvGmQ(h40&YnW09sRCik6Tp z%gjTNk}cad4^$orOj|!NtL$j^vYcm|W%l0FS#Js#y4EdOH(cA>`uUc=A5LDJ z9@;*xvugXE*4`GRpm^2Cg_nnNIxqP9uiB2?=%9+;s?46#&_4^@_k7-&fpa|*$U9E$ z+WAz$ot-^ZU)T$(AL)7Z#;ib|_BT@b>GjnG^_>@WSJ6iH&}6MXYw_$Snmbo)%|Bl^ z6g>G3s5^IcuKWJY6L)MnO&=&)zoByK;Mpb9PYh)@kVRqV>;ua_c=zD-j=x-YT^I(@ zo#i=?%y&(%bH}@_9gh`0{PIu2adk%i5Z@k@`km^&tV#RIK3uW(mwOiEcOZw3RsT7> zbkPauWUg9Krz6OuspgS|y!Lt_f=t*e2WyR5|6)#5B39n?*BXzg5RD)U7R6OwYz77@ zfJQm$vJCYPSWsDVS(e-VgkSZ5Cb^T}WzUZ9JbtQxWsMNLLtP+AO^1u9KWA);nzqk76FXc!*nRh+aE=9JPvzkhUSB$7ev zMp-MYlG1xiVm;WV0=x$3N{c3fvQ`i^=A?_MO^TuDP0DSwWgh=ap(JJssx4YLVUHx@ zAPgdqTZdROp-<&J8sH5@3o1%D-KapiBI@y&jbYJ}<#JR~V){H&RB80-x7qfugLkEO9m} zMDZj+V+_NPnA2vHFrElWm?RN)_=Kl;hDzvy#)&4O0!jKGJJXX@JLoul>`|9Z6|qwbU+?Kz*xz~U`)a_ zi6v-<5aLDBX+dw+^(bLQ^D7c`m$smi$b}Rw!ovv35xx=NYP8V|G-QXfsE~Eoy|&aV zvJ|XJorPsIkvonT&A7WPqSH~C1k{S4Y(Vj(__gO zemng--#WVbvGxA{Ri83fY~rJh0Is08#hedrt~JTl;Q#H~&90}Dn2i+p5Ftnor$i@JgyoxD>3EV1DFRZoW30$JFxI5t z2}!_2+C~e45Mn~CIAU~SHc3(V=;{;55o>PR3b>G)rJyR=-U&&wFclGyEgZ{qnclAE~#_?-izBAROPrl!GaG3k-qu;8wj*Gp$x?3&% oam~3TzN(}vz;zoVZ+sQmrBpi#>+1<4*W4<7WmVq3(uQaM0Rp)AEdT%j literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/head.png b/Resources/Textures/SS220/Interface/Surgery/puppet/head.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ed791b7f4255ada9ae7b4ecb0bc9db484a18b9 GIT binary patch literal 2335 zcmcIld2AF_9G-$)<*)*g+YWz&GP861-uL_7_kHu; z!h-yquBm-f5d`TP$n_V({}-*JV|#c$dica71W9qpK3{=XQU!n(=47EmDVB8)Ly(~> zRxjJ1cetSE{Qdp1k8T{_@uwfPA>#@n7Y;5COj?ANZ1Jt!7VxbWx+~jjZl^6(22B3$ zwKhX?4tjs~uUD~~I<&X7O*#2iyHVGHk&l@-IX33U{b1Sz0dg@ZI(AqY& z`D-^^{~O6Wd24ps)Y?miCw(z{po=~=Wz2vnGioQU>~_Aisrb}K zp!DpG!QSVWb*Nu@hS`%oe_>va3*U_Eb-bzVG%7vf8n9>lXIuB(s=4oZ=HgADY{==D zIo#8$)EnDqt9hmOh&QhZUuu22HSyKOQlm@TmC|`v_U9Apet&jkw;JTYv9b4V7G)fV zN_Nt6OAQ3++`~FjkachOMv(T~)u@fP^tHvJ8mvZ;kr^?K7iRzy6+l>4 zJXllXehif*4>rLOB!ij{l*zf(Iw-2nFBYq3h^&NVj6z4oI0z60CXdFV5yjwQ9;_Lc zgV)w$0z;ci%o!dmZfHrc0QIRlKpi$GE>d;}>SAq_ld-exaFiw~H$l1yioq!-XD2zD zMiV~_cGIO0SLh#|=mqaQSea>R96?l8R@y2Vo2rKi7#5ZwX@aJ4Sb-Z=ipj@t#Te9r z;0K1N%bF>x3Th$pf?95RFvv7Mg{YQ27^y7jYgBzhM8Rf zyCnEN64)rN(g0Bi47FSrL3RaD%t0;9v@+FHjWYEy+>)>V#ZVHH4YhJT(kzc85+DMi z5Zi#YR5L$~^XY&$RlQhMBP~W1v~)y$KFb&u9Xdf)B(>5QW|2ya0e;>D9*ibw1}7aj z?I@<8YZS-0ULqNeB;%z)7zK$p`PLPyk{qgPO=&R51r)>N6%hpd9t@JQ$+E;z0FF4+>ishCV*7$gGB~^qNNkO>~FVTV^SMN^Egk55ZC399JI@B2caa|SY3u64r`l&}?Ohz@6wD;vCN! z1ZF0#A~M>XJB}BvxO*_s@)<~2;<5Y)^;*X>4qI6UAm|g!kCz#0$gJdb;0;5iTaz#1 zar_Ow;>qk==l%c7J|V7H#w%d}S5N}8=0jL(P23vdzpg!Sy(Kwbm}$#XBcVhVA5LmA z>d`@ENGC(P?IJEPb{7tVDZ!x9A>2+0e25ezr%PhuEF$s)td@9EZb}4{=)hT#b>gfA z!IP3;1(gv5A>tyJehrS2QY$Z1&W_ z-RVNW?=3E=yS{2)*F$8_xnC`4w^i!fchSX3hX%zyUc5Z_YSH^A?C5B+>uB%B%##D9 zt6v<;sJnOd?%l~}`Z%<|b{*K2yK0I5Qvdq-^_TY6?UvV-g-48^Mem%=O?~wmcn*1Q zc=q|z`T}`p#-9T+SAIWb(+elAU0=GR>4#(CL#u{tI$w4sg|ee#E; zzYH9lvwP0Dce2JP^&LK4lCz*w^yeL?+XlO%_is-v3LaiFwBN!{Ry_Xn)^vZm_wvim TD(8{q*8UsF&i8N6n)doX&Nw0# literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/head_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/head_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..50490161129e2255385f338b81fc7c5d710f76ea GIT binary patch literal 2294 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 NE6b`qJ*6wx{0p3q{v!YY literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_hand_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_hand_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..413e76baf08cecbb01bc0bccf832314cb1004c6c GIT binary patch literal 2252 zcmcIlYitx%7@bnVw!DfZQbocrRa=DFdF*pWmeTIlb{n=VWrY?rbRKuNV|QnUnQ6Oi zFs)i%f)LQgYDB08lZudNL_!);5UEI&K#?G#snHl?3~aRE4~gQPeE|lV{&ADpy)%36 z`OZDxnR~YcYAUDZJ)DOi$W&jIw-)|BVO}|t;P=TBU#vlpET`o01j6$We>&Ug;Bg`Xbvfkw{?%ue3<7nOfmh(Fg{xWoF zR;Xt}XHCz(mi^60e(}1`3P<8OofrN6S8angI_SCYRc9A9^v?$KHs_rk819=y-S_3* zJ+AE^c3#rmbGI?arf6%kmd<&$sdM$NX%bior-5SP;(>BIY(9=`rzpGj(^-UE{+0u zPi4-Mh3;APWwBmM$J2#RzWN*gx%$wwIM*H&`p+eTF8h0Q zMd>N%WUg9OuOrCh0`tm3-h8VNK_(rLf^|lne<>>{Q444KYl%fwh(?e_r7@Kgnt*}w zAS}slEZ#qWp_1svR@wceU-f`Ssj6KAE81&t<9uD8hs7sF+} zD9zvnx}Zs_At^FyGIG4qY`8I~G`WPRnxU2TlughuBF3o%Wg*QarGb9`*wAP+gVv3T z7FZ>v_pZcxuuTO-Ezp%_O#l@wKsJif#neW{Q1nLS9@;X0-=RyveZlLg@Ox-lrqB1s}k*^N@^(QpLpE;7VhIV!0O&i5)pvuTDoKbc%n5g3`hk=t2KRU%}xkq#zg7j3x1;RxZ7lg2qK zN#S<8!{HlWYy~->%*2dOA5}FwEw6=u|FvxzuI(J*Qh&xJ@{bT~z8Qbk0%#g&dH<)Q>Y33i+j z7zfUn6dWn?xL~zed7clsLX2?R?8Iu4qKL88Cy=-8xoIolMs5x>CkVXBoIyZ_wrTK$ z*~+pcJe^+jdCP)z8?Jow;nXfNf8x1#!DDAP56yeWft~K1(N3QG8GU8Rg0E&alwJv4 z9xUuT8*e)Heb;dAzJluF*J?_32iRVt{MWAG?iXs5^&jCwJv)kg+MjPXk6b?QJiYYe yDc8C$6ieaw&FoKqI#lc%MY8UgmAh|2_EF?$`SiU_or68*Vd<-=@g6L1c=2D@>-Ovb literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_leg.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_leg.png new file mode 100644 index 0000000000000000000000000000000000000000..fe4639fceaa46ff6236a00fad062cdc17fd79638 GIT binary patch literal 2260 zcmcIlYitx%7@hL6l!8T2s8Bl$AVry-=e|Z3Saxe4Vaw7k(1Hl_xYLf^of&qf?Y6>lGnu``tZntYX zf)s4oy1A?LcyR2xt_g)F_Abi&=BhqzK`?yr*v68j8_>%AzP1A;zOBLo>cQr}ayF@X zPkr_5plLa9g;I`W%u|iy_qdNfzE5}$s5gd{_CZKC$H$7046=3^?BF%j$zc0PhZ{h zOwRo+9c5qIbLP+Kc=^VJU{>^Rr1X>Pt8$jNTsA!U8{J2ltx znSG$@{Iy5DQ#RkXW7ApYNdCI@rDHChTQKfa@8A_we%P6JWYN2Cet5n4mS=QNA5ixc zWzL!58CT_v?X@*89RI|NzX%`exubgdrjXR>)DLHjI9&MNlC{4-G;>rla`fc%~gAumeh{(yR>3<3ufD<1K$}v4{@Q2r_eaOy|W~V4?!3 zR#Xqx+u4PoisZo-+XG}k_kkLvxG4%Mn#w|AQ?2Nhu-UWFnK2F$M1aYou}D}oxR?iv z^Kx)(JtiWfd`{Wn!!ms zPTNBi>>A6__Qy$vBgq6b0HYxBCOhGkiITo;`0*Tg!@3~PQOxsoPAj!5&?0Fz<-N6SFY8lU&u6#imNGBN1)l|XCLaXaZhU)r;o4QMP2Hys?fd!6 z^=*Ahx5GI+(DwaJ@1KqlQxENkoy%Kh?tc2GudgSh(9G?&4$YFnGXKH3 HD^~vl(JcFu literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_leg_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_leg_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..e20aca4549a1ee8452fcca46ac72b1c0e48ccc06 GIT binary patch literal 2232 zcmcIlYitx%7@bnVwiGBrpjd0i!AMbN=eavOBTFf}wU4l6TNY?RL+5d)9lJZT%uL&D zgJ~lQf&>CeL==z^Oe)gEABd#EmS6!z5mAB|BSwwEKc+LL*lCXv0DO_CwYF9nyVzU-usc zTlC^5zkX)ioXX?nzviyZ9_n4^e=dLgiqi<6wtv2@@5-4= zGu55rdTMtbXg}D76wF!kLE+`$oSuup{%h>Xo85HDn>E?9oBE5uLoei=8#v!Pfx748 zH}*bVFr}xr_A7V6l7+pm+$;*^nSUcSA6;Ktu)61xfRY!Qt|Bf2HT zeOQ85fcMU80z(rJyVZv!8`=;Ip?<>zsN3biWr}s99Pgq$49oNLP@1H?1nDIx2B$m% zOA0iNral;~W-6*sA9x~F3qJX<7Tb;q1kusa;p$*qhS^NOu~yhMi(wm9i*XlinZN&~P?0kQV{K+6VUHpc zAOfP0+k#jsp)V%*O(5Ea88(bax>2EYMbz(i8pETdD>Pj(I;^>lsMHt`5N+VYXp&}d z(v8#ZFhvTiTcEs;k&Hl+NoWv8K@n|nY+yst)XuR$gF&HMw`@_DL3O}~K~XMEQv^3l zF|u3aa9(k9xGb{_?qyjCm%NlLaxBMtBr%~2jt>}en`4GEKABv_kReCLP=VqU2>`Df zcLSQkNgfi3BF*EnO3ReTtMVL2r`eX68eBYL!-6UXt802R&jv6wcBtk4q{^ z<0Pp`+*ot|1LCl-m# z8BZ46Ij9WjVQ4Qa;}XMiILERIE>g6Lvy>#Nq@;K_g-NQ2Xt%i1@ucA50F>;;d71a% zyh9<9iiFDy%Se)>!l28yjZVxaDT)|feFAygo}0D;Zsg8w<^+KcnKKCJ&^8mEFhwl~ zZotz?uMU)l8=k-V1f_7@ksuwj5Sz1g+R{Ahsy8bM7%?7ufFB9%0+1U7-a9=G+e5of&qf?Y6q?G2faHERd{#vkE(~;f6v{j z&VK3YD-#OKPI`XJUYB{Q_jPaQ)Q8@@eyjUB^W=q$cln|2t|eU~bqAZj+J5-Q^H*ld zdnR;L?K#kVunEd7T>I&)>%&U;^*(| zUz7WAM{m`)_T1`4y>H#k59CDuhAKb1y)JiE#}&g}u$ehAHM%Ne@$8oyJC^U5{&oFu z@cjEy{iPevc;;@Kym#vb>UhD1O_eh)UtBWt>~Q94q9E*?eSGPW5BhJn|K)yUXhc%? zmt`%Q@19xjiS^jpU!3*)&Y$@+`s34wxwfF#=hTm7OgUD1bou(5j}=dEhfbWX{&S>u z;aQ+$wq9OuK+u#K)|CP6dwUiHO**6m>&!a;Vph;1HqO%57K`X08iI-!#&k|-luVeH zLW=4}hWiE(SP|XGGP@u5>t3lrDQ}BPwQW^Fp{-G1L}cLtxH!fFfQV#ra4Zs54L0UR z;W=S>Yq>AYcnyDEL+5@kJfeU} zVJQNz4G>Gj`RS}TDsiS34Qg6AHK;(UBJA~AgJIyJWr`|lEyg^HR5C~Mai-)(NSvfl z+>Vm=Ac3X>}-_jrf&@qOe2s1RhBt^eLVLZ wTiCUD$IaRag}bVXx&v&FnaUjX;Hk`hs3VY9cWdL-_<>nbTID-jvih}u0OYa#PXGV_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_arm.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_arm.png new file mode 100644 index 0000000000000000000000000000000000000000..19ad145c956a5ac0e3b6e1eccc1510f56b3dec77 GIT binary patch literal 2264 zcmcIleQXnD9KJDyF$NAWFotBuO=Ux_ch~FJl@;jPxsTATi~{@6u={wgJ!yO8?$&h+ zx&;LU2?Ugg;K0S8nUf`wD8a=H#32e27?KH5N8}Gfh%7q!M*{d>znBJa|JdZ(_pZv_z18JYv-7hN1esb{;jV?h3-v2&BK#gW`As8&WLPDa%j*isxs3O@q0Y@KHjmczL@(~@{q@Y{ z+2Y;_9o2geMSGf&+y(2u${QZZ>KO9$-87xL-EJ)SpenPlsc$Zr|9bZM{=x2v)MIDg zJNQ!W6CK^vSIoIJ%evpbJ=dG9+(W9qy|p2CZO3KRQLvpko};YISU&H?bsY`6r+r^P z;yd#Zs2{xftn=xelit~Jjy_hfWoy;UOBYtmK0T7zL=^{STq(2hE;pa zfyH_4aI8NjFfH3EoQpvuh(56Yq-tQDq;MFO%Ws{#2T+R}gjL!rQ@3r3oiVB8*o zCqNK{Ah!y!R9s)g?ot4&$%;>wgQ-S&Qx#E{OK%K=7Oj!Og507O>!Ol#fSc8T12d9F z8Yj)T(d?sO*XZX++D?)QoCoGWU^R9eNLi4?*8jwLJod`4sHmUrV#(`*ZqE{GUJ44*1M@DZ75~^ zX+xGgIHELZp^Y{eOpZ2LajVHB;4EbnaTCR{BFPCBt3W4I1f~02sbEqziU$;L#u=Wm z;EYbek^+bGw29_8PPB;(KW27(H3?C~qt(ZeV>a286>#U)hv_2&-lPv55QerX@JI=i z_8fr6QD>#w>8soH+t4Rdzii5Jv-g4>P5JrTZWQjCegvh@3;DT)dlv$Xtl5hFwEybG zRnaqNe{gL%oG~mGb#`f+2j^7z^+CJynZwbxJzcwx+?X_Yq;B%UVq&PVWUYLKZfw0U zdOh#jwd#ghx95C5uy6W6*@x!7R|2L$?+mR${chF%gNtph>p6$$x6XO(V+PU8JBaJ& W`4fr{Y`m)SkwXLi4Z22=Ax^fD;4=hak>4!*eMS1AznJpztHlcyT-knEEyt~Aw)uXKsvbL&I zmVUQtSZ>i7&)v~0hj(|Z@va>^a`VmK+i!A{FQ>dO_O`FDSbr~Yxbd4EM{ixao*mpX ztgUR%!N$W4NLKEO&nMpOOKa=(civ&oJ#3|?9x5F^eR*dNnD%=5*Ihjwsnm!s_wRcp z>#4SmvK#iS1@k&~Kg=ml*Z)RJFWz67wXE&B;hwsMJ3UrkmNI|J%k^!GcV>QD(^q}z zLr~Lm=XuX_+eW>;^)hpE>V}P_X8?UE_%!u(iH4@r5^k5zj}S&Fm9es^v~+LpIb=FUO$qdX)C15-ew;gD+ZQ8(7l z%fqqtn846}h*|H(Vhs)W%TceU1JrJF;1b2!Q731k91P2Gvr(F)Tm%lfs2eih)XnJSip(2z;XS$;C!DZHCSd?^JB@CH3@Pg4dp4B5@<;PI3O7b zx7%3}7g;HY)3lxDD9Qm`ES+Fmpeyj@5kdo5EmbmPWFb-(MFy7`K;xp5rE$R#LHl#Z3z8LgHzrwq1~L|ZtUSiOq3Mj#Hq`-0I!Na2`|Np9wn=91`YAt{}D1lk)A*{V7)*9l!U3=8^L~=YZ(^gN7xDy3JZPJrD zj}IzCIvCo;N-$BZ6L+$#j0+SU#92xdf}|)roH7$r5mFv;CE`iBC<#!K9p@y@fpZpx zK*}OaE6a$Y7<2_WX~69M)x<;*gRAdH4%l-OR=|zi8YWK=c#}MXfC_EX;R$nU&C=K4 z>9n@Q=cx{?`?dE>MjM*tyV5-=v{pZ!Kl8nDuLg*BD}EeRKzFU)J#E@{?6bz(fs8-) z?l)KWj4sX1Yqtg&p3M)BeXz)R?342~vlQjp$o4nbocsr~YA@csq21nO|0;FDs%!Hn wAN=0YTh((bv#gx={5;~|__qcOJ`L}8uG^hmyz9_*yLDWa6qfmp7A#-=53_Lg_W%F@ literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_foot.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_foot.png new file mode 100644 index 0000000000000000000000000000000000000000..ce168984e8dfe7eb9d773ad0040a877725a98729 GIT binary patch literal 2264 zcmcIlYitx%7@blrr9iDhcnD3$fs~@m&U0sXMiyvyYr75GZp#+hqQX4evAZ+N%(UHB zOq(h%MG%mH1cD7>QW14F2$kO2vXkQS{Ef00T|`xXJ9^nLYP> z=brD(y=#J%Wn*(5&w(IltiRk>1Ad>huk0-Fee}qQ#SoO?QoY__v7`$!TvJv87ZEJ$ zo(Vxk8#b@+uQ(DMzp8&q>Cv8r*;l`d&ZrKC&mUgvU;HXux5vA2uiv{_cud>Z`Dflb zE&thXUm7u^>~Qh*QOh%r_P*?WWz55`UAeL23On_5#xCK)j+NCb2kZ8Ld%^t;n3Vv~P<1#Oj={`p@=e5f6Rw z_Rbgb9_i|>yyVQQn%BGK)|6n5@efpS^5*iqC0!RyPr+LD&=_M$#{B%}n!6e{=ANq` z2%UOQu0MONuz33Vk#DU#O&u&)wWea+`7_lMj}K%nB?`i>{DTYkzyHb2&fA_xFAU1s z&a&)zvpf^)i(@^G&e};&Z~9UAJUTgdfbR%NeXi(%jL`>5KWbR<>x9|4ozS6UReujI zn0p*JIVxISZ$i-MarTt~ZQDKxg0l9ip*pKBFrO3kh=aHNb;Kf3fQF#ib7N6nY?dup zkQ-IagADZbBd{uYkVVb_9*BD7Cbhi7kQa1RhQy9$k(H3SbKu!A4iH3Si-%*8ux4^G z4-)6)z}S9_A#faGHG7amL+b)T*sB{d>~t`wNYGB$#X1OvqFHtpOyYza!`&D`p#;Ox zI7gCj@`HeChNN&czGsrPz?}zavaBeFVQp<~jyB4n8;uwU3ya|-Mv^E%pk}*f@iA00 zr==KtvMCyB)KYZ~wi$UrZ?QZGP?}gmB$}qx%%n}gFf7JLF~Wh{OG*I)fuW(1NE&Tg zrLCY!Qty3<%}{$(#%g3!Z!tujgYQ~Q;iCyD#Bi`-53@wTBK@{-e%6UMJ4CRKHic&2#J#v ziaSx#86rT}oE*VEg;N}kC!hfk1&O!#;emBYRoaIG4Fov9W?H-^%6^{*0iqnLDse1H zN-jlWQHqjWAV@TU@(RVG41qgw#YqwhZVGiNG>)=3EucIh z;;29}v`8tmi)RD`zEjt$hjk;MOTb+UfhCbsbR)up2&oaiQO2TLqaA3#4pvdFnxK2_ z$ya27yev5it8hGb9532&_aLI(XIVm$kJbCQH$0sQ+O{SckPgxO1kBVGtBp70;zr=~ zaPoycNWaOq4y}H8z5jpJC(RX`c&$+eJ1B%r{3Cea$~zg@f2^;B|-L8k4Vnn@>0 z_{Ov+bM7Bh3TG(NO^c{N(Js_Q(-H_6sh~6=@Cq(Sj7y>tD#Gd=u2eh;HzCS|=tNnO zWl+|p;BiSnMT({bK~UTZE8aCbzM6z6Y-sgy>#Piu&&EKe7rJ?yCE=p=4NIzos_Siu*4Y`nBIjPPlRUozf4sj5~k5deE6`lSG)FD*W8|@5R96`S!u-FRk?L ID_Pq8FC3Zp)c^nh literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_foot_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_foot_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..35be2fb4c2092ff58c58eb77b8f82d7cc212632a GIT binary patch literal 2253 zcmcIlYitx%7@hJg1!@H&FR{ZEr6{xW+{cV8u>ZMPND zMudtI2q+N=1R7IfMIioABn=kiVHF{QkP0zkjLHupjUs;}pm+8K7-;&(O=kDb?78PV z_k3sW-54q_9-lKc2SJeWr6qw1`1_oFWsid2r%rrTgCJRM#qSUKWK9HUMe#zkkm5Mc zYy>IXx_wJ;*@@5-9lg_vPIWHN{`n_;){;>4((z5DHE*I-`~BMvl=`=e6V!ukf9Gvh zr?32B?XX$J$9;c{Su^}p_d5UU<3_%9^{?Gmxu?!$y)X9f-mqlD&8q!P-)%j3?abv# zksZU@%Xf4&?QcZ#W;L9ge03nZy+7DAL^-@am7?#1eX z@R<)m^@SVH`etq!{odwt%#r+#jb#%ronJDkZ(#UpDnII;eq{M4A055bcGvrO|4pFo zDb8Ln$2+Oo7w>emEt~wp+tV8nCl(ZUPX=`~{YOR+z8Jj;3og3#NK@3;|8jnR)laG6` z1TPQA_G1D=6A-K3izORc6%3(%%>by=;ld?~b)s(0LAe-~2;c=nRVJDv}OK&K%p#U3hIqUG+~b{5g-a; zklTb*Ss8=ShYq$s{xgqaX{GFf_0xE0LC=K!ZWPR5dL@l|X60i$PHiMUivGAI6GQLS^=nbh2x>BPm%&h3;v^=h-OFdQ|;@;47CTW}N03;ox`AL|mMXYAQ0KQu2^ic9e zJW9VQG!3qPXubb`)u+sr>IAhGz#Wvp?DY`#UXyGM@!zi9>v}plKA35{r)J8Dg+gt{ zlX(vhDnq&$+5^KbGB8_imX&dVqG4_+QHYSD>~hOYQbkm`$CZvJ<)I`%Nlu)TI2X>@ z6ap!WxWurGD2fqJgp=-@omfp$6fwB^1oFN;H*E#n$n9a~1c5i1GYF{AHUpk8S2{af z@N`;N8t{dy*8l$f$K%&6$QyaSt?!x8$*zmOqZb=y7k$>z82IH_-=^q{vu7%&-qCB8 z{hGV4ZS*cscs8`Nll3bbE{yA%QpUU!dGNujE4d$A56x_GytGYzy5Rcf?!DK~_IK6{ vG>*9YMOV=^>VwT&b{=ZjVKW>avo;HxFrm_OAon->v@9(u4;);$`n7)n2#xra literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_hand.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_hand.png new file mode 100644 index 0000000000000000000000000000000000000000..a137dc03b216b3d8df311f31584ec4eaa7b33d86 GIT binary patch literal 2272 zcmcIlYitx%7@eh9OQBfBKoLpDL2c2_&TC&YvXru0`v_Z>vO)_Yy)$#S9lE=->`dEj z#k7hPPyzu3(bzyW7;B~K4;DGV2cDH8Vnfl5or+l0S)S%eNhcy|G3HQ-kJUG z`OZD(%)RTq6(w1j4`gB(mQ_~ju0+3&8COO+`tCn?WGRN3?260fEs|9c;*}+H@qC8o z9Wya3fAiK&edPze_pj}nR@~pYDC67@;k*Ujz{vv}%9g%_S9QC#>@IU{74Hl6v|i8N z7@Gd{ch8N+bfdEBbB{;t)o7bo2H^4UK+&hif)GrcXI>S$lkex<6r@xln-zU$S*oy5xH2=nx zg@uQalHzzd0&?=7?cc~GGTg^67Vk}nN&YKw}u zp#_%36CVO~i^zVV(*0zj7rJv2b-ErFD5|Nc$=t-5)krOc!opKDOR+48DoCw4q=P6K z(q<$P+)$GuN?2Fa5N;p>QEkwj1Y#OrLNJ_y4QUCPkYH34gek^M8%s(SdOUY74F*%y znqJ(9x+M7C5?J#!hapu7HMJok!Qw_3(q|-_h3iyZ)#}vSa7+FD4~DXoY8Y;a1Y+{Y z5(NV=h_E$O%f$GF1y=+DU5)ruHIOvQo9u|YT!t|`p1)WL$!e1}(;$_Y1KmJ}PJ*Rb zj-;(5YxOb6HH$#=kJFq$)A3Rdih>MuFuGz@R{YJQDfM`SvXG{OkOa%zP6ClKD~c?z zjNi{XWC!Vxc}PmE#ZCfNbdW%10WaIEyv#8%UTD5sl^P5&jQR28%BqAgQi2K$1Fa55 zl1Wi?0MgG{AnA}eJ82hrJFrU*nzmb$X!9cqdU-%#B&xwmHdq0R&F`>VNsEmKq+Pa1 zq{Ghu5+Z_DKW}AZ015nNUzZwCBOX;o>T(1wOM+jG1ObYW5(Kr73WsWqKqGXtic&>G z?lmV~k%e$&ViZvDSndQM8F6)0GRKMN?BCx0y zDLtBeQMco-fyVz}KPvD4U-k)cr8*F*g=hz*2xC2zvDd_{q5kdKP1lpjDMFbxJk=6P z%muY6O=jOZs2pwMSO{Ck`ia(L{aoR{Jb<`c5F3qQq-N<$0|qcxk)L|Ms5sKCkVPpok1{!WQ(8^ zX4#T~L3BE8D{~k5s@DAW?MGRk&B=BTIDbqpc=yoy)kik(94ip`Bib+f8=lVx%S<^P zpMJRE!o}`q&jdb@{ejN!&$OxBtH->8>*oo(Mhq^sc5bPxd`5MZUq212_6+nC_MgA@ z7MWYWqJ84r literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_hand_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_hand_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..f732d11ec518e2d05ece72c383e3a21e302904d9 GIT binary patch literal 2254 zcmcIlYitx%7@blcZJ|)WShO}Bwnjjio#*cCj4X87t$lw!1_2=mVjwEJ(-6fNPbnNuNMA3Yh5{G;rFqlCzm5g7OQx@0gtSS0IjW>qvF=yA>n4nO{p#&qSGlK7XT2|8+_iqu`djt;Tff`Z^XsWg zg`u6Jx@vZQ(7L|`DVVYPt7%sUbGk12dv7ww?{-opA693VuIeoU(_hOw(|4|WEIIm{ zJ-eFGpw!VUak6dRTy##HhXhez3TQkUQ4K!s zwk3FZIJO?+HZ%b-o87i#L+kwk)T_k+>aaU8iDVoo%h^dM&2Zdolp;tMPPlNA#z-g6 z5IjYpsgDg-i^(Cr*7riH7QA!Y8ckE@alEaq&E7`awOAO3Vc~Fs!YK-a2+U|#O(Bk{ zMroSC2Mj5u=%%8nsKqFVT8rtnL8ZwhMD+}?2#oL zL_idB8xTt-^y$1e1_V=!1vM>_Zd4#$5%qel#&GD&rHU$RZN@B1RB8_J2_|scD1xFf z!hunaAW85v$J5T|2%0B|B-9V1APc52GO#8qq4tqL{eHemHB3R3K$XvJgQDz;BJ(b% zNC5{+VH6o+Fpgw6Oq5s`#<2wDkR>U^IK_l6INzs9EtVP9{A6-vO@bU5L-`QLk~A%` zm_U;ujCL{vCQ_`7u?)wEjNqaiE;-G%Fs8tlM~Dn%wN%NF(J2scGR0yt3%?i#BVhu| zIWXXI0xBdqWgtRT59)fgh!*o}GIW==p|Zq>v{+Pt5mKT;7~r}ZwgL^=;VLQ>19q=H z^@`Mi)u~ZLK@+*-1<8uL+a_6k22gMEvGNf2My4}4zpW8K(qWpPgc(}MY!hO@6NXNY zBwzTW^czC!@ajj_`~O#c%3P^YP{RQ3pt#Li4{q%>$=2Zi?b?H`r<3D>nYMarq@0*9 zgfpJZeRxo5!bwvu7%P!xSQt-6#srcIVGJn>Awra$tV}0WM3e_y>3EVZQUavpz&MF> zVw^=G5VD9#G((G`7;=R;X~^uvYLcSx;ngRQL-yRX6>uZBhM5xt-ek@ophDYX@PzT0 z1G(^YT36-s1nXbD@qK^p(Xt6+&R%+B%9Vk)ey(deb@F_ZeO`n~0p84UUamTrE xR;2mfFTW$DlX4E1-57On{CRtzH#z9Zzn#6}`s7-!X0@dUsVcAW^~_)O@;`Ow_Rs(T literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_leg.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_leg.png new file mode 100644 index 0000000000000000000000000000000000000000..3dcd178f220826458e3cad1250e2f3abdc219b8f GIT binary patch literal 2274 zcmcIld2AF_9G(`0QlJ!3K@K}kjTB^d&OK&ef$eVX5w)@o!qJ%%Ga(2XU0mcV1HaR)D`Ob=K7Q=8N(f4G%3g24BdWXvmlfv2c?83_ zra@5NmTjB5N{$5{ThleE;CS2Oj4PM5sS5&;3r9B;SFVT4JG@)>7kjtyk17Wm|IFT~ zOnKqkRq0a;k9uy5SUKc)`>Wp9Mm^GU>G$22n8|0-_VO2ZH!o925e#?Ptr+dbS zTGN|KTldv=)Iix&SA06*QeQ^XMStgY`o!%+RYhO$ z^t)0;_w{Ez&ukjLW8)d>P|lilCF3rfTQL4q-;m`*PQ*Fo(BhBYJAAA0ANSacHzj3H zVaD7U?(r3#SevbJ(S(_A{KTKsp3Lgw8iHb{Q#+V8@?gQoOIQE)#H_4F=*Wrrf8Sg< z=M+$KgjQ5xK+wo>)|CeB+%*A$hV7Sw0;8--G7;Maq z#Bo_LwjN^$94|4e-AKaFa(@8!s=5T*Z4Oi*Xglm=Y=ncN8D<7d;)DysT^K>3goCAV zmL%ci2Lau5F~pYno=^4ycW$K0G&L5(>gwujb(Brj!x#t)gW)7bk|?M^je5o8VyI%| zrVxCRA?UJZ%Blieh#arhm~I3xO)Md*^}{MgQYIi67UMLGu;JE{QiXp1z@^b>f3;y2 z)PgQaz7GU8g7un&l}UzLqYF|&t)!T_sb*T0YN|$+`VemYfB%P}DD*efYV=549#O!g zh!h3b2B;vv62LW@rK#s|ipB9nsUJi^IYNm_p`-{VRDKf75m%>AZ3$fkp*N4 zrzAL0(IxSylcouj=ZO$13QopO(j?^A^uOAKp%BDta}0E~V?Sr@@^cA9gd zE=m$mDnyGYN7)(FL5MgWa_~5fJ5p%#bs4-oT;yI`@un_a0f-YXaVIZkS!2<5LAK;p)|pBA)FT-PLWEmh{$)bQt>2Qgdh=u9c2W@ zfie~Zhl@NaP&CE!e8?4IgnMSkSCb%x4a`1XdC#7kk^*ex*0BEs0XO|;kfZ?Fba29q zy^$URr&DXO&l4%yIa{i=kN4wkg*UZb6S^Jnbe+caK%J&U=mHh2JFRwjywPEYq`r%#IT4v9+^;RxY z?YURN>h>R7O1iK9frOejpZ#t+L4DuSz4`3z+T2j{`a65^HQkd-jJz+~K6Lgjdwk>9 SM8_)Y2rVus^&QAx{>s0Z8w1<` literal 0 HcmV?d00001 diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_leg_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_leg_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..a7fffc7476a90862fcc7c4bed8f1ed8100117a2c GIT binary patch literal 2237 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 s3&eHI&1t8bZvWBIJU&`sV5t$Qr&3=}y`2n?U Date: Thu, 26 Dec 2024 15:20:17 +0300 Subject: [PATCH 2/6] Added-more-reliable-surgery-graph-conditions --- .../SS220/Surgery/Ui/SurgeryDrapeBUI.cs | 25 ++++++++- .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml | 1 + .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs | 13 +++++ .../Surgery/Systems/SurgeryDrapeSystem.cs | 17 +++++- .../Graph/Conditions/DamageAmountCondition.cs | 44 +++++++++++++++ .../Graph/Conditions/StateCondition.cs | 38 +++++++++++++ .../Graph/Conditions/WhitelistCondition.cs | 35 ++++++++++++ .../SS220/Surgery/Graph/GraphInterfaces.cs | 56 +++++++++++++++++++ .../SS220/Surgery/Graph/SurgeryGraph.cs | 4 +- .../Surgery/Ui/SurgeryDrapeUIMessages.cs | 6 +- .../UserInterface/OnInteractUIComponent.cs | 3 + .../SS220/UserInterface/OnInteractUIEvents.cs | 7 +++ .../SS220/UserInterface/OnInteractUISystem.cs | 15 ++++- 13 files changed, 253 insertions(+), 11 deletions(-) create mode 100644 Content.Shared/SS220/Surgery/Graph/Conditions/DamageAmountCondition.cs create mode 100644 Content.Shared/SS220/Surgery/Graph/Conditions/StateCondition.cs create mode 100644 Content.Shared/SS220/Surgery/Graph/Conditions/WhitelistCondition.cs create mode 100644 Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs index 913be852d7a941..9d3ff795408d64 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs @@ -32,16 +32,35 @@ protected override void UpdateState(BoundUserInterfaceState state) switch (state) { case SurgeryDrapeUpdate update: - _menu?.UpdateOperations(GetAvailableOperations(update.User, update.Target)); + _menu?.UpdateOperations(GetAvailableOperations(EntMan.GetEntity(update.User), + EntMan.GetEntity(update.Target))); break; } } private List GetAvailableOperations(EntityUid user, EntityUid target) { + + // soo... + // For what it needed: to exclude operations which is role specific (such as mindslavefix) + // also think of unavailability of some operation (no mindslave or you cant resurrect alive and etc) + + // we kinda need to check roles of performer and components of target + // also also I want to have an advanced operation firstly to cure immobility or blindness <- how to do it in common way? + // Maybe I need interface like ISurgeryGraphCondition to make it obvious and adoptable <- true, soo true + // for beginning its better to prohibit making invalid operations. + + // Performer shouldnt see surgery if he is not allowed var result = _prototypeManager.EnumeratePrototypes() - .Where(proto => _entityWhitelist.IsWhitelistPass(proto.TargetWhitelist, target) - && _entityWhitelist.IsWhitelistPass(proto.PerformerWhitelist, user)) + .Where((proto) => + { + foreach (var condition in proto.PerformerAvailabilityCondition) + { + if (!condition.Condition(user, EntMan, out _)) + return false; + } + return true; + }) .ToList(); return result; diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml index c4766170a0c738..c3a33b11fa72af 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml @@ -6,4 +6,5 @@ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"> + diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs index 0a4547edc0b143..93c53d4be07593 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs @@ -84,6 +84,14 @@ public void Initialize() TexturePath = partTexturePath }; + var partButton = new PuppetButton + { + Part = part, + // TODO: make it from enum of PuppetParts + SetSize = new Vector2(1f, 2f), + Margin = new Thickness(1f, 2f, 3f, 4f) + }; + bodyPartTexture.Add(part, texture); return texture; @@ -210,4 +218,9 @@ private void RemoveHighlight(PuppetParts part) } } +public sealed class PuppetButton : BaseButton +{ + public PuppetParts Part; +} + diff --git a/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs b/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs index eeec7e3f0f0146..c6101552abfe49 100644 --- a/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs +++ b/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs @@ -1,6 +1,8 @@ // © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Shared.SS220.Surgery.Components; using Content.Shared.SS220.Surgery.Ui; +using Content.Shared.SS220.UserInterface; using Robust.Server.GameObjects; namespace Content.Server.SS220.Surgery.Systems; @@ -13,13 +15,26 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(AfterOpenUI); + SubscribeLocalEvent(TargetUpdate); + } + private void AfterOpenUI(Entity entity, ref AfterInteractUIOpenEvent args) + { + UpdateUserInterface(entity.Owner, args.User, args.Target); + } + + private void TargetUpdate(Entity entity, ref InteractUITargetUpdate args) + { + UpdateUserInterface(entity.Owner, args.User, args.Target); } public void UpdateUserInterface(EntityUid drape, EntityUid user, EntityUid target) { + var netUser = GetNetEntity(user); + var netTarget = GetNetEntity(target); - var state = new SurgeryDrapeUpdate(user, target); + var state = new SurgeryDrapeUpdate(netUser, netTarget); _userInterface.SetUiState(drape, SurgeryDrapeUiKey.Key, state); } } diff --git a/Content.Shared/SS220/Surgery/Graph/Conditions/DamageAmountCondition.cs b/Content.Shared/SS220/Surgery/Graph/Conditions/DamageAmountCondition.cs new file mode 100644 index 00000000000000..acd88ff8ccd8a9 --- /dev/null +++ b/Content.Shared/SS220/Surgery/Graph/Conditions/DamageAmountCondition.cs @@ -0,0 +1,44 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using JetBrains.Annotations; + +namespace Content.Shared.SS220.Surgery.Graph.Conditions; + +[UsedImplicitly] +[Serializable] +[DataDefinition] +public sealed partial class DamageAmountCondition : IAbstractSurgeryGraphAvailabilityCondition +{ + [DataField(required: true)] + public FlippingCondition FlippingCondition; + + [DataField] + public string FailReasonPath = "surgery-availability-condition-damage-amount"; + + [DataField] + public string BaseFailReasonPath = "surgery-availability-condition-base-fail"; + + public bool Condition(EntityUid uid, IEntityManager entityManager, [NotNullWhen(false)] out string? reason) + { + if (!entityManager.TryGetComponent(uid, out var damageableComponent)) + { + reason = Loc.GetString(BaseFailReasonPath); + return false; + } + + reason = Loc.GetString($"{FailReasonPath}-{FlippingCondition.ConditionType.ToString().ToLower()}"); + + return FlippingCondition.IsPassed((x) => damageableComponent.TotalDamage > x, + (x) => damageableComponent.TotalDamage < x); + } + +} + +public enum CheckTypes +{ + More, + Less +} diff --git a/Content.Shared/SS220/Surgery/Graph/Conditions/StateCondition.cs b/Content.Shared/SS220/Surgery/Graph/Conditions/StateCondition.cs new file mode 100644 index 00000000000000..95bffbf26da07b --- /dev/null +++ b/Content.Shared/SS220/Surgery/Graph/Conditions/StateCondition.cs @@ -0,0 +1,38 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using JetBrains.Annotations; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Shared.SS220.Surgery.Graph.Conditions; + +[UsedImplicitly] +[Serializable] +[DataDefinition] +public sealed partial class StateCondition : IAbstractSurgeryGraphAvailabilityCondition +{ + [DataField(required: true)] + public FlippingCondition> FlippingCondition; + + [DataField] + public string FailReasonPath = "surgery-availability-condition-state"; + + [DataField] + public string BaseFailReasonPath = "surgery-availability-condition-base-fail"; + + + public bool Condition(EntityUid uid, IEntityManager entityManager, [NotNullWhen(false)] out string? reason) + { + if (!entityManager.TryGetComponent(uid, out var mobStateComponent)) + { + reason = Loc.GetString(BaseFailReasonPath); + return false; + } + + reason = Loc.GetString($"{FailReasonPath}-{FlippingCondition.ConditionType.ToString().ToLower()}"); + + return FlippingCondition.IsPassed((x) => x.Contains(mobStateComponent.CurrentState), + (x) => !x.Contains(mobStateComponent.CurrentState)); + } +} diff --git a/Content.Shared/SS220/Surgery/Graph/Conditions/WhitelistCondition.cs b/Content.Shared/SS220/Surgery/Graph/Conditions/WhitelistCondition.cs new file mode 100644 index 00000000000000..a3f715903ef99c --- /dev/null +++ b/Content.Shared/SS220/Surgery/Graph/Conditions/WhitelistCondition.cs @@ -0,0 +1,35 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Shared.Whitelist; +using JetBrains.Annotations; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Shared.SS220.Surgery.Graph.Conditions; + +[UsedImplicitly] +[Serializable] +[DataDefinition] +public sealed partial class WhitelistCondition : IAbstractSurgeryGraphAvailabilityCondition +{ + [DataField(required: true)] + public FlippingCondition FlippingCondition; + + [DataField] + public string FailReasonPath = "surgery-availability-condition-damage-amount"; + + public bool Condition(EntityUid uid, IEntityManager entityManager, [NotNullWhen(false)] out string? reason) + { + var whitelist = entityManager.System(); + reason = Loc.GetString(FailReasonPath); + + return FlippingCondition.IsPassed((x) => whitelist.IsWhitelistPass(x, uid), + (x) => whitelist.IsWhitelistFail(x, uid)); + + } +} + +public enum WhitelistCheckTypes +{ + Whitelist, + Blacklist +} diff --git a/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs b/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs new file mode 100644 index 00000000000000..988749f056ccaa --- /dev/null +++ b/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs @@ -0,0 +1,56 @@ + +using System.Diagnostics.CodeAnalysis; + +namespace Content.Shared.SS220.Surgery.Graph; + +/// +/// Used to define if this person can make surgery. +/// +[ImplicitDataDefinitionForInheritors] +public partial interface IAbstractSurgeryGraphAvailabilityCondition +{ + /// + /// Checks if specified condition is passed + /// + /// Condition target + /// Could be null if condition result is true + /// + bool Condition(EntityUid uid, IEntityManager entityManager, [NotNullWhen(false)] out string? reason); +} + +[DataDefinition] +public partial struct FlippingCondition +{ + [DataField(required: true)] + public T Value; + + [DataField(required: true)] + public FlippingConditionType ConditionType; + + /// + /// Checks if passed under ConditionType. + /// + /// True if passed forward arm + /// True if passed reverse arm + public bool IsPassed(Func forwardCheck, Func reverseCheck) + { + switch (ConditionType) + { + case FlippingConditionType.Straight: + if (forwardCheck(Value)) + return true; + break; + case FlippingConditionType.Reverse: + if (reverseCheck(Value)) + return true; + break; + } + return false; + } +} + +public enum FlippingConditionType +{ + Straight, + Reverse +} diff --git a/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs b/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs index cea02d4072d3ae..d27c8b6c0b90bc 100644 --- a/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs +++ b/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs @@ -24,10 +24,10 @@ public sealed partial class SurgeryGraphPrototype : IPrototype, ISerializationHo public PuppetParts TargetPuppetPart; [DataField] - public EntityWhitelist? PerformerWhitelist; + public List PerformerAvailabilityCondition = new(); [DataField] - public EntityWhitelist? TargetWhitelist; + public List TargetAvailabilityCondition = new(); [DataField("graph", priority: 0)] private List _graph = new(); diff --git a/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs b/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs index 3a0d460e1f4cb0..97fa49fb33be37 100644 --- a/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs +++ b/Content.Shared/SS220/Surgery/Ui/SurgeryDrapeUIMessages.cs @@ -11,8 +11,8 @@ public sealed class SurgeryStarted : BoundUserInterfaceMessage } [Serializable, NetSerializable] -public sealed class SurgeryDrapeUpdate(EntityUid user, EntityUid target) : BoundUserInterfaceState +public sealed class SurgeryDrapeUpdate(NetEntity user, NetEntity target) : BoundUserInterfaceState { - public EntityUid User { get; } = user; - public EntityUid Target { get; } = target; + public NetEntity User { get; } = user; + public NetEntity Target { get; } = target; } diff --git a/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs b/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs index f1045b7cd83cf4..a1935bbf9680e0 100644 --- a/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs +++ b/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs @@ -10,6 +10,9 @@ public sealed partial class OnInteractUIComponent : Component [DataField(required: true, customTypeSerializer: typeof(EnumSerializer))] public Enum? Key; + [ViewVariables(VVAccess.ReadWrite)] + public EntityUid? Target; + /// /// Whether the item must be held in one of the user's hands to work. /// This is ignored unless is true. diff --git a/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs b/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs index 9202a82e86f464..650e84f9754cde 100644 --- a/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs +++ b/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs @@ -26,6 +26,13 @@ public sealed class AfterInteractUIOpenEvent(EntityUid who, EntityUid target, En public readonly EntityUid Actor = actor; } +public sealed class InteractUITargetUpdate(EntityUid who, EntityUid target, EntityUid actor) : EntityEventArgs +{ + public EntityUid User { get; } = who; + public EntityUid Target { get; } = target; + public readonly EntityUid Actor = actor; +} + public sealed class InteractUIPlayerChangedEvent : EntityEventArgs { diff --git a/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs b/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs index 3cf26c7dfcf64e..51313b30f6d6e8 100644 --- a/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs +++ b/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs @@ -25,17 +25,28 @@ private void InteractUsing(Entity entity, ref AfterIntera args.Handled = InteractUI(args.User, entity, args.Target.Value); } + + // TODO: make it possible when clicking on other target it will close old ui and open new one + private bool InteractUI(EntityUid user, Entity uiEntity, EntityUid target) { if (uiEntity.Comp.Key == null || !_userInterface.HasUi(uiEntity.Owner, uiEntity.Comp.Key)) return false; - if (_userInterface.IsUiOpen(uiEntity.Owner, uiEntity.Comp.Key, user)) + if (_userInterface.IsUiOpen(uiEntity.Owner, uiEntity.Comp.Key, user) + && uiEntity.Comp.Target == target) { + uiEntity.Comp.Target = null; _userInterface.CloseUi(uiEntity.Owner, uiEntity.Comp.Key, user); return true; } + if (uiEntity.Comp.Target != target) + { + var updateEvent = new InteractUITargetUpdate(user, target, user); + RaiseLocalEvent(uiEntity, updateEvent); + } + if (!_actionBlocker.CanInteract(user, uiEntity.Owner)) return false; @@ -59,7 +70,7 @@ private bool InteractUI(EntityUid user, Entity uiEntity, //Let the component know a user opened it so it can do whatever it needs to do var aie = new AfterInteractUIOpenEvent(user, target, user); RaiseLocalEvent(uiEntity, aie); - + uiEntity.Comp.Target = target; return true; } } From 37648c12b92bd2f1652f00d28191d0b1987d82bc Mon Sep 17 00:00:00 2001 From: Anri Date: Thu, 26 Dec 2024 21:15:19 +0300 Subject: [PATCH 3/6] clickable-puppet --- .../SS220/Surgery/Ui/SurgeryDrapeBUI.cs | 2 - .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml | 21 +--- .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs | 40 ------- .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml | 1 - .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs | 104 ++++++++++++++++-- 5 files changed, 98 insertions(+), 70 deletions(-) diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs index 9d3ff795408d64..04a46726209716 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs @@ -3,7 +3,6 @@ using System.Linq; using Content.Shared.SS220.Surgery.Graph; using Content.Shared.SS220.Surgery.Ui; -using Content.Shared.Whitelist; using Robust.Client.UserInterface; using Robust.Shared.Prototypes; @@ -12,7 +11,6 @@ namespace Content.Client.SS220.Surgery.Ui; public sealed class SurgeryDrapeBUI : BoundUserInterface { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; [ViewVariables] private SurgeryDrapeMenu? _menu; diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml index f98e112ef57ce7..9eb4c5e8581ac2 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml @@ -15,25 +15,12 @@ - - - + + + - - - - - - - - ().OrderBy(variant => LocPuppetPartPath(variant))) - { - var textPart = MakePuppetPartButton(part); - PuppetPartContainer.AddChild(textPart); - } } public void UpdateOperations(List graphPrototypes) @@ -37,42 +28,11 @@ public void UpdateOperations(List graphPrototypes) } - private PuppetPartButton MakePuppetPartButton(PuppetParts part) - { - var partButton = new PuppetPartButton - { - Text = Loc.GetString(LocPuppetPartPath(part)), - Part = part, - Pressed = part == Puppet.SelectedPart, - ToggleMode = true, - StyleClasses = { "OpenBoth" } - }; - - partButton.OnPressed += (_) => - { - Puppet.SelectedPart = part; - UpdatePuppetPartButtons(); - }; - - return partButton; - } - private string LocPuppetPartPath(PuppetParts part) { return Enum.GetName(typeof(PuppetParts), part)!; } - private void UpdatePuppetPartButtons() - { - foreach (var child in PuppetPartContainer.Children) - { - if (child is not PuppetPartButton button) - continue; - - button.Pressed = button.Part == Puppet.SelectedPart; - } - } - private sealed class PuppetPartButton : Button { public PuppetParts Part; diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml index c3a33b11fa72af..c4766170a0c738 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml @@ -6,5 +6,4 @@ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"> - diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs index 93c53d4be07593..d6d7367faa6170 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs @@ -39,8 +39,10 @@ public PuppetParts? SelectedPart private SortedDictionary _parts = new(); private SortedDictionary _highlightedPart = new(); + private List _partButtons = new(); private const string TexturePath = "/Textures/SS220/Interface/Surgery/puppet"; + private Vector2 _baseTextureSize = new(42f, 42f); public SurgeryPuppetBox() { @@ -61,11 +63,16 @@ public void Initialize() textPart = MakePuppetPartButton(part, _highlightedPart, GetPuppetPartSelectedTexturePath); - if (textPart == null) - continue; + if (textPart != null) + { + this.AddChild(textPart); + textPart.Visible = false; + } + + var button = MakePuppetPartButton(part); - this.AddChild(textPart); - textPart.Visible = false; + if (button != null) + AddChild(button); } ResizeAll(); @@ -84,17 +91,34 @@ public void Initialize() TexturePath = partTexturePath }; - var partButton = new PuppetButton + bodyPartTexture.Add(part, texture); + + return texture; + } + + private Control? MakePuppetPartButton(PuppetParts part) + { + var offset = GetPartOffset(part); + var size = GetPartSize(part); + + if (offset == null || size == null) + return null; + + var thickness = GetPartThickness(offset.Value, size.Value, Scale); + + var button = new PuppetButton { Part = part, - // TODO: make it from enum of PuppetParts - SetSize = new Vector2(1f, 2f), - Margin = new Thickness(1f, 2f, 3f, 4f) + SetSize = size.Value * Scale, + Margin = thickness, }; - bodyPartTexture.Add(part, texture); + button.OnPressed += (_) => + { + SelectedPart = part; + }; - return texture; + return button; } private void ResizeAll() @@ -107,6 +131,17 @@ private void ResizeAll() { texture.TextureScale = _scale; } + foreach (var button in _partButtons) + { + var size = GetPartSize(button.Part); + var offset = GetPartOffset(button.Part); + + if (size == null || offset == null) + return; + + button.Margin = GetPartThickness(offset.Value, size.Value, Scale); + button.SetSize = size.Value * Scale; + } Background.TextureScale = _scale; } @@ -216,6 +251,55 @@ private void RemoveHighlight(PuppetParts part) return null; return string.Join('/', [TexturePath, state]); } + + private Vector2? GetPartSize(PuppetParts part) + { + return part switch + { + PuppetParts.Head => new Vector2(9, 7), + PuppetParts.Torso => new Vector2(9f, 9f), + PuppetParts.RightArm => new Vector2(4f, 6f), + PuppetParts.RightHand => new Vector2(4f, 4f), + PuppetParts.LeftArm => new Vector2(4f, 6f), + PuppetParts.LeftHand => new Vector2(4f, 4f), + PuppetParts.LeftLeg => new Vector2(4f, 7f), + PuppetParts.LeftFoot => new Vector2(6f, 3f), + PuppetParts.RightLeg => new Vector2(4f, 7f), + PuppetParts.RightFoot => new Vector2(6f, 3f), + PuppetParts.LowerTorso => new Vector2(9f, 4f), + _ => null + }; + } + + private Vector2? GetPartOffset(PuppetParts part) + { + return part switch + { + PuppetParts.Head => new Vector2(16f, 6f), + PuppetParts.Torso => new Vector2(16f, 13f), + PuppetParts.RightArm => new Vector2(12f, 15f), + PuppetParts.RightHand => new Vector2(12f, 21f), + PuppetParts.LeftArm => new Vector2(25f, 15f), + PuppetParts.LeftHand => new Vector2(26f, 21f), + PuppetParts.LeftLeg => new Vector2(21f, 26f), + PuppetParts.LeftFoot => new Vector2(21f, 32f), + PuppetParts.RightLeg => new Vector2(16f, 26f), + PuppetParts.RightFoot => new Vector2(14f, 32f), + PuppetParts.LowerTorso => new Vector2(16f, 22f), + _ => null + }; + } + + private Thickness GetPartThickness(Vector2 offset, Vector2 size, Vector2 scale) + { + var trueTextureSize = _baseTextureSize * scale; + Vector2 otherMargin = trueTextureSize - (offset + size) * scale; + return new Thickness(offset.X * scale.X, offset.Y * scale.Y, otherMargin.X, otherMargin.Y); + } + private Thickness GetPartThickness(Vector2 offset, Vector2 size) + { + return GetPartThickness(offset, size, Scale); + } } public sealed class PuppetButton : BaseButton From 5d009abbf6f0c966a55b0ff06518e1e3668955d2 Mon Sep 17 00:00:00 2001 From: Anri Date: Thu, 26 Dec 2024 21:19:03 +0300 Subject: [PATCH 4/6] license-220-moment --- Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs b/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs index 988749f056ccaa..f40805df9886f1 100644 --- a/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs +++ b/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs @@ -1,3 +1,4 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt using System.Diagnostics.CodeAnalysis; From 5acdfce60c223cdc6c435029ffb4396f9379edfc Mon Sep 17 00:00:00 2001 From: Anri Date: Sun, 29 Dec 2024 19:39:14 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D0=9B=D0=B0=D0=BC=D0=BF=D1=83=D1=81-=D0=B2?= =?UTF-8?q?=D0=B0=D0=BC=D0=BF=D1=83=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SS220/Surgery/Ui/SurgeryDrapeBUI.cs | 21 +-- .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml | 17 +- .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs | 163 +++++++++++++++++- .../SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs | 7 +- .../Graph/SharedSurgeryAvaibilityChecks.cs | 36 ++++ .../SS220/Surgery/Graph/SurgeryGraph.cs | 18 ++ .../Locale/ru-RU/ss220/surgery/specific.ftl | 1 - .../Locale/ru-RU/ss220/surgery/surgery.ftl | 3 + .../Recipes/Surgery/MindSlaveSurgery.yml | 4 + 9 files changed, 241 insertions(+), 29 deletions(-) create mode 100644 Content.Shared/SS220/Surgery/Graph/SharedSurgeryAvaibilityChecks.cs create mode 100644 Resources/Locale/ru-RU/ss220/surgery/surgery.ftl diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs index 04a46726209716..9c3cc07271f9c4 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs @@ -30,7 +30,7 @@ protected override void UpdateState(BoundUserInterfaceState state) switch (state) { case SurgeryDrapeUpdate update: - _menu?.UpdateOperations(GetAvailableOperations(EntMan.GetEntity(update.User), + _menu?.AddOperations(GetAvailableOperations(EntMan.GetEntity(update.User), EntMan.GetEntity(update.Target))); break; } @@ -38,26 +38,11 @@ protected override void UpdateState(BoundUserInterfaceState state) private List GetAvailableOperations(EntityUid user, EntityUid target) { - - // soo... - // For what it needed: to exclude operations which is role specific (such as mindslavefix) - // also think of unavailability of some operation (no mindslave or you cant resurrect alive and etc) - - // we kinda need to check roles of performer and components of target - // also also I want to have an advanced operation firstly to cure immobility or blindness <- how to do it in common way? - // Maybe I need interface like ISurgeryGraphCondition to make it obvious and adoptable <- true, soo true - // for beginning its better to prohibit making invalid operations. - // Performer shouldnt see surgery if he is not allowed var result = _prototypeManager.EnumeratePrototypes() - .Where((proto) => + .Where((graph) => { - foreach (var condition in proto.PerformerAvailabilityCondition) - { - if (!condition.Condition(user, EntMan, out _)) - return false; - } - return true; + return SharedSurgeryAvaibilityChecks.IsSurgeryGraphAvailablePerformer(user, graph, EntMan); }) .ToList(); diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml index 9eb4c5e8581ac2..1cd91ff0c3307a 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml @@ -16,13 +16,13 @@ + VerticalExpand="False" HorizontalExpand="False"> - - - + + + + + + + + diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs index cf2f0da77270b5..b2a38421397061 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs @@ -4,14 +4,25 @@ using Content.Shared.SS220.Surgery; using Content.Shared.SS220.Surgery.Graph; using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Client.SS220.Surgery.Ui; [GenerateTypedNameReferences] public sealed partial class SurgeryDrapeMenu : FancyWindow { + [Dependency] private readonly IEntityManager _entityManager = default!; + + public event Action>? OnSurgeryCLicked; + + private Dictionary> _operations = new(); + + // maybe I shouldn't keep it here.... public EntityUid Target; public EntityUid Performer; @@ -21,20 +32,162 @@ public SurgeryDrapeMenu() RobustXamlLoader.Load(this); Puppet.Initialize(); + + Puppet.SelectedPartChanged += (part, prevPart) => + { + OperationsLabel.Text = LocPuppetPartPath(part); + UpdateOperations(part, prevPart); + }; + + _operations.Clear(); // never knows what coming after all + foreach (var part in Enum.GetValues()) + { + _operations.Add(part, new(8)); + } + } + + public void UpdateOperations(PuppetParts? currentPart, PuppetParts? previousPart) + { + if (currentPart != null) + { + foreach (var control in _operations[currentPart.Value]) + { + control.Visible = true; + } + } + if (previousPart != null) + { + foreach (var control in _operations[previousPart.Value]) + { + control.Visible = false; + } + } + } + + public void AddOperations(List graphPrototypes) + { + foreach (var val in _operations.Values) + { + val.Clear(); + } + + foreach (var graph in graphPrototypes) + { + var button = MakeOperationButton(graph); + OperationContainer.AddChild(button); + SetFormattedText(button.RichTextLabel, graph.NameLocPath); + button.Visible = graph.TargetPuppetPart == Puppet.SelectedPart; + _operations[graph.TargetPuppetPart].Add(button); + } } - public void UpdateOperations(List graphPrototypes) + private SurgeryPerformButton MakeOperationButton(SurgeryGraphPrototype surgeryGraph) { + var button = new SurgeryPerformButton(surgeryGraph.ID) + { + VerticalAlignment = VAlignment.Top, + StyleClasses = { "OpenBoth" } + }; + button.HorizontalExpandAll = false; + button.VerticalExpandAll = true; + + button.OnPressed += (_) => + { + OnSurgeryCLicked?.Invoke(button.GraphId); + }; + + button.OnMouseEntered += (_) => + { + if (surgeryGraph.PostscriptLocPath != null) + SetFormattedText(OperationPostscript, surgeryGraph.PostscriptLocPath); + + SetFormattedText(OperationName, surgeryGraph.NameLocPath); + SetFormattedText(OperationDescription, surgeryGraph.DescriptionLocPath); + }; + if (SharedSurgeryAvaibilityChecks.IsSurgeryGraphAvailableTarget(Target, surgeryGraph, _entityManager, out var reason)) + { + var tooltip = new Tooltip(); + SetFormattedText(tooltip, reason!); + button.TooltipSupplier = (_) => tooltip; + } + + return button; } - private string LocPuppetPartPath(PuppetParts part) + private string LocPuppetPartPath(PuppetParts? part) + { + if (part == null) + return "surgery-puppet-part-none"; + return $"surgery-puppet-part-{Enum.GetName(typeof(PuppetParts), part)!}"; + } + + /// + /// Some helper function to easily set formatted message from locPath + /// + private void SetFormattedText(Action setterFormatted, Action setterString, string locPath) + { + var loc = Loc.GetString(locPath); + if (FormattedMessage.TryFromMarkup(loc, out var msg)) + setterFormatted(msg); + else + setterString(loc); + } + + private void SetFormattedText(RichTextLabel richTextLabel, string locPath) + { + SetFormattedText((x) => richTextLabel.SetMessage(x), (x) => richTextLabel.Text = x, locPath); + } + + private void SetFormattedText(Tooltip tooltip, string locPath) + { + SetFormattedText(tooltip.SetMessage, (x) => tooltip.Text = x, locPath); + } +} + +public sealed class SurgeryPerformButton : ContainerButton +{ + [ViewVariables] + public ProtoId GraphId; + public RichTextLabel RichTextLabel { get; } + + public SurgeryPerformButton(ProtoId graphId) + { + GraphId = graphId; + + AddStyleClass(StyleClassButton); + RichTextLabel = new RichTextLabel + { + StyleClasses = { StyleClassButton } + }; + AddChild(RichTextLabel); + } + + public void SetMessage(FormattedMessage msg) + { + RichTextLabel.SetMessage(msg); + } + + [ViewVariables] + public string? Text { get => RichTextLabel.Text; set => RichTextLabel.Text = value; } + + [ViewVariables] + public bool HorizontalExpandAll { - return Enum.GetName(typeof(PuppetParts), part)!; + set + { + HorizontalExpand = value; + RichTextLabel.HorizontalExpand = value; + } } - private sealed class PuppetPartButton : Button + [ViewVariables] + public bool VerticalExpandAll { - public PuppetParts Part; + set + { + VerticalExpand = value; + RichTextLabel.VerticalExpand = value; + } } } diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs index d6d7367faa6170..43de120cc2622b 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs @@ -12,7 +12,10 @@ namespace Content.Client.SS220.Surgery.Ui; [GenerateTypedNameReferences] public sealed partial class SurgeryPuppetBox : Control { - // SS220_TODO: if i had time add some properties not to hardcode in xaml + /// + /// First argument is current part, second is previous + /// + public event Action? SelectedPartChanged; public Vector2 Scale { @@ -182,6 +185,8 @@ private void ResizeAll() if (newHighlightPart != null) MakeHighlighted(newHighlightPart.Value); + SelectedPartChanged?.Invoke(newHighlightPart, _selectedPart); + return newHighlightPart; } diff --git a/Content.Shared/SS220/Surgery/Graph/SharedSurgeryAvaibilityChecks.cs b/Content.Shared/SS220/Surgery/Graph/SharedSurgeryAvaibilityChecks.cs new file mode 100644 index 00000000000000..c9557b802df172 --- /dev/null +++ b/Content.Shared/SS220/Surgery/Graph/SharedSurgeryAvaibilityChecks.cs @@ -0,0 +1,36 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +namespace Content.Shared.SS220.Surgery.Graph; + +public static class SharedSurgeryAvaibilityChecks +{ + public static bool IsSurgeryGraphAvailablePerformer(EntityUid target, SurgeryGraphPrototype graph, IEntityManager entityManager) + { + foreach (var condition in graph.PerformerAvailabilityCondition) + { + if (!IsAvailable(target, condition, entityManager, out _)) + return false; + } + return true; + } + + public static bool IsSurgeryGraphAvailableTarget(EntityUid target, SurgeryGraphPrototype graph, IEntityManager entityManager, out string? reason) + { + reason = null; + foreach (var condition in graph.TargetAvailabilityCondition) + { + if (!IsAvailable(target, condition, entityManager, out reason)) + return false; + } + return true; + } + + private static bool IsAvailable(EntityUid target, IAbstractSurgeryGraphAvailabilityCondition condition, + IEntityManager entityManager, out string? reason) + { + if (!condition.Condition(target, entityManager, out reason)) + return false; + + return true; + } +} diff --git a/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs b/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs index d27c8b6c0b90bc..efeee0926ec7ba 100644 --- a/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs +++ b/Content.Shared/SS220/Surgery/Graph/SurgeryGraph.cs @@ -14,6 +14,24 @@ public sealed partial class SurgeryGraphPrototype : IPrototype, ISerializationHo [IdDataField] public string ID { get; private set; } = default!; + /// + /// Unique name to show in UI. Serves for user comfort orientation. + /// + [DataField(required: true)] + public string NameLocPath = ""; + + /// + /// Detailed description of operation: what it does and etc. Could be lore-boxedish + /// + [DataField(required: true)] + public string DescriptionLocPath = ""; + + /// + /// More gameplay specific information. Like if it needs a special tool or operation exclude other one. + /// + [DataField] + public string? PostscriptLocPath; + [DataField(required: true)] public string Start { get; private set; } = default!; diff --git a/Resources/Locale/ru-RU/ss220/surgery/specific.ftl b/Resources/Locale/ru-RU/ss220/surgery/specific.ftl index 4cf510f137cbce..8b39762df6dd4a 100644 --- a/Resources/Locale/ru-RU/ss220/surgery/specific.ftl +++ b/Resources/Locale/ru-RU/ss220/surgery/specific.ftl @@ -1,6 +1,5 @@ surgery-mind-slave-fix-examine-description = На тело наложенные хирургические простыни. surgery-mind-slave-fix-popup = {THE($user)} подготовляет {THE($target)} к операции, используя {$used} -surgery-mind-slave-fix-description = SS220 REDACTED mind-slave-disfunction-fix-surgery-examine-description = В открытой части мозга вы видите имплант подчинения разума. mind-slave-disfunction-fix-surgery-popup = {THE($user)} подключает порты {THE($used)} к импланту {$used} diff --git a/Resources/Locale/ru-RU/ss220/surgery/surgery.ftl b/Resources/Locale/ru-RU/ss220/surgery/surgery.ftl new file mode 100644 index 00000000000000..8b99ff1a2b2a2e --- /dev/null +++ b/Resources/Locale/ru-RU/ss220/surgery/surgery.ftl @@ -0,0 +1,3 @@ +surgery-mind-slave-fix-name = Калибровка импланта подчинения +surgery-mind-slave-fix-description = Имплант подчинения разума может быть настроен под центральную нервную систему носителя. Для этого потребуется произвести непосредственную настройку импланта. +surgery-mind-slave-fix-postscript = Для этой операции необходим [bold] {ent-MindslaveFixerCerebralImplant} [/bold] diff --git a/Resources/Prototypes/SS220/Recipes/Surgery/MindSlaveSurgery.yml b/Resources/Prototypes/SS220/Recipes/Surgery/MindSlaveSurgery.yml index fcecbb16cae1e3..2a6953435ad885 100644 --- a/Resources/Prototypes/SS220/Recipes/Surgery/MindSlaveSurgery.yml +++ b/Resources/Prototypes/SS220/Recipes/Surgery/MindSlaveSurgery.yml @@ -1,5 +1,9 @@ - type: surgeryGraph id: MindSlaveFix + nameLocPath: surgery-mind-slave-fix-name + descriptionLocPath: surgery-mind-slave-fix-description + postscriptLocPath: surgery-mind-slave-fix-postscript + targetPuppetPart: head start: start end: seal wound graph: From e883335d7c527eddcd5b4212ce250441e2e0c8d3 Mon Sep 17 00:00:00 2001 From: Anri Date: Sat, 4 Jan 2025 21:01:28 +0300 Subject: [PATCH 6/6] ResuscitateAction --- .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml | 4 - .../SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs | 15 +- .../ActionSystems/SurgeryResuscitateSystem.cs | 140 ++++++++++++++++++ .../Actions/SurgeryResuscitateAction.cs | 19 +++ .../Locale/ru-RU/ss220/surgery/actions.ftl | 3 + 5 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 Content.Server/SS220/Surgery/ActionSystems/SurgeryResuscitateSystem.cs create mode 100644 Content.Server/SS220/Surgery/Actions/SurgeryResuscitateAction.cs create mode 100644 Resources/Locale/ru-RU/ss220/surgery/actions.ftl diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml index 1cd91ff0c3307a..a005daccceb9d4 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml @@ -37,12 +37,8 @@ - - diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs index b2a38421397061..3c8e5f0349da97 100644 --- a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs @@ -98,11 +98,16 @@ private SurgeryPerformButton MakeOperationButton(SurgeryGraphPrototype surgeryGr button.OnMouseEntered += (_) => { + var formattedText = FormattedMessage.FromMarkupPermissive(Loc.GetString(surgeryGraph.DescriptionLocPath)); + if (surgeryGraph.PostscriptLocPath != null) - SetFormattedText(OperationPostscript, surgeryGraph.PostscriptLocPath); + { + formattedText.PushNewline(); + var postscriptMessage = Loc.GetString(surgeryGraph.PostscriptLocPath); + formattedText.AddMessage(FormattedMessage.FromMarkupPermissive(postscriptMessage)); + } - SetFormattedText(OperationName, surgeryGraph.NameLocPath); - SetFormattedText(OperationDescription, surgeryGraph.DescriptionLocPath); + OperationDescription.SetMessage(formattedText); }; if (SharedSurgeryAvaibilityChecks.IsSurgeryGraphAvailableTarget(Target, surgeryGraph, _entityManager, out var reason)) @@ -111,6 +116,10 @@ private SurgeryPerformButton MakeOperationButton(SurgeryGraphPrototype surgeryGr SetFormattedText(tooltip, reason!); button.TooltipSupplier = (_) => tooltip; } + else + { + button.TooltipSupplier = (_) => null; + } return button; } diff --git a/Content.Server/SS220/Surgery/ActionSystems/SurgeryResuscitateSystem.cs b/Content.Server/SS220/Surgery/ActionSystems/SurgeryResuscitateSystem.cs new file mode 100644 index 00000000000000..0a5ee1182eefa9 --- /dev/null +++ b/Content.Server/SS220/Surgery/ActionSystems/SurgeryResuscitateSystem.cs @@ -0,0 +1,140 @@ +// copypaste from defib + +using Content.Server.Atmos.Rotting; +using Content.Server.EUI; +using Content.Server.Ghost; +using Content.Server.Mind; +using Content.Server.Traits.Assorted; +using Content.Shared.Damage; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Robust.Shared.Player; + +namespace Content.Server.Surgery.ActionSystems; + +public sealed class SurgeryResuscitateSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly RottingSystem _rotting = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public void Resuscitate(EntityUid target, EntityUid? performer, DamageSpecifier heal) + { + if (performer.HasValue) + Resuscitate(target, performer.Value, heal); + else + Resuscitate(target, heal); + } + + public void Resuscitate(EntityUid target, EntityUid performer, DamageSpecifier heal) + { + if (!TryComp(target, out var mob) || + !TryComp(target, out var thresholds)) + return; + + var targetSession = GetEntitySession(target); + var performerSession = GetEntitySession(performer); + + if (performerSession == null) + { + Log.Debug($"Tried to make a resuscitate action with null performer session. performer - {ToPrettyString(performer)}, target - {ToPrettyString(target)}"); + return; + } + + if (_rotting.IsRotten(target)) + { + _popup.PopupCursor("surgery-resuscitate-rotten", performerSession); + } + else if (HasComp(target)) + { + _popup.PopupCursor("surgery-resuscitate-unrevivable", performerSession); + } + else + { + if (_mobState.IsDead(target, mob)) + _damageable.TryChangeDamage(target, heal, true, origin: performer); + + if (_mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold) && + TryComp(target, out var damageableComponent) && + damageableComponent.TotalDamage < threshold) + { + _mobState.ChangeMobState(target, MobState.Critical, mob, performer); + } + + if (_mind.TryGetMind(target, out _, out var mind) && + mind.Session is { } playerSession) + { + targetSession = playerSession; + // notify them they're being revived. + if (mind.CurrentEntity != target) + { + _euiManager.OpenEui(new ReturnToBodyEui(mind, _mind), targetSession); + } + } + else + { + _popup.PopupCursor("surgery-resuscitate-no-mind", performerSession); + } + } + } + + public void Resuscitate(EntityUid target, DamageSpecifier heal) + { + if (!TryComp(target, out var mob) || + !TryComp(target, out var thresholds)) + return; + + var targetSession = GetEntitySession(target); + + if (_rotting.IsRotten(target)) + { + return; + } + else if (HasComp(target)) + { + return; + } + else + { + if (_mobState.IsDead(target, mob)) + _damageable.TryChangeDamage(target, heal, true); + + if (_mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold) && + TryComp(target, out var damageableComponent) && + damageableComponent.TotalDamage < threshold) + { + _mobState.ChangeMobState(target, MobState.Critical, mob); + } + + if (_mind.TryGetMind(target, out _, out var mind) && + mind.Session is { } playerSession) + { + targetSession = playerSession; + // notify them they're being revived. + if (mind.CurrentEntity != target) + { + _euiManager.OpenEui(new ReturnToBodyEui(mind, _mind), targetSession); + } + } + else + { + return; + } + } + } + + private ICommonSession? GetEntitySession(EntityUid uid) + { + if (!_mind.TryGetMind(uid, out var _, out var mindComponent)) + return null; + + return mindComponent.Session; + } + +} diff --git a/Content.Server/SS220/Surgery/Actions/SurgeryResuscitateAction.cs b/Content.Server/SS220/Surgery/Actions/SurgeryResuscitateAction.cs new file mode 100644 index 00000000000000..8dd4144e289ee0 --- /dev/null +++ b/Content.Server/SS220/Surgery/Actions/SurgeryResuscitateAction.cs @@ -0,0 +1,19 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Server.Surgery.ActionSystems; +using Content.Shared.Damage; +using Content.Shared.SS220.Surgery.Graph; + +namespace Content.Server.SS220.Surgery.Actions; + +[DataDefinition] +public sealed partial class SurgeryResuscitateAction : ISurgeryGraphAction +{ + [DataField(required: true)] + public DamageSpecifier Heal = default!; + + public void PerformAction(EntityUid uid, EntityUid? userUid, EntityUid? used, IEntityManager entityManager) + { + entityManager.System().Resuscitate(uid, userUid, Heal); + } +} diff --git a/Resources/Locale/ru-RU/ss220/surgery/actions.ftl b/Resources/Locale/ru-RU/ss220/surgery/actions.ftl new file mode 100644 index 00000000000000..e3ea66e9e05168 --- /dev/null +++ b/Resources/Locale/ru-RU/ss220/surgery/actions.ftl @@ -0,0 +1,3 @@ +surgery-resuscitate-rotten = Ткани пациента прогнили! +surgery-resuscitate-unrevivable = Реанимация пациента невозможна! +surgery-resuscitate-no-mind = Реакция пациента отсутствует!