diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs new file mode 100644 index 000000000000..9c3cc07271f9 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeBUI.cs @@ -0,0 +1,51 @@ +// © 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 Robust.Client.UserInterface; +using Robust.Shared.Prototypes; + +namespace Content.Client.SS220.Surgery.Ui; + +public sealed class SurgeryDrapeBUI : BoundUserInterface +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = 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?.AddOperations(GetAvailableOperations(EntMan.GetEntity(update.User), + EntMan.GetEntity(update.Target))); + break; + } + } + + private List GetAvailableOperations(EntityUid user, EntityUid target) + { + // Performer shouldnt see surgery if he is not allowed + var result = _prototypeManager.EnumeratePrototypes() + .Where((graph) => + { + return SharedSurgeryAvaibilityChecks.IsSurgeryGraphAvailablePerformer(user, graph, EntMan); + }) + .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 000000000000..a005daccceb9 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 000000000000..3c8e5f0349da --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryDrapeMenu.xaml.cs @@ -0,0 +1,202 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt + +using Content.Client.UserInterface.Controls; +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; + + public SurgeryDrapeMenu() + { + IoCManager.InjectDependencies(this); + 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); + } + } + + 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 += (_) => + { + var formattedText = FormattedMessage.FromMarkupPermissive(Loc.GetString(surgeryGraph.DescriptionLocPath)); + + if (surgeryGraph.PostscriptLocPath != null) + { + formattedText.PushNewline(); + var postscriptMessage = Loc.GetString(surgeryGraph.PostscriptLocPath); + formattedText.AddMessage(FormattedMessage.FromMarkupPermissive(postscriptMessage)); + } + + OperationDescription.SetMessage(formattedText); + }; + + if (SharedSurgeryAvaibilityChecks.IsSurgeryGraphAvailableTarget(Target, surgeryGraph, _entityManager, out var reason)) + { + var tooltip = new Tooltip(); + SetFormattedText(tooltip, reason!); + button.TooltipSupplier = (_) => tooltip; + } + else + { + button.TooltipSupplier = (_) => null; + } + + return button; + } + + 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 + { + set + { + HorizontalExpand = value; + RichTextLabel.HorizontalExpand = value; + } + } + + [ViewVariables] + public bool VerticalExpandAll + { + set + { + VerticalExpand = value; + RichTextLabel.VerticalExpand = value; + } + } +} diff --git a/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml new file mode 100644 index 000000000000..c4766170a0c7 --- /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 000000000000..43de120cc262 --- /dev/null +++ b/Content.Client/SS220/Surgery/Ui/SurgeryPuppetBox.xaml.cs @@ -0,0 +1,315 @@ +// © 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 +{ + /// + /// First argument is current part, second is previous + /// + public event Action? SelectedPartChanged; + + 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 List _partButtons = new(); + + private const string TexturePath = "/Textures/SS220/Interface/Surgery/puppet"; + private Vector2 _baseTextureSize = new(42f, 42f); + + 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) + { + this.AddChild(textPart); + textPart.Visible = false; + } + + var button = MakePuppetPartButton(part); + + if (button != null) + AddChild(button); + } + + 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 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, + SetSize = size.Value * Scale, + Margin = thickness, + }; + + button.OnPressed += (_) => + { + SelectedPart = part; + }; + + return button; + } + + private void ResizeAll() + { + foreach (var texture in _parts.Values) + { + texture.TextureScale = _scale; + } + foreach (var texture in _highlightedPart.Values) + { + 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; + } + + /// + /// 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); + + SelectedPartChanged?.Invoke(newHighlightPart, _selectedPart); + + 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]); + } + + 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 +{ + public PuppetParts Part; +} + + diff --git a/Content.Server/SS220/Surgery/ActionSystems/SurgeryResuscitateSystem.cs b/Content.Server/SS220/Surgery/ActionSystems/SurgeryResuscitateSystem.cs new file mode 100644 index 000000000000..0a5ee1182eef --- /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 000000000000..8dd4144e289e --- /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/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs b/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs new file mode 100644 index 000000000000..c6101552abfe --- /dev/null +++ b/Content.Server/SS220/Surgery/Systems/SurgeryDrapeSystem.cs @@ -0,0 +1,40 @@ +// © 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; + +public sealed class SurgeryDrapeSystem : EntitySystem +{ + [Dependency] private readonly UserInterfaceSystem _userInterface = default!; + + 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(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 000000000000..acd88ff8ccd8 --- /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 000000000000..95bffbf26da0 --- /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 000000000000..a3f715903ef9 --- /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 000000000000..f40805df9886 --- /dev/null +++ b/Content.Shared/SS220/Surgery/Graph/GraphInterfaces.cs @@ -0,0 +1,57 @@ +// © 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; + +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/SharedSurgeryAvaibilityChecks.cs b/Content.Shared/SS220/Surgery/Graph/SharedSurgeryAvaibilityChecks.cs new file mode 100644 index 000000000000..c9557b802df1 --- /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 6d6145e443a1..efeee0926ec7 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; @@ -13,12 +14,39 @@ 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!; [DataField(required: true)] public string End { get; private set; } = default!; + [DataField(required: true)] + public PuppetParts TargetPuppetPart; + + [DataField] + public List PerformerAvailabilityCondition = new(); + + [DataField] + public List TargetAvailabilityCondition = new(); + [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 000000000000..0cb8aa00feb4 --- /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 000000000000..be6ded5e216e --- /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 000000000000..97fa49fb33be --- /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(NetEntity user, NetEntity target) : BoundUserInterfaceState +{ + 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 new file mode 100644 index 000000000000..a1935bbf9680 --- /dev/null +++ b/Content.Shared/SS220/UserInterface/OnInteractUIComponent.cs @@ -0,0 +1,27 @@ +// © 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; + + [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. + /// + [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 000000000000..650e84f9754c --- /dev/null +++ b/Content.Shared/SS220/UserInterface/OnInteractUIEvents.cs @@ -0,0 +1,39 @@ +// 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 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 new file mode 100644 index 000000000000..51313b30f6d6 --- /dev/null +++ b/Content.Shared/SS220/UserInterface/OnInteractUISystem.cs @@ -0,0 +1,76 @@ +// 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); + } + + + // 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) + && 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; + + // 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); + uiEntity.Comp.Target = target; + return true; + } +} 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 000000000000..e3ea66e9e051 --- /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 = Реакция пациента отсутствует! diff --git a/Resources/Locale/ru-RU/ss220/surgery/specific.ftl b/Resources/Locale/ru-RU/ss220/surgery/specific.ftl index 4cf510f137cb..8b39762df6dd 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 000000000000..8b99ff1a2b2a --- /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/Entities/Objects/Specific/Medical/surgery.yml b/Resources/Prototypes/SS220/Entities/Objects/Specific/Medical/surgery.yml index e82b42302e13..6550b49bce70 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/Prototypes/SS220/Recipes/Surgery/MindSlaveSurgery.yml b/Resources/Prototypes/SS220/Recipes/Surgery/MindSlaveSurgery.yml index fcecbb16cae1..2a6953435ad8 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: 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 000000000000..8dc58c46567a Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/box.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/butt.png b/Resources/Textures/SS220/Interface/Surgery/puppet/butt.png new file mode 100644 index 000000000000..ae11623ce2f4 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/butt.png differ 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 000000000000..a9812de97584 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/butt_selected.png differ 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 000000000000..2737df2891e9 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/eyes_selected.png differ 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 000000000000..d7ed791b7f42 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/head.png differ 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 000000000000..50490161129e Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/head_selected.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_arm.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_arm.png new file mode 100644 index 000000000000..0bb5ac4199f2 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_arm.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_arm_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_arm_selected.png new file mode 100644 index 000000000000..481ed683b4db Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_arm_selected.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_foot.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_foot.png new file mode 100644 index 000000000000..f7fccb18292f Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_foot.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_foot_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_foot_selected.png new file mode 100644 index 000000000000..a57a9ca50939 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_foot_selected.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/left_hand.png b/Resources/Textures/SS220/Interface/Surgery/puppet/left_hand.png new file mode 100644 index 000000000000..e6099a4f0303 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_hand.png differ 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 000000000000..413e76baf08c Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_hand_selected.png differ 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 000000000000..fe4639fceaa4 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_leg.png differ 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 000000000000..e20aca4549a1 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/left_leg_selected.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/license.txt b/Resources/Textures/SS220/Interface/Surgery/puppet/license.txt new file mode 100644 index 000000000000..2b206c2d46e1 --- /dev/null +++ b/Resources/Textures/SS220/Interface/Surgery/puppet/license.txt @@ -0,0 +1 @@ +All images under EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt. Author is MiXNikita. \ No newline at end of file diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/mouth_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/mouth_selected.png new file mode 100644 index 000000000000..5c78ced1e360 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/mouth_selected.png differ 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 000000000000..19ad145c956a Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_arm.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/right_arm_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/right_arm_selected.png new file mode 100644 index 000000000000..48cac276f136 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_arm_selected.png differ 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 000000000000..ce168984e8df Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_foot.png differ 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 000000000000..35be2fb4c209 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_foot_selected.png differ 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 000000000000..a137dc03b216 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_hand.png differ 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 000000000000..f732d11ec518 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_hand_selected.png differ 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 000000000000..3dcd178f2208 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_leg.png differ 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 000000000000..a7fffc7476a9 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/right_leg_selected.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/torso.png b/Resources/Textures/SS220/Interface/Surgery/puppet/torso.png new file mode 100644 index 000000000000..c3da62fbc2c5 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/torso.png differ diff --git a/Resources/Textures/SS220/Interface/Surgery/puppet/torso_selected.png b/Resources/Textures/SS220/Interface/Surgery/puppet/torso_selected.png new file mode 100644 index 000000000000..2a1b215500a9 Binary files /dev/null and b/Resources/Textures/SS220/Interface/Surgery/puppet/torso_selected.png differ