Unstable 0.17.0.0
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class AfflictionsFile : ContentFile
|
||||
{
|
||||
private readonly static ImmutableHashSet<Type> afflictionTypes;
|
||||
static AfflictionsFile()
|
||||
{
|
||||
afflictionTypes = ReflectionUtils.GetDerivedNonAbstract<Affliction>()
|
||||
.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public AfflictionsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
private void ParseElement(ContentXElement element, bool overriding)
|
||||
{
|
||||
Identifier elementName = element.NameAsIdentifier();
|
||||
if (element.IsOverride())
|
||||
{
|
||||
element.Elements().ForEach(s => ParseElement(s, overriding: true));
|
||||
}
|
||||
else if (elementName == "Afflictions")
|
||||
{
|
||||
element.Elements().ForEach(s => ParseElement(s, overriding: overriding));
|
||||
}
|
||||
else if (elementName == "cprsettings")
|
||||
{
|
||||
var cprSettings = new CPRSettings(element, this);
|
||||
CPRSettings.Prefabs.Add(cprSettings, overriding);
|
||||
}
|
||||
else if (elementName == "damageoverlay")
|
||||
{
|
||||
#if CLIENT
|
||||
var damageOverlay = new CharacterHealth.DamageOverlayPrefab(element, this);
|
||||
CharacterHealth.DamageOverlayPrefab.Prefabs.Add(damageOverlay, overriding);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"No identifier defined for the affliction '{elementName}' in file '{Path}'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (AfflictionPrefab.Prefabs.ContainsKey(identifier))
|
||||
{
|
||||
if (overriding)
|
||||
{
|
||||
DebugConsole.NewMessage(
|
||||
$"Overriding an affliction or a buff with the identifier '{identifier}' using the file '{Path}'",
|
||||
Color.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Duplicate affliction: '{identifier}' defined in {elementName} of '{Path}'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var type = afflictionTypes.FirstOrDefault(t =>
|
||||
t.Name == elementName
|
||||
|| t.Name == $"Affliction{elementName}".ToIdentifier())
|
||||
?? typeof(Affliction);
|
||||
var prefab = CreatePrefab(element, type);
|
||||
AfflictionPrefab.Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc?.Root is null) { return; }
|
||||
ParseElement(doc.Root.FromPackage(ContentPackage), overriding: false);
|
||||
}
|
||||
|
||||
private AfflictionPrefab CreatePrefab(ContentXElement element, Type type)
|
||||
{
|
||||
if (type == typeof(AfflictionHusk)) { return new AfflictionPrefabHusk(element, this, type); }
|
||||
return new AfflictionPrefab(element, this, type);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
#if CLIENT
|
||||
CharacterHealth.DamageOverlayPrefab.Prefabs.RemoveByFile(this);
|
||||
#endif
|
||||
CPRSettings.Prefabs.RemoveByFile(this);
|
||||
AfflictionPrefab.Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
#if CLIENT
|
||||
CharacterHealth.DamageOverlayPrefab.Prefabs.Sort();
|
||||
#endif
|
||||
CPRSettings.Prefabs.Sort();
|
||||
AfflictionPrefab.Prefabs.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class BackgroundCreaturePrefabsFile : OtherFile
|
||||
{
|
||||
public BackgroundCreaturePrefabsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
//this content type only comes into play when a level is generated, so LoadFile and UnloadFile don't have anything to do
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage, AlternativeContentTypeNames("MapCreature")]
|
||||
sealed class BallastFloraFile : GenericPrefabFile<BallastFloraPrefab>
|
||||
{
|
||||
public BallastFloraFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "ballastflorabehavior";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "ballastflorabehaviors";
|
||||
protected override PrefabCollection<BallastFloraPrefab> prefabs => BallastFloraPrefab.Prefabs;
|
||||
|
||||
protected override BallastFloraPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new BallastFloraPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
public class BeaconStationFile : BaseSubFile
|
||||
{
|
||||
public BeaconStationFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class CaveGenerationParametersFile : GenericPrefabFile<CaveGenerationParams>
|
||||
{
|
||||
public CaveGenerationParametersFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "cave";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "cavegenerationparameters";
|
||||
protected override PrefabCollection<CaveGenerationParams> prefabs => CaveGenerationParams.CaveParams;
|
||||
protected override CaveGenerationParams CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new CaveGenerationParams(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class CharacterFile : ContentFile
|
||||
{
|
||||
public CharacterFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Loading character file failed: {Path}");
|
||||
return;
|
||||
}
|
||||
if (CharacterPrefab.Prefabs.AllPrefabs.Any(kvp => kvp.Value.Any(cf => cf?.ContentFile == this)))
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate path: {Path}");
|
||||
return;
|
||||
}
|
||||
var mainElement = doc.Root.FromPackage(ContentPackage);
|
||||
bool isOverride = mainElement.IsOverride();
|
||||
if (isOverride) { mainElement = mainElement.FirstElement(); }
|
||||
if (!CharacterPrefab.CheckSpeciesName(mainElement, this, out Identifier n)) { return; }
|
||||
var prefab = new CharacterPrefab(mainElement, this);
|
||||
CharacterPrefab.Prefabs.Add(prefab, isOverride);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
CharacterPrefab.Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
CharacterPrefab.Prefabs.SortAll();
|
||||
}
|
||||
|
||||
public override void Preload(Action<Sprite> addPreloadedSprite)
|
||||
{
|
||||
#if CLIENT
|
||||
CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(Path.Value);
|
||||
if (characterPrefab?.ConfigElement == null)
|
||||
{
|
||||
throw new Exception($"Failed to load the character config file from {Path}!");
|
||||
}
|
||||
var mainElement = characterPrefab.ConfigElement;
|
||||
mainElement.GetChildElements("sound").ForEach(e => RoundSound.Load(e));
|
||||
if (!CharacterPrefab.CheckSpeciesName(mainElement, this, out Identifier speciesName)) { return; }
|
||||
bool humanoid = mainElement.GetAttributeBool("humanoid", false);
|
||||
RagdollParams ragdollParams;
|
||||
try
|
||||
{
|
||||
if (humanoid)
|
||||
{
|
||||
ragdollParams = RagdollParams.GetRagdollParams<HumanRagdollParams>(speciesName);
|
||||
}
|
||||
else
|
||||
{
|
||||
ragdollParams = RagdollParams.GetRagdollParams<FishRagdollParams>(speciesName);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to preload a ragdoll file for the character \"{characterPrefab.Name}\"", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ragdollParams != null)
|
||||
{
|
||||
HashSet<string> texturePaths = new HashSet<string>
|
||||
{
|
||||
ragdollParams.Texture
|
||||
};
|
||||
foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(limb.normalSpriteParams?.Texture)) { texturePaths.Add(limb.normalSpriteParams.Texture); }
|
||||
if (!string.IsNullOrEmpty(limb.deformSpriteParams?.Texture)) { texturePaths.Add(limb.deformSpriteParams.Texture); }
|
||||
if (!string.IsNullOrEmpty(limb.damagedSpriteParams?.Texture)) { texturePaths.Add(limb.damagedSpriteParams.Texture); }
|
||||
foreach (var decorativeSprite in limb.decorativeSpriteParams)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(decorativeSprite.Texture)) { texturePaths.Add(decorativeSprite.Texture); }
|
||||
}
|
||||
}
|
||||
foreach (string texturePath in texturePaths)
|
||||
{
|
||||
addPreloadedSprite(new Sprite(texturePath, Vector2.Zero));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
|
||||
public class NotSyncedInMultiplayer : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// Base class for content file types, which are loaded
|
||||
/// from filelist.xml via reflection.
|
||||
/// PLEASE AVOID INHERITING FROM THIS CLASS DIRECTLY.
|
||||
/// Inheriting from GenericPrefabFile<T> is likely what
|
||||
/// you want.
|
||||
/// </summary>
|
||||
public abstract class ContentFile
|
||||
{
|
||||
public class TypeInfo
|
||||
{
|
||||
public readonly Type Type;
|
||||
public readonly bool RequiredByCorePackage;
|
||||
public readonly bool NotSyncedInMultiplayer;
|
||||
public readonly ImmutableHashSet<Type>? AlternativeTypes;
|
||||
public readonly ImmutableHashSet<Identifier> Names;
|
||||
|
||||
public TypeInfo(Type type)
|
||||
{
|
||||
Type = type;
|
||||
|
||||
var reqByCoreAttribute = type.GetCustomAttribute<RequiredByCorePackage>();
|
||||
RequiredByCorePackage = reqByCoreAttribute != null;
|
||||
var notSyncedInMultiplayerAttribute = type.GetCustomAttribute<NotSyncedInMultiplayer>();
|
||||
NotSyncedInMultiplayer = notSyncedInMultiplayerAttribute != null;
|
||||
AlternativeTypes = reqByCoreAttribute?.AlternativeTypes;
|
||||
|
||||
HashSet<Identifier> names = new HashSet<Identifier> { type.Name.RemoveFromEnd("File").ToIdentifier() };
|
||||
if (type.GetCustomAttribute<AlternativeContentTypeNames>()?.Names is { } altNames)
|
||||
{
|
||||
names.UnionWith(altNames);
|
||||
}
|
||||
|
||||
Names = names.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public ContentFile? CreateInstance(ContentPackage contentPackage, ContentPath path) =>
|
||||
(ContentFile?)Activator.CreateInstance(Type, contentPackage, path);
|
||||
}
|
||||
|
||||
public readonly static ImmutableHashSet<TypeInfo> Types;
|
||||
static ContentFile()
|
||||
{
|
||||
Types = ReflectionUtils.GetDerivedNonAbstract<ContentFile>()
|
||||
.Select(t => new TypeInfo(t))
|
||||
.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static Result<ContentFile, string> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||
{
|
||||
Result<ContentFile, string> fail(string error)
|
||||
=> Result<ContentFile, string>.Failure(error);
|
||||
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
var type = Types.FirstOrDefault(t => t.Names.Contains(elemName));
|
||||
var filePath = element.GetAttributeContentPath("file", contentPackage);
|
||||
if (type is null)
|
||||
{
|
||||
return fail($"Invalid content type \"{elemName}\"");
|
||||
}
|
||||
|
||||
if (filePath is null)
|
||||
{
|
||||
return fail($"No content path defined for file of type \"{elemName}\"");
|
||||
}
|
||||
try
|
||||
{
|
||||
var file = type.CreateInstance(contentPackage, filePath);
|
||||
return file is null
|
||||
? throw new Exception($"Content type is not implemented correctly")
|
||||
: Result<ContentFile, string>.Success(file);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": {e.Message}\n{e.StackTrace.CleanupStackTrace()}");
|
||||
}
|
||||
}
|
||||
|
||||
protected ContentFile(ContentPackage contentPackage, ContentPath path)
|
||||
{
|
||||
ContentPackage = contentPackage;
|
||||
Path = path;
|
||||
Hash = CalculateHash();
|
||||
}
|
||||
|
||||
public readonly ContentPackage ContentPackage;
|
||||
public readonly ContentPath Path;
|
||||
public readonly Md5Hash Hash;
|
||||
public abstract void LoadFile();
|
||||
public abstract void UnloadFile();
|
||||
public abstract void Sort();
|
||||
|
||||
public virtual void Preload(Action<Sprite> addPreloadedSprite) { }
|
||||
|
||||
public virtual Md5Hash CalculateHash()
|
||||
{
|
||||
return Md5Hash.CalculateForFile(Path.Value, Md5Hash.StringHashOptions.IgnoreWhitespace);
|
||||
}
|
||||
|
||||
public bool NotSyncedInMultiplayer => Types.Any(t => t.Type == GetType() && t.NotSyncedInMultiplayer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class CorpsesFile : GenericPrefabFile<CorpsePrefab>
|
||||
{
|
||||
public CorpsesFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "corpse";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "corpses";
|
||||
protected override PrefabCollection<CorpsePrefab> prefabs => CorpsePrefab.Prefabs;
|
||||
protected override CorpsePrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new CorpsePrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
public sealed class DecalsFile : ContentFile
|
||||
{
|
||||
public DecalsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
DecalManager.LoadFromFile(this);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
DecalManager.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
DecalManager.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
public class EnemySubmarineFile : BaseSubFile
|
||||
{
|
||||
public EnemySubmarineFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class EventManagerSettingsFile : GenericPrefabFile<EventManagerSettings>
|
||||
{
|
||||
public EventManagerSettingsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "EventManagerSettings";
|
||||
protected override PrefabCollection<EventManagerSettings> prefabs => EventManagerSettings.Prefabs;
|
||||
protected override EventManagerSettings CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new EventManagerSettings(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class FactionsFile : GenericPrefabFile<FactionPrefab>
|
||||
{
|
||||
public FactionsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "faction";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "factions";
|
||||
protected override PrefabCollection<FactionPrefab> prefabs => FactionPrefab.Prefabs;
|
||||
protected override FactionPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new FactionPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class GenericPrefabFile<T> : ContentFile where T : Prefab
|
||||
{
|
||||
protected GenericPrefabFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected abstract bool MatchesSingular(Identifier identifier);
|
||||
protected abstract bool MatchesPlural(Identifier identifier);
|
||||
protected abstract PrefabCollection<T> prefabs { get; }
|
||||
protected abstract T CreatePrefab(ContentXElement element);
|
||||
|
||||
private void LoadFromXElement(ContentXElement parentElement, bool overriding)
|
||||
{
|
||||
Identifier elemName = parentElement.NameAsIdentifier();
|
||||
var childElements = parentElement.Elements()
|
||||
#if DEBUG
|
||||
.OrderBy(e => Rand.Int(int.MaxValue, Rand.RandSync.Unsynced)).ToArray()
|
||||
#endif
|
||||
;
|
||||
if (parentElement.IsOverride())
|
||||
{
|
||||
foreach (var element in childElements)
|
||||
{
|
||||
LoadFromXElement(element, true);
|
||||
}
|
||||
}
|
||||
else if (elemName == "clear")
|
||||
{
|
||||
prefabs.AddOverrideFile(this);
|
||||
}
|
||||
else if (MatchesSingular(elemName))
|
||||
{
|
||||
T prefab = CreatePrefab(parentElement);
|
||||
prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (MatchesPlural(elemName))
|
||||
{
|
||||
foreach (var element in childElements)
|
||||
{
|
||||
LoadFromXElement(element, overriding);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
}
|
||||
}
|
||||
|
||||
public override sealed void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root.FromPackage(ContentPackage);
|
||||
LoadFromXElement(rootElement, false);
|
||||
}
|
||||
|
||||
public override sealed void UnloadFile()
|
||||
{
|
||||
prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public sealed override void Sort()
|
||||
{
|
||||
prefabs.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
[NotSyncedInMultiplayer]
|
||||
public abstract class HashlessFile : ContentFile
|
||||
{
|
||||
public HashlessFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public sealed override Md5Hash CalculateHash() => Md5Hash.Blank;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class ItemAssemblyFile : GenericPrefabFile<ItemAssemblyPrefab>
|
||||
{
|
||||
public ItemAssemblyFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "itemassembly";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "itemassemblies";
|
||||
protected override PrefabCollection<ItemAssemblyPrefab> prefabs => ItemAssemblyPrefab.Prefabs;
|
||||
protected override ItemAssemblyPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new ItemAssemblyPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class ItemFile : GenericPrefabFile<ItemPrefab>
|
||||
{
|
||||
public ItemFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "items";
|
||||
protected override PrefabCollection<ItemPrefab> prefabs => ItemPrefab.Prefabs;
|
||||
protected override ItemPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new ItemPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Barotrauma;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
[RequiredByCorePackage]
|
||||
sealed class JobsFile : ContentFile
|
||||
{
|
||||
public JobsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
LoadElements(doc.Root.FromPackage(ContentPackage), false);
|
||||
}
|
||||
|
||||
private void LoadElements(ContentXElement mainElement, bool isOverride)
|
||||
{
|
||||
foreach (var element in mainElement.Elements())
|
||||
{
|
||||
if (element.NameAsIdentifier() == "nojob")
|
||||
{
|
||||
JobPrefab.NoJobElement ??= element;
|
||||
}
|
||||
else if (element.NameAsIdentifier() == "ItemRepairPriorities")
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
ItemRepairPriority prio = new ItemRepairPriority(subElement, this);
|
||||
ItemRepairPriority.Prefabs.Add(prio, isOverride);
|
||||
}
|
||||
}
|
||||
else if (element.IsOverride())
|
||||
{
|
||||
LoadElements(element, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var job = new JobPrefab(element, this);
|
||||
JobPrefab.Prefabs.Add(job, isOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
JobPrefab.Prefabs.RemoveByFile(this);
|
||||
ItemRepairPriority.Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
JobPrefab.Prefabs.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class LevelGenerationParametersFile : ContentFile
|
||||
{
|
||||
public LevelGenerationParametersFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
private void LoadBiomes(ContentXElement element, bool isOverride)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
Biome biome = new Biome(subElement, this);
|
||||
Biome.Prefabs.Add(biome, isOverride);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadLevelGenerationParams(ContentXElement element, bool isOverride)
|
||||
{
|
||||
LevelGenerationParams lParams = new LevelGenerationParams(element, this);
|
||||
LevelGenerationParams.LevelParams.Add(lParams, isOverride);
|
||||
}
|
||||
|
||||
private void LoadSubElements(ContentXElement element, bool overridePropagation)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
LoadSubElements(subElement, true);
|
||||
}
|
||||
else if (subElement.NameAsIdentifier() == "clear")
|
||||
{
|
||||
LevelGenerationParams.LevelParams.AddOverrideFile(this);
|
||||
Biome.Prefabs.AddOverrideFile(this);
|
||||
}
|
||||
else if (subElement.NameAsIdentifier() == "biomes")
|
||||
{
|
||||
LoadBiomes(subElement, overridePropagation);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadLevelGenerationParams(subElement, overridePropagation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc is null) { return; }
|
||||
LoadSubElements(doc.Root.FromPackage(ContentPackage), false);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
LevelGenerationParams.LevelParams.RemoveByFile(this);
|
||||
Biome.Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
LevelGenerationParams.LevelParams.SortAll();
|
||||
Biome.Prefabs.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class LevelObjectPrefabsFile : GenericPrefabFile<LevelObjectPrefab>
|
||||
{
|
||||
public LevelObjectPrefabsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "levelobjects";
|
||||
protected override PrefabCollection<LevelObjectPrefab> prefabs => LevelObjectPrefab.Prefabs;
|
||||
protected override LevelObjectPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new LevelObjectPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class LocationTypesFile : GenericPrefabFile<LocationType>
|
||||
{
|
||||
public LocationTypesFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "locationtypes";
|
||||
protected override PrefabCollection<LocationType> prefabs => LocationType.Prefabs;
|
||||
protected override LocationType CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new LocationType(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class MapGenerationParametersFile : ContentFile
|
||||
{
|
||||
public MapGenerationParametersFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Loading map generation parameters file failed: {Path}");
|
||||
return;
|
||||
}
|
||||
var mainElement = doc.Root.FromPackage(ContentPackage);
|
||||
bool isOverride = mainElement.IsOverride();
|
||||
if (isOverride) { mainElement = mainElement.FirstElement(); }
|
||||
var prefab = new MapGenerationParams(mainElement, this);
|
||||
MapGenerationParams.Params.Add(prefab, isOverride);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
MapGenerationParams.Params.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
MapGenerationParams.Params.Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class MissionsFile : GenericPrefabFile<MissionPrefab>
|
||||
{
|
||||
/*private readonly static ImmutableHashSet<Type> missionTypes;
|
||||
static MissionsFile()
|
||||
{
|
||||
missionTypes = ReflectionUtils.GetDerivedNonAbstract<Mission>()
|
||||
.ToImmutableHashSet();
|
||||
}*/
|
||||
|
||||
public MissionsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier)
|
||||
=> !MatchesPlural(identifier);
|
||||
/*missionTypes.Any(t => identifier == t.Name)
|
||||
|| identifier == "OutpostDestroyMission" || identifier == "OutpostRescueMission";*/
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "missions";
|
||||
protected override PrefabCollection<MissionPrefab> prefabs => MissionPrefab.Prefabs;
|
||||
protected override MissionPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new MissionPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class NPCConversationsFile : ContentFile
|
||||
{
|
||||
public NPCConversationsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
var mainElement = doc.Root.FromPackage(ContentPackage);
|
||||
bool allowOverriding = doc.Root.IsOverride();
|
||||
if (allowOverriding)
|
||||
{
|
||||
mainElement = mainElement.FirstElement();
|
||||
}
|
||||
|
||||
var npcConversationCollection = new NPCConversationCollection(this, mainElement);
|
||||
if (!NPCConversationCollection.Collections.ContainsKey(npcConversationCollection.Language))
|
||||
{
|
||||
NPCConversationCollection.Collections.Add(npcConversationCollection.Language, new PrefabCollection<NPCConversationCollection>());
|
||||
}
|
||||
NPCConversationCollection.Collections[npcConversationCollection.Language].Add(npcConversationCollection, allowOverriding);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
foreach (var collection in NPCConversationCollection.Collections.Values)
|
||||
{
|
||||
collection.RemoveByFile(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
foreach (var collection in NPCConversationCollection.Collections.Values)
|
||||
{
|
||||
collection.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class NPCSetsFile : GenericPrefabFile<NPCSet>
|
||||
{
|
||||
public NPCSetsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "npcset";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "npcsets";
|
||||
protected override PrefabCollection<NPCSet> prefabs => NPCSet.Sets;
|
||||
protected override NPCSet CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new NPCSet(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
#warning TODO: this is almost a GenericPrefabFile. Must refactor further.
|
||||
[RequiredByCorePackage]
|
||||
sealed class OrdersFile : ContentFile
|
||||
{
|
||||
public OrdersFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public void LoadFromXElement(ContentXElement parentElement, bool overriding)
|
||||
{
|
||||
Identifier elemName = new Identifier(parentElement.Name.ToString());
|
||||
if (parentElement.IsOverride())
|
||||
{
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
LoadFromXElement(element, true);
|
||||
}
|
||||
}
|
||||
else if (elemName == "order")
|
||||
{
|
||||
OrderPrefab prefab = new OrderPrefab(parentElement, this);
|
||||
OrderPrefab.Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (elemName == "ordercategory")
|
||||
{
|
||||
OrderCategoryIcon prefab = new OrderCategoryIcon(parentElement, this);
|
||||
OrderCategoryIcon.OrderCategoryIcons.Add(prefab, overriding);
|
||||
}
|
||||
else if (elemName == "orders")
|
||||
{
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
LoadFromXElement(element, overriding);
|
||||
}
|
||||
}
|
||||
else if (elemName == "clear")
|
||||
{
|
||||
OrderCategoryIcon.OrderCategoryIcons.AddOverrideFile(this);
|
||||
OrderPrefab.Prefabs.AddOverrideFile(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
}
|
||||
}
|
||||
|
||||
public override sealed void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root.FromPackage(ContentPackage);
|
||||
LoadFromXElement(rootElement, false);
|
||||
}
|
||||
|
||||
public override sealed void UnloadFile()
|
||||
{
|
||||
OrderCategoryIcon.OrderCategoryIcons.RemoveByFile(this);
|
||||
OrderPrefab.Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override sealed void Sort()
|
||||
{
|
||||
OrderCategoryIcon.OrderCategoryIcons.SortAll();
|
||||
OrderPrefab.Prefabs.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Barotrauma;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
[AlternativeContentTypeNames("None")]
|
||||
public class OtherFile : HashlessFile
|
||||
{
|
||||
public OtherFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
//this content type is completely ignored by the game so LoadFile and UnloadFile don't do anything
|
||||
public sealed override void LoadFile() { }
|
||||
public sealed override void UnloadFile() { }
|
||||
public sealed override void Sort() { }
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage(alternativeTypes: typeof(OutpostFile))]
|
||||
sealed class OutpostConfigFile : GenericPrefabFile<OutpostGenerationParams>
|
||||
{
|
||||
public OutpostConfigFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "OutpostConfig";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "OutpostGenerationParameters";
|
||||
protected override PrefabCollection<OutpostGenerationParams> prefabs => OutpostGenerationParams.OutpostParams;
|
||||
protected override OutpostGenerationParams CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new OutpostGenerationParams(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class OutpostFile : BaseSubFile
|
||||
{
|
||||
public OutpostFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class OutpostModuleFile : BaseSubFile
|
||||
{
|
||||
public OutpostModuleFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Barotrauma.Particles;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
[NotSyncedInMultiplayer]
|
||||
#if CLIENT
|
||||
sealed class ParticlesFile : GenericPrefabFile<ParticlePrefab>
|
||||
{
|
||||
public ParticlesFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "prefabs" || identifier == "particles";
|
||||
protected override PrefabCollection<ParticlePrefab> prefabs => ParticlePrefab.Prefabs;
|
||||
protected override ParticlePrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new ParticlePrefab(element, this);
|
||||
}
|
||||
|
||||
public override Md5Hash CalculateHash() => Md5Hash.Blank;
|
||||
}
|
||||
#else
|
||||
sealed class ParticlesFile : OtherFile
|
||||
{
|
||||
public ParticlesFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } //this content type doesn't do anything on a server
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class RandomEventsFile : ContentFile
|
||||
{
|
||||
public RandomEventsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public void LoadFromXElement(ContentXElement parentElement, bool overriding)
|
||||
{
|
||||
Identifier elemName = new Identifier(parentElement.Name.ToString());
|
||||
if (parentElement.IsOverride())
|
||||
{
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
LoadFromXElement(element, true);
|
||||
}
|
||||
}
|
||||
else if (elemName == "randomevents")
|
||||
{
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
LoadFromXElement(element, overriding);
|
||||
}
|
||||
}
|
||||
else if (elemName == "eventprefabs")
|
||||
{
|
||||
foreach (var subElement in parentElement.Elements())
|
||||
{
|
||||
var prefab = new EventPrefab(subElement, this);
|
||||
EventPrefab.Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
}
|
||||
else if (elemName == "eventsprites")
|
||||
{
|
||||
#if CLIENT
|
||||
foreach (var subElement in parentElement.Elements())
|
||||
{
|
||||
var prefab = new EventSprite(subElement, this);
|
||||
EventSprite.Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (elemName == "eventset")
|
||||
{
|
||||
var prefab = new EventSet(parentElement, this);
|
||||
EventSet.Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (elemName == "clear")
|
||||
{
|
||||
EventPrefab.Prefabs.AddOverrideFile(this);
|
||||
EventSet.Prefabs.AddOverrideFile(this);
|
||||
#if CLIENT
|
||||
EventSprite.Prefabs.AddOverrideFile(this);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root.FromPackage(ContentPackage);
|
||||
LoadFromXElement(rootElement, false);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
EventPrefab.Prefabs.RemoveByFile(this);
|
||||
EventSet.Prefabs.RemoveByFile(this);
|
||||
#if CLIENT
|
||||
EventSprite.Prefabs.RemoveByFile(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
EventPrefab.Prefabs.SortAll();
|
||||
EventSet.Prefabs.SortAll();
|
||||
#if CLIENT
|
||||
EventSprite.Prefabs.SortAll();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using Barotrauma.RuinGeneration;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class RuinConfigFile : GenericPrefabFile<RuinGenerationParams>
|
||||
{
|
||||
public RuinConfigFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "RuinConfig";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "RuinGenerationParameters";
|
||||
protected override PrefabCollection<RuinGenerationParams> prefabs => RuinGenerationParams.RuinParams;
|
||||
protected override RuinGenerationParams CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new RuinGenerationParams(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SkillSettingsFile : ContentFile
|
||||
{
|
||||
public SkillSettingsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
var mainElement = doc.Root.FromPackage(ContentPackage);
|
||||
bool allowOverriding = mainElement.IsOverride();
|
||||
if (allowOverriding)
|
||||
{
|
||||
mainElement = mainElement.FirstElement();
|
||||
}
|
||||
var prefab = new SkillSettings(mainElement, this);
|
||||
SkillSettings.Prefabs.Add(prefab, allowOverriding);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
SkillSettings.Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
SkillSettings.Prefabs.Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
#if CLIENT
|
||||
sealed class SoundsFile : GenericPrefabFile<SoundPrefab>
|
||||
{
|
||||
public SoundsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override PrefabCollection<SoundPrefab> prefabs => SoundPrefab.Prefabs;
|
||||
|
||||
protected override SoundPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
var elemName = element.NameAsIdentifier();
|
||||
if (SoundPrefab.TagToDerivedPrefab.ContainsKey(elemName))
|
||||
{
|
||||
return Activator.CreateInstance(SoundPrefab.TagToDerivedPrefab[elemName], new object[] { element, this }) as SoundPrefab;
|
||||
}
|
||||
return new SoundPrefab(element, this);
|
||||
}
|
||||
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "sounds";
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
|
||||
public override Md5Hash CalculateHash() => Md5Hash.Blank;
|
||||
}
|
||||
#else
|
||||
sealed class SoundsFile : OtherFile
|
||||
{
|
||||
public SoundsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class StructureFile : GenericPrefabFile<StructurePrefab>
|
||||
{
|
||||
public StructureFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "prefabs" || identifier == "structures";
|
||||
protected override PrefabCollection<StructurePrefab> prefabs => StructurePrefab.Prefabs;
|
||||
protected override StructurePrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new StructurePrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class BaseSubFile : ContentFile
|
||||
{
|
||||
protected BaseSubFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
#warning TODO: this doesn't account for collisions, this should probably be using the PrefabCollection class like everything else
|
||||
UintIdentifier = ToolBox.StringToUInt32Hash(Barotrauma.IO.Path.GetFileNameWithoutExtension(path.Value), md5);
|
||||
}
|
||||
|
||||
public readonly UInt32 UintIdentifier;
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
SubmarineInfo.RefreshSavedSub(Path.Value);
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
SubmarineInfo.RefreshSavedSub(Path.Value);
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
//Overrides for subs don't exist! Should we change this?
|
||||
}
|
||||
}
|
||||
|
||||
[NotSyncedInMultiplayer]
|
||||
public class SubmarineFile : BaseSubFile
|
||||
{
|
||||
public SubmarineFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class TalentTreesFile : GenericPrefabFile<TalentTree>
|
||||
{
|
||||
public TalentTreesFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "talenttree";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "talenttrees";
|
||||
protected override PrefabCollection<TalentTree> prefabs => TalentTree.JobTalentTrees;
|
||||
protected override TalentTree CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new TalentTree(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class TalentsFile : GenericPrefabFile<TalentPrefab>
|
||||
{
|
||||
public TalentsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "talent";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "talents";
|
||||
protected override PrefabCollection<TalentPrefab> prefabs => TalentPrefab.TalentPrefabs;
|
||||
protected override TalentPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new TalentPrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public sealed class TextFile : ContentFile
|
||||
{
|
||||
public TextFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
var mainElement = doc.Root.FromPackage(ContentPackage);
|
||||
|
||||
var languageName = mainElement.GetAttributeIdentifier("language", TextManager.DefaultLanguage.Value);
|
||||
|
||||
LanguageIdentifier language = languageName.ToLanguageIdentifier();
|
||||
if (!TextManager.TextPacks.ContainsKey(language))
|
||||
{
|
||||
TextManager.TextPacks.TryAdd(language, ImmutableHashSet<TextPack>.Empty);
|
||||
}
|
||||
|
||||
var newPack = new TextPack(this, mainElement, language);
|
||||
var newHashSet = TextManager.TextPacks[language].Add(newPack);
|
||||
TextManager.TextPacks.TryRemove(language, out _);
|
||||
TextManager.TextPacks.TryAdd(language, newHashSet);
|
||||
TextManager.IncrementLanguageVersion();
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
foreach (var kvp in TextManager.TextPacks.ToArray())
|
||||
{
|
||||
var newHashSet = kvp.Value.Where(p => p.ContentFile != this).ToImmutableHashSet();
|
||||
TextManager.TextPacks.TryRemove(kvp.Key, out _);
|
||||
if (newHashSet.Count != 0) { TextManager.TextPacks.TryAdd(kvp.Key, newHashSet); }
|
||||
}
|
||||
TextManager.IncrementLanguageVersion();
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
//Overrides for text packs don't exist! Should we change this?
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
#warning TODO: This file is just about the only thing that's actually somewhat okay about the current traitor system. Gut the whole thing.
|
||||
|
||||
#if CLIENT
|
||||
using PrefabType = Barotrauma.TraitorMissionPrefab;
|
||||
#elif SERVER
|
||||
using PrefabType = Barotrauma.TraitorMissionPrefab.TraitorMissionEntry;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class TraitorMissionsFile : GenericPrefabFile<PrefabType>
|
||||
{
|
||||
public TraitorMissionsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "TraitorMission";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "TraitorMissions";
|
||||
protected override PrefabCollection<PrefabType> prefabs => PrefabType.Prefabs;
|
||||
protected override PrefabType CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new PrefabType(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
#if CLIENT
|
||||
public sealed class UIStyleFile : HashlessFile
|
||||
{
|
||||
public UIStyleFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
public void LoadFromXElement(ContentXElement parentElement, bool overriding)
|
||||
{
|
||||
Identifier elemName = parentElement.NameAsIdentifier();
|
||||
Identifier elemNameWithFontSuffix = elemName.AppendIfMissing("Font");
|
||||
if (parentElement.IsOverride())
|
||||
{
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
LoadFromXElement(element, true);
|
||||
}
|
||||
}
|
||||
else if (GUIStyle.Fonts.ContainsKey(elemNameWithFontSuffix))
|
||||
{
|
||||
GUIFontPrefab prefab = new GUIFontPrefab(parentElement, this);
|
||||
GUIStyle.Fonts[elemNameWithFontSuffix].Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (GUIStyle.Sprites.ContainsKey(elemName))
|
||||
{
|
||||
GUISpritePrefab prefab = new GUISpritePrefab(parentElement, this);
|
||||
GUIStyle.Sprites[elemName].Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (GUIStyle.SpriteSheets.ContainsKey(elemName))
|
||||
{
|
||||
GUISpriteSheetPrefab prefab = new GUISpriteSheetPrefab(parentElement, this);
|
||||
GUIStyle.SpriteSheets[elemName].Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (GUIStyle.Colors.ContainsKey(elemName))
|
||||
{
|
||||
GUIColorPrefab prefab = new GUIColorPrefab(parentElement, this);
|
||||
GUIStyle.Colors[elemName].Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (elemName == "cursor")
|
||||
{
|
||||
GUICursorPrefab prefab = new GUICursorPrefab(parentElement, this);
|
||||
GUIStyle.CursorSprite.Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
else if (elemName == "style")
|
||||
{
|
||||
foreach (var element in parentElement.Elements())
|
||||
{
|
||||
LoadFromXElement(element, overriding);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIComponentStyle prefab = new GUIComponentStyle(parentElement, this);
|
||||
GUIStyle.ComponentStyles.Add(prefab, overriding);
|
||||
}
|
||||
}
|
||||
|
||||
public override sealed void LoadFile()
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root.FromPackage(ContentPackage);
|
||||
LoadFromXElement(rootElement, false);
|
||||
}
|
||||
|
||||
public override sealed void UnloadFile()
|
||||
{
|
||||
GUIStyle.ComponentStyles.RemoveByFile(this);
|
||||
GUIStyle.CursorSprite.Prefabs.RemoveByFile(this);
|
||||
GUIStyle.Fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(this));
|
||||
GUIStyle.Sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(this));
|
||||
GUIStyle.SpriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(this));
|
||||
GUIStyle.Colors.Values.ForEach(p => p.Prefabs.RemoveByFile(this));
|
||||
}
|
||||
|
||||
public override sealed void Sort()
|
||||
{
|
||||
GUIStyle.ComponentStyles.SortAll();
|
||||
GUIStyle.CursorSprite.Prefabs.Sort();
|
||||
GUIStyle.Fonts.Values.ForEach(p => p.Prefabs.Sort());
|
||||
GUIStyle.Sprites.Values.ForEach(p => p.Prefabs.Sort());
|
||||
GUIStyle.SpriteSheets.Values.ForEach(p => p.Prefabs.Sort());
|
||||
GUIStyle.Colors.Values.ForEach(p => p.Prefabs.Sort());
|
||||
}
|
||||
}
|
||||
#else
|
||||
public sealed class UIStyleFile : OtherFile
|
||||
{
|
||||
public UIStyleFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class UpgradeModulesFile : GenericPrefabFile<UpgradeContentPrefab>
|
||||
{
|
||||
public UpgradeModulesFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) =>
|
||||
identifier == "upgrademodule" ||
|
||||
identifier == "upgradecategory";
|
||||
|
||||
protected override bool MatchesPlural(Identifier identifier) =>
|
||||
identifier == "upgrademodules";
|
||||
|
||||
protected override PrefabCollection<UpgradeContentPrefab> prefabs => UpgradeContentPrefab.PrefabsAndCategories;
|
||||
protected override UpgradeContentPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
if (elemName == "upgradecategory")
|
||||
{
|
||||
return new UpgradeCategory(element, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new UpgradePrefab(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[RequiredByCorePackage]
|
||||
sealed class WreckAIConfigFile : GenericPrefabFile<WreckAIConfig>
|
||||
{
|
||||
public WreckAIConfigFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "wreckaiconfig";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "wreckaiconfigs";
|
||||
protected override PrefabCollection<WreckAIConfig> prefabs => WreckAIConfig.Prefabs;
|
||||
protected override WreckAIConfig CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new WreckAIConfig(element, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class WreckFile : BaseSubFile
|
||||
{
|
||||
public WreckFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
}
|
||||
}
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class ContentPackage
|
||||
{
|
||||
#warning TODO: make this independent of the current version
|
||||
public static readonly Version MinimumHashCompatibleVersion = GameMain.Version;
|
||||
|
||||
public const string LocalModsDir = "LocalMods";
|
||||
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
|
||||
SaveUtil.SaveFolder,
|
||||
"WorkshopMods",
|
||||
"Installed");
|
||||
|
||||
public const string FileListFileName = "filelist.xml";
|
||||
public const string DefaultModVersion = "1.0.0";
|
||||
|
||||
public readonly string Name;
|
||||
public readonly ImmutableArray<string> AltNames;
|
||||
public readonly string Path;
|
||||
public string Dir => Barotrauma.IO.Path.GetDirectoryName(Path) ?? "";
|
||||
public readonly UInt64 SteamWorkshopId;
|
||||
|
||||
public readonly Version GameVersion;
|
||||
public readonly string ModVersion;
|
||||
public readonly Md5Hash Hash;
|
||||
public readonly DateTime? InstallTime;
|
||||
|
||||
public readonly ImmutableArray<ContentFile> Files;
|
||||
public readonly ImmutableArray<string> Errors;
|
||||
|
||||
public async Task<bool> IsUpToDate()
|
||||
{
|
||||
if (SteamWorkshopId != 0 && InstallTime.HasValue)
|
||||
{
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(SteamWorkshopId);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= InstallTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int Index => ContentPackageManager.EnabledPackages.IndexOf(this);
|
||||
|
||||
#warning TODO: remove this, unless we truly believe that determining "multiplayer-incompatible content" is something we should do
|
||||
public readonly bool HasMultiplayerIncompatibleContent;
|
||||
|
||||
protected ContentPackage(XDocument doc, string path)
|
||||
{
|
||||
Path = path.CleanUpPathCrossPlatform();
|
||||
XElement rootElement = doc.Root ?? throw new NullReferenceException("XML document is invalid: root element is null.");
|
||||
|
||||
Name = rootElement.GetAttributeString("name", "").Trim();
|
||||
AltNames = rootElement.GetAttributeStringArray("altnames", Array.Empty<string>())
|
||||
.Select(n => n.Trim()).ToImmutableArray();
|
||||
AssertCondition(!string.IsNullOrEmpty(Name), "Name is null or empty");
|
||||
SteamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
GameVersion = rootElement.GetAttributeVersion("gameversion", GameMain.Version);
|
||||
ModVersion = rootElement.GetAttributeString("modversion", DefaultModVersion);
|
||||
if (rootElement.Attribute("installtime") != null)
|
||||
{
|
||||
InstallTime = ToolBox.Epoch.ToDateTime(rootElement.GetAttributeUInt("installtime", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallTime = null;
|
||||
}
|
||||
|
||||
var fileResults = rootElement.Elements()
|
||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||
.ToArray();
|
||||
|
||||
Files = fileResults
|
||||
.OfType<Success<ContentFile, string>>()
|
||||
.Select(f => f.Value)
|
||||
.ToImmutableArray();
|
||||
|
||||
Errors = fileResults
|
||||
.OfType<Failure<ContentFile, string>>()
|
||||
.Select(f => f.Error)
|
||||
.ToImmutableArray();
|
||||
|
||||
HasMultiplayerIncompatibleContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
||||
|
||||
Hash = CalculateHash();
|
||||
var expectedHash = rootElement.GetAttributeString("expectedhash", "");
|
||||
if (HashMismatches(expectedHash))
|
||||
{
|
||||
DebugConsole.ThrowError($"Hash calculation for content package \"{Name}\" didn't match expected hash ({Hash.StringRepresentation} != {expectedHash})");
|
||||
}
|
||||
}
|
||||
|
||||
public bool HashMismatches(string expectedHash)
|
||||
=> GameVersion >= MinimumHashCompatibleVersion &&
|
||||
!expectedHash.IsNullOrWhiteSpace() &&
|
||||
!expectedHash.Equals(Hash.StringRepresentation, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public IEnumerable<T> GetFiles<T>() where T : ContentFile => Files.Where(f => f is T).Cast<T>();
|
||||
|
||||
public IEnumerable<ContentFile> GetFiles(Type type)
|
||||
=> !type.IsSubclassOf(typeof(ContentFile))
|
||||
? throw new ArgumentException($"Type must be subclass of ContentFile, got {type.Name}")
|
||||
: Files.Where(f => f.GetType() == type || f.GetType().IsSubclassOf(type));
|
||||
|
||||
public bool NameMatches(Identifier name)
|
||||
=> Name == name || AltNames.Any(n => n == name);
|
||||
|
||||
public bool NameMatches(string name)
|
||||
=> NameMatches(name.ToIdentifier());
|
||||
|
||||
public static ContentPackage? TryLoad(string path)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(path);
|
||||
|
||||
try
|
||||
{
|
||||
if (doc.Root.GetAttributeBool("corepackage", false))
|
||||
{
|
||||
return new CorePackage(doc, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RegularPackage(doc, path);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
while (e.InnerException != null) { e = e.InnerException; }
|
||||
DebugConsole.ThrowError($"{e.Message}: {e.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Md5Hash CalculateHash(bool logging = false)
|
||||
{
|
||||
using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.MD5);
|
||||
|
||||
if (logging)
|
||||
{
|
||||
DebugConsole.NewMessage("****************************** Calculating content package hash " + Name);
|
||||
}
|
||||
|
||||
foreach (ContentFile file in Files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var hash = file.Hash;
|
||||
if (logging)
|
||||
{
|
||||
DebugConsole.NewMessage(" " + file.Path + ": " + hash.StringRepresentation);
|
||||
}
|
||||
incrementalHash.AppendData(hash.ByteRepresentation);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while calculating the MD5 hash of the content package \"{Name}\" (file path: {Path}). The content package may be corrupted. You may want to delete or reinstall the package.", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var md5Hash = Md5Hash.BytesAsHash(incrementalHash.GetHashAndReset());
|
||||
if (logging)
|
||||
{
|
||||
DebugConsole.NewMessage("****************************** Package hash: " + md5Hash.StringRepresentation);
|
||||
}
|
||||
|
||||
return md5Hash;
|
||||
}
|
||||
|
||||
protected void AssertCondition(bool condition, string errorMsg)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to load \"{Name ?? Path}\": {errorMsg}");
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFilesOfType<T>() where T : ContentFile
|
||||
{
|
||||
Files.Where(f => f is T).ForEach(f => f.LoadFile());
|
||||
}
|
||||
|
||||
public void UnloadFilesOfType<T>() where T : ContentFile
|
||||
{
|
||||
Files.Where(f => f is T).ForEach(f => f.UnloadFile());
|
||||
}
|
||||
|
||||
public enum LoadResult
|
||||
{
|
||||
Success,
|
||||
Failure
|
||||
}
|
||||
|
||||
public LoadResult LoadPackage()
|
||||
{
|
||||
foreach (var p in LoadPackageEnumerable())
|
||||
{
|
||||
if (p.Exception != null) { return LoadResult.Failure; }
|
||||
}
|
||||
return LoadResult.Success;
|
||||
}
|
||||
|
||||
public IEnumerable<ContentPackageManager.LoadProgress> LoadPackageEnumerable()
|
||||
{
|
||||
ContentFile[] getFilesToLoad(Predicate<ContentFile> predicate)
|
||||
=> Files.Where(predicate.Invoke).ToArray()
|
||||
#if DEBUG
|
||||
//The game should be able to work just fine with a completely arbitrary file load order.
|
||||
//To make sure we don't mess this up, debug builds randomize it so it has a higher chance
|
||||
//of breaking anything that's not implemented correctly.
|
||||
.Randomize()
|
||||
#endif
|
||||
;
|
||||
|
||||
IEnumerable<ContentPackageManager.LoadProgress> loadFiles(ContentFile[] filesToLoad, int indexOffset)
|
||||
{
|
||||
for (int i = 0; i < filesToLoad.Length; i++)
|
||||
{
|
||||
Exception? exception = null;
|
||||
try
|
||||
{
|
||||
//do not allow exceptions thrown here to crash the game
|
||||
filesToLoad[i].LoadFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
if (exception != null)
|
||||
{
|
||||
yield return ContentPackageManager.LoadProgress.Failure(exception);
|
||||
break;
|
||||
}
|
||||
yield return new ContentPackageManager.LoadProgress((i + indexOffset) / (float)Files.Length);
|
||||
}
|
||||
}
|
||||
|
||||
//Load the UI files first. This is to allow the game to render
|
||||
//the text in the loading screen as soon as possible.
|
||||
var priorityFiles = getFilesToLoad(f => f is UIStyleFile);
|
||||
|
||||
var remainder = getFilesToLoad(f => !priorityFiles.Contains(f));
|
||||
|
||||
var loadEnumerable =
|
||||
loadFiles(priorityFiles, 0)
|
||||
.Concat(loadFiles(remainder, priorityFiles.Length));
|
||||
|
||||
foreach (var p in loadEnumerable)
|
||||
{
|
||||
if (p.Exception != null)
|
||||
{
|
||||
HandleLoadException(p.Exception);
|
||||
yield return p;
|
||||
break;
|
||||
}
|
||||
yield return p;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void HandleLoadException(Exception e);
|
||||
|
||||
public void UnloadPackage()
|
||||
{
|
||||
Files.ForEach(f => f.UnloadFile());
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
byte[] shortHash = Encoding.ASCII.GetBytes(Hash.StringRepresentation.Substring(0, 4));
|
||||
return (shortHash[0] << 24) | (shortHash[1] << 16) | (shortHash[2] << 8) | shortHash[3];
|
||||
}
|
||||
|
||||
public static bool PathAllowedAsLocalModFile(string path)
|
||||
{
|
||||
#if DEBUG
|
||||
if (GameMain.VanillaContent.Files.Any(f => f.Path == path))
|
||||
{
|
||||
//file is in vanilla package, this is allowed
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
while (true)
|
||||
{
|
||||
string temp = Barotrauma.IO.Path.GetDirectoryName(path) ?? "";
|
||||
if (string.IsNullOrEmpty(temp)) { break; }
|
||||
path = temp;
|
||||
}
|
||||
return path == LocalModsDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RequiredByCorePackage : Attribute
|
||||
{
|
||||
public readonly ImmutableHashSet<Type> AlternativeTypes;
|
||||
public RequiredByCorePackage(params Type[] alternativeTypes)
|
||||
{
|
||||
AlternativeTypes = alternativeTypes.ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class AlternativeContentTypeNames : Attribute
|
||||
{
|
||||
public readonly ImmutableHashSet<Identifier> Names;
|
||||
public AlternativeContentTypeNames(params string[] names)
|
||||
{
|
||||
Names = names.ToIdentifiers().ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public class CorePackage : ContentPackage
|
||||
{
|
||||
public CorePackage(XDocument doc, string path) : base(doc, path)
|
||||
{
|
||||
AssertCondition(doc.Root.GetAttributeBool("corepackage", false),
|
||||
"Expected a core package, got a regular package");
|
||||
|
||||
var missingFileTypes = ContentFile.Types.Where(
|
||||
t => t.RequiredByCorePackage
|
||||
&& !Files.Any(f => t.Type == f.GetType()
|
||||
|| t.AlternativeTypes.Contains(f.GetType())));
|
||||
AssertCondition(!missingFileTypes.Any(),
|
||||
"Core package requires at least one of the following content types: " +
|
||||
string.Join(", ", missingFileTypes.Select(t => t.Type.Name)));
|
||||
}
|
||||
|
||||
protected override void HandleLoadException(Exception e)
|
||||
{
|
||||
throw new Exception($"An exception was thrown while loading \"{Name}\"", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class RegularPackage : ContentPackage
|
||||
{
|
||||
public RegularPackage(XDocument doc, string path) : base(doc, path)
|
||||
{
|
||||
AssertCondition(!doc.Root.GetAttributeBool("corepackage", false), "Expected a regular package, got a core package");
|
||||
}
|
||||
|
||||
protected override void HandleLoadException(Exception e)
|
||||
{
|
||||
UnloadPackage();
|
||||
DebugConsole.ThrowError($"Failed to load package \"{Name}\"", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static partial class ContentPackageManager
|
||||
{
|
||||
public const string CopyIndicatorFileName = ".copying";
|
||||
public const string VanillaFileList = "Content/ContentPackages/Vanilla.xml";
|
||||
|
||||
public const string CorePackageElementName = "corepackage";
|
||||
public const string RegularPackagesElementName = "regularpackages";
|
||||
public const string RegularPackagesSubElementName = "package";
|
||||
|
||||
public static class EnabledPackages
|
||||
{
|
||||
public static CorePackage? Core { get; private set; } = null;
|
||||
|
||||
private static readonly List<RegularPackage> regular = new List<RegularPackage>();
|
||||
public static IReadOnlyList<RegularPackage> Regular => regular;
|
||||
|
||||
public static IEnumerable<ContentPackage> All =>
|
||||
Core != null
|
||||
? (Core as ContentPackage).ToEnumerable().CollectionConcat(Regular)
|
||||
: Enumerable.Empty<ContentPackage>();
|
||||
|
||||
private static class BackupPackages
|
||||
{
|
||||
public static CorePackage? Core;
|
||||
public static ImmutableArray<RegularPackage>? Regular;
|
||||
}
|
||||
|
||||
public static void SetCore(CorePackage newCore) => SetCoreEnumerable(newCore).Consume();
|
||||
|
||||
public static IEnumerable<LoadProgress> SetCoreEnumerable(CorePackage newCore)
|
||||
{
|
||||
var oldCore = Core;
|
||||
if (newCore == oldCore) { yield break; }
|
||||
Core?.UnloadPackage();
|
||||
Core = newCore;
|
||||
foreach (var p in newCore.LoadPackageEnumerable()) { yield return p; }
|
||||
ThrowIfDuplicates(All);
|
||||
yield return new LoadProgress(1.0f);
|
||||
}
|
||||
|
||||
public static void ReloadCore()
|
||||
{
|
||||
if (Core == null) { return; }
|
||||
Core.UnloadPackage();
|
||||
Core.LoadPackage();
|
||||
ThrowIfDuplicates(All);
|
||||
}
|
||||
|
||||
public static void EnableRegular(RegularPackage p)
|
||||
{
|
||||
if (regular.Contains(p)) { return; }
|
||||
|
||||
var newRegular = regular.ToList();
|
||||
newRegular.Add(p);
|
||||
SetRegular(newRegular);
|
||||
}
|
||||
|
||||
public static void SetRegular(IReadOnlyList<RegularPackage> newRegular)
|
||||
=> SetRegularEnumerable(newRegular).Consume();
|
||||
|
||||
public static IEnumerable<LoadProgress> SetRegularEnumerable(IReadOnlyList<RegularPackage> inNewRegular)
|
||||
{
|
||||
if (ReferenceEquals(inNewRegular, regular)) { yield break; }
|
||||
if (inNewRegular.SequenceEqual(regular)) { yield break; }
|
||||
ThrowIfDuplicates(inNewRegular);
|
||||
var newRegular = inNewRegular.ToList();
|
||||
IEnumerable<RegularPackage> toUnload = regular.Where(r => !newRegular.Contains(r));
|
||||
RegularPackage[] toLoad = newRegular.Where(r => !regular.Contains(r)).ToArray();
|
||||
toUnload.ForEach(r => r.UnloadPackage());
|
||||
|
||||
Range<float> loadingRange = new Range<float>(0.0f, 1.0f);
|
||||
|
||||
for (int i = 0; i < toLoad.Length; i++)
|
||||
{
|
||||
var package = toLoad[i];
|
||||
loadingRange = new Range<float>(i / (float)toLoad.Length, (i + 1) / (float)toLoad.Length);
|
||||
foreach (var progress in package.LoadPackageEnumerable())
|
||||
{
|
||||
if (progress.Exception != null)
|
||||
{
|
||||
//If an exception was thrown while loading this package, refuse to add it to the list of enabled packages
|
||||
newRegular.Remove(package);
|
||||
break;
|
||||
}
|
||||
yield return progress.Transform(loadingRange);
|
||||
}
|
||||
}
|
||||
regular.Clear(); regular.AddRange(newRegular);
|
||||
SortContent();
|
||||
yield return new LoadProgress(1.0f);
|
||||
}
|
||||
|
||||
public static void ThrowIfDuplicates(IEnumerable<ContentPackage> pkgs)
|
||||
{
|
||||
var contentPackages = pkgs as IList<ContentPackage> ?? pkgs.ToArray();
|
||||
if (contentPackages.Any(p1 => contentPackages.AtLeast(2, p2 => p1 == p2)))
|
||||
{
|
||||
throw new InvalidOperationException($"Input contains duplicate packages");
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals([AllowNull] T x, [AllowNull] T y)
|
||||
{
|
||||
if (x is null || y is null)
|
||||
{
|
||||
return x is null == y is null;
|
||||
}
|
||||
return x.GetType() == y.GetType();
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] T obj)
|
||||
{
|
||||
return obj.GetType().GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SortContent()
|
||||
{
|
||||
ThrowIfDuplicates(All);
|
||||
All
|
||||
.SelectMany(r => r.Files)
|
||||
.Distinct(new TypeComparer<ContentFile>())
|
||||
.ForEach(f => f.Sort());
|
||||
}
|
||||
|
||||
public static int IndexOf(ContentPackage contentPackage)
|
||||
{
|
||||
if (contentPackage is CorePackage core)
|
||||
{
|
||||
if (core == Core) { return 0; }
|
||||
return -1;
|
||||
}
|
||||
else if (contentPackage is RegularPackage reg)
|
||||
{
|
||||
return Regular.IndexOf(reg) + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void DisableRemovedMods()
|
||||
{
|
||||
if (Core != null && !ContentPackageManager.CorePackages.Contains(Core))
|
||||
{
|
||||
SetCore(ContentPackageManager.CorePackages.First());
|
||||
}
|
||||
SetRegular(Regular.Where(p => ContentPackageManager.RegularPackages.Contains(p)).ToArray());
|
||||
}
|
||||
|
||||
public static void BackUp()
|
||||
{
|
||||
if (BackupPackages.Core != null || BackupPackages.Regular != null)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to back up enabled packages multiple times");
|
||||
}
|
||||
|
||||
BackupPackages.Core = Core;
|
||||
BackupPackages.Regular = Regular.ToImmutableArray();
|
||||
}
|
||||
|
||||
public static void Restore()
|
||||
{
|
||||
if (BackupPackages.Core == null || BackupPackages.Regular == null)
|
||||
{
|
||||
DebugConsole.AddWarning("Tried to restore enabled packages multiple times/without performing a backup");
|
||||
return;
|
||||
}
|
||||
|
||||
SetCore(BackupPackages.Core);
|
||||
SetRegular(BackupPackages.Regular);
|
||||
|
||||
BackupPackages.Core = null;
|
||||
BackupPackages.Regular = null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class PackageSource : ICollection<ContentPackage>
|
||||
{
|
||||
private readonly Predicate<string>? skipPredicate;
|
||||
|
||||
public PackageSource(string dir, Predicate<string>? skipPredicate)
|
||||
{
|
||||
this.skipPredicate = skipPredicate;
|
||||
directory = dir;
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
public void SwapPackage(ContentPackage oldPackage, ContentPackage newPackage)
|
||||
{
|
||||
bool contains = false;
|
||||
if (oldPackage is CorePackage oldCore && corePackages.Contains(oldCore))
|
||||
{
|
||||
corePackages.Remove(oldCore);
|
||||
contains = true;
|
||||
}
|
||||
else if (oldPackage is RegularPackage oldRegular && regularPackages.Contains(oldRegular))
|
||||
{
|
||||
regularPackages.Remove(oldRegular);
|
||||
contains = true;
|
||||
}
|
||||
|
||||
if (contains)
|
||||
{
|
||||
if (newPackage is CorePackage newCore)
|
||||
{
|
||||
corePackages.Add(newCore);
|
||||
}
|
||||
else if (newPackage is RegularPackage newRegular)
|
||||
{
|
||||
regularPackages.Add(newRegular);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
//remove packages that have been deleted from the directory
|
||||
corePackages.RemoveWhere(p => !File.Exists(p.Path));
|
||||
regularPackages.RemoveWhere(p => !File.Exists(p.Path));
|
||||
|
||||
//load packages that have been added to the directory
|
||||
var subDirs = Directory.GetDirectories(directory);
|
||||
foreach (string subDir in subDirs)
|
||||
{
|
||||
var fileListPath = Path.Combine(subDir, ContentPackage.FileListFileName).CleanUpPathCrossPlatform();
|
||||
if (this.Any(p => p.Path.Equals(fileListPath, StringComparison.OrdinalIgnoreCase))) { continue; }
|
||||
if (File.Exists(fileListPath))
|
||||
{
|
||||
if (skipPredicate?.Invoke(fileListPath) is true) { continue; }
|
||||
|
||||
ContentPackage? newPackage = ContentPackage.TryLoad(fileListPath);
|
||||
if (newPackage is CorePackage corePackage)
|
||||
{
|
||||
corePackages.Add(corePackage);
|
||||
}
|
||||
else if (newPackage is RegularPackage regularPackage)
|
||||
{
|
||||
regularPackages.Add(regularPackage);
|
||||
}
|
||||
|
||||
if (!(newPackage is null))
|
||||
{
|
||||
Debug.WriteLine($"Loaded \"{newPackage.Name}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string directory;
|
||||
private readonly HashSet<RegularPackage> regularPackages = new HashSet<RegularPackage>();
|
||||
public IEnumerable<RegularPackage> Regular => regularPackages;
|
||||
|
||||
private readonly HashSet<CorePackage> corePackages = new HashSet<CorePackage>();
|
||||
public IEnumerable<CorePackage> Core => corePackages;
|
||||
|
||||
public IEnumerator<ContentPackage> GetEnumerator()
|
||||
{
|
||||
foreach (var core in Core) { yield return core; }
|
||||
foreach (var regular in Regular) { yield return regular; }
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
void ICollection<ContentPackage>.Add(ContentPackage item) => throw new InvalidOperationException();
|
||||
|
||||
void ICollection<ContentPackage>.Clear() => throw new InvalidOperationException();
|
||||
|
||||
public bool Contains(ContentPackage item)
|
||||
=> item switch
|
||||
{
|
||||
CorePackage core => corePackages.Contains(core),
|
||||
RegularPackage regular => this.regularPackages.Contains(regular),
|
||||
_ => throw new ArgumentException($"Expected regular or core package, got {item.GetType().Name}")
|
||||
};
|
||||
|
||||
void ICollection<ContentPackage>.CopyTo(ContentPackage[] array, int arrayIndex)
|
||||
{
|
||||
foreach (var package in corePackages)
|
||||
{
|
||||
array[arrayIndex] = package;
|
||||
arrayIndex++;
|
||||
}
|
||||
|
||||
foreach (var package in regularPackages)
|
||||
{
|
||||
array[arrayIndex] = package;
|
||||
arrayIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<ContentPackage>.Remove(ContentPackage item) => throw new InvalidOperationException();
|
||||
|
||||
public int Count => corePackages.Count + regularPackages.Count;
|
||||
public bool IsReadOnly => true;
|
||||
}
|
||||
|
||||
public static readonly PackageSource LocalPackages = new PackageSource(ContentPackage.LocalModsDir, skipPredicate: null);
|
||||
public static readonly PackageSource WorkshopPackages = new PackageSource(ContentPackage.WorkshopModsDir, skipPredicate: SteamManager.Workshop.IsInstallingToPath);
|
||||
|
||||
public static CorePackage? VanillaCorePackage { get; private set; } = null;
|
||||
|
||||
public static IEnumerable<CorePackage> CorePackages
|
||||
=> (VanillaCorePackage is null
|
||||
? Enumerable.Empty<CorePackage>()
|
||||
: VanillaCorePackage.ToEnumerable())
|
||||
.CollectionConcat(LocalPackages.Core.CollectionConcat(WorkshopPackages.Core));
|
||||
|
||||
public static IEnumerable<RegularPackage> RegularPackages
|
||||
=> LocalPackages.Regular.CollectionConcat(WorkshopPackages.Regular);
|
||||
|
||||
public static IEnumerable<ContentPackage> AllPackages
|
||||
=> LocalPackages.CollectionConcat(WorkshopPackages);
|
||||
|
||||
public static void UpdateContentPackageList()
|
||||
{
|
||||
LocalPackages.Refresh();
|
||||
WorkshopPackages.Refresh();
|
||||
EnabledPackages.DisableRemovedMods();
|
||||
}
|
||||
|
||||
public static ContentPackage? ReloadContentPackage(ContentPackage p)
|
||||
{
|
||||
ContentPackage? newPackage = ContentPackage.TryLoad(p.Path);
|
||||
if (newPackage is CorePackage core)
|
||||
{
|
||||
if (EnabledPackages.Core == p) { EnabledPackages.SetCore(core); }
|
||||
}
|
||||
else if (newPackage is RegularPackage regular)
|
||||
{
|
||||
int index = EnabledPackages.Regular.IndexOf(p);
|
||||
if (index >= 0)
|
||||
{
|
||||
var newRegular = EnabledPackages.Regular.ToArray();
|
||||
newRegular[index] = regular;
|
||||
EnabledPackages.SetRegular(newRegular);
|
||||
}
|
||||
}
|
||||
|
||||
if (newPackage != null)
|
||||
{
|
||||
LocalPackages.SwapPackage(p, newPackage);
|
||||
WorkshopPackages.SwapPackage(p, newPackage);
|
||||
}
|
||||
EnabledPackages.DisableRemovedMods();
|
||||
return newPackage;
|
||||
}
|
||||
|
||||
public readonly struct LoadProgress
|
||||
{
|
||||
public readonly float Value;
|
||||
public readonly Exception? Exception;
|
||||
|
||||
public LoadProgress(float value)
|
||||
{
|
||||
Value = value;
|
||||
Exception = null;
|
||||
}
|
||||
|
||||
private LoadProgress(Exception exception)
|
||||
{
|
||||
Value = -1f;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public static LoadProgress Failure(Exception exception)
|
||||
=> new LoadProgress(exception);
|
||||
|
||||
public LoadProgress Transform(Range<float> range)
|
||||
=> Exception != null
|
||||
? this
|
||||
: new LoadProgress(MathHelper.Lerp(range.Start, range.End, Value));
|
||||
}
|
||||
|
||||
public static void LoadVanillaFileList()
|
||||
{
|
||||
VanillaCorePackage = new CorePackage(XDocument.Load(VanillaFileList), VanillaFileList);
|
||||
}
|
||||
|
||||
public static IEnumerable<LoadProgress> Init()
|
||||
{
|
||||
Range<float> loadingRange = new Range<float>(0.0f, 1.0f);
|
||||
|
||||
SteamManager.Workshop.DeleteFailedCopies();
|
||||
UpdateContentPackageList();
|
||||
|
||||
if (VanillaCorePackage is null) { LoadVanillaFileList(); }
|
||||
|
||||
CorePackage enabledCorePackage = VanillaCorePackage!;
|
||||
List<RegularPackage> enabledRegularPackages = new List<RegularPackage>();
|
||||
|
||||
#if CLIENT
|
||||
TaskPool.Add("EnqueueWorkshopUpdates", EnqueueWorkshopUpdates(), t => { });
|
||||
#else
|
||||
#warning TODO: implement Workshop updates for servers at some point
|
||||
#endif
|
||||
|
||||
var contentPackagesElement = XMLExtensions.TryLoadXml(GameSettings.PlayerConfigPath)?.Root
|
||||
?.GetChildElement("ContentPackages");
|
||||
if (contentPackagesElement != null)
|
||||
{
|
||||
T? findPackage<T>(IEnumerable<T> packages, XElement? elem) where T : ContentPackage
|
||||
{
|
||||
if (elem is null) { return null; }
|
||||
string name = elem.GetAttributeString("name", "");
|
||||
string path = elem.GetAttributeStringUnrestricted("path", "").CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
return
|
||||
packages.FirstOrDefault(p => p.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
|
||||
?? packages.FirstOrDefault(p => p.NameMatches(name));
|
||||
}
|
||||
|
||||
var corePackageElement = contentPackagesElement.GetChildElement(CorePackageElementName);
|
||||
enabledCorePackage = findPackage(CorePackages, corePackageElement) ?? VanillaCorePackage!;
|
||||
|
||||
var regularPackagesElement = contentPackagesElement.GetChildElement(RegularPackagesElementName);
|
||||
if (regularPackagesElement != null)
|
||||
{
|
||||
XElement[] regularPackageElements = regularPackagesElement.GetChildElements(RegularPackagesSubElementName).ToArray();
|
||||
for (int i = 0; i < regularPackageElements.Length; i++)
|
||||
{
|
||||
var regularPackage = findPackage(RegularPackages, regularPackageElements[i]);
|
||||
if (regularPackage != null) { enabledRegularPackages.Add(regularPackage); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int pkgCount = 1 + enabledRegularPackages.Count; //core + regular
|
||||
|
||||
loadingRange = new Range<float>(0.01f, 1.0f / pkgCount);
|
||||
foreach (var p in EnabledPackages.SetCoreEnumerable(enabledCorePackage))
|
||||
{
|
||||
yield return p.Transform(loadingRange);
|
||||
}
|
||||
|
||||
loadingRange = new Range<float>(1.0f / pkgCount, 1.0f);
|
||||
foreach (var p in EnabledPackages.SetRegularEnumerable(enabledRegularPackages))
|
||||
{
|
||||
yield return p.Transform(loadingRange);
|
||||
}
|
||||
|
||||
yield return new LoadProgress(1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Barotrauma.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public sealed class ContentPath
|
||||
{
|
||||
public readonly static ContentPath Empty = new ContentPath(null, "");
|
||||
|
||||
public const string ModDirStr = "%ModDir%";
|
||||
public const string OtherModDirFmt = "%ModDir:{0}%";
|
||||
private static readonly Regex OtherModDirRegex = new Regex(
|
||||
string.Format(OtherModDirFmt, "(.+?)"));
|
||||
|
||||
public readonly string? RawValue;
|
||||
|
||||
public readonly ContentPackage? ContentPackage;
|
||||
|
||||
private string? cachedValue;
|
||||
private string? cachedFullPath;
|
||||
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RawValue.IsNullOrEmpty()) { return ""; }
|
||||
if (!cachedValue.IsNullOrEmpty()) { return cachedValue!; }
|
||||
|
||||
string? modName = ContentPackage?.Name;
|
||||
|
||||
var otherMods = OtherModDirRegex.Matches(RawValue ?? throw new NullReferenceException($"{nameof(RawValue)} is null."))
|
||||
.Select(m => m.Groups[1].Value.Trim().ToIdentifier())
|
||||
.Distinct().Where(id => !id.IsEmpty && id != modName).ToHashSet();
|
||||
cachedValue = RawValue!;
|
||||
if (!(ContentPackage is null))
|
||||
{
|
||||
string modPath = Path.GetDirectoryName(ContentPackage.Path)!;
|
||||
cachedValue = cachedValue
|
||||
.Replace(ModDirStr, modPath, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.Name), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
if (ContentPackage.SteamWorkshopId != 0)
|
||||
{
|
||||
cachedValue = cachedValue
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.SteamWorkshopId.ToString(CultureInfo.InvariantCulture)), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
var allPackages = ContentPackageManager.AllPackages;
|
||||
foreach (Identifier otherModName in otherMods)
|
||||
{
|
||||
if (!UInt64.TryParse(otherModName.Value, out UInt64 workshopId)) { workshopId = 0; }
|
||||
ContentPackage? otherMod =
|
||||
allPackages.FirstOrDefault(p => workshopId != 0 && p.SteamWorkshopId != 0 && workshopId == p.SteamWorkshopId)
|
||||
?? allPackages.FirstOrDefault(p => p.Name == otherModName)
|
||||
?? allPackages.FirstOrDefault(p => p.NameMatches(otherModName))
|
||||
?? throw new MissingContentPackageException(ContentPackage, otherModName.Value);
|
||||
cachedValue = cachedValue.Replace(string.Format(OtherModDirFmt, otherModName.Value), Path.GetDirectoryName(otherMod.Path));
|
||||
}
|
||||
cachedValue = cachedValue.CleanUpPath();
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public string FullPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedFullPath.IsNullOrEmpty())
|
||||
{
|
||||
if (Value.IsNullOrEmpty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
cachedFullPath = Path.GetFullPath(Value).CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
}
|
||||
return cachedFullPath!;
|
||||
}
|
||||
}
|
||||
|
||||
private ContentPath(ContentPackage? contentPackage, string? rawValue)
|
||||
{
|
||||
ContentPackage = contentPackage;
|
||||
RawValue = rawValue;
|
||||
cachedValue = null;
|
||||
cachedFullPath = null;
|
||||
}
|
||||
|
||||
public static ContentPath FromRaw(string? rawValue)
|
||||
=> new ContentPath(null, rawValue);
|
||||
|
||||
public static ContentPath FromRaw(ContentPackage? contentPackage, string? rawValue)
|
||||
=> new ContentPath(contentPackage, rawValue);
|
||||
|
||||
public static ContentPath FromEvaluated(ContentPackage? contentPackage, string? evaluatedValue)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static bool StringEquality(string? a, string? b)
|
||||
=> (a.IsNullOrEmpty() && b.IsNullOrEmpty()) ||
|
||||
string.Equals(Path.GetFullPath(a.CleanUpPathCrossPlatform(false) ?? ""),
|
||||
Path.GetFullPath(b.CleanUpPathCrossPlatform(false) ?? ""),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool operator==(ContentPath a, ContentPath b)
|
||||
=> StringEquality(a.Value, b.Value);
|
||||
|
||||
public static bool operator!=(ContentPath a, ContentPath b) => !(a == b);
|
||||
|
||||
public static bool operator==(ContentPath a, string? b)
|
||||
=> StringEquality(a.Value, b);
|
||||
|
||||
public static bool operator!=(ContentPath a, string? b) => !(a == b);
|
||||
|
||||
public static bool operator==(string? a, ContentPath b)
|
||||
=> StringEquality(a, b.Value);
|
||||
|
||||
public static bool operator!=(string? a, ContentPath b) => !(a == b);
|
||||
|
||||
protected bool Equals(ContentPath other)
|
||||
{
|
||||
return RawValue == other.RawValue && Equals(ContentPackage, other.ContentPackage) && cachedValue == other.cachedValue && cachedFullPath == other.cachedFullPath;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((ContentPath)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(RawValue, ContentPackage, cachedValue, cachedFullPath);
|
||||
}
|
||||
|
||||
public bool IsNullOrEmpty() => string.IsNullOrEmpty(Value);
|
||||
public bool IsNullOrWhiteSpace() => string.IsNullOrWhiteSpace(Value);
|
||||
|
||||
public bool EndsWith(string suffix) => Value.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public override string? ToString() => Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public sealed class ContentXElement
|
||||
{
|
||||
public ContentPackage? ContentPackage { get; private set; }
|
||||
public readonly XElement Element;
|
||||
|
||||
public ContentXElement(ContentPackage? contentPackage, XElement element)
|
||||
{
|
||||
ContentPackage = contentPackage;
|
||||
Element = element;
|
||||
}
|
||||
|
||||
public static implicit operator XElement?(ContentXElement? cxe) => cxe?.Element;
|
||||
//public static implicit operator ContentXElement?(XElement? xe) => xe is null ? null : new ContentXElement(null, xe);
|
||||
|
||||
public XName Name => Element.Name;
|
||||
public Identifier NameAsIdentifier() => Element.NameAsIdentifier();
|
||||
|
||||
public string BaseUri => Element.BaseUri;
|
||||
|
||||
public XDocument Document => Element.Document ?? throw new NullReferenceException("XML element is invalid: document is null.");
|
||||
|
||||
public ContentXElement? FirstElement() => Elements().FirstOrDefault();
|
||||
|
||||
public ContentXElement? Parent => Element.Parent is null ? null : new ContentXElement(ContentPackage, Element.Parent);
|
||||
public bool HasElements => Element.HasElements;
|
||||
|
||||
public bool IsOverride() => Element.IsOverride();
|
||||
|
||||
public bool ComesAfter(ContentXElement other) => Element.ComesAfter(other.Element);
|
||||
|
||||
public ContentXElement? GetChildElement(string name)
|
||||
=> Element.GetChildElement(name) is { } elem ? new ContentXElement(ContentPackage, elem) : null;
|
||||
|
||||
public IEnumerable<ContentXElement> Elements()
|
||||
=> Element.Elements().Select(e => new ContentXElement(ContentPackage, e));
|
||||
|
||||
public IEnumerable<ContentXElement> ElementsBeforeSelf()
|
||||
=> Element.ElementsBeforeSelf().Select(e => new ContentXElement(ContentPackage, e));
|
||||
|
||||
public IEnumerable<ContentXElement> Descendants()
|
||||
=> Element.Descendants().Select(e => new ContentXElement(ContentPackage, e));
|
||||
|
||||
public IEnumerable<ContentXElement> GetChildElements(string name)
|
||||
=> Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
public XAttribute? Attribute(string name) => Element.Attribute(name);
|
||||
|
||||
public XAttribute? GetAttribute(string name) => Element.GetAttribute(name);
|
||||
|
||||
public IEnumerable<XAttribute> Attributes() => Element.Attributes();
|
||||
public IEnumerable<XAttribute> Attributes(string name) => Element.Attributes(name);
|
||||
|
||||
public string ElementInnerText() => Element.ElementInnerText();
|
||||
|
||||
public Identifier GetAttributeIdentifier(string key, string def) => Element.GetAttributeIdentifier(key, def);
|
||||
public Identifier GetAttributeIdentifier(string key, Identifier def) => Element.GetAttributeIdentifier(key, def);
|
||||
public Identifier[]? GetAttributeIdentifierArray(string key, Identifier[] def, bool trim = true) => Element.GetAttributeIdentifierArray(key, def, trim);
|
||||
public string? GetAttributeString(string key, string? def) => Element.GetAttributeString(key, def);
|
||||
public string GetAttributeStringUnrestricted(string key, string def) => Element.GetAttributeStringUnrestricted(key, def);
|
||||
public string[]? GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant = false) => Element.GetAttributeStringArray(key, def, convertToLowerInvariant);
|
||||
public ContentPath? GetAttributeContentPath(string key) => Element.GetAttributeContentPath(key, ContentPackage);
|
||||
public int GetAttributeInt(string key, int def) => Element.GetAttributeInt(key, def);
|
||||
public int[]? GetAttributeIntArray(string key, int[]? def) => Element.GetAttributeIntArray(key, def);
|
||||
public ushort[]? GetAttributeUshortArray(string key, ushort[]? def) => Element.GetAttributeUshortArray(key, def);
|
||||
public float GetAttributeFloat(string key, float def) => Element.GetAttributeFloat(key, def);
|
||||
public float[]? GetAttributeFloatArray(string key, float[]? def) => Element.GetAttributeFloatArray(key, def);
|
||||
public float GetAttributeFloat(float def, params string[] keys) => Element.GetAttributeFloat(def, keys);
|
||||
public bool GetAttributeBool(string key, bool def) => Element.GetAttributeBool(key, def);
|
||||
public Point GetAttributePoint(string key, in Point def) => Element.GetAttributePoint(key, def);
|
||||
public Vector2 GetAttributeVector2(string key, in Vector2 def) => Element.GetAttributeVector2(key, def);
|
||||
public Vector4 GetAttributeVector4(string key, in Vector4 def) => Element.GetAttributeVector4(key, def);
|
||||
public Color GetAttributeColor(string key, in Color def) => Element.GetAttributeColor(key, def);
|
||||
public Color? GetAttributeColor(string key) => Element.GetAttributeColor(key);
|
||||
public Color[]? GetAttributeColorArray(string key, Color[]? def) => Element.GetAttributeColorArray(key, def);
|
||||
public Rectangle GetAttributeRect(string key, in Rectangle def) => Element.GetAttributeRect(key, def);
|
||||
public T GetAttributeEnum<T>(string key, in T def) where T : struct, Enum => Element.GetAttributeEnum(key, def);
|
||||
public (T1, T2) GetAttributeTuple<T1, T2>(string key, in (T1, T2) def) => Element.GetAttributeTuple(key, def);
|
||||
public (T1, T2)[] GetAttributeTupleArray<T1, T2>(string key, in (T1, T2)[] def) => Element.GetAttributeTupleArray(key, def);
|
||||
|
||||
public Identifier VariantOf() => Element.VariantOf();
|
||||
|
||||
public bool DoesAttributeReferenceFileNameAlone(string key) => Element.DoesAttributeReferenceFileNameAlone(key);
|
||||
|
||||
public string ParseContentPathFromUri() => Element.ParseContentPathFromUri();
|
||||
|
||||
public void SetAttributeValue(string key, string val) => Element.SetAttributeValue(key, val);
|
||||
|
||||
public void Add(ContentXElement elem)
|
||||
{
|
||||
Element.Add(elem.Element);
|
||||
elem.ContentPackage = ContentPackage;
|
||||
#warning TODO: update %ModDir% instances in case the content package changes
|
||||
}
|
||||
|
||||
public void AddFirst(ContentXElement elem)
|
||||
{
|
||||
Element.AddFirst(elem.Element);
|
||||
elem.ContentPackage = ContentPackage;
|
||||
#warning TODO: update %ModDir% instances in case the content package changes
|
||||
}
|
||||
|
||||
public void AddAfterSelf(ContentXElement elem)
|
||||
{
|
||||
Element.AddAfterSelf(elem.Element);
|
||||
elem.ContentPackage = ContentPackage;
|
||||
#warning TODO: update %ModDir% instances in case the content package changes
|
||||
}
|
||||
|
||||
public void Remove() => Element.Remove();
|
||||
}
|
||||
|
||||
public static class ContentXElementExtensions
|
||||
{
|
||||
public static ContentXElement FromPackage(this XElement element, ContentPackage? contentPackage)
|
||||
=> new ContentXElement(contentPackage, element);
|
||||
|
||||
public static IEnumerable<ContentXElement> Elements(this IEnumerable<ContentXElement> elements)
|
||||
=> elements.SelectMany(e => e.Elements());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
// Identifier struct to eliminate case-sensitive comparisons
|
||||
public readonly struct Identifier : IComparable, IEquatable<Identifier>
|
||||
{
|
||||
public readonly static Identifier Empty = default;
|
||||
|
||||
private readonly static int emptyHash = "".GetHashCode(StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private readonly string? value;
|
||||
private readonly Lazy<int>? hashCode;
|
||||
|
||||
public string Value => value ?? "";
|
||||
public int HashCode => hashCode?.Value ?? emptyHash;
|
||||
|
||||
public Identifier(string? str)
|
||||
{
|
||||
value = str;
|
||||
hashCode = new Lazy<int>(() => (str ?? "").GetHashCode(StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public bool IsEmpty => Value.IsNullOrEmpty();
|
||||
|
||||
public Identifier IfEmpty(in Identifier id)
|
||||
=> IsEmpty ? id : this;
|
||||
|
||||
public Identifier Replace(in Identifier subStr, in Identifier newStr)
|
||||
=> Replace(subStr.Value ?? "", newStr.Value ?? "");
|
||||
|
||||
public Identifier Replace(string subStr, string newStr)
|
||||
=> (Value?.Replace(subStr, newStr, StringComparison.OrdinalIgnoreCase)).ToIdentifier();
|
||||
|
||||
public Identifier Remove(Identifier subStr)
|
||||
=> Remove(subStr.Value ?? "");
|
||||
|
||||
public Identifier Remove(string subStr)
|
||||
=> (Value?.Remove(subStr, StringComparison.OrdinalIgnoreCase)).ToIdentifier();
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj switch
|
||||
{
|
||||
Identifier i => this == i,
|
||||
string s => this == s,
|
||||
_ => base.Equals(obj)
|
||||
};
|
||||
|
||||
public bool StartsWith(string str) => Value?.StartsWith(str, StringComparison.OrdinalIgnoreCase) ?? str.IsNullOrEmpty();
|
||||
|
||||
public bool StartsWith(Identifier id) => StartsWith(id.Value ?? "");
|
||||
|
||||
public bool EndsWith(string str) => Value?.EndsWith(str, StringComparison.OrdinalIgnoreCase) ?? str.IsNullOrEmpty();
|
||||
|
||||
public bool EndsWith(Identifier id) => EndsWith(id.Value ?? "");
|
||||
|
||||
public Identifier AppendIfMissing(string suffix)
|
||||
=> EndsWith(suffix) ? this : $"{this}{suffix}".ToIdentifier();
|
||||
|
||||
public Identifier RemoveFromEnd(string suffix)
|
||||
=> (Value?.RemoveFromEnd(suffix, StringComparison.OrdinalIgnoreCase)).ToIdentifier();
|
||||
|
||||
public bool Contains(string str) => Value?.Contains(str, StringComparison.OrdinalIgnoreCase) ?? str.IsNullOrEmpty();
|
||||
|
||||
public bool Contains(in Identifier id) => Contains(id.Value ?? "");
|
||||
|
||||
public override string ToString() => Value ?? "";
|
||||
|
||||
public override int GetHashCode() => HashCode;
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
return string.Compare(Value, obj?.ToString() ?? "", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public bool Equals([AllowNull] Identifier other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
private static bool StringEquality(string? a, string? b)
|
||||
=> (a.IsNullOrEmpty() && b.IsNullOrEmpty()) || string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool operator ==(in Identifier a, in Identifier b) =>
|
||||
StringEquality(a.Value, b.Value);
|
||||
|
||||
public static bool operator !=(in Identifier a, in Identifier b) =>
|
||||
!(a == b);
|
||||
|
||||
public static bool operator ==(in Identifier identifier, string? str) =>
|
||||
StringEquality(identifier.Value, str);
|
||||
|
||||
public static bool operator !=(in Identifier identifier, string? str) =>
|
||||
!(identifier == str);
|
||||
|
||||
public static bool operator ==(string? str, in Identifier identifier) =>
|
||||
identifier == str;
|
||||
|
||||
public static bool operator !=(string? str, in Identifier identifier) =>
|
||||
!(identifier == str);
|
||||
|
||||
public static bool operator ==(in Identifier? a, in Identifier? b) =>
|
||||
StringEquality(a?.Value, b?.Value);
|
||||
|
||||
public static bool operator !=(in Identifier? a, in Identifier? b) =>
|
||||
!(a == b);
|
||||
|
||||
public static bool operator ==(in Identifier? a, string? b) =>
|
||||
StringEquality(a?.Value, b);
|
||||
|
||||
public static bool operator !=(in Identifier? a, string? b) =>
|
||||
!(a == b);
|
||||
|
||||
public static bool operator ==(string str, in Identifier? identifier) =>
|
||||
identifier == str;
|
||||
|
||||
public static bool operator !=(string str, in Identifier? identifier) =>
|
||||
!(identifier == str);
|
||||
}
|
||||
|
||||
public static class IdentifierExtensions
|
||||
{
|
||||
public static IEnumerable<Identifier> ToIdentifiers(this IEnumerable<string> strings)
|
||||
{
|
||||
foreach (string s in strings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) { continue; }
|
||||
yield return new Identifier(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier[] ToIdentifiers(this string[] strings)
|
||||
=> ((IEnumerable<string>)strings).ToIdentifiers().ToArray();
|
||||
|
||||
public static Identifier ToIdentifier(this string? s)
|
||||
{
|
||||
return new Identifier(s);
|
||||
}
|
||||
|
||||
public static Identifier ToIdentifier<T>(this T t) where T: notnull
|
||||
{
|
||||
return t.ToString().ToIdentifier();
|
||||
}
|
||||
|
||||
public static bool Contains(this ISet<Identifier> set, string identifier)
|
||||
{
|
||||
return set.Contains(identifier.ToIdentifier());
|
||||
}
|
||||
|
||||
public static bool ContainsKey<T>(this IReadOnlyDictionary<Identifier, T> dictionary, string key)
|
||||
{
|
||||
return dictionary.ContainsKey(key.ToIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public sealed class MissingContentPackageException : Exception
|
||||
{
|
||||
public override string Message { get; }
|
||||
|
||||
public MissingContentPackageException(ContentPackage? whoAsked, string? missingPackage)
|
||||
{
|
||||
Message = $"\"{whoAsked?.Name ?? "[NULL]"}\" depends on a package " +
|
||||
$"with name or ID \"{missingPackage ?? "[NULL]"}\" " +
|
||||
$"that is not currently installed.";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user