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