| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
- // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
- // SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
- //
- // SPDX-License-Identifier: AGPL-3.0-or-later
- using Content.Client._Shitmed.Choice.UI;
- using Content.Client.Administration.UI.CustomControls;
- using Content.Shared._Shitmed.Medical.Surgery;
- using Content.Shared.Body.Components;
- using Content.Shared.Body.Part;
- using JetBrains.Annotations;
- using Robust.Client.GameObjects;
- using Robust.Client.Player;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Utility;
- namespace Content.Client._Shitmed.Medical.Surgery;
- [UsedImplicitly]
- public sealed class SurgeryBui : BoundUserInterface
- {
- [Dependency] private readonly IEntityManager _entities = default!;
- [Dependency] private readonly IPlayerManager _player = default!;
- private readonly SurgerySystem _system;
- [ViewVariables]
- private SurgeryWindow? _window;
- private EntityUid? _part;
- private bool _isBody;
- private (EntityUid Ent, EntProtoId Proto)? _surgery;
- private readonly List<EntProtoId> _previousSurgeries = new();
- public SurgeryBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) => _system = _entities.System<SurgerySystem>();
- protected override void ReceiveMessage(BoundUserInterfaceMessage message)
- {
- if (_window is null
- || message is not SurgeryBuiRefreshMessage)
- return;
- RefreshUI();
- }
- protected override void UpdateState(BoundUserInterfaceState state)
- {
- if (state is not SurgeryBuiState s)
- return;
- Update(s);
- }
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- _window?.Dispose();
- }
- private void Update(SurgeryBuiState state)
- {
- if (_window == null)
- {
- _window = new SurgeryWindow();
- _window.OnClose += Close;
- _window.Title = Loc.GetString("surgery-ui-window-title");
- _window.PartsButton.OnPressed += _ =>
- {
- _part = null;
- _isBody = false;
- _surgery = null;
- _previousSurgeries.Clear();
- View(ViewType.Parts);
- };
- _window.SurgeriesButton.OnPressed += _ =>
- {
- _surgery = null;
- _previousSurgeries.Clear();
- if (!_entities.TryGetNetEntity(_part, out var netPart)
- || State is not SurgeryBuiState s
- || !s.Choices.TryGetValue(netPart.Value, out var surgeries))
- return;
- OnPartPressed(netPart.Value, surgeries);
- };
- _window.StepsButton.OnPressed += _ =>
- {
- if (!_entities.TryGetNetEntity(_part, out var netPart)
- || _previousSurgeries.Count == 0)
- return;
- var last = _previousSurgeries[^1];
- _previousSurgeries.RemoveAt(_previousSurgeries.Count - 1);
- if (_system.GetSingleton(last) is not { } previousId
- || !_entities.TryGetComponent(previousId, out SurgeryComponent? previous))
- return;
- OnSurgeryPressed((previousId, previous), netPart.Value, last);
- };
- }
- _window.Surgeries.DisposeAllChildren();
- _window.Steps.DisposeAllChildren();
- _window.Parts.DisposeAllChildren();
- View(ViewType.Parts);
- var oldSurgery = _surgery;
- var oldPart = _part;
- _part = null;
- _surgery = null;
- var options = new List<(NetEntity netEntity, EntityUid entity, string Name, BodyPartType? PartType)>();
- foreach (var choice in state.Choices.Keys)
- if (_entities.TryGetEntity(choice, out var ent))
- {
- if (_entities.TryGetComponent(ent, out BodyPartComponent? part))
- options.Add((choice, ent.Value, _entities.GetComponent<MetaDataComponent>(ent.Value).EntityName, part.PartType));
- else if (_entities.TryGetComponent(ent, out BodyComponent? body))
- options.Add((choice, ent.Value, _entities.GetComponent<MetaDataComponent>(ent.Value).EntityName, null));
- }
- options.Sort((a, b) =>
- {
- int GetScore(BodyPartType? partType)
- {
- return partType switch
- {
- BodyPartType.Head => 1,
- BodyPartType.Torso => 2,
- BodyPartType.Arm => 3,
- BodyPartType.Hand => 4,
- BodyPartType.Leg => 5,
- BodyPartType.Foot => 6,
- // BodyPartType.Tail => 7, No tails yet!
- BodyPartType.Other => 8,
- _ => 9
- };
- }
- return GetScore(a.PartType) - GetScore(b.PartType);
- });
- foreach (var (netEntity, entity, partName, _) in options)
- {
- //var netPart = _entities.GetNetEntity(part.Owner);
- var surgeries = state.Choices[netEntity];
- var partButton = new ChoiceControl();
- partButton.Set(partName, null);
- partButton.Button.OnPressed += _ => OnPartPressed(netEntity, surgeries);
- _window.Parts.AddChild(partButton);
- foreach (var surgeryId in surgeries)
- {
- if (_system.GetSingleton(surgeryId) is not { } surgery ||
- !_entities.TryGetComponent(surgery, out SurgeryComponent? surgeryComp))
- continue;
- if (oldPart == entity && oldSurgery?.Proto == surgeryId)
- OnSurgeryPressed((surgery, surgeryComp), netEntity, surgeryId);
- }
- if (oldPart == entity && oldSurgery == null)
- OnPartPressed(netEntity, surgeries);
- }
- if (!_window.IsOpen)
- _window.OpenCentered();
- }
- private void AddStep(EntProtoId stepId, NetEntity netPart, EntProtoId surgeryId)
- {
- if (_window == null
- || _system.GetSingleton(stepId) is not { } step)
- return;
- var stepName = new FormattedMessage();
- stepName.AddText(_entities.GetComponent<MetaDataComponent>(step).EntityName);
- var stepButton = new SurgeryStepButton { Step = step };
- stepButton.Button.OnPressed += _ => SendMessage(new SurgeryStepChosenBuiMsg(netPart, surgeryId, stepId, _isBody));
- _window.Steps.AddChild(stepButton);
- }
- private void OnSurgeryPressed(Entity<SurgeryComponent> surgery, NetEntity netPart, EntProtoId surgeryId)
- {
- if (_window == null)
- return;
- _part = _entities.GetEntity(netPart);
- _isBody = _entities.HasComponent<BodyComponent>(_part);
- _surgery = (surgery, surgeryId);
- _window.Steps.DisposeAllChildren();
- // This apparently does not consider if theres multiple surgery requirements in one surgery. Maybe thats fine.
- if (surgery.Comp.Requirement is { } requirementId && _system.GetSingleton(requirementId) is { } requirement)
- {
- var label = new ChoiceControl();
- label.Button.OnPressed += _ =>
- {
- _previousSurgeries.Add(surgeryId);
- if (_entities.TryGetComponent(requirement, out SurgeryComponent? requirementComp))
- OnSurgeryPressed((requirement, requirementComp), netPart, requirementId);
- };
- var msg = new FormattedMessage();
- var surgeryName = _entities.GetComponent<MetaDataComponent>(requirement).EntityName;
- msg.AddMarkup($"[bold]{Loc.GetString("surgery-ui-window-require")}: {surgeryName}[/bold]");
- label.Set(msg, null);
- _window.Steps.AddChild(label);
- _window.Steps.AddChild(new HSeparator { Margin = new Thickness(0, 0, 0, 1) });
- }
- foreach (var stepId in surgery.Comp.Steps)
- AddStep(stepId, netPart, surgeryId);
- View(ViewType.Steps);
- RefreshUI();
- }
- private void OnPartPressed(NetEntity netPart, List<EntProtoId> surgeryIds)
- {
- if (_window == null)
- return;
- _part = _entities.GetEntity(netPart);
- _isBody = _entities.HasComponent<BodyComponent>(_part);
- _window.Surgeries.DisposeAllChildren();
- var surgeries = new List<(Entity<SurgeryComponent> Ent, EntProtoId Id, string Name)>();
- foreach (var surgeryId in surgeryIds)
- {
- if (_system.GetSingleton(surgeryId) is not { } surgery ||
- !_entities.TryGetComponent(surgery, out SurgeryComponent? surgeryComp))
- {
- continue;
- }
- var name = _entities.GetComponent<MetaDataComponent>(surgery).EntityName;
- surgeries.Add(((surgery, surgeryComp), surgeryId, name));
- }
- surgeries.Sort((a, b) =>
- {
- var priority = a.Ent.Comp.Priority.CompareTo(b.Ent.Comp.Priority);
- if (priority != 0)
- return priority;
- return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
- });
- foreach (var surgery in surgeries)
- {
- var surgeryButton = new ChoiceControl();
- surgeryButton.Set(surgery.Name, null);
- surgeryButton.Button.OnPressed += _ => OnSurgeryPressed(surgery.Ent, netPart, surgery.Id);
- _window.Surgeries.AddChild(surgeryButton);
- }
- RefreshUI();
- View(ViewType.Surgeries);
- }
- private void RefreshUI()
- {
- if (_window == null
- || !_window.IsOpen
- || _part == null
- || !_entities.HasComponent<SurgeryComponent>(_surgery?.Ent)
- || !_entities.TryGetComponent(_player.LocalEntity ?? EntityUid.Invalid, out SurgeryTargetComponent? surgeryComp)
- || !surgeryComp.CanOperate)
- return;
- var next = _system.GetNextStep(Owner, _part.Value, _surgery.Value.Ent);
- var i = 0;
- foreach (var child in _window.Steps.Children)
- {
- if (child is not SurgeryStepButton stepButton)
- continue;
- var status = StepStatus.Incomplete;
- if (next == null)
- status = StepStatus.Complete;
- else if (next.Value.Surgery.Owner != _surgery.Value.Ent)
- status = StepStatus.Incomplete;
- else if (next.Value.Step == i)
- status = StepStatus.Next;
- else if (i < next.Value.Step)
- status = StepStatus.Complete;
- stepButton.Button.Disabled = status != StepStatus.Next;
- var stepName = new FormattedMessage();
- stepName.AddText(_entities.GetComponent<MetaDataComponent>(stepButton.Step).EntityName);
- if (status == StepStatus.Complete)
- stepButton.Button.Modulate = Color.Green;
- else
- {
- stepButton.Button.Modulate = Color.White;
- if (_player.LocalEntity is { } player
- && status == StepStatus.Next
- && !_system.CanPerformStep(player, Owner, _part.Value, stepButton.Step, false, out var popup, out var reason, out _))
- stepButton.ToolTip = popup;
- }
- var texture = _entities.GetComponentOrNull<SpriteComponent>(stepButton.Step)?.Icon?.Default;
- stepButton.Set(stepName, texture);
- i++;
- }
- }
- private void View(ViewType type)
- {
- if (_window == null)
- return;
- _window.PartsButton.Parent!.Margin = new Thickness(0, 0, 0, 10);
- _window.Parts.Visible = type == ViewType.Parts;
- _window.PartsButton.Disabled = type == ViewType.Parts;
- _window.Surgeries.Visible = type == ViewType.Surgeries;
- _window.SurgeriesButton.Disabled = type != ViewType.Steps;
- _window.Steps.Visible = type == ViewType.Steps;
- _window.StepsButton.Disabled = type != ViewType.Steps || _previousSurgeries.Count == 0;
- if (_entities.TryGetComponent(_part, out MetaDataComponent? partMeta) &&
- _entities.TryGetComponent(_surgery?.Ent, out MetaDataComponent? surgeryMeta))
- _window.Title = $"Surgery - {partMeta.EntityName}, {surgeryMeta.EntityName}";
- else if (partMeta != null)
- _window.Title = $"Surgery - {partMeta.EntityName}";
- else
- _window.Title = "Surgery";
- }
- private enum ViewType
- {
- Parts,
- Surgeries,
- Steps
- }
- private enum StepStatus
- {
- Next,
- Complete,
- Incomplete
- }
- }
|