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