4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Diagnostics.CodeAnalysis;
9 using Microsoft.Xna.Framework;
14 [SuppressMessage(
"ReSharper",
"UnusedVariable")]
15 internal sealed
class MedicalClinicUI
17 private enum ElementState
24 private struct PendingAfflictionElement
26 public readonly GUIComponent UIElement;
27 public readonly MedicalClinic.NetAffliction
Target;
28 public readonly GUITextBlock Price;
30 public PendingAfflictionElement(MedicalClinic.NetAffliction target, GUIComponent element, GUITextBlock price)
39 private struct PendingHealElement
41 public readonly GUIComponent UIElement;
42 public MedicalClinic.NetCrewMember
Target;
43 public readonly GUIListBox AfflictionList;
44 public readonly List<PendingAfflictionElement> Afflictions;
46 public PendingHealElement(MedicalClinic.NetCrewMember target, GUIComponent element, GUIListBox afflictionList)
50 AfflictionList = afflictionList;
51 Afflictions =
new List<PendingAfflictionElement>();
54 public PendingAfflictionElement? FindAfflictionElement(MedicalClinic.NetAffliction target) => Afflictions.FirstOrNull(element => element.Target.Identifier == target.Identifier);
58 private readonly
struct AfflictionElement
60 public readonly GUIImage? UIImage;
61 public readonly GUIComponent UIElement;
62 public readonly MedicalClinic.NetAffliction
Target;
64 public AfflictionElement(MedicalClinic.NetAffliction target, GUIComponent element, GUIImage? icon)
73 private readonly
struct CrewElement
75 public readonly GUIComponent UIElement;
76 public readonly CharacterInfo
Target;
77 public readonly GUIListBox AfflictionList;
78 public readonly List<AfflictionElement> Afflictions;
79 public readonly GUIComponent OverflowIndicator;
81 public CrewElement(CharacterInfo target, GUIComponent overflowIndicator, GUIComponent element, GUIListBox afflictionList)
83 OverflowIndicator = overflowIndicator;
86 AfflictionList = afflictionList;
87 Afflictions =
new List<AfflictionElement>();
92 private readonly
struct PendingHealList
94 public readonly GUIListBox HealList;
95 public readonly GUITextBlock? ErrorBlock;
96 public readonly GUITextBlock PriceBlock;
97 public readonly List<PendingHealElement> HealElements;
98 public readonly GUIButton HealButton;
100 public PendingHealList(GUIListBox healList, GUITextBlock priceBlock, GUIButton healButton, GUITextBlock? errorBlock)
103 ErrorBlock = errorBlock;
104 PriceBlock = priceBlock;
105 HealButton = healButton;
106 HealElements =
new List<PendingHealElement>();
109 public void UpdateElement(PendingHealElement newElement)
111 foreach (PendingHealElement element
in HealElements.ToList())
113 if (element.Target.CharacterEquals(newElement.Target))
115 HealElements.Remove(element);
116 HealElements.Add(newElement);
122 public PendingHealElement? FindCrewElement(MedicalClinic.NetCrewMember crewMember) => HealElements.FirstOrNull(element => element.Target.CharacterInfoID == crewMember.CharacterInfoID);
126 private readonly
struct CrewHealList
128 public readonly GUIComponent Panel;
129 public readonly GUIListBox HealList;
130 public readonly GUIComponent TreatAllButton;
131 public readonly List<CrewElement> HealElements;
133 public CrewHealList(GUIListBox healList, GUIComponent panel, GUIComponent treatAllButton)
137 TreatAllButton = treatAllButton;
138 HealElements =
new List<CrewElement>();
142 private readonly
struct PopupAffliction
144 public readonly MedicalClinic.NetAffliction
Target;
145 public readonly ImmutableArray<GUIComponent> ElementsToDisable;
146 public readonly GUIComponent TargetElement;
148 public PopupAffliction(ImmutableArray<GUIComponent> elementsToDisable, GUIComponent component, MedicalClinic.NetAffliction target)
151 ElementsToDisable = elementsToDisable;
152 TargetElement = component;
156 private readonly
struct PopupAfflictionList
158 public readonly MedicalClinic.NetCrewMember
Target;
159 public readonly GUIListBox ListElement;
160 public readonly GUIButton TreatAllButton;
161 public readonly HashSet<PopupAffliction> Afflictions;
163 public PopupAfflictionList(MedicalClinic.NetCrewMember crewMember, GUIListBox listElement, GUIButton treatAllButton)
165 ListElement = listElement;
167 TreatAllButton = treatAllButton;
168 Afflictions =
new HashSet<PopupAffliction>();
172 private readonly MedicalClinic medicalClinic;
173 private readonly GUIComponent container;
174 private Point prevResolution;
176 private PendingHealList? pendingHealList;
177 private CrewHealList? crewHealList;
179 private GUIFrame? selectedCrewElement;
180 private PopupAfflictionList? selectedCrewAfflictionList;
181 private bool isWaitingForServer;
182 private const float refreshTimerMax = 3f;
183 private float refreshTimer;
185 private PlayerBalanceElement? playerBalanceElement;
187 public MedicalClinicUI(MedicalClinic clinic, GUIComponent parent)
189 medicalClinic = clinic;
191 clinic.OnUpdate = OnMedicalClinicUpdated;
195 CreateRefreshButton();
196 void CreateRefreshButton()
198 new GUIButton(
new RectTransform(
new Vector2(0.2f, 0.1f), parent.RectTransform,
Anchor.TopCenter),
"Recreate UI - NOT PRESENT IN RELEASE!")
200 OnClicked = (_, _) =>
202 parent.ClearChildren();
204 CreateRefreshButton();
205 RequestLatestPending();
214 private void OnMedicalClinicUpdated()
218 UpdatePopupAfflictions();
221 private void UpdatePopupAfflictions()
223 if (selectedCrewAfflictionList is not { } afflictionList) {
return; }
225 foreach (PopupAffliction popupAffliction
in afflictionList.Afflictions)
227 ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
228 if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
230 ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
234 afflictionList.TreatAllButton.Enabled =
true;
235 if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target)))
237 afflictionList.TreatAllButton.Enabled =
false;
241 private void UpdatePending()
243 if (pendingHealList is not { } healList) {
return; }
245 ImmutableArray<MedicalClinic.NetCrewMember> pendingList = medicalClinic.PendingHeals.ToImmutableArray();
248 foreach (MedicalClinic.NetCrewMember crewMember in pendingList)
250 if (healList.FindCrewElement(crewMember) is { } element)
252 element.Target = crewMember;
253 healList.UpdateElement(element);
257 CreatePendingHealElement(healList.HealList.Content, crewMember, healList, ImmutableArray<MedicalClinic.NetAffliction>.Empty);
261 foreach (PendingHealElement element
in healList.HealElements.ToList())
263 if (pendingList.Any(member => member.CharacterEquals(element.Target)))
265 UpdatePendingAfflictions(element);
269 healList.HealElements.Remove(element);
270 healList.HealList.Content.RemoveChild(element.UIElement);
273 int totalCost = medicalClinic.GetTotalCost();
274 healList.PriceBlock.Text = TextManager.FormatCurrency(totalCost);
275 healList.PriceBlock.TextColor = GUIStyle.Red;
276 healList.HealButton.Enabled =
false;
277 if (medicalClinic.GetBalance() >= totalCost)
279 healList.PriceBlock.TextColor = GUIStyle.TextColorNormal;
280 if (medicalClinic.PendingHeals.Any())
282 healList.HealButton.Enabled =
true;
287 private void UpdatePendingAfflictions(PendingHealElement element)
289 MedicalClinic.NetCrewMember crewMember = element.Target;
290 foreach (MedicalClinic.NetAffliction affliction in crewMember.Afflictions.ToList())
292 if (element.FindAfflictionElement(affliction) is { } existingAffliction)
294 existingAffliction.Price.Text = TextManager.FormatCurrency(affliction.Strength);
298 CreatePendingAffliction(element.AfflictionList, crewMember, affliction, element);
301 foreach (PendingAfflictionElement afflictionElement
in element.Afflictions.ToList())
303 if (crewMember.Afflictions.Any(affliction => affliction.AfflictionEquals(afflictionElement.Target))) {
continue; }
305 element.Afflictions.Remove(afflictionElement);
306 element.AfflictionList.Content.RemoveChild(afflictionElement.UIElement);
310 public void UpdateCrewPanel()
312 if (crewHealList is not { } healList) {
return; }
314 ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters();
317 foreach (CharacterInfo info
in crew)
319 if (healList.HealElements.Any(element => element.Target == info)) {
continue; }
321 CreateCrewEntry(healList.HealList.Content, healList, info, healList.Panel);
325 foreach (CrewElement element
in healList.HealElements.ToList())
327 if (crew.Any(info => element.Target == info))
329 UpdateAfflictionList(element);
333 healList.HealElements.Remove(element);
334 healList.HealList.Content.RemoveChild(element.UIElement);
337 IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(
static element => element.Target.Character?.HealthPercentage ?? 100);
339 foreach (CrewElement element
in orderedList)
341 element.UIElement.SetAsLastChild();
344 healList.TreatAllButton.Enabled =
false;
345 foreach (CrewElement element
in healList.HealElements)
347 if (element.Afflictions.Count is 0) {
continue; }
349 healList.TreatAllButton.Enabled =
true;
354 private static void UpdateAfflictionList(CrewElement healElement)
356 CharacterHealth? health = healElement.Target.Character?.CharacterHealth;
357 if (health is
null) {
return; }
360 Dictionary<AfflictionPrefab, float> afflictionAndStrength =
new Dictionary<AfflictionPrefab, float>();
362 foreach (Affliction affliction
in health.GetAllAfflictions().Where(MedicalClinic.IsHealable))
364 if (afflictionAndStrength.TryGetValue(affliction.Prefab, out
float strength))
366 strength += affliction.Strength;
367 afflictionAndStrength[affliction.Prefab] = strength;
371 afflictionAndStrength.Add(affliction.Prefab, affliction.Strength);
375 foreach (AfflictionElement element
in healElement.Afflictions)
377 element.UIElement.Visible =
false;
380 healElement.OverflowIndicator.Visible =
false;
382 foreach (var (prefab, strength) in afflictionAndStrength)
385 foreach (AfflictionElement existingElement
in healElement.Afflictions)
387 if (!existingElement.Target.AfflictionEquals(prefab)) {
continue; }
389 if (existingElement.UIImage is { } icon)
391 icon.Color = CharacterHealth.GetAfflictionIconColor(prefab, strength);
397 if (found) {
continue; }
399 CreateCrewAfflictionIcon(healElement, healElement.AfflictionList.Content, prefab, strength);
402 foreach (AfflictionElement element
in healElement.Afflictions.ToList())
404 if (afflictionAndStrength.Any(pair => element.Target.AfflictionEquals(pair.Key))) {
continue; }
406 healElement.AfflictionList.Content.RemoveChild(element.UIElement);
407 healElement.Afflictions.Remove(element);
410 for (
int i = 0; i < 3 && i < healElement.Afflictions.Count; i++)
412 healElement.Afflictions[i].UIElement.Visible =
true;
415 healElement.OverflowIndicator.Visible = healElement.Afflictions.Count > 3;
416 healElement.OverflowIndicator.SetAsLastChild();
419 private static void CreateCrewAfflictionIcon(CrewElement healElement, GUIComponent parent, AfflictionPrefab prefab,
float strength)
421 GUIFrame backgroundFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.25f, 1f), parent.RectTransform), style:
null)
423 CanBeFocused =
false,
427 GUIImage? uiIcon =
null;
428 if (prefab.Icon is { } icon)
430 uiIcon =
new GUIImage(
new RectTransform(Vector2.One, backgroundFrame.RectTransform), icon, scaleToFit:
true)
432 Color = CharacterHealth.GetAfflictionIconColor(prefab, strength)
436 healElement.Afflictions.Add(
new AfflictionElement(
new MedicalClinic.NetAffliction { Prefab = prefab }, backgroundFrame, uiIcon));
439 private void CreateUI()
441 container.ClearChildren();
442 pendingHealList =
null;
443 playerBalanceElement =
null;
444 int panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560));
446 GUIFrame paddedParent =
new GUIFrame(
new RectTransform(
new Vector2(0.95f), container.RectTransform,
Anchor.Center), style:
null);
448 GUILayoutGroup clinicContent =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.45f, 1.0f), paddedParent.RectTransform)
450 MaxSize = new Point(panelMaxWidth, container.Rect.Height)
454 RelativeSpacing = 0.01f
457 GUILayoutGroup clinicLabelLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.1f), clinicContent.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
458 new GUIImage(
new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"CrewManagementHeaderIcon", scaleToFit:
true);
459 new GUITextBlock(
new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get(
"medicalclinic.medicalclinic"), font: GUIStyle.LargeFont);
461 GUIFrame clinicBackground =
new GUIFrame(
new RectTransform(Vector2.One, clinicContent.RectTransform));
463 CreateLeftSidePanel(clinicBackground);
465 GUILayoutGroup crewContent =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.45f, 1.0f), paddedParent.RectTransform, anchor:
Anchor.TopRight)
467 MaxSize = new Point(panelMaxWidth, container.Rect.Height)
471 RelativeSpacing = 0.01f
474 playerBalanceElement = CampaignUI.AddBalanceElement(crewContent,
new Vector2(1f, 0.1f));
476 GUIFrame crewBackground =
new GUIFrame(
new RectTransform(Vector2.One, crewContent.RectTransform));
478 CreateRightSidePanel(crewBackground);
480 prevResolution =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
483 private void CreateLeftSidePanel(GUIComponent parent)
486 GUILayoutGroup clinicContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.95f), parent.RectTransform,
Anchor.Center))
488 RelativeSpacing = 0.015f,
492 GUIListBox crewList =
new GUIListBox(
new RectTransform(Vector2.One, clinicContainer.RectTransform));
494 GUIButton treatAllButton =
new GUIButton(
new RectTransform(
new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get(
"medicalclinic.treateveryone"))
496 OnClicked = (button, _) =>
498 if (isWaitingForServer) {
return true; }
500 button.Enabled =
false;
501 isWaitingForServer =
true;
503 bool wasSuccessful = medicalClinic.TreatAllButtonAction(_ => ReEnableButton());
504 if (!wasSuccessful) { ReEnableButton(); }
506 void ReEnableButton()
508 isWaitingForServer =
false;
509 button.Enabled =
true;
515 crewHealList =
new CrewHealList(crewList, parent, treatAllButton);
518 private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
520 GUIButton crewBackground =
new GUIButton(
new RectTransform(
new Vector2(1f, 0.1f), parent.RectTransform), style:
"ListBoxElement");
522 GUILayoutGroup crewLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f), crewBackground.RectTransform,
Anchor.Center), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
524 GUILayoutGroup characterBlockLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.45f, 0.9f), crewLayout.RectTransform), isHorizontal:
true,
Anchor.CenterLeft);
525 CreateCharacterBlock(characterBlockLayout, info);
527 GUIListBox afflictionList =
new GUIListBox(
new RectTransform(
new Vector2(0.45f, 1f), crewLayout.RectTransform), style:
null, isHorizontal:
true);
529 GUILayoutGroup healthLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.1f, 1f), crewLayout.RectTransform), isHorizontal:
true,
Anchor.Center);
531 new GUITextBlock(
new RectTransform(Vector2.One, healthLayout.RectTransform),
string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
533 TextGetter = () => TextManager.GetWithVariable(
"percentageformat",
"[value]", $
"{(int)MathF.Round(info.Character?.HealthPercentage ?? 100f)}"),
534 TextColor = GUIStyle.Green
537 GUITextBlock overflowIndicator =
538 new GUITextBlock(
new RectTransform(
new Vector2(0.25f, 1f), afflictionList.Content.RectTransform, scaleBasis:
ScaleBasis.BothHeight), text:
"+", textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
541 CanBeFocused =
false,
542 TextColor = GUIStyle.Red
545 MedicalClinic.NetCrewMember member =
new MedicalClinic.NetCrewMember(info);
547 crewBackground.OnClicked = (_, _) =>
549 SelectCharacter(member,
new Vector2(panel.Rect.Right, crewBackground.Rect.Top));
553 healList.HealElements.Add(
new CrewElement(info, overflowIndicator, crewBackground, afflictionList));
556 private void CreateRightSidePanel(GUIComponent parent)
558 GUILayoutGroup pendingHealContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.95f), parent.RectTransform, anchor:
Anchor.Center))
560 RelativeSpacing = 0.015f,
564 new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.05f), pendingHealContainer.RectTransform), TextManager.Get(
"medicalclinic.pendingheals"), font: GUIStyle.SubHeadingFont);
566 GUIFrame healListContainer =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.9f), pendingHealContainer.RectTransform), style:
null);
567 GUITextBlock? errorBlock =
null;
568 if (!GameMain.IsSingleplayer)
570 errorBlock =
new GUITextBlock(
new RectTransform(Vector2.One, healListContainer.RectTransform), text: TextManager.Get(
"pleasewaitupnp"), font: GUIStyle.LargeFont, textAlignment: Alignment.Center);
573 GUIListBox healList =
new GUIListBox(
new RectTransform(Vector2.One, healListContainer.RectTransform))
575 Spacing = GUI.IntScale(8),
576 Visible = GameMain.IsSingleplayer
579 GUILayoutGroup footerLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.1f), pendingHealContainer.RectTransform));
581 GUILayoutGroup priceLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.5f), footerLayout.RectTransform), isHorizontal:
true);
582 GUITextBlock priceLabelBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), priceLayout.RectTransform), TextManager.Get(
"campaignstore.total"));
583 GUITextBlock priceBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), priceLayout.RectTransform), TextManager.FormatCurrency(medicalClinic.GetTotalCost()), font: GUIStyle.SubHeadingFont,
584 textAlignment: Alignment.Right);
586 GUILayoutGroup buttonLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.5f), footerLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterRight);
587 GUIButton healButton =
new GUIButton(
new RectTransform(
new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get(
"medicalclinic.heal"))
590 Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetBalance() >= medicalClinic.GetTotalCost(),
591 OnClicked = (button, _) =>
593 button.Enabled =
false;
594 isWaitingForServer =
true;
595 bool wasSuccessful = medicalClinic.HealAllButtonAction(request =>
597 isWaitingForServer =
false;
598 switch (request.HealResult)
600 case MedicalClinic.HealRequestResult.InsufficientFunds:
601 GUI.NotifyPrompt(TextManager.Get(
"medicalclinic.unabletoheal"), TextManager.Get(
"medicalclinic.insufficientfunds"));
603 case MedicalClinic.HealRequestResult.Refused:
604 GUI.NotifyPrompt(TextManager.Get(
"medicalclinic.unabletoheal"), TextManager.Get(
"medicalclinic.healrefused"));
608 button.Enabled =
true;
614 isWaitingForServer =
false;
615 button.Enabled =
true;
622 GUIButton clearButton =
new GUIButton(
new RectTransform(
new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get(
"campaignstore.clearall"))
625 OnClicked = (button, _) =>
627 if (isWaitingForServer) {
return true; }
629 button.Enabled =
false;
630 isWaitingForServer =
true;
632 bool wasSuccessful = medicalClinic.ClearAllButtonAction(_ => ReEnableButton());
633 if (!wasSuccessful) { ReEnableButton(); }
635 void ReEnableButton()
637 isWaitingForServer =
false;
638 button.Enabled =
true;
644 PendingHealList list =
new PendingHealList(healList, priceBlock, healButton, errorBlock);
646 foreach (MedicalClinic.NetCrewMember heal in GetPendingCharacters())
648 CreatePendingHealElement(healList.Content, heal, list, heal.Afflictions);
651 pendingHealList = list;
654 private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
656 CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
657 if (healInfo is
null) {
return; }
659 GUIFrame pendingHealBackground =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.25f), parent.RectTransform), style:
"ListBoxElement")
663 GUILayoutGroup pendingHealLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f), pendingHealBackground.RectTransform,
Anchor.Center));
665 GUILayoutGroup topHeaderLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.3f), pendingHealLayout.RectTransform), isHorizontal:
true,
Anchor.CenterLeft) { Stretch =
true };
667 CreateCharacterBlock(topHeaderLayout, healInfo);
669 GUILayoutGroup bottomLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.7f), pendingHealLayout.RectTransform), childAnchor:
Anchor.Center);
671 GUIListBox pendingAfflictionList =
new GUIListBox(
new RectTransform(Vector2.One, bottomLayout.RectTransform))
673 AutoHideScrollBar =
false,
674 ScrollBarVisible =
true
677 PendingHealElement healElement =
new PendingHealElement(crewMember, pendingHealBackground, pendingAfflictionList);
679 foreach (MedicalClinic.NetAffliction affliction in afflictions)
681 CreatePendingAffliction(pendingAfflictionList, crewMember, affliction, healElement);
684 healList.HealElements.Add(healElement);
685 RecalculateLayouts(pendingHealLayout, topHeaderLayout, bottomLayout);
686 pendingAfflictionList.ForceUpdate();
689 private void CreatePendingAffliction(GUIListBox parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction, PendingHealElement healElement)
691 GUIFrame backgroundFrame =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.33f), parent.Content.RectTransform), style:
"ListBoxElement")
696 GUILayoutGroup parentLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal:
true) { Stretch =
true };
698 if (!(affliction.Prefab is { } prefab)) {
return; }
700 if (prefab.Icon is { } icon)
702 new GUIImage(
new RectTransform(Vector2.One, parentLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), icon, scaleToFit:
true)
704 Color = CharacterHealth.GetAfflictionIconColor(prefab, affliction.Strength)
708 GUILayoutGroup textLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, parentLayout.RectTransform), isHorizontal:
true);
710 LocalizedString name = prefab.Name;
712 GUIFrame textContainer =
new GUIFrame(
new RectTransform(
new Vector2(0.6f, 1f), textLayout.RectTransform), style:
null);
713 GUITextBlock afflictionName =
new GUITextBlock(
new RectTransform(Vector2.One, textContainer.RectTransform), name, font: GUIStyle.SubHeadingFont);
715 GUITextBlock healCost =
new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
717 Padding = Vector4.Zero
720 GUIButton healButton =
new GUIButton(
new RectTransform(
new Vector2(0.2f, 1f), textLayout.RectTransform), style:
"CrewManagementRemoveButton")
723 OnClicked = (button, _) =>
725 button.Enabled =
false;
726 bool wasSuccessful = medicalClinic.RemovePendingButtonAction(crewMember, affliction, _ =>
728 button.Enabled =
true;
733 button.Enabled =
true;
739 EnsureTextDoesntOverflow(name.Value, afflictionName, textContainer.Rect, ImmutableArray.Create(textLayout, parentLayout));
741 healElement.Afflictions.Add(
new PendingAfflictionElement(affliction, backgroundFrame, healCost));
743 RecalculateLayouts(parentLayout, textLayout);
745 parent.ForceUpdate();
748 private static void CreateCharacterBlock(GUIComponent parent, CharacterInfo info)
750 new GUICustomComponent(
new RectTransform(Vector2.One, parent.RectTransform, scaleBasis:
ScaleBasis.BothHeight), (spriteBatch, component) =>
752 info.DrawPortrait(spriteBatch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width);
755 GUILayoutGroup textGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.8f), parent.RectTransform));
757 string? characterName = info.Name;
758 LocalizedString? jobName =
null;
760 GUITextBlock? nameBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.5f), textGroup.RectTransform), characterName),
763 if (info.Job is { Name: { } name, Prefab: { UIColor: var color} } job)
766 jobBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.5f), textGroup.RectTransform), jobName);
767 nameBlock.TextColor = color;
770 if (parent is GUILayoutGroup layoutGroup)
772 ImmutableArray<GUILayoutGroup> layoutGroups = ImmutableArray.Create(layoutGroup, textGroup);
774 EnsureTextDoesntOverflow(characterName, nameBlock, parent.Rect, layoutGroups);
776 if (jobBlock is
null) {
return; }
778 EnsureTextDoesntOverflow(jobName?.Value, jobBlock, parent.Rect, layoutGroups);
782 private void SelectCharacter(MedicalClinic.NetCrewMember crewMember, Vector2 location)
784 CharacterInfo? info = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
785 if (info is
null) {
return; }
787 if (isWaitingForServer) {
return; }
791 GUIFrame mainFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.28f, 0.5f), container.RectTransform)
793 ScreenSpaceOffset = location.ToPoint()
796 GUILayoutGroup mainLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.9f), mainFrame.RectTransform,
Anchor.Center)) { RelativeSpacing = 0.01f, Stretch =
true };
798 if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight)
800 mainFrame.RectTransform.ScreenSpaceOffset =
new Point((
int)location.X, GameMain.GraphicsHeight - mainFrame.Rect.Height);
803 GUITextBlock feedbackBlock =
new GUITextBlock(
new RectTransform(Vector2.One, mainFrame.RectTransform), TextManager.Get(
"pleasewaitupnp"), textAlignment: Alignment.Center, font: GUIStyle.LargeFont, wrap:
true)
808 GUIButton treatAllButton =
new GUIButton(
new RectTransform(
new Vector2(1f, 0.2f), mainLayout.RectTransform), TextManager.Get(
"medicalclinic.treatall"))
811 Font = GUIStyle.SubHeadingFont,
815 GUIListBox afflictionList =
new GUIListBox(
new RectTransform(
new Vector2(1f, 0.8f), mainLayout.RectTransform)) { Visible =
false };
817 PopupAfflictionList popupAfflictionList =
new PopupAfflictionList(crewMember, afflictionList, treatAllButton);
818 selectedCrewElement = mainFrame;
819 selectedCrewAfflictionList = popupAfflictionList;
821 isWaitingForServer =
true;
822 bool wasSuccessful = medicalClinic.RequestAfflictions(info, OnReceived);
826 isWaitingForServer =
false;
830 void OnReceived(MedicalClinic.AfflictionRequest request)
832 isWaitingForServer =
false;
834 if (request.Result != MedicalClinic.RequestResult.Success)
836 switch (request.Result)
838 case MedicalClinic.RequestResult.CharacterInfoMissing:
839 DebugConsole.ThrowError($
"Unable to select character \"{info.Character?.DisplayName}\" in medical clini because the character health was missing.");
841 case MedicalClinic.RequestResult.CharacterNotFound:
842 DebugConsole.ThrowError($
"Unable to select character \"{info.Character?.DisplayName} in medical clinic because the server was unable to find a character with ID {info.ID}.");
846 feedbackBlock.Text = GetErrorText(request.Result);
847 feedbackBlock.TextColor = GUIStyle.Red;
851 List<GUIComponent> allComponents =
new List<GUIComponent>();
852 foreach (MedicalClinic.NetAffliction affliction in request.Afflictions)
854 CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction);
855 allComponents.AddRange(createdComponents.AllCreatedElements);
856 popupAfflictionList.Afflictions.Add(
new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, affliction));
859 allComponents.Add(treatAllButton);
860 treatAllButton.OnClicked = (_, _) =>
862 ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
863 if (!afflictions.Any()) {
return true; }
865 AddPending(allComponents.ToImmutableArray(), crewMember, afflictions);
869 afflictionList.Visible =
true;
870 feedbackBlock.Visible =
false;
871 treatAllButton.Visible =
true;
872 UpdatePopupAfflictions();
876 private readonly record
struct CreatedPopupAfflictionElement(GUIComponent MainElement, ImmutableArray<GUIComponent> AllCreatedElements);
878 private CreatedPopupAfflictionElement CreatePopupAffliction(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction)
880 ToolBox.ThrowIfNull(affliction.Prefab);
882 GUIFrame backgroundFrame =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.33f), parent.RectTransform), style:
"ListBoxElement");
883 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform,
Anchor.BottomCenter), style:
"HorizontalLine");
885 GUILayoutGroup mainLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.9f), backgroundFrame.RectTransform,
Anchor.Center))
887 RelativeSpacing = 0.05f
890 GUILayoutGroup topLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.33f), mainLayout.RectTransform), isHorizontal:
true) { Stretch =
true };
892 Color iconColor = CharacterHealth.GetAfflictionIconColor(affliction.Prefab, affliction.Strength);
894 GUIImage icon =
new GUIImage(
new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), affliction.Prefab.Icon, scaleToFit:
true)
897 DisabledColor = iconColor * 0.5f
900 GUILayoutGroup topTextLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, topLayout.RectTransform), isHorizontal:
true);
902 GUITextBlock prefabBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), topTextLayout.RectTransform), affliction.Prefab.Name, font: GUIStyle.SubHeadingFont);
904 Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
906 LocalizedString vitalityText = affliction.VitalityDecrease == 0 ?
string.Empty : TextManager.GetWithVariable(
"medicalclinic.vitalitydifference",
"[amount]", (-affliction.VitalityDecrease).ToString());
907 GUITextBlock vitalityBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.25f, 1f), topTextLayout.RectTransform), vitalityText, textAlignment: Alignment.Center)
909 TextColor = textColor,
910 DisabledTextColor = textColor * 0.5f,
911 Padding = Vector4.Zero,
912 AutoScaleHorizontal =
true
915 LocalizedString severityText =
Affliction.GetStrengthText(affliction.Strength, affliction.Prefab.MaxStrength);
916 GUITextBlock severityBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.25f, 1f), topTextLayout.RectTransform), severityText, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
918 TextColor = textColor,
919 DisabledTextColor = textColor * 0.5f,
920 Padding = Vector4.Zero,
921 AutoScaleHorizontal =
true
924 EnsureTextDoesntOverflow(affliction.Prefab.Name.Value, prefabBlock, prefabBlock.Rect, ImmutableArray.Create(mainLayout, topLayout, topTextLayout));
926 GUILayoutGroup bottomLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
928 GUILayoutGroup bottomTextLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.8f, 1f), bottomLayout.RectTransform))
930 RelativeSpacing = 0.05f
932 LocalizedString description = affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.OtherCharacter);
933 GUITextBlock descriptionBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.6f), bottomTextLayout.RectTransform),
935 font: GUIStyle.SmallFont,
938 ToolTip = description
940 bool truncated =
false;
941 while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains(
'\n'))
943 var split = descriptionBlock.WrappedText.Value.Split(
'\n');
944 descriptionBlock.Text =
string.Join(
'\n', split.Take(split.Length - 1));
949 descriptionBlock.Text += TextManager.Get(
"ellipsis");
952 GUITextBlock priceBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont);
954 GUIButton buyButton =
new GUIButton(
new RectTransform(
new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style:
"CrewManagementAddButton")
959 ImmutableArray<GUIComponent> elementsToDisable = ImmutableArray.Create<GUIComponent>(prefabBlock, backgroundFrame, icon, vitalityBlock, severityBlock, buyButton, descriptionBlock, priceBlock);
961 buyButton.OnClicked = (_, __) =>
963 if (!buyButton.Enabled) {
return false; }
965 AddPending(elementsToDisable, crewMember, ImmutableArray.Create(affliction));
969 return new CreatedPopupAfflictionElement(backgroundFrame, elementsToDisable);
972 private void AddPending(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetCrewMember crewMember, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
974 MedicalClinic.NetCrewMember existingMember;
976 if (medicalClinic.PendingHeals.FirstOrNull(m => m.CharacterEquals(crewMember)) is { } foundHeal)
978 existingMember = foundHeal;
982 MedicalClinic.NetCrewMember newMember = crewMember with
984 Afflictions = ImmutableArray<MedicalClinic.NetAffliction>.Empty
987 existingMember = newMember;
990 foreach (MedicalClinic.NetAffliction affliction in afflictions)
992 if (existingMember.Afflictions.FirstOrNull(a => a.AfflictionEquals(affliction)) !=
null)
998 existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
1000 ToggleElements(ElementState.Disabled, elementsToDisable);
1001 bool wasSuccessful = medicalClinic.AddPendingButtonAction(existingMember, request =>
1003 if (request.Result == MedicalClinic.RequestResult.Timeout)
1005 ToggleElements(ElementState.Enabled, elementsToDisable);
1011 ToggleElements(ElementState.Enabled, elementsToDisable);
1015 #warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString
1016 public static void EnsureTextDoesntOverflow(
string? text, GUITextBlock textBlock, Rectangle bounds, ImmutableArray<GUILayoutGroup>? layoutGroups =
null)
1018 if (
string.IsNullOrWhiteSpace(text)) {
return; }
1020 string originalText = text;
1022 UpdateLayoutGroups();
1024 while (textBlock.Rect.X + textBlock.TextSize.X + textBlock.Padding.X + textBlock.Padding.W > bounds.Right)
1026 if (
string.IsNullOrWhiteSpace(text)) {
break; }
1029 textBlock.Text = text +
"...";
1030 textBlock.ToolTip = originalText;
1032 UpdateLayoutGroups();
1035 void UpdateLayoutGroups()
1037 if (layoutGroups is
null) {
return; }
1039 foreach (GUILayoutGroup layoutGroup
in layoutGroups)
1041 layoutGroup.Recalculate();
1046 public void RequestLatestPending()
1050 if (GameMain.IsSingleplayer || !(pendingHealList is { ErrorBlock: { } errorBlock, HealList: { } healList })) {
return; }
1052 errorBlock.Visible =
true;
1053 errorBlock.TextColor = GUIStyle.TextColorNormal;
1054 errorBlock.Text = TextManager.Get(
"pleasewaitupnp");
1055 healList.Visible =
false;
1057 isWaitingForServer =
true;
1059 medicalClinic.RequestLatestPending(OnReceived);
1061 void OnReceived(MedicalClinic.PendingRequest request)
1063 isWaitingForServer =
false;
1065 if (request.Result != MedicalClinic.RequestResult.Success)
1067 errorBlock.Text = GetErrorText(request.Result);
1068 errorBlock.TextColor = GUIStyle.Red;
1072 medicalClinic.PendingHeals.Clear();
1073 foreach (MedicalClinic.NetCrewMember member in request.CrewMembers)
1075 medicalClinic.PendingHeals.Add(member);
1078 OnMedicalClinicUpdated();
1080 errorBlock.Visible =
false;
1081 healList.Visible =
true;
1085 public void UpdateAfflictions(MedicalClinic.NetCrewMember crewMember)
1087 if (selectedCrewAfflictionList is not { } afflictionList || !afflictionList.Target.CharacterEquals(crewMember)) {
return; }
1089 List<GUIComponent> allComponents =
new List<GUIComponent>();
1090 foreach (PopupAffliction existingAffliction
in afflictionList.Afflictions.ToHashSet())
1092 if (crewMember.Afflictions.None(received => received.AfflictionEquals(existingAffliction.Target)))
1095 existingAffliction.TargetElement.RectTransform.Parent =
null;
1096 afflictionList.Afflictions.Remove(existingAffliction);
1100 allComponents.AddRange(existingAffliction.ElementsToDisable);
1104 foreach (MedicalClinic.NetAffliction received in crewMember.Afflictions)
1107 if (afflictionList.Afflictions.Any(existing => existing.Target.AfflictionEquals(received))) {
continue; }
1109 CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.ListElement.Content, crewMember, received);
1110 allComponents.AddRange(createdComponents.AllCreatedElements);
1111 afflictionList.Afflictions.Add(
new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, received));
1114 allComponents.Add(afflictionList.TreatAllButton);
1115 afflictionList.TreatAllButton.OnClicked = (_, _) =>
1117 var afflictions = crewMember.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
1118 if (!afflictions.Any()) {
return true; }
1120 AddPending(allComponents.ToImmutableArray(), crewMember, afflictions);
1124 UpdatePopupAfflictions();
1127 public void ClosePopup()
1129 if (selectedCrewElement is { } popup)
1131 popup.RectTransform.Parent =
null;
1134 selectedCrewElement =
null;
1135 selectedCrewAfflictionList =
null;
1138 private static LocalizedString GetErrorText(MedicalClinic.RequestResult result)
1140 return result
switch
1142 MedicalClinic.RequestResult.Timeout => TextManager.Get(
"medicalclinic.requesttimeout"),
1143 _ => TextManager.Get(
"error")
1147 private ImmutableArray<MedicalClinic.NetCrewMember> GetPendingCharacters() => medicalClinic.PendingHeals.ToImmutableArray();
1149 private static void ToggleElements(ElementState state, ImmutableArray<GUIComponent> elements)
1151 foreach (GUIComponent component
in elements)
1153 component.Enabled = state
switch
1155 ElementState.Enabled =>
true,
1156 ElementState.Disabled =>
false,
1157 _ =>
throw new ArgumentOutOfRangeException(nameof(state), state,
null)
1162 private static void RecalculateLayouts(params GUILayoutGroup[] layouts)
1164 foreach (GUILayoutGroup layout
in layouts)
1166 layout.Recalculate();
1170 public void Update(
float deltaTime)
1172 if (prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight)
1178 playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
1181 refreshTimer += deltaTime;
1183 if (refreshTimer > refreshTimerMax)
1190 public void OnDeselected()
1192 if (GameMain.NetworkMember is not
null)
1194 MedicalClinic.SendUnsubscribeRequest();