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;
130 public ImmutableHashSet<Identifier>
OutpostTags {
get;
set; } = ImmutableHashSet<Identifier>.Empty;
162 if (hashTask ==
null)
177 get {
return hashTask !=
null && !hashTask.IsCompleted; }
206 return "Barotrauma.SubmarineInfo (" +
Name +
")";
215 private bool? requiredContentPackagesInstalled;
220 if (requiredContentPackagesInstalled.HasValue) {
return requiredContentPackagesInstalled.Value; }
221 return RequiredContentPackages.All(reqName => ContentPackageManager.EnabledPackages.All.Any(contentPackage => contentPackage.NameMatches(reqName)));
225 requiredContentPackagesInstalled = value;
229 private bool? subsLeftBehind;
234 if (subsLeftBehind.HasValue) {
return subsLeftBehind.Value; }
236 return subsLeftBehind.Value;
250 public readonly Dictionary<Identifier, List<Character>>
OutpostNPCs =
new Dictionary<Identifier, List<Character>>();
261 DisplayName = TextManager.Get(
"UnspecifiedSubFileName");
267 public SubmarineInfo(
string filePath,
string hash =
"", XElement element =
null,
bool tryLoad =
true)
270 if (!
string.IsNullOrEmpty(filePath) && File.Exists(filePath))
276 DisplayName = Path.GetFileNameWithoutExtension(filePath);
281 DebugConsole.ThrowError(
"Error loading submarine " + filePath +
"!", e);
284 if (!
string.IsNullOrWhiteSpace(hash))
293 if (element ==
null && tryLoad)
337 RecommendedCrewSizeMax = original.RecommendedCrewSizeMax;
361 PreviewImage = original.PreviewImage !=
null ?
new Sprite(original.PreviewImage) :
null;
367 XDocument doc =
null;
368 int maxLoadRetries = 4;
369 for (
int i = 0; i <= maxLoadRetries; i++)
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...");
377 if (doc?.Root ==
null)
412 RecommendedCrewSizeMax =
SubmarineElement.GetAttributeInt(
"recommendedcrewsizemax", 0);
415 foreach (Identifier hiddenLayer
in SubmarineElement.GetAttributeIdentifierArray(
"layerhiddenbydefault", Array.Empty<Identifier>()))
421 if (recommendedCrewExperience ==
"Beginner")
425 else if (recommendedCrewExperience ==
"Intermediate")
429 else if (recommendedCrewExperience ==
"Experienced")
469 string classStr =
SubmarineElement.GetAttributeString(
"class",
"Undefined");
470 if (classStr ==
"DeepDiver")
475 else if (Enum.TryParse(classStr, out
SubmarineClass submarineClass))
487 string[] contentPackageNames =
SubmarineElement.GetAttributeStringArray(
"requiredcontentpackages", Array.Empty<
string>());
488 foreach (
string contentPackageName
in contentPackageNames)
493 InitProjectSpecific();
496 partial
void InitProjectSpecific();
501 PreviewImage?.Remove();
504 if (savedSubmarines.Contains(
this)) { savedSubmarines.Remove(
this); }
509 if (
FilePath ==
null) {
return false; }
514 string pathToCompare =
FilePath.CleanUpPath();
515 if (vanillaSubs.Any(sub => sub.Path == pathToCompare))
525 if (hash !=
null) {
return; }
526 if (hashTask !=
null) {
return; }
528 hashTask =
new Task(() =>
537 return Tags.HasFlag(tag);
542 if (
Tags.HasFlag(tag))
return;
549 if (!
Tags.HasFlag(tag))
return;
558 subsLeftBehind =
false;
562 foreach (var subElement
in element.Elements())
564 if (!subElement.Name.ToString().Equals(
"linkedsubmarine", StringComparison.OrdinalIgnoreCase)) {
continue; }
565 if (subElement.Attribute(
"location") ==
null) {
continue; }
567 subsLeftBehind =
true;
568 ushort targetDockingPortID = (ushort)subElement.GetAttributeInt(
"originallinkedto", 0);
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)
590 bool structureCrushDepthsDefined =
false;
591 realWorldCrushDepth =
float.PositiveInfinity;
592 foreach (var structureElement
in SubmarineElement.GetChildElements(
"structure"))
594 string name = structureElement.Attribute(
"name")?.Value ??
"";
595 Identifier identifier = structureElement.GetAttributeIdentifier(
"identifier",
"");
597 if (structurePrefab ==
null || !structurePrefab.Body) {
continue; }
598 if (!structureCrushDepthsDefined && structureElement.Attribute(
"crushdepth") !=
null)
600 structureCrushDepthsDefined =
true;
602 float structureCrushDepth = structureElement.GetAttributeFloat(
"crushdepth",
float.PositiveInfinity);
603 realWorldCrushDepth = Math.Min(structureCrushDepth, realWorldCrushDepth);
605 if (!structureCrushDepthsDefined)
609 return structureCrushDepthsDefined;
621 public void SaveAs(
string filePath, System.IO.MemoryStream previewImage =
null)
623 var newElement =
new XElement(
627 !
string.Equals(a.Name.LocalName,
"previewimage", StringComparison.InvariantCultureIgnoreCase) &&
628 !
string.Equals(a.Name.LocalName,
"name", StringComparison.InvariantCultureIgnoreCase)),
651 XDocument doc =
new XDocument(newElement);
653 doc.Root.Add(
new XAttribute(
"name",
Name));
656 doc.Root.Add(
new XAttribute(
"previewimage", Convert.ToBase64String(previewImage.ToArray())));
659 SaveUtil.CompressStringToFile(filePath, doc.ToString());
664 savedSubmarines.Add(subInfo);
669 string fullPath = Path.GetFullPath(filePath);
670 for (
int i = savedSubmarines.Count - 1; i >= 0; i--)
672 if (Path.GetFullPath(savedSubmarines[i].FilePath) == fullPath)
674 savedSubmarines[i].Dispose();
682 if (File.Exists(filePath))
685 if (!subInfo.IsFileCorrupted)
687 savedSubmarines.Add(subInfo);
689 savedSubmarines = savedSubmarines.OrderBy(s => s.FilePath ??
"").ToList();
695 var contentPackageSubs = ContentPackageManager.EnabledPackages.All.SelectMany(c => c.GetFiles<
BaseSubFile>());
697 for (
int i = savedSubmarines.Count - 1; i >= 0; i--)
699 if (File.Exists(savedSubmarines[i].FilePath))
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; }
706 savedSubmarines[i].Dispose();
709 List<string> filePaths =
new List<string>();
710 foreach (
BaseSubFile subFile
in contentPackageSubs)
712 if (!File.Exists(subFile.
Path.
Value)) {
continue; }
713 if (!filePaths.Any(fp => fp == subFile.
Path))
719 filePaths.RemoveAll(p => savedSubmarines.Any(sub => sub.FilePath == p));
721 foreach (
string path
in filePaths)
724 if (!subInfo.IsFileCorrupted)
726 savedSubmarines.Add(subInfo);
736 public static XDocument
OpenFile(
string file, out Exception exception)
738 XDocument doc =
null;
739 string extension =
"";
744 extension = System.IO.Path.GetExtension(file);
752 if (
string.IsNullOrWhiteSpace(extension))
758 if (extension ==
".sub")
760 System.IO.Stream stream;
763 stream = SaveUtil.DecompressFileToStream(file);
765 catch (System.IO.FileNotFoundException e)
768 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed! (File not found) " + Environment.StackTrace.CleanupStackTrace(), e);
774 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed!", e);
781 using (var reader = XMLExtensions.CreateReader(stream))
783 doc = XDocument.Load(reader);
792 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed! (" + e.Message +
")");
796 else if (extension ==
".xml")
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);
808 DebugConsole.ThrowError(
"Loading submarine \"" + file +
"\" failed! (" + e.Message +
")");
814 DebugConsole.ThrowError(
"Couldn't load submarine \"" + file +
"! (Unrecognized file extension)");
821 public int GetPrice(
Location location =
null, ImmutableHashSet<Character> characterList =
null)
823 if (location is
null)
827 location = currentLocation;
843 price *= campaign.Settings.ShipyardPriceMultiplier;
846 if (characterList.Any())
850 price *= 1f - characterList.Max(
static c => c.GetStatValue(
StatTypes.ShipyardBuyMultiplierAffiliated));
852 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)
override 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 ...
static ContentPackage VanillaContent
static readonly Version Version
static GameSession GameSession
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)
IEnumerable< Identifier > ModuleFlags
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
bool ShouldBeRuin
Ruin modules are of type SubmarineType.OutpostModule, until the ruin generator (or the test game mode...
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)
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 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.