Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using System;
6 using System.Linq;
7 using System.Threading;
8 using System.Threading.Tasks;
9 
10 namespace Barotrauma
11 {
12  internal abstract partial class CampaignMode : GameMode
13  {
14  public bool CrewDead
15  {
16  get;
17  protected set;
18  }
19 
20  protected Color overlayColor;
21  protected Sprite overlaySprite;
22 
23  private TransitionType prevCampaignUIAutoOpenType;
24 
26 
29 
32 
34  {
35  get;
36  protected set;
37  }
38 
39  private CancellationTokenSource startRoundCancellationToken;
40 
41  public bool ForceMapUI
42  {
43  get;
44  protected set;
45  }
46 
47  private bool showCampaignUI;
48  private bool wasChatBoxOpen;
49  public bool ShowCampaignUI
50  {
51  get { return showCampaignUI; }
52  set
53  {
54  if (value == showCampaignUI) { return; }
55  var chatBox = CrewManager?.ChatBox ?? GameMain.Client?.ChatBox;
56  if (value)
57  {
58  if (chatBox != null)
59  {
60  wasChatBoxOpen = chatBox.ToggleOpen;
61  chatBox.ToggleOpen = false;
62  }
63  }
64  else if (chatBox != null)
65  {
66  chatBox.ToggleOpen = wasChatBoxOpen;
67  }
68  if (!value)
69  {
70  switch (CampaignUI?.SelectedTab)
71  {
72  case InteractionType.PurchaseSub:
74  break;
75  case InteractionType.MedicalClinic:
76  CampaignUI.MedicalClinic?.OnDeselected();
77  break;
78  case InteractionType.Store:
80  break;
81  }
82  }
83 
84  showCampaignUI = value;
85  }
86  }
87 
92  public virtual Wallet Wallet => GetWallet();
93 
94  public override void ShowStartMessage()
95  {
96  foreach (Mission mission in Missions.ToList())
97  {
98  if (!mission.Prefab.ShowStartMessage) { continue; }
99  new GUIMessageBox(
100  RichString.Rich(mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name),
101  RichString.Rich(mission.Description), Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon)
102  {
103  IconColor = mission.Prefab.IconColor,
104  UserData = "missionstartmessage"
105  };
106  }
107  }
108 
109  private static bool IsOwner(Client client) => client != null && client.IsOwner;
110 
114  public static bool AllowedToManageCampaign(ClientPermissions permissions)
115  {
116  //allow managing the round if the client has permissions, is the owner, the only client in the server,
117  //or if no-one has management permissions
118  if (GameMain.Client == null) { return true; }
119  return
120  GameMain.Client.HasPermission(permissions) ||
123  AnyOneAllowedToManageCampaign(permissions);
124  }
125 
126  public static bool AllowedToManageWallets()
127  {
128  return AllowedToManageCampaign(ClientPermissions.ManageMoney);
129  }
130 
131  public static bool AllowImmediateItemDelivery()
132  {
133  if (GameMain.Client == null) { return true; }
134  return
135  GameMain.Client.ServerSettings.AllowImmediateItemDelivery ||
138  }
139 
141  {
142  int buttonWidth = (int)(450 * GUI.xScale * (GUI.IsUltrawide ? 3.0f : 1.0f));
143  int buttonHeight = (int)(40 * GUI.yScale);
144  var rectT = HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y, buttonWidth, buttonHeight), GUI.Canvas);
145  rectT.Pivot = Pivot.Center;
146  return new GUIButton(rectT, TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
147  {
148  Pulse = true,
149  TextBlock =
150  {
151  Shadow = true,
152  AutoScaleHorizontal = true
153  }
154  };
155  }
156 
157 
158  public override void Draw(SpriteBatch spriteBatch)
159  {
160  if (overlayColor.A > 0)
161  {
162  if (overlaySprite != null)
163  {
164  GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.Black * (overlayColor.A / 255.0f), isFilled: true);
166  overlaySprite.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2, overlayColor, overlaySprite.size / 2, scale: scale);
167  }
168  else
169  {
170  GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), overlayColor, isFilled: true);
171  }
172  }
173 
174  SlideshowPlayer?.DrawManually(spriteBatch);
175 
176  if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI ||
177  CoroutineManager.IsCoroutineRunning("LevelTransition"))
178  {
179  endRoundButton.Visible = false;
180  if (ReadyCheckButton != null)
181  {
182  ReadyCheckButton.Visible = false;
183  }
184  return;
185  }
186  if (Submarine.MainSub == null || Level.Loaded == null) { return; }
187 
188  bool allowEndingRound = false;
191  RichString overrideEndRoundButtonToolTip = string.Empty;
192  var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
193  LocalizedString buttonText = "";
194  switch (availableTransition)
195  {
196  case TransitionType.ProgressToNextLocation:
197  case TransitionType.ProgressToNextEmptyLocation:
198  if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
199  {
200  string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
201  buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.DisplayName ?? "[ERROR]");
202  allowEndingRound = !ForceMapUI && !ShowCampaignUI;
203  }
204  break;
205  case TransitionType.LeaveLocation:
206  buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.DisplayName ?? "[ERROR]");
207  allowEndingRound = !ForceMapUI && !ShowCampaignUI;
208  break;
209  case TransitionType.ReturnToPreviousLocation:
210  case TransitionType.ReturnToPreviousEmptyLocation:
211  if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
212  {
213  string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
214  buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.DisplayName ?? "[ERROR]");
215  allowEndingRound = !ForceMapUI && !ShowCampaignUI;
216  }
217  break;
218  case TransitionType.None:
219  default:
220  bool inFriendlySub = Character.Controlled is { IsInFriendlySub: true };
221  if (Level.Loaded.Type == LevelData.LevelType.Outpost && !Level.Loaded.IsEndBiome &&
222  (inFriendlySub || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false)))
223  {
224  if (Missions.Any(m => m is SalvageMission salvageMission && salvageMission.AnyTargetNeedsToBeRetrievedToSub))
225  {
226  overrideEndRoundButtonToolTip = TextManager.Get("SalvageTargetNotInSub");
227  endRoundButton.Color = GUIStyle.Red * 0.7f;
228  endRoundButton.HoverColor = GUIStyle.Red;
229  }
230  buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.DisplayName ?? "[ERROR]");
231  allowEndingRound = !ForceMapUI && !ShowCampaignUI;
232  }
233  else
234  {
235  allowEndingRound = false;
236  }
237  break;
238  }
239  if (Level.IsLoadedOutpost &&
240  (!ObjectiveManager.AllActiveObjectivesCompleted() && this is not MultiPlayerCampaign))
241  {
242  allowEndingRound = false;
243  }
244  if (ReadyCheckButton != null)
245  {
246  ReadyCheckButton.Visible = allowEndingRound && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10.0f;
247  }
248 
249  endRoundButton.Visible = allowEndingRound && Character.Controlled is { IsIncapacitated: false };
251  {
253  {
254  buttonText = TextManager.Get("map");
255  }
256  else if (prevCampaignUIAutoOpenType != availableTransition &&
257  availableTransition == TransitionType.ProgressToNextEmptyLocation)
258  {
259  HintManager.OnAvailableTransition(availableTransition);
260  //opening the campaign map pauses the game and prevents HintManager from running -> update it manually to get the hint to show up immediately
261  HintManager.Update();
262  Map.SelectLocation(-1);
264  prevCampaignUIAutoOpenType = availableTransition;
265  }
266  endRoundButton.Text = ToolBox.LimitString(buttonText.Value, endRoundButton.Font, endRoundButton.Rect.Width - 5);
267  if (overrideEndRoundButtonToolTip != string.Empty)
268  {
269  endRoundButton.ToolTip = overrideEndRoundButtonToolTip;
270  }
271  else if (endRoundButton.Text != buttonText)
272  {
273  endRoundButton.ToolTip = buttonText;
274  }
276  {
278  }
280  {
281  endRoundButton.RectTransform.ScreenSpaceOffset = new Point(0, HUDLayoutSettings.Padding + GameMain.Client.FollowSubTickBox.Rect.Height);
282  }
283  else
284  {
286  }
287  }
288  endRoundButton.DrawManually(spriteBatch);
289  if (this is MultiPlayerCampaign && ReadyCheckButton != null)
290  {
292  ReadyCheckButton.DrawManually(spriteBatch);
293  if (ReadyCheck.ReadyCheckCooldown > DateTime.Now)
294  {
295  float progress = (ReadyCheck.ReadyCheckCooldown - DateTime.Now).Seconds / 60.0f;
296  ReadyCheckButton.Color = ToolBox.GradientLerp(progress, Color.White, GUIStyle.Red);
297  }
298  }
299  }
300 
301  public Task SelectSummaryScreen(RoundSummary roundSummary, LevelData newLevel, bool mirror, Action action)
302  {
303  var roundSummaryScreen = RoundSummaryScreen.Select(overlaySprite, roundSummary);
304 
305  GUI.ClearCursorWait();
306 
307  startRoundCancellationToken = new CancellationTokenSource();
308  var loadTask = Task.Run(async () =>
309  {
310  await Task.Yield();
311  Rand.ThreadId = Environment.CurrentManagedThreadId;
312  try
313  {
314  GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
315  }
316  catch (Exception e)
317  {
318  roundSummaryScreen.LoadException = e;
319  }
320  Rand.ThreadId = 0;
321  startRoundCancellationToken = null;
322  }, startRoundCancellationToken.Token);
323  TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
324  {
325  overlayColor = Color.Transparent;
326  action?.Invoke();
327  });
328 
329  return loadTask;
330  }
331 
332  public void CancelStartRound()
333  {
334  startRoundCancellationToken?.Cancel();
335  }
336 
338  {
339  if (startRoundCancellationToken != null &&
340  startRoundCancellationToken.Token.IsCancellationRequested)
341  {
342  startRoundCancellationToken.Token.ThrowIfCancellationRequested();
343  startRoundCancellationToken = null;
344  }
345  }
346 
347  partial void NPCInteractProjSpecific(Character npc, Character interactor)
348  {
349  if (npc == null || interactor == null) { return; }
350 
351  switch (npc.CampaignInteractionType)
352  {
353  case InteractionType.None:
354  case InteractionType.Talk:
355  case InteractionType.Examine:
356  return;
357  case InteractionType.Upgrade when !UpgradeManager.CanUpgradeSub():
358  UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade").Value, IsSinglePlayer, npc);
359  return;
360  case InteractionType.Crew when GameMain.NetworkMember != null:
362  goto default;
363  case InteractionType.MedicalClinic:
364  CampaignUI.MedicalClinic.RequestLatestPending();
365  goto default;
366  default:
367  ShowCampaignUI = true;
369  CampaignUI.UpgradeStore?.RequestRefresh();
370  break;
371  }
372 
373  if (npc.AIController is HumanAIController humanAi && humanAi.IsInHostileFaction())
374  {
375  npc.Speak(TextManager.Get("dialoglowrepcampaigninteraction").Value, identifier: "dialoglowrepcampaigninteraction".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
376  }
377  }
378 
379  public override void AddToGUIUpdateList()
380  {
381  if (ShowCampaignUI || ForceMapUI)
382  {
384  if (CampaignUI?.UpgradeStore?.HoveredEntity != null)
385  {
386  if (CampaignUI.SelectedTab != InteractionType.Upgrade) { return; }
387  CampaignUI?.UpgradeStore?.ItemInfoFrame.AddToGUIUpdateList(order: 1);
388  }
389  }
390  base.AddToGUIUpdateList();
394  }
395 
396  protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
397  {
399  bool lowFuel = Submarine.MainSub.Info.LowFuel;
400  if (PendingSubmarineSwitch != null)
401  {
403  }
404  if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains("reactorfuel"))))
405  {
406  var extraConfirmationBox =
407  new GUIMessageBox(TextManager.Get("lowfuelheader"),
408  TextManager.Get("lowfuelwarning"),
409  new LocalizedString[2] { TextManager.Get("ok"), TextManager.Get("cancel") });
410  extraConfirmationBox.Buttons[0].OnClicked = (b, o) => { Confirm(); return true; };
411  extraConfirmationBox.Buttons[0].OnClicked += extraConfirmationBox.Close;
412  extraConfirmationBox.Buttons[1].OnClicked = extraConfirmationBox.Close;
413  }
414  else
415  {
416  Confirm();
417  }
418 
419  void Confirm()
420  {
421  var availableTransition = GetAvailableTransition(out _, out _);
422  if (Character.Controlled != null &&
423  availableTransition == TransitionType.ReturnToPreviousLocation &&
425  {
426  onConfirm();
427  }
428  else if (Character.Controlled != null &&
429  availableTransition == TransitionType.ProgressToNextLocation &&
431  {
432  onConfirm();
433  }
434  else
435  {
436  onReturnToMapScreen();
437  }
438  }
439  }
440 
441  public override void Update(float deltaTime)
442  {
443  base.Update(deltaTime);
444 
445  MedicalClinic?.Update(deltaTime);
446 
447  if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
448  {
449  GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
450  }
451 #if DEBUG
452  if (GUI.KeyboardDispatcher.Subscriber == null && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.M))
453  {
455 
457  GUIMessageBox.MessageBoxes.Add(summaryFrame);
458  GameMain.GameSession.RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
459  }
460 #endif
461  if (ShowCampaignUI || ForceMapUI)
462  {
463  CampaignUI?.Update(deltaTime);
464  }
465  }
466 
467  }
468 }
Task SelectSummaryScreen(RoundSummary roundSummary, LevelData newLevel, bool mirror, Action action)
TransitionType GetAvailableTransition()
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
void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
UpgradeStore UpgradeStore
Definition: CampaignUI.cs:46
CampaignMode.InteractionType SelectedTab
Definition: CampaignUI.cs:18
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
Dictionary< Identifier, List< PurchasedItem > > PurchasedItems
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
ChatBox ChatBox
Present only in single player games. In multiplayer. The chatbox is found from GameSession....
Submarine Submarine
Definition: Entity.cs:53
OnClickedHandler OnClicked
Definition: GUIButton.cs:16
override Color Color
Definition: GUIButton.cs:41
override RichString ToolTip
Definition: GUIButton.cs:150
override Color HoverColor
Definition: GUIButton.cs:51
override GUIFont? Font
Definition: GUIButton.cs:125
LocalizedString Text
Definition: GUIButton.cs:138
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren=false, bool recursive=true)
By default, all the gui elements are drawn automatically in the same order they appear on the update ...
virtual Rectangle Rect
GUIComponentStyle Style
RectTransform RectTransform
static readonly List< GUIComponent > MessageBoxes
static int GraphicsWidth
Definition: GameMain.cs:162
static GameSession?? GameSession
Definition: GameMain.cs:88
static int GraphicsHeight
Definition: GameMain.cs:168
static GameClient Client
Definition: GameMain.cs:188
void StartRound(string levelSeed, float? difficulty=null, LevelGenerationParams? levelGenerationParams=null)
void SendCrewState(bool updatePending,(CharacterInfo info, string newName) renameCharacter=default, CharacterInfo firedCharacter=null, bool validateHires=false)
Notify the server of crew changes
IEnumerable< Identifier > OutpostModuleTags
Inherited flags from outpost generation.
static bool IsLoadedFriendlyOutpost
Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reput...
static bool IsLoadedOutpost
Is there a loaded level set and is it an outpost?
LocalizedString DisplayName
Definition: Location.cs:58
bool HasPermission(ClientPermissions permission)
Definition: GameClient.cs:2620
Point ScreenSpaceOffset
Screen space offset. From top left corner. In pixels.
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Definition: RichString.cs:67
GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, CampaignMode.TransitionType transitionType=CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults=null)
Definition: RoundSummary.cs:50
static RoundSummaryScreen Select(Sprite backgroundSprite, RoundSummary roundSummary)
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
void OnDeselected()
Definition: Store.cs:2142
bool LowFuel
Note: Refreshed for loaded submarines when they are saved, when they are loaded, and on round end....
void CreateUpgradeErrorMessage(string text, bool isSinglePlayer, Character character)