Server LuaCsForBarotrauma
SubmarineInfo.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.ComponentModel;
6 #if DEBUG
7 using System.IO;
8 #else
9 using Barotrauma.IO;
10 #endif
11 using System.Linq;
12 using System.Threading;
13 using System.Threading.Tasks;
14 using System.Xml.Linq;
15 
16 namespace Barotrauma
17 {
18  [Flags]
19  public enum SubmarineTag
20  {
21  [Description("Shuttle")]
22  Shuttle = 1,
23  [Description("Hide in menus")]
24  HideInMenus = 2
25  }
26 
29 
30  partial class SubmarineInfo : IDisposable
31  {
32  private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
33  public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
34 
35  private Task hashTask;
36  private Md5Hash hash;
37 
38  public readonly DateTime LastModifiedTime;
39 
40  public SubmarineTag Tags { get; private set; }
41 
42  public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
43 
44  public enum CrewExperienceLevel
45  {
46  Unknown,
47  CrewExperienceLow,
48  CrewExperienceMid,
49  CrewExperienceHigh
50  }
52 
53  public int Tier
54  {
55  get;
56  set;
57  }
58 
62  public int EqualityCheckVal { get; private set; }
63 
64  public HashSet<string> RequiredContentPackages = new HashSet<string>();
65 
66  public string Name
67  {
68  get;
69  set;
70  }
71 
73  {
74  get;
75  set;
76  }
77 
79  {
80  get;
81  set;
82  }
83 
84  public int Price
85  {
86  get;
87  set;
88  }
89 
91  {
92  get;
93  set;
94  }
95 
96  public bool NoItems
97  {
98  get;
99  set;
100  }
101 
105  public bool LowFuel
106  {
107  get;
108  set;
109  }
110 
111  public Version GameVersion
112  {
113  get;
114  set;
115  }
116 
117  public SubmarineType Type { get; set; }
118 
119  public bool IsManuallyOutfitted { get; set; }
120 
122 
125  public WreckInfo WreckInfo { get; set; }
127 
129 
130  public ImmutableHashSet<Identifier> OutpostTags { get; set; } = ImmutableHashSet<Identifier>.Empty;
131 
132  public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
133 
134  public bool IsWreck => Type == SubmarineType.Wreck;
135  public bool IsBeacon => Type == SubmarineType.BeaconStation;
136  public bool IsEnemySubmarine => Type == SubmarineType.EnemySubmarine;
137  public bool IsPlayer => Type == SubmarineType.Player;
138  public bool IsRuin => Type == SubmarineType.Ruin;
139 
144  public bool ShouldBeRuin => Type is SubmarineType.Ruin or SubmarineType.OutpostModule &&
145  (OutpostModuleInfo.ModuleFlags.Contains("ruin".ToIdentifier()) ||
146  OutpostModuleInfo.ModuleFlags.Contains("ruinentrance".ToIdentifier()) ||
147  OutpostModuleInfo.ModuleFlags.Contains("ruinvault".ToIdentifier()) ||
148  OutpostModuleInfo.ModuleFlags.Contains("ruinworkshop".ToIdentifier()) ||
149  OutpostModuleInfo.ModuleFlags.Contains("ruinshrine".ToIdentifier()));
150 
151  public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined;
152  public bool IsCampaignCompatibleIgnoreClass => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus);
153 
154  public bool AllowPreviewImage => Type == SubmarineType.Player;
155 
157  {
158  get
159  {
160  if (hash == null)
161  {
162  if (hashTask == null)
163  {
164  XDocument doc = OpenFile(FilePath);
165  StartHashDocTask(doc);
166  }
167  hashTask.Wait();
168  hashTask = null;
169  }
170 
171  return hash;
172  }
173  }
174 
175  public bool CalculatingHash
176  {
177  get { return hashTask != null && !hashTask.IsCompleted; }
178  }
179 
180  public Vector2 Dimensions
181  {
182  get;
183  private set;
184  }
185 
186  public int CargoCapacity
187  {
188  get;
189  private set;
190  }
191 
192  public string FilePath
193  {
194  get;
195  set;
196  }
197 
198  public XElement SubmarineElement
199  {
200  get;
201  private set;
202  }
203 
204  public override string ToString()
205  {
206  return "Barotrauma.SubmarineInfo (" + Name + ")";
207  }
208 
209  public bool IsFileCorrupted
210  {
211  get;
212  private set;
213  }
214 
215  private bool? requiredContentPackagesInstalled;
217  {
218  get
219  {
220  if (requiredContentPackagesInstalled.HasValue) { return requiredContentPackagesInstalled.Value; }
221  return RequiredContentPackages.All(reqName => ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.NameMatches(reqName)));
222  }
223  set
224  {
225  requiredContentPackagesInstalled = value;
226  }
227  }
228 
229  private bool? subsLeftBehind;
230  public bool SubsLeftBehind
231  {
232  get
233  {
234  if (subsLeftBehind.HasValue) { return subsLeftBehind.Value; }
236  return subsLeftBehind.Value;
237  }
238  }
239 
240  public readonly List<ushort> LeftBehindDockingPortIDs = new List<ushort>();
241  public readonly List<ushort> BlockedDockingPortIDs = new List<ushort>();
242 
244  {
245  get; private set;
246  }
247 
249 
250  public readonly Dictionary<Identifier, List<Character>> OutpostNPCs = new Dictionary<Identifier, List<Character>>();
251 
255  public HashSet<Identifier> LayersHiddenByDefault { get; private set; } = new HashSet<Identifier>();
256 
257  //constructors & generation ----------------------------------------------------
258  public SubmarineInfo()
259  {
260  FilePath = null;
261  DisplayName = TextManager.Get("UnspecifiedSubFileName");
263  IsFileCorrupted = false;
264  RequiredContentPackages = new HashSet<string>();
265  }
266 
267  public SubmarineInfo(string filePath, string hash = "", XElement element = null, bool tryLoad = true)
268  {
269  FilePath = filePath;
270  if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
271  {
272  LastModifiedTime = File.GetLastWriteTime(filePath);
273  }
274  try
275  {
276  DisplayName = Path.GetFileNameWithoutExtension(filePath);
278  }
279  catch (Exception e)
280  {
281  DebugConsole.ThrowError("Error loading submarine " + filePath + "!", e);
282  }
283 
284  if (!string.IsNullOrWhiteSpace(hash))
285  {
286  this.hash = Md5Hash.StringAsHash(hash);
287  }
288 
289  IsFileCorrupted = false;
290 
291  RequiredContentPackages = new HashSet<string>();
292 
293  if (element == null && tryLoad)
294  {
295  Reload();
296  }
297  else
298  {
299  SubmarineElement = element;
300  }
301 
302  Name = SubmarineElement.GetAttributeString("name", null) ?? Name;
303 
304  Init();
305  }
306 
307  public SubmarineInfo(Submarine sub) : this(sub.Info)
308  {
310  SubmarineElement = new XElement("Submarine");
312  Init();
313  }
314 
315  public SubmarineInfo(SubmarineInfo original)
316  {
317  Name = original.Name;
318  DisplayName = original.DisplayName;
319  Description = original.Description;
320  Price = original.Price;
322  NoItems = original.NoItems;
323  LowFuel = original.LowFuel;
324  GameVersion = original.GameVersion;
325  Type = original.Type;
326  SubmarineClass = original.SubmarineClass;
327  hash = !string.IsNullOrEmpty(original.FilePath) && File.Exists(original.FilePath) ? original.MD5Hash : null;
328  Dimensions = original.Dimensions;
329  CargoCapacity = original.CargoCapacity;
330  FilePath = original.FilePath;
331  RequiredContentPackages = new HashSet<string>(original.RequiredContentPackages);
332  IsFileCorrupted = original.IsFileCorrupted;
337  RecommendedCrewSizeMax = original.RecommendedCrewSizeMax;
338  Tier = original.Tier;
340  Tags = original.Tags;
343  OutpostTags = original.OutpostTags;
344  if (original.OutpostModuleInfo != null)
345  {
347  }
348  else if (original.BeaconStationInfo != null)
349  {
351  }
352  else if (original.EnemySubmarineInfo != null)
353  {
355  }
356  else if (original.WreckInfo != null)
357  {
358  WreckInfo = new WreckInfo(original.WreckInfo);
359  }
360 #if CLIENT
361  PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
362 #endif
363  }
364 
365  public void Reload()
366  {
367  XDocument doc = null;
368  int maxLoadRetries = 4;
369  for (int i = 0; i <= maxLoadRetries; i++)
370  {
371  doc = OpenFile(FilePath, out Exception e);
372  if (e != null && !(e is System.IO.IOException)) { break; }
373  if (doc != null || i == maxLoadRetries || !File.Exists(FilePath)) { break; }
374  DebugConsole.NewMessage("Opening submarine file \"" + FilePath + "\" failed, retrying in 250 ms...");
375  Thread.Sleep(250);
376  }
377  if (doc?.Root == null)
378  {
379  IsFileCorrupted = true;
380  return;
381  }
382  if (hash == null)
383  {
384  StartHashDocTask(doc);
385  }
386  SubmarineElement = doc.Root;
387  }
388 
389  private void Init()
390  {
391  DisplayName = TextManager.Get("Submarine.Name." + Name).Fallback(Name);
392 
393  Description = TextManager.Get("Submarine.Description." + Name).Fallback(SubmarineElement.GetAttributeString("description", ""));
394 
395  EqualityCheckVal = SubmarineElement.GetAttributeInt("checkval", 0);
396 
397  Price = SubmarineElement.GetAttributeInt("price", 1000);
398 
399  InitialSuppliesSpawned = SubmarineElement.GetAttributeBool("initialsuppliesspawned", false);
400  NoItems = SubmarineElement.GetAttributeBool("noitems", false);
401  LowFuel = SubmarineElement.GetAttributeBool("lowfuel", false);
402  IsManuallyOutfitted = SubmarineElement.GetAttributeBool("ismanuallyoutfitted", false);
403 
404  GameVersion = new Version(SubmarineElement.GetAttributeString("gameversion", "0.0.0.0"));
405  if (Enum.TryParse(SubmarineElement.GetAttributeString("tags", ""), out SubmarineTag tags))
406  {
407  Tags = tags;
408  }
409  Dimensions = SubmarineElement.GetAttributeVector2("dimensions", Vector2.Zero);
410  CargoCapacity = SubmarineElement.GetAttributeInt("cargocapacity", -1);
411  RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
412  RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
413  var recommendedCrewExperience = SubmarineElement.GetAttributeIdentifier("recommendedcrewexperience", CrewExperienceLevel.Unknown.ToIdentifier());
414 
415  foreach (Identifier hiddenLayer in SubmarineElement.GetAttributeIdentifierArray("layerhiddenbydefault", Array.Empty<Identifier>()))
416  {
417  LayersHiddenByDefault.Add(hiddenLayer);
418  }
419 
420  // Backwards compatibility
421  if (recommendedCrewExperience == "Beginner")
422  {
424  }
425  else if (recommendedCrewExperience == "Intermediate")
426  {
428  }
429  else if (recommendedCrewExperience == "Experienced")
430  {
431  RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceHigh;
432  }
433  else
434  {
435  Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience);
436  }
437  Tier = SubmarineElement.GetAttributeInt("tier", GetDefaultTier(Price));
438 
439  OutpostTags = SubmarineElement.GetAttributeIdentifierImmutableHashSet(nameof(OutpostTags), ImmutableHashSet<Identifier>.Empty);
440 
441  if (SubmarineElement?.Attribute("type") != null)
442  {
443  if (Enum.TryParse(SubmarineElement.GetAttributeString("type", ""), out SubmarineType type))
444  {
445  Type = type;
446  if (Type == SubmarineType.OutpostModule)
447  {
449  }
450  else if (Type == SubmarineType.BeaconStation)
451  {
453  }
454  else if (Type == SubmarineType.EnemySubmarine)
455  {
457  }
458  else if (Type == SubmarineType.Wreck)
459  {
460  WreckInfo = new WreckInfo(this, SubmarineElement);
461  }
462  }
463  }
464 
465  if (Type == SubmarineType.Player)
466  {
467  if (SubmarineElement?.Attribute("class") != null)
468  {
469  string classStr = SubmarineElement.GetAttributeString("class", "Undefined");
470  if (classStr == "DeepDiver")
471  {
472  //backwards compatibility
474  }
475  else if (Enum.TryParse(classStr, out SubmarineClass submarineClass))
476  {
477  SubmarineClass = submarineClass;
478  }
479  }
480  }
481  else
482  {
483  SubmarineClass = SubmarineClass.Undefined;
484  }
485 
486  RequiredContentPackages.Clear();
487  string[] contentPackageNames = SubmarineElement.GetAttributeStringArray("requiredcontentpackages", Array.Empty<string>());
488  foreach (string contentPackageName in contentPackageNames)
489  {
490  RequiredContentPackages.Add(contentPackageName);
491  }
492 
493  InitProjectSpecific();
494  }
495 
496  partial void InitProjectSpecific();
497 
498  public void Dispose()
499  {
500 #if CLIENT
501  PreviewImage?.Remove();
502  PreviewImage = null;
503 #endif
504  if (savedSubmarines.Contains(this)) { savedSubmarines.Remove(this); }
505  }
506 
507  public bool IsVanillaSubmarine()
508  {
509  if (FilePath == null) { return false; }
510  var vanilla = GameMain.VanillaContent;
511  if (vanilla != null)
512  {
513  var vanillaSubs = vanilla.GetFiles<BaseSubFile>();
514  string pathToCompare = FilePath.CleanUpPath();
515  if (vanillaSubs.Any(sub => sub.Path == pathToCompare))
516  {
517  return true;
518  }
519  }
520  return false;
521  }
522 
523  public void StartHashDocTask(XDocument doc)
524  {
525  if (hash != null) { return; }
526  if (hashTask != null) { return; }
527 
528  hashTask = new Task(() =>
529  {
530  hash = Md5Hash.CalculateForString(doc.ToString(), Md5Hash.StringHashOptions.IgnoreWhitespace);
531  });
532  hashTask.Start();
533  }
534 
535  public bool HasTag(SubmarineTag tag)
536  {
537  return Tags.HasFlag(tag);
538  }
539 
540  public void AddTag(SubmarineTag tag)
541  {
542  if (Tags.HasFlag(tag)) return;
543 
544  Tags |= tag;
545  }
546 
547  public void RemoveTag(SubmarineTag tag)
548  {
549  if (!Tags.HasFlag(tag)) return;
550 
551  Tags &= ~tag;
552  }
553 
554  public void CheckSubsLeftBehind(XElement element = null)
555  {
556  if (element == null) { element = SubmarineElement; }
557 
558  subsLeftBehind = false;
560  LeftBehindDockingPortIDs.Clear();
561  BlockedDockingPortIDs.Clear();
562  foreach (var subElement in element.Elements())
563  {
564  if (!subElement.Name.ToString().Equals("linkedsubmarine", StringComparison.OrdinalIgnoreCase)) { continue; }
565  if (subElement.Attribute("location") == null) { continue; }
566 
567  subsLeftBehind = true;
568  ushort targetDockingPortID = (ushort)subElement.GetAttributeInt("originallinkedto", 0);
569  LeftBehindDockingPortIDs.Add(targetDockingPortID);
570  XElement targetPortElement = targetDockingPortID == 0 ? null :
571  element.Elements().FirstOrDefault(e => e.GetAttributeInt("ID", 0) == targetDockingPortID);
572  if (targetPortElement != null && targetPortElement.GetAttributeIntArray("linked", Array.Empty<int>()).Length > 0)
573  {
574  BlockedDockingPortIDs.Add(targetDockingPortID);
576  }
577  }
578  }
579 
583  public bool IsCrushDepthDefinedInStructures(out float realWorldCrushDepth)
584  {
585  if (SubmarineElement == null)
586  {
587  realWorldCrushDepth = Level.DefaultRealWorldCrushDepth;
588  return false;
589  }
590  bool structureCrushDepthsDefined = false;
591  realWorldCrushDepth = float.PositiveInfinity;
592  foreach (var structureElement in SubmarineElement.GetChildElements("structure"))
593  {
594  string name = structureElement.Attribute("name")?.Value ?? "";
595  Identifier identifier = structureElement.GetAttributeIdentifier("identifier", "");
596  var structurePrefab = Structure.FindPrefab(name, identifier);
597  if (structurePrefab == null || !structurePrefab.Body) { continue; }
598  if (!structureCrushDepthsDefined && structureElement.Attribute("crushdepth") != null)
599  {
600  structureCrushDepthsDefined = true;
601  }
602  float structureCrushDepth = structureElement.GetAttributeFloat("crushdepth", float.PositiveInfinity);
603  realWorldCrushDepth = Math.Min(structureCrushDepth, realWorldCrushDepth);
604  }
605  if (!structureCrushDepthsDefined)
606  {
607  realWorldCrushDepth = Level.DefaultRealWorldCrushDepth;
608  }
609  return structureCrushDepthsDefined;
610  }
611  public void AddOutpostNPCIdentifierOrTag(Character npc, Identifier idOrTag)
612  {
613  if (!OutpostNPCs.ContainsKey(idOrTag))
614  {
615  OutpostNPCs.Add(idOrTag, new List<Character>());
616  }
617  OutpostNPCs[idOrTag].Add(npc);
618  }
619 
620  //saving/loading ----------------------------------------------------
621  public void SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
622  {
623  var newElement = new XElement(
624  SubmarineElement.Name,
625  SubmarineElement.Attributes()
626  .Where(a =>
627  !string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase) &&
628  !string.Equals(a.Name.LocalName, "name", StringComparison.InvariantCultureIgnoreCase)),
629  SubmarineElement.Elements());
630 
631  if (Type == SubmarineType.OutpostModule)
632  {
633  OutpostModuleInfo.Save(newElement);
634  OutpostModuleInfo = new OutpostModuleInfo(this, newElement);
635  }
636  else if (Type == SubmarineType.BeaconStation)
637  {
638  BeaconStationInfo.Save(newElement);
639  BeaconStationInfo = new BeaconStationInfo(this, newElement);
640  }
641  else if (Type == SubmarineType.EnemySubmarine)
642  {
643  EnemySubmarineInfo.Save(newElement);
644  EnemySubmarineInfo = new EnemySubmarineInfo(this, newElement);
645  }
646  else if (Type == SubmarineType.Wreck)
647  {
648  WreckInfo.Save(newElement);
649  WreckInfo = new WreckInfo(this, newElement);
650  }
651  XDocument doc = new XDocument(newElement);
652 
653  doc.Root.Add(new XAttribute("name", Name));
654  if (previewImage != null && AllowPreviewImage)
655  {
656  doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
657  }
658 
659  SaveUtil.CompressStringToFile(filePath, doc.ToString());
660  }
661 
662  public static void AddToSavedSubs(SubmarineInfo subInfo)
663  {
664  savedSubmarines.Add(subInfo);
665  }
666 
667  public static void RemoveSavedSub(string filePath)
668  {
669  string fullPath = Path.GetFullPath(filePath);
670  for (int i = savedSubmarines.Count - 1; i >= 0; i--)
671  {
672  if (Path.GetFullPath(savedSubmarines[i].FilePath) == fullPath)
673  {
674  savedSubmarines[i].Dispose();
675  }
676  }
677  }
678 
679  public static void RefreshSavedSub(string filePath)
680  {
681  RemoveSavedSub(filePath);
682  if (File.Exists(filePath))
683  {
684  var subInfo = new SubmarineInfo(filePath);
685  if (!subInfo.IsFileCorrupted)
686  {
687  savedSubmarines.Add(subInfo);
688  }
689  savedSubmarines = savedSubmarines.OrderBy(s => s.FilePath ?? "").ToList();
690  }
691  }
692 
693  public static void RefreshSavedSubs()
694  {
695  var contentPackageSubs = ContentPackageManager.EnabledPackages.All.SelectMany(c => c.GetFiles<BaseSubFile>());
696 
697  for (int i = savedSubmarines.Count - 1; i >= 0; i--)
698  {
699  if (File.Exists(savedSubmarines[i].FilePath))
700  {
701  bool isDownloadedSub = Path.GetFullPath(Path.GetDirectoryName(savedSubmarines[i].FilePath)) == Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
702  bool isInContentPackage = contentPackageSubs.Any(f => f.Path == savedSubmarines[i].FilePath);
703  if (isDownloadedSub) { continue; }
704  if (savedSubmarines[i].LastModifiedTime == File.GetLastWriteTime(savedSubmarines[i].FilePath) && isInContentPackage) { continue; }
705  }
706  savedSubmarines[i].Dispose();
707  }
708 
709  List<string> filePaths = new List<string>();
710  foreach (BaseSubFile subFile in contentPackageSubs)
711  {
712  if (!File.Exists(subFile.Path.Value)) { continue; }
713  if (!filePaths.Any(fp => fp == subFile.Path))
714  {
715  filePaths.Add(subFile.Path.Value);
716  }
717  }
718 
719  filePaths.RemoveAll(p => savedSubmarines.Any(sub => sub.FilePath == p));
720 
721  foreach (string path in filePaths)
722  {
723  var subInfo = new SubmarineInfo(path);
724  if (!subInfo.IsFileCorrupted)
725  {
726  savedSubmarines.Add(subInfo);
727  }
728  }
729  }
730 
731  public static XDocument OpenFile(string file)
732  {
733  return OpenFile(file, out _);
734  }
735 
736  public static XDocument OpenFile(string file, out Exception exception)
737  {
738  XDocument doc = null;
739  string extension = "";
740  exception = null;
741 
742  try
743  {
744  extension = System.IO.Path.GetExtension(file);
745  }
746  catch
747  {
748  //no file extension specified: try using the default one
749  file += ".sub";
750  }
751 
752  if (string.IsNullOrWhiteSpace(extension))
753  {
754  extension = ".sub";
755  file += ".sub";
756  }
757 
758  if (extension == ".sub")
759  {
760  System.IO.Stream stream;
761  try
762  {
763  stream = SaveUtil.DecompressFileToStream(file);
764  }
765  catch (System.IO.FileNotFoundException e)
766  {
767  exception = e;
768  DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (File not found) " + Environment.StackTrace.CleanupStackTrace(), e);
769  return null;
770  }
771  catch (Exception e)
772  {
773  exception = e;
774  DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed!", e);
775  return null;
776  }
777 
778  try
779  {
780  stream.Position = 0;
781  using (var reader = XMLExtensions.CreateReader(stream))
782  {
783  doc = XDocument.Load(reader);
784  }
785  stream.Close();
786  stream.Dispose();
787  }
788 
789  catch (Exception e)
790  {
791  exception = e;
792  DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")");
793  return null;
794  }
795  }
796  else if (extension == ".xml")
797  {
798  try
799  {
800  ToolBox.IsProperFilenameCase(file);
801  using var stream = File.Open(file, System.IO.FileMode.Open, System.IO.FileAccess.Read);
802  using var reader = XMLExtensions.CreateReader(stream);
803  doc = XDocument.Load(reader);
804  }
805  catch (Exception e)
806  {
807  exception = e;
808  DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")");
809  return null;
810  }
811  }
812  else
813  {
814  DebugConsole.ThrowError("Couldn't load submarine \"" + file + "! (Unrecognized file extension)");
815  return null;
816  }
817 
818  return doc;
819  }
820 
821  public int GetPrice(Location location = null, ImmutableHashSet<Character> characterList = null)
822  {
823  if (location is null)
824  {
825  if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation is { } currentLocation)
826  {
827  location = currentLocation;
828  }
829  else
830  {
831 
832  return Price;
833  }
834  }
835 
836  characterList ??= GameSession.GetSessionCrewCharacters(CharacterType.Both);
837 
838  float price = Price;
839 
840  // Adjust by campaign difficulty settings
841  if (GameMain.GameSession?.Campaign is CampaignMode campaign)
842  {
843  price *= campaign.Settings.ShipyardPriceMultiplier;
844  }
845 
846  if (characterList.Any())
847  {
848  if (location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
849  {
850  price *= 1f - characterList.Max(static c => c.GetStatValue(StatTypes.ShipyardBuyMultiplierAffiliated));
851  }
852  price *= 1f - characterList.Max(static c => c.GetStatValue(StatTypes.ShipyardBuyMultiplier));
853  }
854 
855  return (int)price;
856  }
857 
858  public static int GetDefaultTier(int price) => price > 20000 ? HighestTier : price > 10000 ? 2 : 1;
859 
860  public const int HighestTier = 3;
861  }
862 }
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
Definition: Attack.cs:102
readonly ContentPath Path
Definition: ContentFile.cs:137
IEnumerable< ContentFile > GetFiles(Type type)
string??????????? Value
Definition: ContentPath.cs:27
override void Save(XElement element)
virtual void Save(XElement element)
static FactionAffiliation GetPlayerAffiliationStatus(Faction faction)
Get what kind of affiliation this faction has towards the player depending on who they chose to side ...
Definition: Factions.cs:30
static ContentPackage VanillaContent
Definition: GameMain.cs:71
static readonly Version Version
Definition: GameMain.cs:21
static GameSession GameSession
Definition: GameMain.cs:45
static ImmutableHashSet< Character > GetSessionCrewCharacters(CharacterType type)
Returns a list of crew characters currently in the game with a given filter.
CampaignMode? Campaign
Definition: GameSession.cs:128
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
Location CurrentLocation
Definition: Map.cs:47
static Md5Hash StringAsHash(string hash)
Definition: Md5Hash.cs:80
static Md5Hash CalculateForString(string str, StringHashOptions options)
Definition: Md5Hash.cs:128
IEnumerable< Identifier > ModuleFlags
void Save(XElement element)
static StructurePrefab FindPrefab(string name, Identifier identifier)
LocalizedString DisplayName
SubmarineInfo(SubmarineInfo original)
SubmarineInfo(string filePath, string hash="", XElement element=null, bool tryLoad=true)
SubmarineInfo(Submarine sub)
CrewExperienceLevel RecommendedCrewExperience
LocalizedString Description
int EqualityCheckVal
A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files mat...
readonly List< ushort > LeftBehindDockingPortIDs
bool ShouldBeRuin
Ruin modules are of type SubmarineType.OutpostModule, until the ruin generator (or the test game mode...
HashSet< Identifier > LayersHiddenByDefault
Names of layers that get automatically hidden when loading the sub
OutpostGenerationParams OutpostGenerationParams
static void RefreshSavedSubs()
override string ToString()
static int GetDefaultTier(int price)
ExtraSubmarineInfo GetExtraSubmarineInfo
HashSet< string > RequiredContentPackages
void AddTag(SubmarineTag tag)
void CheckSubsLeftBehind(XElement element=null)
static void RemoveSavedSub(string filePath)
void SaveAs(string filePath, System.IO.MemoryStream previewImage=null)
static void AddToSavedSubs(SubmarineInfo subInfo)
ImmutableHashSet< Identifier > OutpostTags
void RemoveTag(SubmarineTag tag)
int GetPrice(Location location=null, ImmutableHashSet< Character > characterList=null)
static XDocument OpenFile(string file)
OutpostModuleInfo OutpostModuleInfo
SubmarineClass SubmarineClass
static XDocument OpenFile(string file, out Exception exception)
readonly Dictionary< Identifier, List< Character > > OutpostNPCs
static void RefreshSavedSub(string filePath)
void StartHashDocTask(XDocument doc)
EnemySubmarineInfo EnemySubmarineInfo
void AddOutpostNPCIdentifierOrTag(Character npc, Identifier idOrTag)
bool IsCrushDepthDefinedInStructures(out float realWorldCrushDepth)
Calculated from SubmarineElement. Can be used when the sub hasn't been loaded and we can't access Sub...
static IEnumerable< SubmarineInfo > SavedSubmarines
bool HasTag(SubmarineTag tag)
readonly List< ushort > BlockedDockingPortIDs
bool LowFuel
Note: Refreshed for loaded submarines when they are saved, when they are loaded, and on round end....
BeaconStationInfo BeaconStationInfo
readonly DateTime LastModifiedTime
CharacterType
Definition: Enums.cs:711
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:195
FactionAffiliation
Definition: Factions.cs:9