1 using Microsoft.Xna.Framework;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.ComponentModel;
12 using System.Threading;
13 using System.Threading.Tasks;
14 using System.Xml.Linq;
21 [Description(
"Shuttle")]
23 [Description(
"Hide in menus")]
32 private static List<SubmarineInfo> savedSubmarines =
new List<SubmarineInfo>();
35 private Task hashTask;
147 if (hashTask ==
null)
162 get {
return hashTask !=
null && !hashTask.IsCompleted; }
191 return "Barotrauma.SubmarineInfo (" +
Name +
")";
200 private bool? requiredContentPackagesInstalled;
205 if (requiredContentPackagesInstalled.HasValue) {
return requiredContentPackagesInstalled.Value; }
206 return RequiredContentPackages.All(reqName => ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.NameMatches(reqName)));
210 requiredContentPackagesInstalled = value;
214 private bool? subsLeftBehind;
219 if (subsLeftBehind.HasValue) {
return subsLeftBehind.Value; }
221 return subsLeftBehind.Value;
235 public readonly Dictionary<Identifier, List<Character>>
OutpostNPCs =
new Dictionary<Identifier, List<Character>>();
246 DisplayName = TextManager.Get(
"UnspecifiedSubFileName");
252 public SubmarineInfo(
string filePath,
string hash =
"", XElement element =
null,
bool tryLoad =
true)
255 if (!
string.IsNullOrEmpty(filePath) && File.Exists(filePath))
261 DisplayName = Path.GetFileNameWithoutExtension(filePath);
266 DebugConsole.ThrowError(
"Error loading submarine " + filePath +
"!", e);
269 if (!
string.IsNullOrWhiteSpace(hash))
278 if (element ==
null && tryLoad)
322 RecommendedCrewSizeMax = original.RecommendedCrewSizeMax;
347 XDocument doc =
null;
348 int maxLoadRetries = 4;
349 for (
int i = 0; i <= maxLoadRetries; i++)
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...");
357 if (doc?.Root ==
null)
392 RecommendedCrewSizeMax =
SubmarineElement.GetAttributeInt(
"recommendedcrewsizemax", 0);
395 foreach (Identifier hiddenLayer
in SubmarineElement.GetAttributeIdentifierArray(
"layerhiddenbydefault", Array.Empty<Identifier>()))
401 if (recommendedCrewExperience ==
"Beginner")
405 else if (recommendedCrewExperience ==
"Intermediate")
409 else if (recommendedCrewExperience ==
"Experienced")
443 string classStr =
SubmarineElement.GetAttributeString(
"class",
"Undefined");
444 if (classStr ==
"DeepDiver")
449 else if (Enum.TryParse(classStr, out
SubmarineClass submarineClass))
461 string[] contentPackageNames =
SubmarineElement.GetAttributeStringArray(
"requiredcontentpackages", Array.Empty<
string>());
462 foreach (
string contentPackageName
in contentPackageNames)
467 InitProjectSpecific();
470 partial
void InitProjectSpecific();
478 if (savedSubmarines.Contains(
this)) { savedSubmarines.
Remove(
this); }
483 if (
FilePath ==
null) {
return false; }
488 string pathToCompare =
FilePath.CleanUpPath();
489 if (vanillaSubs.Any(sub => sub.Path == pathToCompare))
499 if (hash !=
null) {
return; }
500 if (hashTask !=
null) {
return; }
502 hashTask =
new Task(() =>
511 return Tags.HasFlag(tag);
516 if (
Tags.HasFlag(tag))
return;
523 if (!
Tags.HasFlag(tag))
return;
532 subsLeftBehind =
false;
536 foreach (var subElement
in element.Elements())
538 if (!subElement.Name.ToString().Equals(
"linkedsubmarine", StringComparison.OrdinalIgnoreCase)) {
continue; }
539 if (subElement.Attribute(
"location") ==
null) {
continue; }
541 subsLeftBehind =
true;
542 ushort targetDockingPortID = (ushort)subElement.GetAttributeInt(
"originallinkedto", 0);
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)
564 bool structureCrushDepthsDefined =
false;
565 realWorldCrushDepth =
float.PositiveInfinity;
566 foreach (var structureElement
in SubmarineElement.GetChildElements(
"structure"))
568 string name = structureElement.Attribute(
"name")?.Value ??
"";
569 Identifier identifier = structureElement.GetAttributeIdentifier(
"identifier",
"");
571 if (structurePrefab ==
null || !structurePrefab.Body) {
continue; }
572 if (!structureCrushDepthsDefined && structureElement.Attribute(
"crushdepth") !=
null)
574 structureCrushDepthsDefined =
true;
576 float structureCrushDepth = structureElement.GetAttributeFloat(
"crushdepth",
float.PositiveInfinity);
577 realWorldCrushDepth = Math.Min(structureCrushDepth, realWorldCrushDepth);
579 if (!structureCrushDepthsDefined)
583 return structureCrushDepthsDefined;
595 public void SaveAs(
string filePath, System.IO.MemoryStream previewImage =
null)
597 var newElement =
new XElement(
601 !
string.Equals(a.Name.LocalName,
"previewimage", StringComparison.InvariantCultureIgnoreCase) &&
602 !
string.Equals(a.Name.LocalName,
"name", StringComparison.InvariantCultureIgnoreCase)),
620 XDocument doc =
new XDocument(newElement);
622 doc.Root.Add(
new XAttribute(
"name",
Name));
625 doc.Root.Add(
new XAttribute(
"previewimage", Convert.ToBase64String(previewImage.ToArray())));
628 SaveUtil.CompressStringToFile(filePath, doc.ToString());
633 savedSubmarines.Add(subInfo);
638 string fullPath = Path.GetFullPath(filePath);
639 for (
int i = savedSubmarines.Count - 1; i >= 0; i--)
641 if (Path.GetFullPath(savedSubmarines[i].FilePath) == fullPath)
643 savedSubmarines[i].Dispose();
651 if (File.Exists(filePath))
654 if (!subInfo.IsFileCorrupted)
656 savedSubmarines.Add(subInfo);
658 savedSubmarines = savedSubmarines.OrderBy(s => s.FilePath ??
"").ToList();
664 var contentPackageSubs = ContentPackageManager.EnabledPackages.All.SelectMany(c => c.GetFiles<
BaseSubFile>());
666 for (
int i = savedSubmarines.Count - 1; i >= 0; i--)
668 if (File.Exists(savedSubmarines[i].FilePath))
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; }
675 savedSubmarines[i].Dispose();
678 List<string> filePaths =
new List<string>();
679 foreach (
BaseSubFile subFile
in contentPackageSubs)
681 if (!File.Exists(subFile.
Path.
Value)) {
continue; }
682 if (!filePaths.Any(fp => fp == subFile.
Path))
688 filePaths.RemoveAll(p => savedSubmarines.Any(sub => sub.FilePath == p));
690 foreach (
string path
in filePaths)
693 if (!subInfo.IsFileCorrupted)
695 savedSubmarines.Add(subInfo);
705 public static XDocument
OpenFile(
string file, out Exception exception)
707 XDocument doc =
null;
708 string extension =
"";
713 extension = System.IO.Path.GetExtension(file);
721 if (
string.IsNullOrWhiteSpace(extension))
727 if (extension ==
".sub")
729 System.IO.Stream stream;
732 stream = SaveUtil.DecompressFileToStream(file);
734 catch (System.IO.FileNotFoundException e)
737 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed! (File not found) " + Environment.StackTrace.CleanupStackTrace(), e);
743 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed!", e);
750 using (var reader = XMLExtensions.CreateReader(stream))
752 doc = XDocument.Load(reader);
761 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed! (" + e.Message +
")");
765 else if (extension ==
".xml")
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);
777 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed! (" + e.Message +
")");
783 DebugConsole.ThrowError(
"Couldn't load submarine \"" + file +
"! (Unrecognized file extension)");
790 public int GetPrice(
Location location =
null, ImmutableHashSet<Character> characterList =
null)
792 if (location is
null)
796 location = currentLocation;
812 price *= campaign.Settings.ShipyardPriceMultiplier;
815 if (characterList.Any())
819 price *= 1f - characterList.Max(
static c => c.GetStatValue(
StatTypes.ShipyardBuyMultiplierAffiliated));
821 price *= 1f - characterList.Max(
static c => c.GetStatValue(
StatTypes.ShipyardBuyMultiplier));
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
readonly ContentPath Path
IEnumerable< ContentFile > GetFiles(Type type)
static FactionAffiliation GetPlayerAffiliationStatus(Faction faction)
Get what kind of affiliation this faction has towards the player depending on who they chose to side ...
static GameSession?? GameSession
static ContentPackage VanillaContent
static readonly Version Version
static ImmutableHashSet< Character > GetSessionCrewCharacters(CharacterType type)
Returns a list of crew characters currently in the game with a given filter.
const float DefaultRealWorldCrushDepth
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
static Md5Hash StringAsHash(string hash)
static Md5Hash CalculateForString(string str, StringHashOptions options)
void Save(XElement element)
static StructurePrefab FindPrefab(string name, Identifier identifier)
void SaveToXElement(XElement element)
bool IsVanillaSubmarine()
LocalizedString DisplayName
SubmarineInfo(SubmarineInfo original)
SubmarineInfo(string filePath, string hash="", XElement element=null, bool tryLoad=true)
bool IsCampaignCompatibleIgnoreClass
XElement SubmarineElement
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
int RecommendedCrewSizeMin
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)
bool LeftBehindSubDockingPortOccupied
ExtraSubmarineInfo GetExtraSubmarineInfo
HashSet< string > RequiredContentPackages
void AddTag(SubmarineTag tag)
void CheckSubsLeftBehind(XElement element=null)
bool IsCampaignCompatible
static void RemoveSavedSub(string filePath)
void SaveAs(string filePath, System.IO.MemoryStream previewImage=null)
static void AddToSavedSubs(SubmarineInfo subInfo)
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)
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 RequiredContentPackagesInstalled
bool LowFuel
Note: Refreshed for loaded submarines when they are saved, when they are loaded, and on round end....
BeaconStationInfo BeaconStationInfo
bool InitialSuppliesSpawned
readonly DateTime LastModifiedTime
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.