Client LuaCsForBarotrauma
MedicalClinicUI.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Linq;
9 using Microsoft.Xna.Framework;
10 using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
11 
12 namespace Barotrauma
13 {
14  [SuppressMessage("ReSharper", "UnusedVariable")]
15  internal sealed class MedicalClinicUI
16  {
17  private enum ElementState
18  {
19  Enabled,
20  Disabled
21  }
22 
23  // Represents a pending affliction in the right side pending heal list
24  private struct PendingAfflictionElement
25  {
26  public readonly GUIComponent UIElement;
27  public readonly MedicalClinic.NetAffliction Target;
28  public readonly GUITextBlock Price;
29 
30  public PendingAfflictionElement(MedicalClinic.NetAffliction target, GUIComponent element, GUITextBlock price)
31  {
32  UIElement = element;
33  Target = target;
34  Price = price;
35  }
36  }
37 
38  // Represents a pending heal on the right side list
39  private struct PendingHealElement
40  {
41  public readonly GUIComponent UIElement;
42  public MedicalClinic.NetCrewMember Target;
43  public readonly GUIListBox AfflictionList;
44  public readonly List<PendingAfflictionElement> Afflictions;
45 
46  public PendingHealElement(MedicalClinic.NetCrewMember target, GUIComponent element, GUIListBox afflictionList)
47  {
48  UIElement = element;
49  Target = target;
50  AfflictionList = afflictionList;
51  Afflictions = new List<PendingAfflictionElement>();
52  }
53 
54  public PendingAfflictionElement? FindAfflictionElement(MedicalClinic.NetAffliction target) => Afflictions.FirstOrNull(element => element.Target.Identifier == target.Identifier);
55  }
56 
57  // Represents an affliction on the left side crew entry
58  private readonly struct AfflictionElement
59  {
60  public readonly GUIImage? UIImage;
61  public readonly GUIComponent UIElement;
62  public readonly MedicalClinic.NetAffliction Target;
63 
64  public AfflictionElement(MedicalClinic.NetAffliction target, GUIComponent element, GUIImage? icon)
65  {
66  UIElement = element;
67  UIImage = icon;
68  Target = target;
69  }
70  }
71 
72  // Represent an entry on the left side crew list
73  private readonly struct CrewElement
74  {
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;
80 
81  public CrewElement(CharacterInfo target, GUIComponent overflowIndicator, GUIComponent element, GUIListBox afflictionList)
82  {
83  OverflowIndicator = overflowIndicator;
84  UIElement = element;
85  Target = target;
86  AfflictionList = afflictionList;
87  Afflictions = new List<AfflictionElement>();
88  }
89  }
90 
91  // Represents the right side pending list
92  private readonly struct PendingHealList
93  {
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;
99 
100  public PendingHealList(GUIListBox healList, GUITextBlock priceBlock, GUIButton healButton, GUITextBlock? errorBlock)
101  {
102  HealList = healList;
103  ErrorBlock = errorBlock;
104  PriceBlock = priceBlock;
105  HealButton = healButton;
106  HealElements = new List<PendingHealElement>();
107  }
108 
109  public void UpdateElement(PendingHealElement newElement)
110  {
111  foreach (PendingHealElement element in HealElements.ToList())
112  {
113  if (element.Target.CharacterEquals(newElement.Target))
114  {
115  HealElements.Remove(element);
116  HealElements.Add(newElement);
117  return;
118  }
119  }
120  }
121 
122  public PendingHealElement? FindCrewElement(MedicalClinic.NetCrewMember crewMember) => HealElements.FirstOrNull(element => element.Target.CharacterInfoID == crewMember.CharacterInfoID);
123  }
124 
125  // Represents the left side crew list
126  private readonly struct CrewHealList
127  {
128  public readonly GUIComponent Panel;
129  public readonly GUIListBox HealList;
130  public readonly GUIComponent TreatAllButton;
131  public readonly List<CrewElement> HealElements;
132 
133  public CrewHealList(GUIListBox healList, GUIComponent panel, GUIComponent treatAllButton)
134  {
135  Panel = panel;
136  HealList = healList;
137  TreatAllButton = treatAllButton;
138  HealElements = new List<CrewElement>();
139  }
140  }
141 
142  private readonly struct PopupAffliction
143  {
144  public readonly MedicalClinic.NetAffliction Target;
145  public readonly ImmutableArray<GUIComponent> ElementsToDisable;
146  public readonly GUIComponent TargetElement;
147 
148  public PopupAffliction(ImmutableArray<GUIComponent> elementsToDisable, GUIComponent component, MedicalClinic.NetAffliction target)
149  {
150  Target = target;
151  ElementsToDisable = elementsToDisable;
152  TargetElement = component;
153  }
154  }
155 
156  private readonly struct PopupAfflictionList
157  {
158  public readonly MedicalClinic.NetCrewMember Target;
159  public readonly GUIListBox ListElement;
160  public readonly GUIButton TreatAllButton;
161  public readonly HashSet<PopupAffliction> Afflictions;
162 
163  public PopupAfflictionList(MedicalClinic.NetCrewMember crewMember, GUIListBox listElement, GUIButton treatAllButton)
164  {
165  ListElement = listElement;
166  Target = crewMember;
167  TreatAllButton = treatAllButton;
168  Afflictions = new HashSet<PopupAffliction>();
169  }
170  }
171 
172  private readonly MedicalClinic medicalClinic;
173  private readonly GUIComponent container;
174  private Point prevResolution;
175 
176  private PendingHealList? pendingHealList;
177  private CrewHealList? crewHealList;
178 
179  private GUIFrame? selectedCrewElement;
180  private PopupAfflictionList? selectedCrewAfflictionList;
181  private bool isWaitingForServer;
182  private const float refreshTimerMax = 3f;
183  private float refreshTimer;
184 
185  private PlayerBalanceElement? playerBalanceElement;
186 
187  public MedicalClinicUI(MedicalClinic clinic, GUIComponent parent)
188  {
189  medicalClinic = clinic;
190  container = parent;
191  clinic.OnUpdate = OnMedicalClinicUpdated;
192 
193 #if DEBUG
194  // creates a button that re-creates the UI
195  CreateRefreshButton();
196  void CreateRefreshButton()
197  {
198  new GUIButton(new RectTransform(new Vector2(0.2f, 0.1f), parent.RectTransform, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!")
199  {
200  OnClicked = (_, _) =>
201  {
202  parent.ClearChildren();
203  CreateUI();
204  CreateRefreshButton();
205  RequestLatestPending();
206  return true;
207  }
208  };
209  }
210 #endif
211  CreateUI();
212  }
213 
214  private void OnMedicalClinicUpdated()
215  {
216  UpdateCrewPanel();
217  UpdatePending();
218  UpdatePopupAfflictions();
219  }
220 
221  private void UpdatePopupAfflictions()
222  {
223  if (selectedCrewAfflictionList is not { } afflictionList) { return; }
224 
225  foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
226  {
227  ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
228  if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
229  {
230  ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
231  }
232  }
233 
234  afflictionList.TreatAllButton.Enabled = true;
235  if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target)))
236  {
237  afflictionList.TreatAllButton.Enabled = false;
238  }
239  }
240 
241  private void UpdatePending()
242  {
243  if (pendingHealList is not { } healList) { return; }
244 
245  ImmutableArray<MedicalClinic.NetCrewMember> pendingList = medicalClinic.PendingHeals.ToImmutableArray();
246 
247  // check if there are crew members that are not in the UI
248  foreach (MedicalClinic.NetCrewMember crewMember in pendingList)
249  {
250  if (healList.FindCrewElement(crewMember) is { } element)
251  {
252  element.Target = crewMember;
253  healList.UpdateElement(element);
254  continue;
255  }
256 
257  CreatePendingHealElement(healList.HealList.Content, crewMember, healList, ImmutableArray<MedicalClinic.NetAffliction>.Empty);
258  }
259 
260  // check if there are elements that the crew doesn't have
261  foreach (PendingHealElement element in healList.HealElements.ToList())
262  {
263  if (pendingList.Any(member => member.CharacterEquals(element.Target)))
264  {
265  UpdatePendingAfflictions(element);
266  continue;
267  }
268 
269  healList.HealElements.Remove(element);
270  healList.HealList.Content.RemoveChild(element.UIElement);
271  }
272 
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)
278  {
279  healList.PriceBlock.TextColor = GUIStyle.TextColorNormal;
280  if (medicalClinic.PendingHeals.Any())
281  {
282  healList.HealButton.Enabled = true;
283  }
284  }
285  }
286 
287  private void UpdatePendingAfflictions(PendingHealElement element)
288  {
289  MedicalClinic.NetCrewMember crewMember = element.Target;
290  foreach (MedicalClinic.NetAffliction affliction in crewMember.Afflictions.ToList())
291  {
292  if (element.FindAfflictionElement(affliction) is { } existingAffliction)
293  {
294  existingAffliction.Price.Text = TextManager.FormatCurrency(affliction.Strength);
295  continue;
296  }
297 
298  CreatePendingAffliction(element.AfflictionList, crewMember, affliction, element);
299  }
300 
301  foreach (PendingAfflictionElement afflictionElement in element.Afflictions.ToList())
302  {
303  if (crewMember.Afflictions.Any(affliction => affliction.AfflictionEquals(afflictionElement.Target))) { continue; }
304 
305  element.Afflictions.Remove(afflictionElement);
306  element.AfflictionList.Content.RemoveChild(afflictionElement.UIElement);
307  }
308  }
309 
310  public void UpdateCrewPanel()
311  {
312  if (crewHealList is not { } healList) { return; }
313 
314  ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters();
315 
316  // check if there are crew members that are not in the UI
317  foreach (CharacterInfo info in crew)
318  {
319  if (healList.HealElements.Any(element => element.Target == info)) { continue; }
320 
321  CreateCrewEntry(healList.HealList.Content, healList, info, healList.Panel);
322  }
323 
324  // check if there are elements that the crew doesn't have
325  foreach (CrewElement element in healList.HealElements.ToList())
326  {
327  if (crew.Any(info => element.Target == info))
328  {
329  UpdateAfflictionList(element);
330  continue;
331  }
332 
333  healList.HealElements.Remove(element);
334  healList.HealList.Content.RemoveChild(element.UIElement);
335  }
336 
337  IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(static element => element.Target.Character?.HealthPercentage ?? 100);
338 
339  foreach (CrewElement element in orderedList)
340  {
341  element.UIElement.SetAsLastChild();
342  }
343 
344  healList.TreatAllButton.Enabled = false;
345  foreach (CrewElement element in healList.HealElements)
346  {
347  if (element.Afflictions.Count is 0) { continue; }
348 
349  healList.TreatAllButton.Enabled = true;
350  break;
351  }
352  }
353 
354  private static void UpdateAfflictionList(CrewElement healElement)
355  {
356  CharacterHealth? health = healElement.Target.Character?.CharacterHealth;
357  if (health is null) { return; }
358 
359  // sum up all the afflictions and their strengths
360  Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>();
361 
362  foreach (Affliction affliction in health.GetAllAfflictions().Where(MedicalClinic.IsHealable))
363  {
364  if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength))
365  {
366  strength += affliction.Strength;
367  afflictionAndStrength[affliction.Prefab] = strength;
368  continue;
369  }
370 
371  afflictionAndStrength.Add(affliction.Prefab, affliction.Strength);
372  }
373 
374  // hide all the elements because we only want to show 3 later on
375  foreach (AfflictionElement element in healElement.Afflictions)
376  {
377  element.UIElement.Visible = false;
378  }
379 
380  healElement.OverflowIndicator.Visible = false;
381 
382  foreach (var (prefab, strength) in afflictionAndStrength)
383  {
384  bool found = false;
385  foreach (AfflictionElement existingElement in healElement.Afflictions)
386  {
387  if (!existingElement.Target.AfflictionEquals(prefab)) { continue; }
388 
389  if (existingElement.UIImage is { } icon)
390  {
391  icon.Color = CharacterHealth.GetAfflictionIconColor(prefab, strength);
392  }
393 
394  found = true;
395  }
396 
397  if (found) { continue; }
398 
399  CreateCrewAfflictionIcon(healElement, healElement.AfflictionList.Content, prefab, strength);
400  }
401 
402  foreach (AfflictionElement element in healElement.Afflictions.ToList())
403  {
404  if (afflictionAndStrength.Any(pair => element.Target.AfflictionEquals(pair.Key))) { continue; }
405 
406  healElement.AfflictionList.Content.RemoveChild(element.UIElement);
407  healElement.Afflictions.Remove(element);
408  }
409 
410  for (int i = 0; i < 3 && i < healElement.Afflictions.Count; i++)
411  {
412  healElement.Afflictions[i].UIElement.Visible = true;
413  }
414 
415  healElement.OverflowIndicator.Visible = healElement.Afflictions.Count > 3;
416  healElement.OverflowIndicator.SetAsLastChild();
417  }
418 
419  private static void CreateCrewAfflictionIcon(CrewElement healElement, GUIComponent parent, AfflictionPrefab prefab, float strength)
420  {
421  GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(0.25f, 1f), parent.RectTransform), style: null)
422  {
423  CanBeFocused = false,
424  Visible = false
425  };
426 
427  GUIImage? uiIcon = null;
428  if (prefab.Icon is { } icon)
429  {
430  uiIcon = new GUIImage(new RectTransform(Vector2.One, backgroundFrame.RectTransform), icon, scaleToFit: true)
431  {
432  Color = CharacterHealth.GetAfflictionIconColor(prefab, strength)
433  };
434  }
435 
436  healElement.Afflictions.Add(new AfflictionElement(new MedicalClinic.NetAffliction { Prefab = prefab }, backgroundFrame, uiIcon));
437  }
438 
439  private void CreateUI()
440  {
441  container.ClearChildren();
442  pendingHealList = null;
443  playerBalanceElement = null;
444  int panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560));
445 
446  GUIFrame paddedParent = new GUIFrame(new RectTransform(new Vector2(0.95f), container.RectTransform, Anchor.Center), style: null);
447 
448  GUILayoutGroup clinicContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), paddedParent.RectTransform)
449  {
450  MaxSize = new Point(panelMaxWidth, container.Rect.Height)
451  })
452  {
453  Stretch = true,
454  RelativeSpacing = 0.01f
455  };
456 
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);
460 
461  GUIFrame clinicBackground = new GUIFrame(new RectTransform(Vector2.One, clinicContent.RectTransform));
462 
463  CreateLeftSidePanel(clinicBackground);
464 
465  GUILayoutGroup crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), paddedParent.RectTransform, anchor: Anchor.TopRight)
466  {
467  MaxSize = new Point(panelMaxWidth, container.Rect.Height)
468  })
469  {
470  Stretch = true,
471  RelativeSpacing = 0.01f
472  };
473 
474  playerBalanceElement = CampaignUI.AddBalanceElement(crewContent, new Vector2(1f, 0.1f));
475 
476  GUIFrame crewBackground = new GUIFrame(new RectTransform(Vector2.One, crewContent.RectTransform));
477 
478  CreateRightSidePanel(crewBackground);
479 
480  prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
481  }
482 
483  private void CreateLeftSidePanel(GUIComponent parent)
484  {
485  crewHealList = null;
486  GUILayoutGroup clinicContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), parent.RectTransform, Anchor.Center))
487  {
488  RelativeSpacing = 0.015f,
489  Stretch = true
490  };
491 
492  GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, clinicContainer.RectTransform));
493 
494  GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
495  {
496  OnClicked = (button, _) =>
497  {
498  if (isWaitingForServer) { return true; }
499 
500  button.Enabled = false;
501  isWaitingForServer = true;
502 
503  bool wasSuccessful = medicalClinic.TreatAllButtonAction(_ => ReEnableButton());
504  if (!wasSuccessful) { ReEnableButton(); }
505 
506  void ReEnableButton()
507  {
508  isWaitingForServer = false;
509  button.Enabled = true;
510  }
511  return true;
512  }
513  };
514 
515  crewHealList = new CrewHealList(crewList, parent, treatAllButton);
516  }
517 
518  private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
519  {
520  GUIButton crewBackground = new GUIButton(new RectTransform(new Vector2(1f, 0.1f), parent.RectTransform), style: "ListBoxElement");
521 
522  GUILayoutGroup crewLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), crewBackground.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft);
523 
524  GUILayoutGroup characterBlockLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.9f), crewLayout.RectTransform), isHorizontal: true, Anchor.CenterLeft);
525  CreateCharacterBlock(characterBlockLayout, info);
526 
527  GUIListBox afflictionList = new GUIListBox(new RectTransform(new Vector2(0.45f, 1f), crewLayout.RectTransform), style: null, isHorizontal: true);
528 
529  GUILayoutGroup healthLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.1f, 1f), crewLayout.RectTransform), isHorizontal: true, Anchor.Center);
530 
531  new GUITextBlock(new RectTransform(Vector2.One, healthLayout.RectTransform), string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
532  {
533  TextGetter = () => TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)MathF.Round(info.Character?.HealthPercentage ?? 100f)}"),
534  TextColor = GUIStyle.Green
535  };
536 
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)
539  {
540  Visible = false,
541  CanBeFocused = false,
542  TextColor = GUIStyle.Red
543  };
544 
545  MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember(info);
546 
547  crewBackground.OnClicked = (_, _) =>
548  {
549  SelectCharacter(member, new Vector2(panel.Rect.Right, crewBackground.Rect.Top));
550  return true;
551  };
552 
553  healList.HealElements.Add(new CrewElement(info, overflowIndicator, crewBackground, afflictionList));
554  }
555 
556  private void CreateRightSidePanel(GUIComponent parent)
557  {
558  GUILayoutGroup pendingHealContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), parent.RectTransform, anchor: Anchor.Center))
559  {
560  RelativeSpacing = 0.015f,
561  Stretch = true
562  };
563 
564  new GUITextBlock(new RectTransform(new Vector2(1f, 0.05f), pendingHealContainer.RectTransform), TextManager.Get("medicalclinic.pendingheals"), font: GUIStyle.SubHeadingFont);
565 
566  GUIFrame healListContainer = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), pendingHealContainer.RectTransform), style: null);
567  GUITextBlock? errorBlock = null;
568  if (!GameMain.IsSingleplayer)
569  {
570  errorBlock = new GUITextBlock(new RectTransform(Vector2.One, healListContainer.RectTransform), text: TextManager.Get("pleasewaitupnp"), font: GUIStyle.LargeFont, textAlignment: Alignment.Center);
571  }
572 
573  GUIListBox healList = new GUIListBox(new RectTransform(Vector2.One, healListContainer.RectTransform))
574  {
575  Spacing = GUI.IntScale(8),
576  Visible = GameMain.IsSingleplayer
577  };
578 
579  GUILayoutGroup footerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), pendingHealContainer.RectTransform));
580 
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);
585 
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"))
588  {
589  ClickSound = GUISoundType.ConfirmTransaction,
590  Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetBalance() >= medicalClinic.GetTotalCost(),
591  OnClicked = (button, _) =>
592  {
593  button.Enabled = false;
594  isWaitingForServer = true;
595  bool wasSuccessful = medicalClinic.HealAllButtonAction(request =>
596  {
597  isWaitingForServer = false;
598  switch (request.HealResult)
599  {
600  case MedicalClinic.HealRequestResult.InsufficientFunds:
601  GUI.NotifyPrompt(TextManager.Get("medicalclinic.unabletoheal"), TextManager.Get("medicalclinic.insufficientfunds"));
602  break;
603  case MedicalClinic.HealRequestResult.Refused:
604  GUI.NotifyPrompt(TextManager.Get("medicalclinic.unabletoheal"), TextManager.Get("medicalclinic.healrefused"));
605  break;
606  }
607 
608  button.Enabled = true;
609  ClosePopup();
610  });
611 
612  if (!wasSuccessful)
613  {
614  isWaitingForServer = false;
615  button.Enabled = true;
616  }
617  ClosePopup();
618  return true;
619  }
620  };
621 
622  GUIButton clearButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("campaignstore.clearall"))
623  {
624  ClickSound = GUISoundType.Cart,
625  OnClicked = (button, _) =>
626  {
627  if (isWaitingForServer) { return true; }
628 
629  button.Enabled = false;
630  isWaitingForServer = true;
631 
632  bool wasSuccessful = medicalClinic.ClearAllButtonAction(_ => ReEnableButton());
633  if (!wasSuccessful) { ReEnableButton(); }
634 
635  void ReEnableButton()
636  {
637  isWaitingForServer = false;
638  button.Enabled = true;
639  }
640  return true;
641  }
642  };
643 
644  PendingHealList list = new PendingHealList(healList, priceBlock, healButton, errorBlock);
645 
646  foreach (MedicalClinic.NetCrewMember heal in GetPendingCharacters())
647  {
648  CreatePendingHealElement(healList.Content, heal, list, heal.Afflictions);
649  }
650 
651  pendingHealList = list;
652  }
653 
654  private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
655  {
656  CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
657  if (healInfo is null) { return; }
658 
659  GUIFrame pendingHealBackground = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), parent.RectTransform), style: "ListBoxElement")
660  {
661  CanBeFocused = false
662  };
663  GUILayoutGroup pendingHealLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), pendingHealBackground.RectTransform, Anchor.Center));
664 
665  GUILayoutGroup topHeaderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), pendingHealLayout.RectTransform), isHorizontal: true, Anchor.CenterLeft) { Stretch = true };
666 
667  CreateCharacterBlock(topHeaderLayout, healInfo);
668 
669  GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.7f), pendingHealLayout.RectTransform), childAnchor: Anchor.Center);
670 
671  GUIListBox pendingAfflictionList = new GUIListBox(new RectTransform(Vector2.One, bottomLayout.RectTransform))
672  {
673  AutoHideScrollBar = false,
674  ScrollBarVisible = true
675  };
676 
677  PendingHealElement healElement = new PendingHealElement(crewMember, pendingHealBackground, pendingAfflictionList);
678 
679  foreach (MedicalClinic.NetAffliction affliction in afflictions)
680  {
681  CreatePendingAffliction(pendingAfflictionList, crewMember, affliction, healElement);
682  }
683 
684  healList.HealElements.Add(healElement);
685  RecalculateLayouts(pendingHealLayout, topHeaderLayout, bottomLayout);
686  pendingAfflictionList.ForceUpdate();
687  }
688 
689  private void CreatePendingAffliction(GUIListBox parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction, PendingHealElement healElement)
690  {
691  GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.Content.RectTransform), style: "ListBoxElement")
692  {
693  CanBeFocused = false
694  };
695 
696  GUILayoutGroup parentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal: true) { Stretch = true };
697 
698  if (!(affliction.Prefab is { } prefab)) { return; }
699 
700  if (prefab.Icon is { } icon)
701  {
702  new GUIImage(new RectTransform(Vector2.One, parentLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), icon, scaleToFit: true)
703  {
704  Color = CharacterHealth.GetAfflictionIconColor(prefab, affliction.Strength)
705  };
706  }
707 
708  GUILayoutGroup textLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parentLayout.RectTransform), isHorizontal: true);
709 
710  LocalizedString name = prefab.Name;
711 
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);
714 
715  GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
716  {
717  Padding = Vector4.Zero
718  };
719 
720  GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), style: "CrewManagementRemoveButton")
721  {
722  ClickSound = GUISoundType.Cart,
723  OnClicked = (button, _) =>
724  {
725  button.Enabled = false;
726  bool wasSuccessful = medicalClinic.RemovePendingButtonAction(crewMember, affliction, _ =>
727  {
728  button.Enabled = true;
729  });
730 
731  if (!wasSuccessful)
732  {
733  button.Enabled = true;
734  }
735  return true;
736  }
737  };
738 
739  EnsureTextDoesntOverflow(name.Value, afflictionName, textContainer.Rect, ImmutableArray.Create(textLayout, parentLayout));
740 
741  healElement.Afflictions.Add(new PendingAfflictionElement(affliction, backgroundFrame, healCost));
742 
743  RecalculateLayouts(parentLayout, textLayout);
744 
745  parent.ForceUpdate();
746  }
747 
748  private static void CreateCharacterBlock(GUIComponent parent, CharacterInfo info)
749  {
750  new GUICustomComponent(new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: ScaleBasis.BothHeight), (spriteBatch, component) =>
751  {
752  info.DrawPortrait(spriteBatch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width);
753  });
754 
755  GUILayoutGroup textGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.8f), parent.RectTransform));
756 
757  string? characterName = info.Name;
758  LocalizedString? jobName = null;
759 
760  GUITextBlock? nameBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), textGroup.RectTransform), characterName),
761  jobBlock = null;
762 
763  if (info.Job is { Name: { } name, Prefab: { UIColor: var color} } job)
764  {
765  jobName = name;
766  jobBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), textGroup.RectTransform), jobName);
767  nameBlock.TextColor = color;
768  }
769 
770  if (parent is GUILayoutGroup layoutGroup)
771  {
772  ImmutableArray<GUILayoutGroup> layoutGroups = ImmutableArray.Create(layoutGroup, textGroup);
773 
774  EnsureTextDoesntOverflow(characterName, nameBlock, parent.Rect, layoutGroups);
775 
776  if (jobBlock is null) { return; }
777 
778  EnsureTextDoesntOverflow(jobName?.Value, jobBlock, parent.Rect, layoutGroups);
779  }
780  }
781 
782  private void SelectCharacter(MedicalClinic.NetCrewMember crewMember, Vector2 location)
783  {
784  CharacterInfo? info = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
785  if (info is null) { return; }
786 
787  if (isWaitingForServer) { return; }
788 
789  ClosePopup();
790 
791  GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.5f), container.RectTransform)
792  {
793  ScreenSpaceOffset = location.ToPoint()
794  });
795 
796  GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true };
797 
798  if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight)
799  {
800  mainFrame.RectTransform.ScreenSpaceOffset = new Point((int)location.X, GameMain.GraphicsHeight - mainFrame.Rect.Height);
801  }
802 
803  GUITextBlock feedbackBlock = new GUITextBlock(new RectTransform(Vector2.One, mainFrame.RectTransform), TextManager.Get("pleasewaitupnp"), textAlignment: Alignment.Center, font: GUIStyle.LargeFont, wrap: true)
804  {
805  Visible = true
806  };
807 
808  GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1f, 0.2f), mainLayout.RectTransform), TextManager.Get("medicalclinic.treatall"))
809  {
810  ClickSound = GUISoundType.Cart,
811  Font = GUIStyle.SubHeadingFont,
812  Visible = false
813  };
814 
815  GUIListBox afflictionList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), mainLayout.RectTransform)) { Visible = false };
816 
817  PopupAfflictionList popupAfflictionList = new PopupAfflictionList(crewMember, afflictionList, treatAllButton);
818  selectedCrewElement = mainFrame;
819  selectedCrewAfflictionList = popupAfflictionList;
820 
821  isWaitingForServer = true;
822  bool wasSuccessful = medicalClinic.RequestAfflictions(info, OnReceived);
823 
824  if (!wasSuccessful)
825  {
826  isWaitingForServer = false;
827  ClosePopup();
828  }
829 
830  void OnReceived(MedicalClinic.AfflictionRequest request)
831  {
832  isWaitingForServer = false;
833 
834  if (request.Result != MedicalClinic.RequestResult.Success)
835  {
836  switch (request.Result)
837  {
838  case MedicalClinic.RequestResult.CharacterInfoMissing:
839  DebugConsole.ThrowError($"Unable to select character \"{info.Character?.DisplayName}\" in medical clini because the character health was missing.");
840  break;
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}.");
843  break;
844  }
845 
846  feedbackBlock.Text = GetErrorText(request.Result);
847  feedbackBlock.TextColor = GUIStyle.Red;
848  return;
849  }
850 
851  List<GUIComponent> allComponents = new List<GUIComponent>();
852  foreach (MedicalClinic.NetAffliction affliction in request.Afflictions)
853  {
854  CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction);
855  allComponents.AddRange(createdComponents.AllCreatedElements);
856  popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, affliction));
857  }
858 
859  allComponents.Add(treatAllButton);
860  treatAllButton.OnClicked = (_, _) =>
861  {
862  ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
863  if (!afflictions.Any()) { return true; }
864 
865  AddPending(allComponents.ToImmutableArray(), crewMember, afflictions);
866  return true;
867  };
868 
869  afflictionList.Visible = true;
870  feedbackBlock.Visible = false;
871  treatAllButton.Visible = true;
872  UpdatePopupAfflictions();
873  }
874  }
875 
876  private readonly record struct CreatedPopupAfflictionElement(GUIComponent MainElement, ImmutableArray<GUIComponent> AllCreatedElements);
877 
878  private CreatedPopupAfflictionElement CreatePopupAffliction(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction)
879  {
880  ToolBox.ThrowIfNull(affliction.Prefab);
881 
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");
884 
885  GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), backgroundFrame.RectTransform, Anchor.Center))
886  {
887  RelativeSpacing = 0.05f
888  };
889 
890  GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.33f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true };
891 
892  Color iconColor = CharacterHealth.GetAfflictionIconColor(affliction.Prefab, affliction.Strength);
893 
894  GUIImage icon = new GUIImage(new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), affliction.Prefab.Icon, scaleToFit: true)
895  {
896  Color = iconColor,
897  DisabledColor = iconColor * 0.5f
898  };
899 
900  GUILayoutGroup topTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, topLayout.RectTransform), isHorizontal: true);
901 
902  GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), affliction.Prefab.Name, font: GUIStyle.SubHeadingFont);
903 
904  Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
905 
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)
908  {
909  TextColor = textColor,
910  DisabledTextColor = textColor * 0.5f,
911  Padding = Vector4.Zero,
912  AutoScaleHorizontal = true
913  };
914 
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)
917  {
918  TextColor = textColor,
919  DisabledTextColor = textColor * 0.5f,
920  Padding = Vector4.Zero,
921  AutoScaleHorizontal = true
922  };
923 
924  EnsureTextDoesntOverflow(affliction.Prefab.Name.Value, prefabBlock, prefabBlock.Rect, ImmutableArray.Create(mainLayout, topLayout, topTextLayout));
925 
926  GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
927 
928  GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform))
929  {
930  RelativeSpacing = 0.05f
931  };
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),
934  description,
935  font: GUIStyle.SmallFont,
936  wrap: true)
937  {
938  ToolTip = description
939  };
940  bool truncated = false;
941  while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
942  {
943  var split = descriptionBlock.WrappedText.Value.Split('\n');
944  descriptionBlock.Text = string.Join('\n', split.Take(split.Length - 1));
945  truncated = true;
946  }
947  if (truncated)
948  {
949  descriptionBlock.Text += TextManager.Get("ellipsis");
950  }
951 
952  GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont);
953 
954  GUIButton buyButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style: "CrewManagementAddButton")
955  {
956  ClickSound = GUISoundType.Cart
957  };
958 
959  ImmutableArray<GUIComponent> elementsToDisable = ImmutableArray.Create<GUIComponent>(prefabBlock, backgroundFrame, icon, vitalityBlock, severityBlock, buyButton, descriptionBlock, priceBlock);
960 
961  buyButton.OnClicked = (_, __) =>
962  {
963  if (!buyButton.Enabled) { return false; }
964 
965  AddPending(elementsToDisable, crewMember, ImmutableArray.Create(affliction));
966  return true;
967  };
968 
969  return new CreatedPopupAfflictionElement(backgroundFrame, elementsToDisable);
970  }
971 
972  private void AddPending(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetCrewMember crewMember, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
973  {
974  MedicalClinic.NetCrewMember existingMember;
975 
976  if (medicalClinic.PendingHeals.FirstOrNull(m => m.CharacterEquals(crewMember)) is { } foundHeal)
977  {
978  existingMember = foundHeal;
979  }
980  else
981  {
982  MedicalClinic.NetCrewMember newMember = crewMember with
983  {
984  Afflictions = ImmutableArray<MedicalClinic.NetAffliction>.Empty
985  };
986 
987  existingMember = newMember;
988  }
989 
990  foreach (MedicalClinic.NetAffliction affliction in afflictions)
991  {
992  if (existingMember.Afflictions.FirstOrNull(a => a.AfflictionEquals(affliction)) != null)
993  {
994  return;
995  }
996  }
997 
998  existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
999 
1000  ToggleElements(ElementState.Disabled, elementsToDisable);
1001  bool wasSuccessful = medicalClinic.AddPendingButtonAction(existingMember, request =>
1002  {
1003  if (request.Result == MedicalClinic.RequestResult.Timeout)
1004  {
1005  ToggleElements(ElementState.Enabled, elementsToDisable);
1006  }
1007  });
1008 
1009  if (!wasSuccessful)
1010  {
1011  ToggleElements(ElementState.Enabled, elementsToDisable);
1012  }
1013  }
1014 
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)
1017  {
1018  if (string.IsNullOrWhiteSpace(text)) { return; }
1019 
1020  string originalText = text;
1021 
1022  UpdateLayoutGroups();
1023 
1024  while (textBlock.Rect.X + textBlock.TextSize.X + textBlock.Padding.X + textBlock.Padding.W > bounds.Right)
1025  {
1026  if (string.IsNullOrWhiteSpace(text)) { break; }
1027 
1028  text = text[..^1];
1029  textBlock.Text = text + "...";
1030  textBlock.ToolTip = originalText;
1031 
1032  UpdateLayoutGroups();
1033  }
1034 
1035  void UpdateLayoutGroups()
1036  {
1037  if (layoutGroups is null) { return; }
1038 
1039  foreach (GUILayoutGroup layoutGroup in layoutGroups)
1040  {
1041  layoutGroup.Recalculate();
1042  }
1043  }
1044  }
1045 
1046  public void RequestLatestPending()
1047  {
1048  UpdateCrewPanel();
1049 
1050  if (GameMain.IsSingleplayer || !(pendingHealList is { ErrorBlock: { } errorBlock, HealList: { } healList })) { return; }
1051 
1052  errorBlock.Visible = true;
1053  errorBlock.TextColor = GUIStyle.TextColorNormal;
1054  errorBlock.Text = TextManager.Get("pleasewaitupnp");
1055  healList.Visible = false;
1056 
1057  isWaitingForServer = true;
1058 
1059  medicalClinic.RequestLatestPending(OnReceived);
1060 
1061  void OnReceived(MedicalClinic.PendingRequest request)
1062  {
1063  isWaitingForServer = false;
1064 
1065  if (request.Result != MedicalClinic.RequestResult.Success)
1066  {
1067  errorBlock.Text = GetErrorText(request.Result);
1068  errorBlock.TextColor = GUIStyle.Red;
1069  return;
1070  }
1071 
1072  medicalClinic.PendingHeals.Clear();
1073  foreach (MedicalClinic.NetCrewMember member in request.CrewMembers)
1074  {
1075  medicalClinic.PendingHeals.Add(member);
1076  }
1077 
1078  OnMedicalClinicUpdated();
1079 
1080  errorBlock.Visible = false;
1081  healList.Visible = true;
1082  }
1083  }
1084 
1085  public void UpdateAfflictions(MedicalClinic.NetCrewMember crewMember)
1086  {
1087  if (selectedCrewAfflictionList is not { } afflictionList || !afflictionList.Target.CharacterEquals(crewMember)) { return; }
1088 
1089  List<GUIComponent> allComponents = new List<GUIComponent>();
1090  foreach (PopupAffliction existingAffliction in afflictionList.Afflictions.ToHashSet())
1091  {
1092  if (crewMember.Afflictions.None(received => received.AfflictionEquals(existingAffliction.Target)))
1093  {
1094  // remove from UI
1095  existingAffliction.TargetElement.RectTransform.Parent = null;
1096  afflictionList.Afflictions.Remove(existingAffliction);
1097  }
1098  else
1099  {
1100  allComponents.AddRange(existingAffliction.ElementsToDisable);
1101  }
1102  }
1103 
1104  foreach (MedicalClinic.NetAffliction received in crewMember.Afflictions)
1105  {
1106  // we're not that concerned about updating the strength of the afflictions
1107  if (afflictionList.Afflictions.Any(existing => existing.Target.AfflictionEquals(received))) { continue; }
1108 
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));
1112  }
1113 
1114  allComponents.Add(afflictionList.TreatAllButton);
1115  afflictionList.TreatAllButton.OnClicked = (_, _) =>
1116  {
1117  var afflictions = crewMember.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
1118  if (!afflictions.Any()) { return true; }
1119 
1120  AddPending(allComponents.ToImmutableArray(), crewMember, afflictions);
1121  return true;
1122  };
1123 
1124  UpdatePopupAfflictions();
1125  }
1126 
1127  public void ClosePopup()
1128  {
1129  if (selectedCrewElement is { } popup)
1130  {
1131  popup.RectTransform.Parent = null;
1132  }
1133 
1134  selectedCrewElement = null;
1135  selectedCrewAfflictionList = null;
1136  }
1137 
1138  private static LocalizedString GetErrorText(MedicalClinic.RequestResult result)
1139  {
1140  return result switch
1141  {
1142  MedicalClinic.RequestResult.Timeout => TextManager.Get("medicalclinic.requesttimeout"),
1143  _ => TextManager.Get("error")
1144  };
1145  }
1146 
1147  private ImmutableArray<MedicalClinic.NetCrewMember> GetPendingCharacters() => medicalClinic.PendingHeals.ToImmutableArray();
1148 
1149  private static void ToggleElements(ElementState state, ImmutableArray<GUIComponent> elements)
1150  {
1151  foreach (GUIComponent component in elements)
1152  {
1153  component.Enabled = state switch
1154  {
1155  ElementState.Enabled => true,
1156  ElementState.Disabled => false,
1157  _ => throw new ArgumentOutOfRangeException(nameof(state), state, null)
1158  };
1159  }
1160  }
1161 
1162  private static void RecalculateLayouts(params GUILayoutGroup[] layouts)
1163  {
1164  foreach (GUILayoutGroup layout in layouts)
1165  {
1166  layout.Recalculate();
1167  }
1168  }
1169 
1170  public void Update(float deltaTime)
1171  {
1172  if (prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight)
1173  {
1174  CreateUI();
1175  }
1176  else
1177  {
1178  playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
1179  }
1180 
1181  refreshTimer += deltaTime;
1182 
1183  if (refreshTimer > refreshTimerMax)
1184  {
1185  UpdateCrewPanel();
1186  refreshTimer = 0;
1187  }
1188  }
1189 
1190  public void OnDeselected()
1191  {
1192  if (GameMain.NetworkMember is not null)
1193  {
1194  MedicalClinic.SendUnsubscribeRequest();
1195  }
1196  ClosePopup();
1197  }
1198  }
1199 }
GUISoundType
Definition: GUI.cs:21