Client LuaCsForBarotrauma
CampaignUI.cs
3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 
10 namespace Barotrauma
11 {
12  class CampaignUI
13  {
14  private CampaignMode.InteractionType selectedTab;
15 
16  private GUIFrame[] tabs;
17 
18  public CampaignMode.InteractionType SelectedTab => selectedTab;
19 
20  private Point prevResolution;
21 
22  private GUIComponent locationInfoPanel;
23 
24  private GUIListBox missionList;
25  private readonly List<GUITickBox> missionTickBoxes = new List<GUITickBox>();
26  private readonly List<GUITextBlock> missionRewardTexts = new List<GUITextBlock>();
27 
28  private bool hasMaxMissions;
29 
30  private SubmarineSelection submarineSelection;
31 
32  private Location selectedLocation;
33 
34  public Action StartRound;
35 
36  public LevelData SelectedLevel { get; private set; }
37 
38  private GUIButton StartButton { get; set; }
39 
40  public CampaignMode Campaign { get; }
41 
42  public HRManagerUI HRManagerUI { get; set; }
43 
44  public Store Store { get; private set; }
45 
46  public UpgradeStore UpgradeStore { get; set; }
47 
48  public MedicalClinicUI MedicalClinic { get; set; }
49 
50  public CampaignUI(CampaignMode campaign, GUIComponent container)
51  {
52  Campaign = campaign;
53 
54  if (campaign.Map == null) { throw new InvalidOperationException("Failed to create campaign UI (campaign map was null)."); }
55  if (campaign.Map.CurrentLocation == null) { throw new InvalidOperationException("Failed to create campaign UI (current location not set)."); }
56 
57  CreateUI(container);
58 
60  campaign.Map.OnMissionsSelected = (connection, missions) =>
61  {
62  if (missionList?.Content != null)
63  {
64  foreach (GUIComponent missionElement in missionList.Content.Children)
65  {
66  if (missionElement.FindChild(c => c is GUITickBox, recursive: true) is GUITickBox tickBox)
67  {
68  tickBox.Selected = missions.Contains(tickBox.UserData as Mission);
69  }
70  }
71  }
72  };
73  }
74 
75  private void CreateUI(GUIComponent container)
76  {
77  container.ClearChildren();
78 
79  tabs = new GUIFrame[Enum.GetValues(typeof(CampaignMode.InteractionType)).Length];
80 
81  // map tab -------------------------------------------------------------------------
82 
83  tabs[(int)CampaignMode.InteractionType.Map] = CreateDefaultTabContainer(container, new Vector2(0.9f));
84  var mapFrame = new GUIFrame(new RectTransform(Vector2.One, GetTabContainer(CampaignMode.InteractionType.Map).RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f);
85  var mapContainer = new GUICustomComponent(new RectTransform(Vector2.One, mapFrame.RectTransform), DrawMap, UpdateMap);
86  var notificationFrame = new GUIFrame(new RectTransform(new Point(mapContainer.Rect.Width, GUI.IntScale(40)), mapContainer.RectTransform, Anchor.BottomCenter), style: "ChatBox");
87 
88  new GUIFrame(new RectTransform(Vector2.One, mapFrame.RectTransform), style: "InnerGlow", color: Color.Black * 0.9f)
89  {
90  CanBeFocused = false
91  };
92 
93  var notificationContainer = new GUICustomComponent(new RectTransform(new Vector2(0.98f, 1.0f), notificationFrame.RectTransform, Anchor.Center), DrawMapNotifications, null)
94  {
95  HideElementsOutsideFrame = true
96  };
97  var notificationHeader = new GUIImage(new RectTransform(new Vector2(0.1f, 1.0f), notificationFrame.RectTransform, Anchor.CenterLeft), style: "GUISlopedHeaderRight");
98  var text = new GUITextBlock(new RectTransform(Vector2.One, notificationHeader.RectTransform, Anchor.Center), TextManager.Get("breakingnews"), font: GUIStyle.LargeFont);
99  notificationHeader.RectTransform.MinSize = new Point((int)(text.TextSize.X * 1.3f), 0);
100 
101  // crew tab -------------------------------------------------------------------------
102 
103  var crewTab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
104  tabs[(int)CampaignMode.InteractionType.Crew] = crewTab;
105  HRManagerUI = new HRManagerUI(this, crewTab);
106 
107  // store tab -------------------------------------------------------------------------
108 
109  var storeTab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
110  tabs[(int)CampaignMode.InteractionType.Store] = storeTab;
111  Store = new Store(this, storeTab);
112 
113  // upgrade tab -------------------------------------------------------------------------
114 
115  tabs[(int)CampaignMode.InteractionType.Upgrade] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
116  UpgradeStore = new UpgradeStore(this, GetTabContainer(CampaignMode.InteractionType.Upgrade));
117 
118  // Submarine buying tab
119  tabs[(int)CampaignMode.InteractionType.PurchaseSub] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f);
120 
121  tabs[(int)CampaignMode.InteractionType.MedicalClinic] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
122  MedicalClinic = new MedicalClinicUI(Campaign.MedicalClinic, GetTabContainer(CampaignMode.InteractionType.MedicalClinic));
123 
124  // mission info -------------------------------------------------------------------------
125 
126  locationInfoPanel = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.75f), GetTabContainer(CampaignMode.InteractionType.Map).RectTransform, Anchor.CenterRight)
127  { RelativeOffset = new Vector2(0.02f, 0.0f) },
128  color: Color.Black)
129  {
130  Visible = false
131  };
132 
133  // -------------------------------------------------------------------------
134 
135  SelectTab(CampaignMode.InteractionType.Map);
136 
137  prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
138  }
139 
140  private GUIFrame CreateDefaultTabContainer(GUIComponent container, Vector2 frameSize, bool visible = true)
141  {
142  var innerFrame = new GUIFrame(new RectTransform(frameSize, container.RectTransform, Anchor.Center))
143  {
144  Visible = visible
145  };
146  new GUIFrame(new RectTransform(innerFrame.Rect.Size - GUIStyle.ItemFrameMargin, innerFrame.RectTransform, Anchor.Center), style: null)
147  {
148  UserData = "container"
149  };
150  return innerFrame;
151  }
152 
154  {
155  var tabFrame = tabs[(int)tab];
156  return tabFrame?.GetChildByUserData("container") ?? tabFrame;
157  }
158 
159  private void DrawMap(SpriteBatch spriteBatch, GUICustomComponent mapContainer)
160  {
161  if (GameMain.GraphicsWidth != prevResolution.X || GameMain.GraphicsHeight != prevResolution.Y)
162  {
163  CreateUI(tabs[(int)CampaignMode.InteractionType.Map].Parent);
164  }
165 
166  Campaign?.Map?.Draw(Campaign, spriteBatch, mapContainer);
167  }
168 
169  private void DrawMapNotifications(SpriteBatch spriteBatch, GUICustomComponent notificationContainer)
170  {
171  Campaign?.Map?.DrawNotifications(spriteBatch, notificationContainer);
172  }
173 
174  private void UpdateMap(float deltaTime, GUICustomComponent mapContainer)
175  {
176  var map = Campaign?.Map;
177  if (map == null) { return; }
178  if (selectedLocation != null && selectedLocation == Campaign.GetCurrentDisplayLocation())
179  {
180  map.SelectLocation(-1);
181  }
182  map.Update(Campaign, deltaTime, mapContainer);
183  foreach (GUITickBox tickBox in missionTickBoxes)
184  {
185  bool disable = hasMaxMissions && !tickBox.Selected;
186  tickBox.Enabled = CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap) && !disable;
187  tickBox.Box.DisabledColor = disable ? tickBox.Box.Color * 0.5f : tickBox.Box.Color * 0.8f;
188  foreach (GUIComponent child in tickBox.Parent.Parent.Children)
189  {
190  if (child is GUITextBlock textBlock)
191  {
192  textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor =
193  disable ? new Color(textBlock.TextColor, 0.5f) : new Color(textBlock.TextColor, 1.0f);
194  }
195  }
196  }
197  }
198 
199  public void Update(float deltaTime)
200  {
201  switch (SelectedTab)
202  {
203  case CampaignMode.InteractionType.PurchaseSub:
204  submarineSelection?.Update();
205  break;
206  case CampaignMode.InteractionType.Crew:
207  HRManagerUI?.Update();
208  break;
209  case CampaignMode.InteractionType.Store:
210  Store?.Update(deltaTime);
211  break;
212  case CampaignMode.InteractionType.MedicalClinic:
213  MedicalClinic?.Update(deltaTime);
214  break;
215  case CampaignMode.InteractionType.Map:
216  if (StartButton != null)
217  {
218  StartButton.Enabled = CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap) && Character.Controlled is { IsIncapacitated: false };
219  }
220  break;
221  }
222  }
223 
224  public void RefreshLocationInfo()
225  {
226  if (selectedLocation != null && Campaign?.Map?.SelectedConnection != null)
227  {
228  SelectLocation(selectedLocation, Campaign.Map.SelectedConnection);
229  }
230  }
231 
232  public void SelectLocation(Location location, LocationConnection connection)
233  {
234  missionTickBoxes.Clear();
235  missionRewardTexts.Clear();
236  locationInfoPanel.ClearChildren();
237  //don't select the map panel if we're looking at some other tab
238  if (selectedTab == CampaignMode.InteractionType.Map)
239  {
241  locationInfoPanel.Visible = location != null;
242  }
243 
244  Location prevSelectedLocation = selectedLocation;
245  float prevMissionListScroll = missionList?.BarScroll ?? 0.0f;
246 
247  selectedLocation = location;
248  if (location == null) { return; }
249 
250  int padding = GUI.IntScale(20);
251 
252  var content = new GUILayoutGroup(new RectTransform(locationInfoPanel.Rect.Size - new Point(padding * 2), locationInfoPanel.RectTransform, Anchor.Center), childAnchor: Anchor.TopRight)
253  {
254  Stretch = true,
255  RelativeSpacing = 0.02f,
256  };
257 
258  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.DisplayName, font: GUIStyle.LargeFont)
259  {
260  AutoScaleHorizontal = true
261  };
262  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
263 
264  Sprite portrait = location.Type.GetPortrait(location.PortraitId);
265  portrait.EnsureLazyLoaded();
266 
267  var portraitContainer = new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform), onDraw: (sb, customComponent) =>
268  {
269  portrait.Draw(sb, customComponent.Rect.Center.ToVector2(), Color.Gray, portrait.size / 2, scale: Math.Max(customComponent.Rect.Width / portrait.size.X, customComponent.Rect.Height / portrait.size.Y));
270  })
271  {
272  HideElementsOutsideFrame = true
273  };
274 
275  var textContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), portraitContainer.RectTransform, Anchor.Center))
276  {
277  RelativeSpacing = 0.05f
278  };
279 
280  if (connection?.LevelData != null)
281  {
282  if (location.Faction?.Prefab != null)
283  {
284  var factionLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
285  TextManager.Get("Faction"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
286  new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), factionLabel.RectTransform), location.Faction.Prefab.Name, textAlignment: Alignment.CenterRight, textColor: location.Faction.Prefab.IconColor);
287  }
288  var biomeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
289  TextManager.Get("Biome", "location"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
290  new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), biomeLabel.RectTransform), connection.Biome.DisplayName, textAlignment: Alignment.CenterRight);
291 
292  var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
293  TextManager.Get("LevelDifficulty"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
294  new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), TextManager.GetWithVariable("percentageformat", "[value]", ((int)connection.LevelData.Difficulty).ToString()), textAlignment: Alignment.CenterRight);
295 
296  if (connection.LevelData.HasBeaconStation)
297  {
298  var beaconStationContent = new GUILayoutGroup(new RectTransform(biomeLabel.RectTransform.NonScaledSize, textContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
299  string style = connection.LevelData.IsBeaconActive ? "BeaconStationActive" : "BeaconStationInactive";
300  var icon = new GUIImage(new RectTransform(new Point((int)(beaconStationContent.Rect.Height * 1.2f)), beaconStationContent.RectTransform),
301  style, scaleToFit: true)
302  {
304  HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f),
305  ToolTip = RichString.Rich(TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip"))
306  };
307  new GUITextBlock(new RectTransform(Vector2.One, beaconStationContent.RectTransform),
308  TextManager.Get("submarinetype.beaconstation", "beaconstationsonarlabel"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
309  {
310  Padding = Vector4.Zero,
311  ToolTip = icon.ToolTip
312  };
313  }
314  if (connection.LevelData.HasHuntingGrounds)
315  {
316  var huntingGroundsContent = new GUILayoutGroup(new RectTransform(biomeLabel.RectTransform.NonScaledSize, textContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
317  var icon = new GUIImage(new RectTransform(new Point((int)(huntingGroundsContent.Rect.Height * 1.5f)), huntingGroundsContent.RectTransform),
318  "HuntingGrounds", scaleToFit: true)
319  {
321  HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f),
322  ToolTip = RichString.Rich(TextManager.Get("HuntingGroundsTooltip"))
323  };
324  new GUITextBlock(new RectTransform(Vector2.One, huntingGroundsContent.RectTransform),
325  TextManager.Get("missionname.huntinggrounds"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
326  {
327  Padding = Vector4.Zero,
328  ToolTip = icon.ToolTip
329  };
330  }
331  }
332 
333  missionList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), content.RectTransform))
334  {
335  Spacing = (int)(5 * GUI.yScale)
336  };
337  missionList.OnSelected = (GUIComponent selected, object userdata) =>
338  {
339  var tickBox = selected.FindChild(c => c is GUITickBox, recursive: true) as GUITickBox;
340  if (GUI.MouseOn == tickBox) { return false; }
341  if (tickBox != null)
342  {
343  if (CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap) && tickBox.Enabled)
344  {
345  tickBox.Selected = !tickBox.Selected;
346  }
347  }
348  return true;
349  };
350 
351  SelectedLevel = connection?.LevelData;
352  Location currentDisplayLocation = Campaign.GetCurrentDisplayLocation();
353  if (connection != null && connection.Locations.Contains(currentDisplayLocation))
354  {
355  List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList();
356 
357  if (!availableMissions.Any()) { availableMissions.Insert(0, null); }
358 
359  availableMissions.AddRange(location.AvailableMissions.Where(m => m.Locations[0] == m.Locations[1]));
360 
361  missionList.Content.ClearChildren();
362 
363  bool isPrevMissionInNextLocation = false;
364  foreach (Mission mission in availableMissions)
365  {
366  bool isMissionInNextLocation = mission != null && location.AvailableMissions.Contains(mission);
367  if (isMissionInNextLocation && !isPrevMissionInNextLocation)
368  {
369  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionList.Content.RectTransform), TextManager.Get("outpostmissions"),
370  textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont, wrap: true)
371  {
372  CanBeFocused = false
373  };
374  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionList.Content.RectTransform), style: "HorizontalLine")
375  {
376  CanBeFocused = false
377  };
378  }
379  isPrevMissionInNextLocation = isMissionInNextLocation;
380 
381  var missionPanel = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), missionList.Content.RectTransform), style: null)
382  {
383  UserData = mission
384  };
385  var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionPanel.RectTransform, Anchor.Center))
386  {
387  Stretch = true,
388  CanBeFocused = true,
389  AbsoluteSpacing = GUI.IntScale(5)
390  };
391 
392  var missionName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), font: GUIStyle.SubHeadingFont, wrap: true);
393  missionName.RectTransform.MinSize = new Point(0, GUI.IntScale(15));
394  if (mission == null)
395  {
396  missionTextContent.RectTransform.MinSize = missionName.RectTransform.MinSize = new Point(0, GUI.IntScale(35));
397  missionTextContent.ChildAnchor = Anchor.CenterLeft;
398  }
399  else
400  {
401  GUITickBox tickBox = null;
402  if (!isMissionInNextLocation)
403  {
404  tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, label: string.Empty)
405  {
406  UserData = mission,
407  Selected = Campaign.Map.CurrentLocation?.SelectedMissions.Contains(mission) ?? false
408  };
409  tickBox.RectTransform.MinSize = new Point(tickBox.Rect.Height, 0);
410  tickBox.RectTransform.IsFixedSize = true;
412  tickBox.OnSelected += (GUITickBox tb) =>
413  {
414  if (!CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap)) { return false; }
415 
416  if (tb.Selected)
417  {
419  }
420  else
421  {
423  }
424 
425  foreach (GUITextBlock rewardText in missionRewardTexts)
426  {
427  Mission otherMission = rewardText.UserData as Mission;
428  rewardText.Text = otherMission.GetMissionRewardText(Submarine.MainSub);
429  }
430 
431  UpdateMaxMissions(connection.OtherLocation(currentDisplayLocation));
432 
433  if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending &&
434  CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap))
435  {
437  }
438  return true;
439  };
440  missionTickBoxes.Add(tickBox);
441  }
442 
443  GUILayoutGroup difficultyIndicatorGroup = null;
444  if (mission.Difficulty.HasValue)
445  {
446  difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
447  isHorizontal: true, childAnchor: Anchor.CenterRight)
448  {
449  AbsoluteSpacing = 1,
450  UserData = "difficulty"
451  };
452  var difficultyColor = mission.GetDifficultyColor();
453  for (int i = 0; i < mission.Difficulty; i++)
454  {
455  new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
456  {
457  Color = difficultyColor,
458  SelectedColor = difficultyColor,
459  HoverColor = difficultyColor
460  };
461  }
462  }
463 
464  float extraPadding = 0;// 0.8f * tickBox.Rect.Width;
465  float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0;
466  missionName.Padding = new Vector4(missionName.Padding.X + (tickBox?.Rect.Width ?? 0) * 1.2f + extraPadding,
467  missionName.Padding.Y,
468  missionName.Padding.Z + extraZPadding + extraPadding,
469  missionName.Padding.W);
470  missionName.CalculateHeightFromText();
471 
472  //spacing
473  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(10)) }, style: null);
474 
475  var rewardText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(mission.GetMissionRewardText(Submarine.MainSub)), wrap: true)
476  {
477  UserData = mission
478  };
479  missionRewardTexts.Add(rewardText);
480 
481  LocalizedString reputationText = mission.GetReputationRewardText();
482  if (!reputationText.IsNullOrEmpty())
483  {
484  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(reputationText), wrap: true);
485  }
486  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(mission.Description), wrap: true);
487  }
488  missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(0));
489  foreach (GUIComponent child in missionTextContent.Children)
490  {
491  if (child is GUITextBlock textBlock)
492  {
493  textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent;
494  textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor;
495  }
496  }
497  missionPanel.OnAddedToGUIUpdateList = (c) =>
498  {
499  missionTextContent.Children.ForEach(child => child.State = c.State);
500  if (missionTextContent.FindChild("difficulty", recursive: true) is GUILayoutGroup group)
501  {
502  group.State = c.State;
503  }
504  };
505 
506  if (mission != availableMissions.Last())
507  {
508  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionList.Content.RectTransform), style: "HorizontalLine")
509  {
510  CanBeFocused = false
511  };
512  }
513  }
514  if (prevSelectedLocation == selectedLocation)
515  {
516  missionList.BarScroll = prevMissionListScroll;
517  missionList.UpdateDimensions();
518  missionList.UpdateScrollBarSize();
519  }
520  }
521  var destination = connection.OtherLocation(currentDisplayLocation);
522  UpdateMaxMissions(destination);
523 
524  var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), isHorizontal: true);
525 
526  new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), buttonArea.RectTransform), "", font: GUIStyle.SubHeadingFont)
527  {
528  TextGetter = () =>
529  {
530  int missionCount = 0;
532  {
533  missionCount = Campaign.Map.CurrentLocation.SelectedMissions.Count(m => m.Locations.Contains(location) && !GameMain.GameSession.Missions.Contains(m));
534  }
535  return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{missionCount}/{Campaign.Settings.TotalMaxMissionCount}");
536  }
537  };
538 
539  StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform),
540  TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge")
541  {
542  OnClicked = (GUIButton btn, object obj) =>
543  {
544  if (missionList.Content.FindChild(c => c is GUITickBox tickBox && tickBox.Selected, recursive: true) == null &&
545  missionList.Content.Children.Any(c => c.UserData is Mission mission && mission.Locations.Contains(Campaign?.Map?.CurrentLocation)))
546  {
547  var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
548  noMissionVerification.Buttons[0].OnClicked = (btn, userdata) =>
549  {
550  StartRound?.Invoke();
551  noMissionVerification.Close();
552  return true;
553  };
554  noMissionVerification.Buttons[1].OnClicked = noMissionVerification.Close;
555  }
556  else
557  {
558  StartRound?.Invoke();
559  }
560  return true;
561  },
562  Enabled = true,
564  };
565 
566  buttonArea.RectTransform.MinSize = new Point(0, StartButton.RectTransform.MinSize.Y);
567 
568  if (Level.Loaded != null &&
569  connection?.LevelData == Level.Loaded.LevelData &&
570  currentDisplayLocation == Campaign.Map?.CurrentLocation)
571  {
572  StartButton.Visible = false;
573  missionList.Enabled = false;
574  }
575  }
576 
577  public void SelectTab(CampaignMode.InteractionType tab, Character npc = null)
578  {
580  {
581  HintManager.OnShowCampaignInterface(tab);
582  }
583 
584  selectedTab = tab;
585  for (int i = 0; i < tabs.Length; i++)
586  {
587  if (tabs[i] != null)
588  {
589  tabs[i].Visible = (int)selectedTab == i;
590  }
591  }
592 
593  locationInfoPanel.Visible = tab == CampaignMode.InteractionType.Map && selectedLocation != null;
594 
595  switch (selectedTab)
596  {
597  case CampaignMode.InteractionType.Store:
598  Store.SelectStore(npc);
599  break;
600  case CampaignMode.InteractionType.Crew:
603  break;
604  case CampaignMode.InteractionType.PurchaseSub:
605  submarineSelection ??= new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform);
606  submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true);
607  break;
608  case CampaignMode.InteractionType.Map:
610  //refresh mission rewards (may have been changed by e.g. a pending submarine switch)
611  foreach (GUITextBlock rewardText in missionRewardTexts)
612  {
613  Mission mission = (Mission)rewardText.UserData;
614  rewardText.Text = mission.GetMissionRewardText(Submarine.MainSub);
615  }
616  break;
617  }
618  }
619 
620  public static LocalizedString GetMoney()
621  {
622  return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.GetBalance()));
623  }
624 
626  {
627  return TextManager.FormatCurrency(GameMain.GameSession?.Campaign is { } campaign ? campaign.GetBalance() : 0);
628  }
629 
631  {
632  return TextManager.FormatCurrency(GameMain.GameSession?.Campaign is { } campaign ? campaign.Bank.Balance : 0);
633  }
634 
636  {
637  return TextManager.FormatCurrency(GameMain.GameSession?.Campaign is { } campaign ? campaign.Wallet.Balance : 0);
638  }
639 
640  private void UpdateMaxMissions(Location location)
641  {
642  hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.TotalMaxMissionCount;
643  }
644 
645  public readonly struct PlayerBalanceElement
646  {
647  public readonly bool DisplaySeparateBalances;
651 
652  public PlayerBalanceElement(bool displaySeparateBalances, GUILayoutGroup parentComponent, GUILayoutGroup totalBalanceContainer, GUILayoutGroup bankBalanceContainer)
653  {
654  DisplaySeparateBalances = displaySeparateBalances;
655  ParentComponent = parentComponent;
656  TotalBalanceContainer = totalBalanceContainer;
657  BankBalanceContainer = bankBalanceContainer;
658  }
659 
660  public PlayerBalanceElement(PlayerBalanceElement element, bool displaySeparateBalances)
661  {
662  DisplaySeparateBalances = displaySeparateBalances;
666  }
667  }
668 
669  public static PlayerBalanceElement? AddBalanceElement(GUIComponent elementParent, Vector2 relativeSize)
670  {
671  var parent = new GUILayoutGroup(new RectTransform(relativeSize, elementParent.RectTransform), isHorizontal: true, childAnchor: Anchor.TopRight);
673  {
674  AddBalance(parent, true, TextManager.Get("campaignstore.balance"), GetTotalBalance);
675  return null;
676  }
677  else
678  {
679  bool displaySeparateBalances = CampaignMode.AllowedToManageWallets();
680  var totalBalanceContainer = AddBalance(parent, displaySeparateBalances, TextManager.Get("campaignstore.total"), GetTotalBalance);
681  var bankBalanceContainer = AddBalance(parent, displaySeparateBalances, TextManager.Get("crewwallet.bank"), GetBankBalance);
682  AddBalance(parent, true, TextManager.Get("crewwallet.wallet"), GetWalletBalance);
683  var playerBalanceElement = new PlayerBalanceElement(displaySeparateBalances, parent, totalBalanceContainer, bankBalanceContainer);
684  parent.Recalculate();
685  return playerBalanceElement;
686  }
687 
688  static GUILayoutGroup AddBalance(GUIComponent parent, bool visible, LocalizedString text, GUITextBlock.TextGetterHandler textGetter)
689  {
690  float balanceContainerWidth = GameMain.IsSingleplayer ? 1 : 1 / 3f;
691  var rt = new RectTransform(new Vector2(balanceContainerWidth, 1.0f), parent.RectTransform)
692  {
693  MaxSize = new Point(GUI.IntScale(GUI.AdjustForTextScale(120)), int.MaxValue)
694  };
695  var balanceContainer = new GUILayoutGroup(rt, childAnchor: Anchor.TopRight)
696  {
697  RelativeSpacing = 0.005f,
698  Visible = visible
699  };
700  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), balanceContainer.RectTransform), text,
701  font: GUIStyle.Font, textAlignment: Alignment.BottomRight)
702  {
703  AutoScaleVertical = true,
705  };
706  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), balanceContainer.RectTransform), "",
707  textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopRight)
708  {
709  AutoScaleVertical = true,
710  TextScale = 1.1f,
711  TextGetter = textGetter
712  };
713  return balanceContainer;
714  }
715  }
716 
717  public static PlayerBalanceElement? UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement)
718  {
719  if (playerBalanceElement is { } balanceElement)
720  {
721  bool displaySeparateBalances = CampaignMode.AllowedToManageWallets();
722  if (displaySeparateBalances != balanceElement.DisplaySeparateBalances)
723  {
724  balanceElement.TotalBalanceContainer.Visible = displaySeparateBalances;
725  balanceElement.BankBalanceContainer.Visible = displaySeparateBalances;
726  playerBalanceElement = new PlayerBalanceElement(balanceElement, displaySeparateBalances);
727  balanceElement.ParentComponent.Recalculate();
728  }
729  }
730  return playerBalanceElement;
731  }
732  }
733 }
readonly LocalizedString DisplayName
Definition: Biome.cs:13
virtual Wallet Wallet
Gets the current personal wallet In singleplayer this is the campaign bank and in multiplayer this is...
static bool AllowedToManageCampaign(ClientPermissions permissions)
There is a server-side implementation of the method in MultiPlayerCampaign
Location GetCurrentDisplayLocation()
The location that's displayed as the "current one" in the map screen. Normally the current outpost or...
UpgradeStore UpgradeStore
Definition: CampaignUI.cs:46
CampaignMode.InteractionType SelectedTab
Definition: CampaignUI.cs:18
static ? PlayerBalanceElement AddBalanceElement(GUIComponent elementParent, Vector2 relativeSize)
Definition: CampaignUI.cs:669
static ? PlayerBalanceElement UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement)
Definition: CampaignUI.cs:717
static LocalizedString GetBankBalance()
Definition: CampaignUI.cs:630
GUIComponent GetTabContainer(CampaignMode.InteractionType tab)
Definition: CampaignUI.cs:153
static LocalizedString GetWalletBalance()
Definition: CampaignUI.cs:635
CampaignUI(CampaignMode campaign, GUIComponent container)
Definition: CampaignUI.cs:50
static LocalizedString GetTotalBalance()
Definition: CampaignUI.cs:625
void SelectTab(CampaignMode.InteractionType tab, Character npc=null)
Definition: CampaignUI.cs:577
void Update(float deltaTime)
Definition: CampaignUI.cs:199
MedicalClinicUI MedicalClinic
Definition: CampaignUI.cs:48
HRManagerUI HRManagerUI
Definition: CampaignUI.cs:42
LevelData SelectedLevel
Definition: CampaignUI.cs:36
void SelectLocation(Location location, LocationConnection connection)
Definition: CampaignUI.cs:232
static LocalizedString GetMoney()
Definition: CampaignUI.cs:620
CampaignMode Campaign
Definition: CampaignUI.cs:40
FactionPrefab Prefab
Definition: Factions.cs:18
override bool Enabled
Definition: GUIButton.cs:27
virtual ComponentState State
GUIComponent GetChildByUserData(object obj)
Definition: GUIComponent.cs:66
virtual void ClearChildren()
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
Definition: GUIComponent.cs:95
virtual RichString ToolTip
virtual Rectangle Rect
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
GUIComponent that can be used to render custom content on the UI
OnSelectedHandler OnSelected
Triggers when some element is clicked on the listbox. Note that SelectedData is not set yet when this...
Definition: GUIListBox.cs:21
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
delegate LocalizedString TextGetterHandler()
override bool Enabled
Definition: GUITickBox.cs:40
OnSelectedHandler OnSelected
Definition: GUITickBox.cs:13
override bool Selected
Definition: GUITickBox.cs:18
static int GraphicsWidth
Definition: GameMain.cs:162
static GameSession?? GameSession
Definition: GameMain.cs:88
static int GraphicsHeight
Definition: GameMain.cs:168
static bool IsSingleplayer
Definition: GameMain.cs:34
static GameClient Client
Definition: GameMain.cs:188
The "HR manager" UI, which is used to hire/fire characters and rename crewmates.
Definition: HRManagerUI.cs:15
readonly float Difficulty
Definition: LevelData.cs:24
Location OtherLocation(Location location)
IEnumerable< Mission > GetMissionsInConnection(LocationConnection connection)
Definition: Location.cs:1098
LocationType Type
Definition: Location.cs:94
IEnumerable< Mission > AvailableMissions
Definition: Location.cs:435
void SelectMission(Mission mission)
Definition: Location.cs:455
IEnumerable< Mission > SelectedMissions
Definition: Location.cs:445
void DeselectMission(Mission mission)
Definition: Location.cs:464
LocalizedString DisplayName
Definition: Location.cs:59
Sprite GetPortrait(int randomSeed)
readonly LocalizedString Name
Definition: LocationType.cs:27
static MapGenerationParams Instance
Action< Location, LocationConnection > OnLocationSelected
void DrawNotifications(SpriteBatch spriteBatch, GUICustomComponent container)
Action< LocationConnection, IEnumerable< Mission > > OnMissionsSelected
void Draw(CampaignMode campaign, SpriteBatch spriteBatch, GUICustomComponent mapContainer)
void ResetPendingSub()
Resets pendingSubInfo and forces crush depth to be calculated again for icon displaying purposes
virtual RichString GetMissionRewardText(Submarine sub)
Returns the full reward text of the mission (e.g. "Reward: 2,000 mk" or "Reward: 500 mk x 2 (out of m...
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,...
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Definition: RichString.cs:67
void Update(float deltaTime)
Definition: Store.cs:2270
void SelectStore(Character merchant)
Definition: Store.cs:212
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.
void RefreshSubmarineDisplay(bool updateSubs, bool setTransferOptionToTrue=false)
readonly GUILayoutGroup TotalBalanceContainer
Definition: CampaignUI.cs:649
PlayerBalanceElement(PlayerBalanceElement element, bool displaySeparateBalances)
Definition: CampaignUI.cs:660
readonly GUILayoutGroup ParentComponent
Definition: CampaignUI.cs:648
PlayerBalanceElement(bool displaySeparateBalances, GUILayoutGroup parentComponent, GUILayoutGroup totalBalanceContainer, GUILayoutGroup bankBalanceContainer)
Definition: CampaignUI.cs:652
readonly GUILayoutGroup BankBalanceContainer
Definition: CampaignUI.cs:650