Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs
2 using Barotrauma.IO;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Xml.Linq;
9 
10 namespace Barotrauma
11 {
13  {
14  [Flags]
15  public enum NetFlags : UInt16
16  {
17  Misc = 0x1,
18  MapAndMissions = 0x2,
19  UpgradeManager = 0x4,
20  SubList = 0x8,
21  ItemsInBuyCrate = 0x10,
22  ItemsInSellFromSubCrate = 0x20,
23  PurchasedItems = 0x80,
24  SoldItems = 0x100,
25  Reputation = 0x200,
26  CharacterInfo = 0x800
27  }
28 
29  private readonly Dictionary<NetFlags, UInt16> lastUpdateID;
30 
31  public UInt16 GetLastUpdateIdForFlag(NetFlags flag)
32  {
33  if (!ValidateFlag(flag)) { return 0; }
34  return lastUpdateID[flag];
35  }
36  public void SetLastUpdateIdForFlag(NetFlags flag, UInt16 id)
37  {
38  if (!ValidateFlag(flag)) { return; }
39  lastUpdateID[flag] = id;
40  }
41 
43  {
44  if (!ValidateFlag(flag)) { return; }
45  if (!lastUpdateID.ContainsKey(flag)) { lastUpdateID[flag] = 0; }
46  lastUpdateID[flag]++;
47  }
49  {
50  foreach (NetFlags flag in Enum.GetValues(typeof(NetFlags)))
51  {
52  if (!lastUpdateID.ContainsKey(flag)) { lastUpdateID[flag] = 0; }
53  lastUpdateID[flag]++;
54  }
55  }
56 
57  private static bool ValidateFlag(NetFlags flag)
58  {
59  if (MathHelper.IsPowerOfTwo((int)flag)) { return true; }
60 #if DEBUG
61  throw new InvalidOperationException($"\"{flag}\" is not a valid campaign update flag.");
62 #else
63  return false;
64 #endif
65  }
66 
67 
68  private UInt16 lastSaveID;
69  public UInt16 LastSaveID
70  {
71  get
72  {
73 #if SERVER
74  if (GameMain.Server != null && lastSaveID < 1) { lastSaveID++; }
75 #endif
76  return lastSaveID;
77  }
78  set
79  {
80 #if SERVER
81  //trigger a campaign update to notify the clients of the changed save ID
83 #endif
84  lastSaveID = value;
85  }
86  }
87 
88  private static byte currentCampaignID;
89 
90  public byte CampaignID
91  {
92  get; set;
93  }
94 
95  private MultiPlayerCampaign(CampaignSettings settings) : base(GameModePreset.MultiPlayerCampaign, settings)
96  {
97  currentCampaignID++;
98  lastUpdateID = new Dictionary<NetFlags, ushort>();
99  foreach (NetFlags flag in Enum.GetValues(typeof(NetFlags)))
100  {
101 #if SERVER
102  //server starts from a higher ID to ensure we send the initial state
103  lastUpdateID[flag] = 1;
104 #else
105  lastUpdateID[flag] = 0;
106 #endif
107  }
108  CampaignID = currentCampaignID;
109  UpgradeManager = new UpgradeManager(this);
110  InitFactions();
111  }
112 
113  public static MultiPlayerCampaign StartNew(string mapSeed, CampaignSettings settings)
114  {
115  MultiPlayerCampaign campaign = new MultiPlayerCampaign(settings);
116  //only the server generates the map, the clients load it from a save file
117  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
118  {
119  campaign.Settings = settings;
120  campaign.map = new Map(campaign, mapSeed);
121  }
122  campaign.InitProjSpecific();
123  return campaign;
124  }
125 
126  public static MultiPlayerCampaign LoadNew(XElement element)
127  {
128  MultiPlayerCampaign campaign = new MultiPlayerCampaign(CampaignSettings.Empty);
129  campaign.Load(element);
130  campaign.InitProjSpecific();
131  campaign.IsFirstRound = false;
132  return campaign;
133  }
134 
135  partial void InitProjSpecific();
136 
137  public static string GetCharacterDataSavePath(string savePath)
138  {
139  return Path.Combine(Path.GetDirectoryName(savePath), Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
140  }
141 
142  public static string GetCharacterDataSavePath()
143  {
145  }
146 
150  private void Load(XElement element)
151  {
152  PurchasedLostShuttlesInLatestSave = element.GetAttributeBool("purchasedlostshuttles", false);
153  PurchasedHullRepairsInLatestSave = element.GetAttributeBool("purchasedhullrepairs", false);
154  PurchasedItemRepairsInLatestSave = element.GetAttributeBool("purchaseditemrepairs", false);
155  CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
156  if (CheatsEnabled)
157  {
158  DebugConsole.CheatsEnabled = true;
159  if (!AchievementManager.CheatsEnabled)
160  {
161  AchievementManager.CheatsEnabled = true;
162 #if CLIENT
163  new GUIMessageBox("Cheats enabled", "Cheat commands have been enabled on the server. You will not receive achievements until you restart the game.");
164 #else
165  DebugConsole.NewMessage("Cheat commands have been enabled.", Color.Red);
166 #endif
167  }
168  }
169 
170  foreach (var subElement in element.Elements())
171  {
172  switch (subElement.Name.ToString().ToLowerInvariant())
173  {
174  case CampaignSettings.LowerCaseSaveElementName:
175  Settings = new CampaignSettings(subElement);
176 #if CLIENT
177  GameMain.NetworkMember.ServerSettings.CampaignSettings = Settings;
178 #endif
179  break;
180  case "map":
181  if (map == null)
182  {
183  //map not created yet, loading this campaign for the first time
184  map = Map.Load(this, subElement);
185  }
186  else
187  {
188  //map already created, update it
189  //if we're not downloading the initial save file (LastSaveID > 0),
190  //show notifications about location type changes
191  map.LoadState(this, subElement, LastSaveID > 0);
192  }
193  break;
194  case "metadata":
195  var prevReputations = Factions.ToDictionary(k => k, v => v.Reputation.Value);
196  CampaignMetadata.Load(subElement);
197  foreach (var faction in Factions)
198  {
199  if (!MathUtils.NearlyEqual(prevReputations[faction], faction.Reputation.Value))
200  {
201  faction.Reputation.OnReputationValueChanged?.Invoke(faction.Reputation);
202  Reputation.OnAnyReputationValueChanged.Invoke(faction.Reputation);
203  }
204  }
205  break;
206  case "upgrademanager":
207  case "pendingupgrades":
208  UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: false);
209  break;
210  case "bots" when GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer:
211  CrewManager.HasBots = subElement.GetAttributeBool("hasbots", false);
212  CrewManager.AddCharacterElements(subElement);
213  ActiveOrdersElement = subElement.GetChildElement("activeorders");
214  break;
215  case "cargo":
216  CargoManager?.LoadPurchasedItems(subElement);
217  break;
218  case "pets":
219  petsElement = subElement;
220  break;
221  case "stats":
222  LoadStats(subElement);
223  break;
224  case "eventmanager":
225  GameMain.GameSession.EventManager.Load(subElement);
226  break;
227  case Wallet.LowerCaseSaveElementName:
228  Bank = new Wallet(Option<Character>.None(), subElement);
229  break;
230 #if SERVER
231  case "traitormanager":
232  GameMain.Server?.TraitorManager?.Load(subElement);
233  break;
234  case "savedexperiencepoints":
235  foreach (XElement savedExp in subElement.Elements())
236  {
237  savedExperiencePoints.Add(new SavedExperiencePoints(savedExp));
238  }
239  break;
240 #endif
241  }
242  }
243 
244  int oldMoney = element.GetAttributeInt("money", 0);
245  if (oldMoney > 0)
246  {
247  Bank = new Wallet(Option<Character>.None())
248  {
249  Balance = oldMoney
250  };
251  }
252 
253  UpgradeManager ??= new UpgradeManager(this);
254 
255 #if SERVER
256  characterData.Clear();
257  string characterDataPath = GetCharacterDataSavePath();
258  if (!File.Exists(characterDataPath))
259  {
260  DebugConsole.ThrowError($"Failed to load the character data for the campaign. Could not find the file \"{characterDataPath}\".");
261  }
262  else
263  {
264  var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
265  if (characterDataDoc?.Root == null) { return; }
266  foreach (var subElement in characterDataDoc.Root.Elements())
267  {
268  characterData.Add(new CharacterCampaignData(subElement));
269  }
270  }
271 #endif
272  }
273 
274  public static List<SubmarineInfo> GetCampaignSubs()
275  {
276  bool isSubmarineVisible(SubmarineInfo s)
277  => !GameMain.NetworkMember.ServerSettings.HiddenSubs.Any(h
278  => s.Name.Equals(h, StringComparison.OrdinalIgnoreCase));
279 
280  var availableSubs = SubmarineInfo.SavedSubmarines;
281 #if CLIENT
282  if (GameMain.Client != null)
283  {
284  availableSubs = GameMain.Client.ServerSubmarines;
285  }
286 #endif
287 
288  List<SubmarineInfo> campaignSubs =
289  availableSubs
290  .Where(s =>
292  && isSubmarineVisible(s))
293  .ToList();
294 
295  if (!campaignSubs.Any())
296  {
297  //None of the available subs were marked as campaign-compatible, just include all visible subs
298  campaignSubs.AddRange(availableSubs.Where(isSubmarineVisible));
299  }
300 
301  if (!campaignSubs.Any())
302  {
303  //No subs are visible at all! Just make the selected one available
304  campaignSubs.Add(GameMain.NetLobbyScreen.SelectedSub);
305  }
306 
307  return campaignSubs;
308  }
309 
310  private static void WriteItems(IWriteMessage msg, Dictionary<Identifier, List<PurchasedItem>> purchasedItems)
311  {
312  msg.WriteByte((byte)purchasedItems.Count);
313  foreach (var storeItems in purchasedItems)
314  {
315  msg.WriteIdentifier(storeItems.Key);
316  msg.WriteUInt16((UInt16)storeItems.Value.Count);
317  foreach (var item in storeItems.Value)
318  {
319  msg.WriteIdentifier(item.ItemPrefabIdentifier);
320  msg.WriteBoolean(item.DeliverImmediately);
321  msg.WriteRangedInteger(item.Quantity, 0, CargoManager.MaxQuantity);
322  }
323  }
324  }
325 
326  private static Dictionary<Identifier, List<PurchasedItem>> ReadPurchasedItems(IReadMessage msg, Client sender)
327  {
328  var items = new Dictionary<Identifier, List<PurchasedItem>>();
329  byte storeCount = msg.ReadByte();
330  for (int i = 0; i < storeCount; i++)
331  {
332  Identifier storeId = msg.ReadIdentifier();
333  items.Add(storeId, new List<PurchasedItem>());
334  UInt16 itemCount = msg.ReadUInt16();
335  for (int j = 0; j < itemCount; j++)
336  {
337  Identifier itemId = msg.ReadIdentifier();
338  bool deliverImmediately = msg.ReadBoolean();
339 #if SERVER
340  if (!AllowImmediateItemDelivery(sender)) { deliverImmediately = false; }
341 #endif
342  int quantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
343  items[storeId].Add(new PurchasedItem(itemId, quantity, sender) { DeliverImmediately = deliverImmediately });
344  }
345  }
346  return items;
347  }
348 
349  private static void WriteItems(IWriteMessage msg, Dictionary<Identifier, List<SoldItem>> soldItems)
350  {
351  msg.WriteByte((byte)soldItems.Count);
352  foreach (var storeItems in soldItems)
353  {
354  msg.WriteIdentifier(storeItems.Key);
355  msg.WriteUInt16((UInt16)storeItems.Value.Count);
356  foreach (var item in storeItems.Value)
357  {
358  msg.WriteIdentifier(item.ItemPrefab.Identifier);
359  msg.WriteUInt16((UInt16)item.ID);
360  msg.WriteBoolean(item.Removed);
361  msg.WriteByte(item.SellerID);
362  msg.WriteByte((byte)item.Origin);
363  }
364  }
365  }
366 
367  private static Dictionary<Identifier, List<SoldItem>> ReadSoldItems(IReadMessage msg)
368  {
369  var soldItems = new Dictionary<Identifier, List<SoldItem>>();
370  byte storeCount = msg.ReadByte();
371  for (int i = 0; i < storeCount; i++)
372  {
373  Identifier storeId = msg.ReadIdentifier();
374  soldItems.Add(storeId, new List<SoldItem>());
375  UInt16 itemCount = msg.ReadUInt16();
376  for (int j = 0; j < itemCount; j++)
377  {
378  Identifier prefabId = msg.ReadIdentifier();
379  UInt16 itemId = msg.ReadUInt16();
380  bool removed = msg.ReadBoolean();
381  byte sellerId = msg.ReadByte();
382  byte origin = msg.ReadByte();
383  soldItems[storeId].Add(new SoldItem(ItemPrefab.Prefabs[prefabId], itemId, removed, sellerId, (SoldItem.SellOrigin)origin));
384  }
385  }
386  return soldItems;
387  }
388  }
389 }
Stores information about the Character that is needed between rounds in the menu etc....
static GameSession?? GameSession
Definition: GameMain.cs:88
static NetLobbyScreen NetLobbyScreen
Definition: GameMain.cs:55
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static GameClient Client
Definition: GameMain.cs:188
void LoadState(CampaignMode campaign, XElement element, bool showNotifications)
Load the state of an existing map from xml (current state of locations, where the crew is now,...
static Map Load(CampaignMode campaign, XElement element)
Load a previously saved map from an xml element
static MultiPlayerCampaign StartNew(string mapSeed, CampaignSettings settings)
readonly List< SubmarineInfo > ServerSubmarines
Definition: GameClient.cs:94
static IEnumerable< SubmarineInfo > SavedSubmarines
This class handles all upgrade logic. Storing, applying, checking and validation of upgrades.
int ReadRangedInteger(int min, int max)
void WriteRangedInteger(int val, int min, int max)
void WriteIdentifier(Identifier val)