Client LuaCsForBarotrauma
CampaignSetupUI.cs
2 using Barotrauma.IO;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Linq;
9 
10 namespace Barotrauma
11 {
12  abstract class CampaignSetupUI
13  {
14  protected readonly GUIComponent newGameContainer, loadGameContainer;
15 
16  protected GUIListBox saveList;
17 
18  protected GUITextBox saveNameBox, seedBox;
19 
21 
22  public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
23 
24  public delegate void LoadGameDelegate(string loadPath, Option<uint> backupIndex);
26 
27  protected enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }
29 
31  {
32  get;
33  protected set;
34  }
35 
37  {
38  get;
39  protected set;
40  }
41 
42  public GUIButton CampaignCustomizeButton { get; set; }
44 
46  {
47  this.newGameContainer = newGameContainer;
48  this.loadGameContainer = loadGameContainer;
49  }
50 
51  protected List<CampaignMode.SaveInfo> prevSaveFiles;
52  protected GUIComponent CreateSaveElement(CampaignMode.SaveInfo saveInfo)
53  {
54  if (string.IsNullOrEmpty(saveInfo.FilePath))
55  {
56  DebugConsole.AddWarning("Error when updating campaign load menu: path to a save file was empty.\n" + Environment.StackTrace);
57  return null;
58  }
59 
60  var saveFrame = new GUIFrame(
61  new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) },
62  style: "ListBoxElement")
63  {
64  UserData = saveInfo
65  };
66 
67  var nameText = new GUITextBlock(
68  new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform),
69  Path.GetFileNameWithoutExtension(saveInfo.FilePath),
70  textColor: GUIStyle.TextColorBright)
71  {
72  CanBeFocused = false
73  };
74 
75  if (saveInfo.EnabledContentPackageNames != null && saveInfo.EnabledContentPackageNames.Any())
76  {
77  if (!GameSession.IsCompatibleWithEnabledContentPackages(saveInfo.EnabledContentPackageNames, out LocalizedString errorMsg))
78  {
79  nameText.TextColor = GUIStyle.Red;
80  saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning"));
81  }
82  }
83 
84  prevSaveFiles ??= new List<CampaignMode.SaveInfo>();
85  prevSaveFiles.Add(saveInfo);
86 
87  new GUITextBlock(
88  new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft),
89  text: saveInfo.SubmarineName,
90  font: GUIStyle.SmallFont)
91  {
92  CanBeFocused = false,
93  UserData = saveInfo.FilePath
94  };
95 
96  string saveTimeStr = string.Empty;
97  if (saveInfo.SaveTime.TryUnwrap(out var time))
98  {
99  saveTimeStr = time.ToLocalUserString();
100  }
101  new GUITextBlock(
102  new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
103  text: saveTimeStr,
104  textAlignment: Alignment.Right,
105  font: GUIStyle.SmallFont)
106  {
107  CanBeFocused = false,
108  UserData = saveInfo.FilePath
109  };
110 
111  return saveFrame;
112  }
113 
114  protected void SortSaveList()
115  {
117  {
118  if (c1.GUIComponent.UserData is not CampaignMode.SaveInfo file1
119  || c2.GUIComponent.UserData is not CampaignMode.SaveInfo file2)
120  {
121  return 0;
122  }
123 
124  if (!file1.SaveTime.TryUnwrap(out var file1WriteTime)
125  || !file2.SaveTime.TryUnwrap(out var file2WriteTime))
126  {
127  return 0;
128  }
129 
130  return file2WriteTime.CompareTo(file1WriteTime);
131  });
132  }
133 
135  {
153 
154  public readonly CampaignSettings CreateSettings()
155  {
156  return new CampaignSettings(element: null)
157  {
158  PresetName = SelectedPreset.GetValue(),
162  StartingBalanceAmount = StartingFunds.GetValue(),
175  };
176  }
177  }
178 
179  public readonly struct SettingValue<T>
180  {
181  private readonly Func<T> getter;
182  private readonly Action<T> setter;
183 
184  public T GetValue()
185  {
186  return getter.Invoke();
187  }
188 
189  public void SetValue(T value)
190  {
191  setter.Invoke(value);
192  }
193 
194  public SettingValue(Func<T> get, Action<T> set)
195  {
196  getter = get;
197  setter = set;
198  }
199  }
200 
201  private readonly struct SettingCarouselElement<T>
202  {
203  public readonly LocalizedString Label;
204  public readonly T Value;
205  public readonly bool IsHidden;
206 
207  public SettingCarouselElement(T value, string label, bool isHidden = false)
208  {
209  Value = value;
210  Label = TextManager.Get(label).Fallback(label);
211  IsHidden = isHidden;
212  }
213  }
214 
215  protected static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings, bool isSinglePlayer)
216  {
217  const float verticalSize = 0.14f;
218 
219  bool loadingPreset = false;
220 
221  GUILayoutGroup presetDropdownLayout = new GUILayoutGroup(
222  new RectTransform(new Vector2(1f, verticalSize), parent.RectTransform),
223  isHorizontal: true,
224  childAnchor: Anchor.CenterLeft);
225  new GUITextBlock(
226  new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform),
227  TextManager.Get("campaignsettingpreset"));
228  GUIDropDown presetDropdown = new GUIDropDown(
229  new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform),
230  elementCount: CampaignModePresets.List.Length + 1);
231  presetDropdown.AddItem(TextManager.Get("karmapreset.custom"), null);
232  presetDropdown.Select(0);
233 
234  presetDropdownLayout.RectTransform.MinSize = new Point(0, presetDropdown.Rect.Height);
235 
236  foreach (CampaignSettings settings in CampaignModePresets.List)
237  {
238  string name = settings.PresetName;
239  presetDropdown.AddItem(TextManager.Get($"preset.{name}").Fallback(name), settings);
240 
241  if (settings.PresetName.Equals(prevSettings.PresetName, StringComparison.OrdinalIgnoreCase))
242  {
243  presetDropdown.SelectItem(settings);
244  }
245  }
246 
247  var presetValue = new SettingValue<string>(
248  get: () => presetDropdown.SelectedData is CampaignSettings settings ? settings.PresetName : string.Empty,
249  set: static _ => { }); // we do not need a way to set this value
250 
251  GUIListBox settingsList = new GUIListBox(new RectTransform(new Vector2(1f, 1f - verticalSize), parent.RectTransform))
252  {
253  Spacing = GUI.IntScale(5)
254  };
255 
256  // GENERAL CAMPAIGN SETTINGS:
257 
258  NetLobbyScreen.CreateSubHeader("campaignsettingcategories.general", settingsList.Content);
259 
260  // Tutorial
261  SettingValue<bool> tutorialEnabled = isSinglePlayer
262  ? CreateTickbox(
263  settingsList.Content,
264  TextManager.Get("CampaignOption.EnableTutorial"),
265  TextManager.Get("campaignoption.enabletutorial.tooltip"),
266  prevSettings.TutorialEnabled,
267  verticalSize,
268  OnValuesChanged)
269  : new SettingValue<bool>(static () => false, static _ => { });
270 
271  // Jovian radiation
272  SettingValue<bool> radiationEnabled = CreateTickbox(
273  settingsList.Content,
274  TextManager.Get("CampaignOption.EnableRadiation"),
275  TextManager.Get("campaignoption.enableradiation.tooltip"),
276  prevSettings.RadiationEnabled,
277  verticalSize,
278  OnValuesChanged);
279 
280  // RESOURCE-RELATED CAMPAIGN SETTINGS:
281 
282  NetLobbyScreen.CreateSubHeader("campaignsettingcategories.resources", settingsList.Content);
283 
284  // Starting set
285  ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions =
286  StartItemSet.Sets
287  .OrderBy(s => s.Order)
288  .Select(set => new SettingCarouselElement<Identifier>(
289  set.Identifier,
290  $"startitemset.{set.Identifier}"))
291  .ToImmutableArray();
292  SettingCarouselElement<Identifier> prevStartingSet = startingSetOptions
293  .FirstOrNull(element => element.Value == prevSettings.StartItemSet)
294  ?? startingSetOptions[1];
295  SettingValue<Identifier> startingSetInput = CreateSelectionCarousel(
296  settingsList.Content,
297  TextManager.Get("startitemset"),
298  TextManager.Get("startitemsettooltip"),
299  prevStartingSet,
300  verticalSize,
301  startingSetOptions,
302  OnValuesChanged);
303 
304  // Starting money
305  ImmutableArray<SettingCarouselElement<StartingBalanceAmountOption>> fundOptions = ImmutableArray.Create(
306  new SettingCarouselElement<StartingBalanceAmountOption>(StartingBalanceAmountOption.Low, "startingfunds.low"),
307  new SettingCarouselElement<StartingBalanceAmountOption>(StartingBalanceAmountOption.Medium, "startingfunds.medium"),
308  new SettingCarouselElement<StartingBalanceAmountOption>(StartingBalanceAmountOption.High, "startingfunds.high")
309  );
310  SettingCarouselElement<StartingBalanceAmountOption> prevStartingFund = fundOptions
311  .FirstOrNull(element => element.Value == prevSettings.StartingBalanceAmount)
312  ?? fundOptions[1];
313  SettingValue<StartingBalanceAmountOption> startingFundsInput = CreateSelectionCarousel(
314  settingsList.Content,
315  TextManager.Get("startingfundsdescription"),
316  TextManager.Get("startingfundstooltip"),
317  prevStartingFund,
318  verticalSize,
319  fundOptions,
320  OnValuesChanged);
321 
322  // Max mission count
323  SettingValue<int> maxMissionCountInput = CreateGUIIntegerInputCarousel(
324  settingsList.Content,
325  TextManager.Get("maxmissioncount"),
326  TextManager.Get("maxmissioncounttooltip"),
327  prevSettings.MaxMissionCount,
328  valueStep: 1,
329  minValue: CampaignSettings.MinMissionCountLimit,
330  maxValue: CampaignSettings.MaxMissionCountLimit,
331  verticalSize,
332  OnValuesChanged);
333 
334  // Mission reward multiplier
335  CampaignSettings.MultiplierSettings rewardMultiplierSettings = CampaignSettings.GetMultiplierSettings("MissionRewardMultiplier");
336  SettingValue<float> rewardMultiplier = CreateGUIFloatInputCarousel(
337  settingsList.Content,
338  TextManager.Get("campaignoption.missionrewardmultiplier"),
339  TextManager.Get("campaignoption.missionrewardmultiplier.tooltip"),
340  prevSettings.MissionRewardMultiplier,
341  valueStep: rewardMultiplierSettings.Step,
342  minValue: rewardMultiplierSettings.Min,
343  maxValue: rewardMultiplierSettings.Max,
344  verticalSize,
345  OnValuesChanged);
346 
347  // Shop buying prices multiplier
348  CampaignSettings.MultiplierSettings shopPriceMultiplierSettings = CampaignSettings.GetMultiplierSettings("ShopPriceMultiplier");
349  SettingValue<float> shopPriceMultiplier = CreateGUIFloatInputCarousel(
350  settingsList.Content,
351  TextManager.Get("campaignoption.shoppricemultiplier"),
352  TextManager.Get("campaignoption.shoppricemultiplier.tooltip"),
353  prevSettings.ShopPriceMultiplier,
354  valueStep: shopPriceMultiplierSettings.Step,
355  minValue: shopPriceMultiplierSettings.Min,
356  maxValue: shopPriceMultiplierSettings.Max,
357  verticalSize,
358  OnValuesChanged);
359 
360  // Shipyard prices multiplier
361  CampaignSettings.MultiplierSettings shipyardPriceMultiplierSettings = CampaignSettings.GetMultiplierSettings("ShipyardPriceMultiplier");
362  SettingValue<float> shipyardPriceMultiplier = CreateGUIFloatInputCarousel(
363  settingsList.Content,
364  TextManager.Get("campaignoption.shipyardpricemultiplier"),
365  TextManager.Get("campaignoption.shipyardpricemultiplier.tooltip"),
366  prevSettings.ShipyardPriceMultiplier,
367  valueStep: shipyardPriceMultiplierSettings.Step,
368  minValue: shipyardPriceMultiplierSettings.Min,
369  maxValue: shipyardPriceMultiplierSettings.Max,
370  verticalSize,
371  OnValuesChanged);
372 
373  // OVERALL HAZARD-RELATED CAMPAIGN SETTINGS:
374 
375  NetLobbyScreen.CreateSubHeader("campaignsettingcategories.hazards", settingsList.Content);
376 
377  // World hostility (used to be "Difficulty" or level difficulty)
378  ImmutableArray<SettingCarouselElement<WorldHostilityOption>> hostilityOptions = ImmutableArray.Create(
379  new SettingCarouselElement<WorldHostilityOption>(WorldHostilityOption.Low, "worldhostility.low"),
380  new SettingCarouselElement<WorldHostilityOption>(WorldHostilityOption.Medium, "worldhostility.medium"),
381  new SettingCarouselElement<WorldHostilityOption>(WorldHostilityOption.High, "worldhostility.high"),
382  new SettingCarouselElement<WorldHostilityOption>(WorldHostilityOption.Hellish, "worldhostility.hellish", isHidden: true)
383  );
384  SettingCarouselElement<WorldHostilityOption> prevHostility = hostilityOptions
385  .FirstOrNull(element => element.Value == prevSettings.WorldHostility)
386  ?? hostilityOptions[1];
387  SettingValue<WorldHostilityOption> hostilityInput = CreateSelectionCarousel(
388  settingsList.Content,
389  TextManager.Get("worldhostility"),
390  TextManager.Get("worldhostility.tooltip"),
391  prevHostility,
392  verticalSize,
393  hostilityOptions,
394  OnValuesChanged);
395 
396  // Crew max vitality multiplier
397  CampaignSettings.MultiplierSettings crewVitalityMultiplierSettings = CampaignSettings.GetMultiplierSettings("CrewVitalityMultiplier");
398  SettingValue<float> crewVitalityMultiplier = CreateGUIFloatInputCarousel(
399  settingsList.Content,
400  TextManager.Get("campaignoption.maxvitalitymultipliercrew"),
401  TextManager.Get("campaignoption.maxvitalitymultipliercrew.tooltip"),
402  prevSettings.CrewVitalityMultiplier,
403  valueStep: crewVitalityMultiplierSettings.Step,
404  minValue: crewVitalityMultiplierSettings.Min,
405  maxValue: crewVitalityMultiplierSettings.Max,
406  verticalSize,
407  OnValuesChanged);
408 
409  // Non-crew max vitality multiplier
410  CampaignSettings.MultiplierSettings nonCrewVitalityMultiplierSettings = CampaignSettings.GetMultiplierSettings("NonCrewVitalityMultiplier");
411  SettingValue<float> nonCrewVitalityMultiplier = CreateGUIFloatInputCarousel(
412  settingsList.Content,
413  TextManager.Get("campaignoption.maxvitalitymultipliernoncrew"),
414  TextManager.Get("campaignoption.maxvitalitymultipliernoncrew.tooltip"),
415  prevSettings.NonCrewVitalityMultiplier,
416  valueStep: nonCrewVitalityMultiplierSettings.Step,
417  minValue: nonCrewVitalityMultiplierSettings.Min,
418  maxValue: nonCrewVitalityMultiplierSettings.Max,
419  verticalSize,
420  OnValuesChanged);
421 
422  // Oxygen source multiplier
423  CampaignSettings.MultiplierSettings oxygenSourceMultiplierSettings = CampaignSettings.GetMultiplierSettings("OxygenMultiplier");
424  SettingValue<float> oxygenMultiplier = CreateGUIFloatInputCarousel(
425  settingsList.Content,
426  TextManager.Get("campaignoption.oxygensourcemultiplier"),
427  TextManager.Get("campaignoption.oxygensourcemultiplier.tooltip"),
428  prevSettings.OxygenMultiplier,
429  valueStep: oxygenSourceMultiplierSettings.Step,
430  minValue: oxygenSourceMultiplierSettings.Min,
431  maxValue: oxygenSourceMultiplierSettings.Max,
432  verticalSize,
433  OnValuesChanged);
434 
435  // Reactor fuel multiplier
436  CampaignSettings.MultiplierSettings reactorFuelMultiplierSettings = CampaignSettings.GetMultiplierSettings("FuelMultiplier");
437  SettingValue<float> fuelMultiplier = CreateGUIFloatInputCarousel(
438  settingsList.Content,
439  TextManager.Get("campaignoption.reactorfuelmultiplier"),
440  TextManager.Get("campaignoption.reactorfuelmultiplier.tooltip"),
441  prevSettings.FuelMultiplier,
442  valueStep: reactorFuelMultiplierSettings.Step,
443  minValue: reactorFuelMultiplierSettings.Min,
444  maxValue: reactorFuelMultiplierSettings.Max,
445  verticalSize,
446  OnValuesChanged);
447 
448  // Repair fail effect multiplier
449  CampaignSettings.MultiplierSettings repairFailMultiplierSettings = CampaignSettings.GetMultiplierSettings("RepairFailMultiplier");
450  SettingValue<float> repairFailMultiplier = CreateGUIFloatInputCarousel(
451  settingsList.Content,
452  TextManager.Get("campaignoption.repairfailmultiplier"),
453  TextManager.Get("campaignoption.repairfailmultiplier.tooltip"),
454  prevSettings.RepairFailMultiplier,
455  valueStep: repairFailMultiplierSettings.Step,
456  minValue: repairFailMultiplierSettings.Min,
457  maxValue: repairFailMultiplierSettings.Max,
458  verticalSize,
459  OnValuesChanged);
460 
461  ImmutableArray<SettingCarouselElement<PatdownProbabilityOption>> patdownProbabilityPresets = ImmutableArray.Create(
462  new SettingCarouselElement<PatdownProbabilityOption>(PatdownProbabilityOption.Off, "probability.off"),
463  new SettingCarouselElement<PatdownProbabilityOption>(PatdownProbabilityOption.Low, "probability.low"),
464  new SettingCarouselElement<PatdownProbabilityOption>(PatdownProbabilityOption.Medium, "probability.medium"),
465  new SettingCarouselElement<PatdownProbabilityOption>(PatdownProbabilityOption.High, "probability.high")
466  );
467  SettingCarouselElement<PatdownProbabilityOption> prevPatdownProbability = patdownProbabilityPresets
468  .FirstOrNull(element => element.Value == prevSettings.PatdownProbability)
469  ?? patdownProbabilityPresets[1]; // middle option
470  SettingValue<PatdownProbabilityOption> patdownProbability = CreateSelectionCarousel(
471  settingsList.Content,
472  TextManager.Get("campaignoption.patdownprobability"),
473  TextManager.Get("campaignoption.patdownprobability.tooltip"),
474  prevPatdownProbability,
475  verticalSize,
476  patdownProbabilityPresets,
477  OnValuesChanged);
478 
479  // Show initial husk warning
480  SettingValue<bool> huskWarning = CreateTickbox(
481  settingsList.Content,
482  TextManager.Get("campaignoption.showhuskwarning"),
483  TextManager.Get("campaignoption.showhuskwarning.tooltip"),
484  prevSettings.ShowHuskWarning,
485  verticalSize,
486  OnValuesChanged);
487 
488  presetDropdown.OnSelected = (_, o) =>
489  {
490  if (o is not CampaignSettings settings) { return false; }
491 
492  loadingPreset = true;
493  tutorialEnabled.SetValue(isSinglePlayer && settings.TutorialEnabled);
494  radiationEnabled.SetValue(settings.RadiationEnabled);
495  maxMissionCountInput.SetValue(settings.MaxMissionCount);
496  startingFundsInput.SetValue(settings.StartingBalanceAmount);
497  hostilityInput.SetValue(settings.WorldHostility);
498  startingSetInput.SetValue(settings.StartItemSet);
499  crewVitalityMultiplier.SetValue(settings.CrewVitalityMultiplier);
500  nonCrewVitalityMultiplier.SetValue(settings.NonCrewVitalityMultiplier);
501  oxygenMultiplier.SetValue(settings.OxygenMultiplier);
502  fuelMultiplier.SetValue(settings.FuelMultiplier);
503  rewardMultiplier.SetValue(settings.MissionRewardMultiplier);
504  shopPriceMultiplier.SetValue(settings.ShopPriceMultiplier);
505  shipyardPriceMultiplier.SetValue(settings.ShipyardPriceMultiplier);
506  repairFailMultiplier.SetValue(settings.RepairFailMultiplier);
507  patdownProbability.SetValue(settings.PatdownProbability);
508  huskWarning.SetValue(settings.ShowHuskWarning);
509  loadingPreset = false;
510  return true;
511  };
512 
513  void OnValuesChanged()
514  {
515  if (loadingPreset) { return; }
516  presetDropdown.Select(0); // Switch to the Custom preset if this is an actual user-made change
517  }
518 
519  return new CampaignSettingElements
520  {
521  SelectedPreset = presetValue,
522  TutorialEnabled = tutorialEnabled,
523  RadiationEnabled = radiationEnabled,
524  MaxMissionCount = maxMissionCountInput,
525  StartingFunds = startingFundsInput,
526  WorldHostility = hostilityInput,
527  StartItemSet = startingSetInput,
528  CrewVitalityMultiplier = crewVitalityMultiplier,
529  NonCrewVitalityMultiplier = nonCrewVitalityMultiplier,
530  OxygenMultiplier = oxygenMultiplier,
531  FuelMultiplier = fuelMultiplier,
532  MissionRewardMultiplier = rewardMultiplier,
533  ShopPriceMultiplier = shopPriceMultiplier,
534  ShipyardPriceMultiplier = shipyardPriceMultiplier,
535  RepairFailMultiplier = repairFailMultiplier,
536  PatdownProbability = patdownProbability,
537  ShowHuskWarning = huskWarning,
538  };
539 
540  // Create a number input with plus and minus buttons because for some reason
541  // the default GUINumberInput buttons don't work when in a GUIMessageBox
542  static SettingValue<int> CreateGUIIntegerInputCarousel(
543  GUIComponent parent,
544  LocalizedString description,
545  LocalizedString tooltip,
546  int defaultValue,
547  int valueStep,
548  int minValue,
549  int maxValue,
550  float verticalSize,
551  Action onChanged)
552  {
553  GUILayoutGroup inputContainer = CreateSettingBase(
554  parent,
555  description,
556  tooltip,
557  horizontalSize: 0.55f,
558  verticalSize: verticalSize);
559 
560  GUIButton minusButton = new GUIButton(
561  new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
562  style: "GUIMinusButton",
563  textAlignment: Alignment.Center);
564  RectTransform numberInputRect = new(Vector2.One, inputContainer.RectTransform, Anchor.Center);
565  GUIButton plusButton = new GUIButton(
566  new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
567  style: "GUIPlusButton",
568  textAlignment: Alignment.Center);
569  GUINumberInput numberInput = new GUINumberInput(
570  numberInputRect,
571  NumberType.Int,
572  textAlignment: Alignment.Center,
573  style: "GUITextBox",
574  buttonVisibility: GUINumberInput.ButtonVisibility.ForceVisible,
575  customPlusMinusButtons: (plusButton, minusButton))
576  {
577  IntValue = defaultValue,
578  MinValueInt = minValue,
579  MaxValueInt = maxValue,
580  ValueStep = valueStep,
581  ToolTip = tooltip
582  };
583  inputContainer.RectTransform.Parent.MinSize = new Point(0, numberInput.RectTransform.MinSize.Y);
584 
585  numberInput.OnValueChanged += _ => onChanged();
586 
587  return new SettingValue<int>(
588  () => numberInput.IntValue,
589  i => numberInput.IntValue = i);
590  }
591 
592  static SettingValue<float> CreateGUIFloatInputCarousel(
593  GUIComponent parent,
594  LocalizedString description,
595  LocalizedString tooltip,
596  float defaultValue,
597  float valueStep,
598  float minValue,
599  float maxValue,
600  float verticalSize,
601  Action onChanged)
602  {
603  GUILayoutGroup inputContainer = CreateSettingBase(
604  parent,
605  description,
606  tooltip,
607  horizontalSize: 0.55f,
608  verticalSize: verticalSize);
609 
610  GUIButton minusButton = new GUIButton(
611  new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
612  style: "GUIMinusButton",
613  textAlignment: Alignment.Center);
614  RectTransform numberInputRect = new(Vector2.One, inputContainer.RectTransform, Anchor.Center);
615  GUIButton plusButton = new GUIButton(
616  new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
617  style: "GUIPlusButton",
618  textAlignment: Alignment.Center);
619  GUINumberInput numberInput = new GUINumberInput(
620  numberInputRect,
621  NumberType.Float,
622  textAlignment: Alignment.Center,
623  style: "GUITextBox",
624  buttonVisibility: GUINumberInput.ButtonVisibility.ForceVisible,
625  customPlusMinusButtons: (plusButton, minusButton))
626  {
627  FloatValue = defaultValue,
628  MinValueFloat = minValue,
629  MaxValueFloat = maxValue,
630  ValueStep = valueStep,
631  ToolTip = tooltip
632  };
633  numberInput.RectTransform.Parent.MinSize = new Point(0, numberInput.RectTransform.MinSize.Y);
634 
635  numberInput.OnValueChanged += _ => onChanged();
636 
637  return new SettingValue<float>(
638  () => numberInput.FloatValue,
639  i => numberInput.FloatValue = (float)Math.Round(i, 1));
640  }
641 
642  static SettingValue<T> CreateSelectionCarousel<T>(
643  GUIComponent parent,
644  LocalizedString description,
645  LocalizedString tooltip,
646  SettingCarouselElement<T> defaultValue,
647  float verticalSize,
648  ImmutableArray<SettingCarouselElement<T>> options,
649  Action onChanged)
650  {
651  GUILayoutGroup inputContainer = CreateSettingBase(
652  parent,
653  description,
654  tooltip,
655  horizontalSize: 0.55f,
656  verticalSize: verticalSize);
657 
658  GUIButton minusButton = new GUIButton(
659  new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
660  style: "GUIButtonToggleLeft",
661  textAlignment: Alignment.Center)
662  {
663  UserData = -1
664  };
665  GUIFrame inputFrame = new GUIFrame(
666  new RectTransform(Vector2.One, inputContainer.RectTransform),
667  style: null);
668  GUINumberInput numberInput = new GUINumberInput(
669  new RectTransform(Vector2.One, inputFrame.RectTransform, Anchor.Center),
670  NumberType.Int,
671  textAlignment: Alignment.Center,
672  style: "GUITextBox",
673  buttonVisibility: GUINumberInput.ButtonVisibility.ForceHidden)
674  {
675  IntValue = options.IndexOf(defaultValue),
676  MinValueInt = 0,
677  MaxValueInt = options.Length,
678  Visible = false,
679  ToolTip = tooltip
680  };
681  inputContainer.RectTransform.Parent.MinSize = new Point(0, numberInput.RectTransform.MinSize.Y);
682  GUITextBox inputLabel = new GUITextBox(
683  new RectTransform(Vector2.One, inputFrame.RectTransform, Anchor.Center),
684  text: defaultValue.Label.Value,
685  textAlignment: Alignment.Center,
686  createPenIcon: false)
687  {
688  CanBeFocused = false
689  };
690 
691  GUIButton plusButton = new GUIButton(
692  new RectTransform(Vector2.One, inputContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
693  style: "GUIButtonToggleRight",
694  textAlignment: Alignment.Center)
695  {
696  UserData = 1
697  };
698 
699  minusButton.OnClicked = plusButton.OnClicked = ChangeValue;
700 
701  bool ChangeValue(GUIButton btn, object userData)
702  {
703  if (userData is not int change) { return false; }
704 
705  int hiddenOptions = 0;
706 
707  for (int i = options.Length - 1; i >= 0; i--)
708  {
709  if (options[i].IsHidden)
710  {
711  hiddenOptions++;
712  continue;
713  }
714  break;
715  }
716 
717  int limit = options.Length - hiddenOptions;
718 
719  if (PlayerInput.IsShiftDown())
720  {
721  limit = options.Length;
722  }
723 
724  int newValue = MathUtils.PositiveModulo(Math.Clamp(numberInput.IntValue + change, min: -1, max: limit), limit);
725  SetValue(newValue);
726  return true;
727  }
728 
729  numberInput.OnValueChanged += _ => onChanged();
730 
731  void SetValue(int value)
732  {
733  numberInput.IntValue = value;
734  inputLabel.Text = options[value].Label.Value;
735  }
736 
737  return new SettingValue<T>(
738  () => options[numberInput.IntValue].Value,
739  t => SetValue(options.IndexOf(e => Equals(e.Value, t)))
740  );
741  }
742 
743  static SettingValue<bool> CreateTickbox(
744  GUIComponent parent,
745  LocalizedString description,
746  LocalizedString tooltip,
747  bool defaultValue,
748  float verticalSize,
749  Action onChanged)
750  {
751  GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, 0.625f, verticalSize);
752  GUILayoutGroup tickboxContainer = new GUILayoutGroup(
753  new RectTransform(new Vector2(0.375f, 1.0f), inputContainer.RectTransform),
754  childAnchor: Anchor.Center);
755  GUITickBox tickBox = new GUITickBox(
756  new RectTransform(Vector2.One, tickboxContainer.RectTransform),
757  string.Empty)
758  {
759  Selected = defaultValue,
760  ToolTip = tooltip
761  };
762  tickBox.Box.IgnoreLayoutGroups = true;
763  tickBox.Box.RectTransform.SetPosition(Anchor.CenterLeft);
764  inputContainer.RectTransform.Parent.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y);
765 
766  tickBox.OnSelected += _ =>
767  {
768  onChanged();
769  return true;
770  };
771 
772  return new SettingValue<bool>(() => tickBox.Selected, b => tickBox.Selected = b);
773  }
774 
775  static GUILayoutGroup CreateSettingBase(
776  GUIComponent parent,
777  LocalizedString description,
778  LocalizedString tooltip,
779  float horizontalSize,
780  float verticalSize)
781  {
782  GUILayoutGroup settingHolder = new GUILayoutGroup(
783  new RectTransform(new Vector2(1f, verticalSize), parent.RectTransform),
784  isHorizontal: true,
785  childAnchor: Anchor.CenterLeft);
786  GUITextBlock descriptionBlock = new GUITextBlock(
787  new RectTransform(new Vector2(horizontalSize, 1f), settingHolder.RectTransform),
788  description,
789  font: parent.Rect.Width < 320 ? GUIStyle.SmallFont : GUIStyle.Font,
790  wrap: true)
791  {
792  ToolTip = tooltip
793  };
794  GUILayoutGroup inputContainer = new GUILayoutGroup(
795  new RectTransform(new Vector2(1f - horizontalSize, 0.8f), settingHolder.RectTransform),
796  isHorizontal: true,
797  childAnchor: Anchor.CenterLeft)
798  {
799  RelativeSpacing = 0.05f,
800  Stretch = true
801  };
802  inputContainer.RectTransform.IsFixedSize = true;
803  settingHolder.RectTransform.MinSize = new Point(0, (int)descriptionBlock.TextSize.Y);
804  return inputContainer;
805  }
806  }
807 
808  public abstract void CreateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null);
809 
810  protected bool DeleteSave(GUIButton button, object obj)
811  {
812  if (obj is not CampaignMode.SaveInfo saveInfo) { return false; }
813 
814  var header = TextManager.Get("deletedialoglabel");
815  var body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveInfo.FilePath));
816 
817  EventEditorScreen.AskForConfirmation(header, body, () =>
818  {
819  SaveUtil.DeleteSave(saveInfo.FilePath);
820  prevSaveFiles?.RemoveAll(s => s.FilePath == saveInfo.FilePath);
821  CreateLoadMenu(prevSaveFiles.ToList());
822  return true;
823  });
824 
825  return true;
826  }
827 
828  protected void CreateBackupMenu(IEnumerable<SaveUtil.BackupIndexData> indexData, Action<SaveUtil.BackupIndexData> loadBackup)
829  {
830  var backupPopup = new GUIMessageBox("", "", new[] { TextManager.Get("Load"), TextManager.Get("Cancel") }, new Vector2(0.3f, 0.5f), minSize: new Point(500, 500));
831 
832  GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.8f), backupPopup.Content.RectTransform, Anchor.TopCenter));
833 
834  GUIListBox backupList = new GUIListBox(new RectTransform(Vector2.One, campaignSettingContent.RectTransform));
835 
836  bool isIronman = GameMain.NetworkMember?.ServerSettings is { IronmanModeActive: true };
837 
838  if (!indexData.Any() || isIronman)
839  {
840  LocalizedString errorMsg = isIronman
841  ? TextManager.Get("ironmanmodebackupdisclaimer")
842  : TextManager.Get("nobackups");
843  var errorBlock = new GUITextBlock(new RectTransform(Vector2.One, campaignSettingContent.RectTransform), errorMsg, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)
844  {
845  TextColor = GUIStyle.Red,
846  IgnoreLayoutGroups = true
847  };
848 
849  if (errorBlock.Font.MeasureString(errorMsg).X > campaignSettingContent.Rect.Width)
850  {
851  errorBlock.Wrap = true;
852  errorBlock.SetTextPos();
853  }
854  }
855 
856  if (!isIronman)
857  {
858  foreach (var data in indexData.OrderByDescending(static i => i.SaveTime))
859  {
860  GUIFrame indexFrame = new GUIFrame(
861  new RectTransform(new Vector2(1.0f, 1f / SaveUtil.MaxBackupCount), backupList.Content.RectTransform), style: "ListBoxElement")
862  {
863  UserData = data
864  };
865 
866  GUILayoutGroup indexLayout = new GUILayoutGroup(
867  new RectTransform(Vector2.One, indexFrame.RectTransform),
868  isHorizontal: true,
869  childAnchor: Anchor.CenterLeft)
870  {
871  RelativeSpacing = 0.05f,
872  Stretch = true
873  };
874 
875  GUILayoutGroup leftLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.8f), indexLayout.RectTransform), childAnchor: Anchor.TopCenter)
876  {
877  RelativeSpacing = 0.05f,
878  Stretch = true
879  };
880 
881  LocalizedString locationName = data.LocationType.IsEmpty || data.LocationNameIdentifier.IsEmpty ?
882  TextManager.Get("unknown") :
883  Location.GetName(data.LocationType, data.LocationNameFormatIndex, data.LocationNameIdentifier);
884 
885  var locationNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), leftLayout.RectTransform), locationName, textAlignment: Alignment.CenterLeft)
886  {
887  TextColor = Color.White
888  };
889 
890  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), leftLayout.RectTransform), TextManager.Get($"savestate.{data.LevelType}"), textAlignment: Alignment.CenterLeft);
891 
892 
893  GUILayoutGroup rightLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.8f), indexLayout.RectTransform), childAnchor: Anchor.TopCenter)
894  {
895  RelativeSpacing = 0.05f,
896  Stretch = true
897  };
898 
899  TimeSpan difference = SerializableDateTime.UtcNow - data.SaveTime;
900 
901  double totalMinutes = difference.TotalMinutes;
902 
903  LocalizedString timeFormat = totalMinutes switch
904  {
905  < 1 => TextManager.Get("subeditor.savedjustnow"),
906  > 60 => TextManager.GetWithVariable("saveagehours", "[hours]", ((int)Math.Floor(difference.TotalHours)).ToString()),
907  _ => TextManager.GetWithVariable("subeditor.saveageminutes", "[minutes]", difference.Minutes.ToString())
908  };
909 
910  new GUITextBlock(new RectTransform(Vector2.One, rightLayout.RectTransform), timeFormat, textAlignment: Alignment.CenterRight);
911 
912  locationNameBlock.Text = ToolBox.LimitString(locationName, locationNameBlock.Font, locationNameBlock.Rect.Width);
913  }
914  }
915 
916  backupList.AfterSelected = (selected, _) =>
917  {
918  // to my understanding, there's no way to unselect an item in a GUIListBox
919  // so no need to check if selected is null
920  backupPopup.Buttons[0].Enabled = true;
921  return true;
922  };
923 
924  backupPopup.Buttons[1].OnClicked += (button, o) =>
925  {
926  backupPopup.Close();
927  return true;
928  };
929 
930  backupPopup.Buttons[0].Enabled = false;
931  backupPopup.Buttons[0].OnClicked += (button, o) =>
932  {
933  if (backupList.SelectedComponent?.UserData is not SaveUtil.BackupIndexData selectedIndexData) { return false; }
934 
935  backupPopup.Close();
936 
937  loadBackup?.Invoke(selectedIndexData);
938  return true;
939  };
940  }
941  }
942 }
delegate void LoadGameDelegate(string loadPath, Option< uint > backupIndex)
readonly GUIComponent newGameContainer
CampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer)
GUIComponent CreateSaveElement(CampaignMode.SaveInfo saveInfo)
Action< SubmarineInfo, string, string, CampaignSettings > StartNewGame
static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings, bool isSinglePlayer)
List< CampaignMode.SaveInfo > prevSaveFiles
GUIMessageBox CampaignCustomizeSettings
abstract void CreateLoadMenu(IEnumerable< CampaignMode.SaveInfo > saveFiles=null)
void CreateBackupMenu(IEnumerable< SaveUtil.BackupIndexData > indexData, Action< SaveUtil.BackupIndexData > loadBackup)
bool DeleteSave(GUIButton button, object obj)
OnClickedHandler OnClicked
Definition: GUIButton.cs:16
virtual Rectangle Rect
RectTransform RectTransform
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
Definition: GUIDropDown.cs:270
void Select(int index)
Definition: GUIDropDown.cs:376
OnSelectedHandler AfterSelected
Triggers after some element has been selected from the listbox.
Definition: GUIListBox.cs:26
GUIComponent SelectedComponent
Definition: GUIListBox.cs:155
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
OnValueChangedHandler OnValueChanged
OnSelectedHandler OnSelected
Definition: GUITickBox.cs:13
override bool Selected
Definition: GUITickBox.cs:18
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static bool IsCompatibleWithEnabledContentPackages(IList< string > contentPackageNames, out LocalizedString errorMsg)
Defines a point in the event that GoTo actions can jump to.
Definition: Label.cs:7
static LocalizedString GetName(Identifier locationTypeIdentifier, int nameFormatIndex, Identifier nameId)
Definition: Location.cs:1183
void SetPosition(Anchor anchor, Pivot? pivot=null)
RectTransform?? Parent
void SortChildren(Comparison< RectTransform > comparison)
Point?? MinSize
Min size in pixels. Does not affect scaling.
bool IsFixedSize
If false, the element will resize if the parent is resized (with the children). If true,...
NumberType
Definition: Enums.cs:741
StartingBalanceAmountOption
Definition: Enums.cs:718
WorldHostilityOption
Definition: Enums.cs:733
PatdownProbabilityOption
Definition: Enums.cs:725
SettingValue< PatdownProbabilityOption > PatdownProbability
SettingValue< WorldHostilityOption > WorldHostility
SettingValue< StartingBalanceAmountOption > StartingFunds
SettingValue(Func< T > get, Action< T > set)
static GUITextBlock CreateSubHeader(string textTag, GUIComponent parent, string toolTipTag=null)
DateTime wrapper that tries to offer a reliable string representation that's also human-friendly
static SerializableDateTime UtcNow