Unstable 0.17.0.0

This commit is contained in:
Markus Isberg
2022-02-26 02:43:01 +09:00
parent a83f375681
commit 3974067915
913 changed files with 32472 additions and 32364 deletions
@@ -2,14 +2,14 @@
namespace Barotrauma
{
interface ISerializableEntity
public interface ISerializableEntity
{
string Name
{
get;
}
Dictionary<string, SerializableProperty> SerializableProperties
Dictionary<Identifier, SerializableProperty> SerializableProperties
{
get;
}
@@ -3,6 +3,7 @@ using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
@@ -10,6 +11,8 @@ using System.Reflection;
using System.Xml.Linq;
using Barotrauma.Networking;
//TODO: come back to this later, clever use of reflection would make this nicer >:)
namespace Barotrauma
{
[AttributeUsage(AttributeTargets.Property)]
@@ -77,7 +80,8 @@ namespace Barotrauma
//I would love to see a better way to do this
AllowLinkingWifiToChat,
IsSwappableItem,
AllowRotating
AllowRotating,
Attachable
}
public bool IsEditable(ISerializableEntity entity)
@@ -94,18 +98,27 @@ namespace Barotrauma
{
return entity is Item item && item.body == null && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen;
}
case ConditionType.Attachable:
{
return entity is Holdable holdable && holdable.Attachable;
}
}
return false;
}
}
public enum IsPropertySaveable
{
Yes,
No
}
[AttributeUsage(AttributeTargets.Property)]
public class Serialize : Attribute
{
public object defaultValue;
public bool isSaveable;
public string translationTextTag;
public readonly object DefaultValue;
public readonly IsPropertySaveable IsSaveable;
public readonly Identifier TranslationTextTag;
/// <summary>
/// If set to true, the instance values saved in a submarine file will always override the prefab values, even if using a mod that normally overrides instance values.
@@ -122,48 +135,49 @@ namespace Barotrauma
/// <param name="translationTextTag">If set to anything else than null, SerializableEntityEditors will show what the text gets translated to or warn if the text is not found in the language files.
/// <param name="alwaysUseInstanceValues">If set to true, the instance values saved in a submarine file will always override the prefab values, even if using a mod that normally overrides instance values.
/// Setting the value to a non-empty string will let the user select the text from one whose tag starts with the given string (e.g. RoomName. would show all texts with a RoomName.* tag)</param>
public Serialize(object defaultValue, bool isSaveable, string description = "", string translationTextTag = null, bool alwaysUseInstanceValues = false)
public Serialize(object defaultValue, IsPropertySaveable isSaveable, string description = "", string translationTextTag = "", bool alwaysUseInstanceValues = false)
{
this.defaultValue = defaultValue;
this.isSaveable = isSaveable;
this.translationTextTag = translationTextTag;
this.DefaultValue = defaultValue;
this.IsSaveable = isSaveable;
this.TranslationTextTag = translationTextTag.ToIdentifier();
Description = description;
AlwaysUseInstanceValues = alwaysUseInstanceValues;
}
}
class SerializableProperty
public class SerializableProperty
{
private static Dictionary<Type, string> supportedTypes = new Dictionary<Type, string>
private readonly static ImmutableDictionary<Type, string> supportedTypes = new Dictionary<Type, string>
{
{ typeof(bool), "bool" },
{ typeof(int), "int" },
{ typeof(float), "float" },
{ typeof(string), "string" },
{ typeof(Identifier), "identifier" },
{ typeof(LocalizedString), "localizedstring" },
{ typeof(Point), "point" },
{ typeof(Vector2), "vector2" },
{ typeof(Vector3), "vector3" },
{ typeof(Vector4), "vector4" },
{ typeof(Rectangle), "rectangle" },
{ typeof(Color), "color" },
{ typeof(string[]), "stringarray" }
};
{ typeof(string[]), "stringarray" },
{ typeof(Identifier[]), "identifierarray" }
}.ToImmutableDictionary();
private static readonly Dictionary<Type, Dictionary<string, SerializableProperty>> cachedProperties =
new Dictionary<Type, Dictionary<string, SerializableProperty>>();
private static readonly Dictionary<Type, Dictionary<Identifier, SerializableProperty>> cachedProperties =
new Dictionary<Type, Dictionary<Identifier, SerializableProperty>>();
public readonly string Name;
public readonly string NameToLowerInvariant;
public readonly AttributeCollection Attributes;
public readonly Type PropertyType;
public readonly bool OverridePrefabValues;
public PropertyInfo PropertyInfo { get; private set; }
public readonly PropertyInfo PropertyInfo;
public SerializableProperty(PropertyDescriptor property)
{
Name = property.Name;
NameToLowerInvariant = Name.ToLowerInvariant();
PropertyInfo = property.ComponentType.GetProperty(property.Name);
PropertyType = property.PropertyType;
Attributes = property.Attributes;
@@ -215,8 +229,7 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError("Failed to set the value of the property \"" + Name + "\" of \"" + parentObject + "\" to " + value);
DebugConsole.ThrowError("(Type not supported)");
DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (Type \"{PropertyType.Name}\" not supported)");
return false;
}
@@ -274,12 +287,20 @@ namespace Barotrauma
case "rectangle":
PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect(value, true));
break;
case "identifier":
PropertyInfo.SetValue(parentObject, value.ToIdentifier());
break;
case "localizedstring":
PropertyInfo.SetValue(parentObject, new RawLString(value));
break;
case "stringarray":
PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray(value));
break;
case "identifierarray":
PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray(value).ToIdentifiers().ToArray());
break;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to set the value of the property \"" + Name + "\" of \"" + parentObject.ToString() + "\" to " + value.ToString(), e);
@@ -307,7 +328,8 @@ namespace Barotrauma
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to set the value of the property \"" + Name + "\" of \"" + parentObject + "\" to " + value + " (not a valid " + PropertyInfo.PropertyType + ")", e);
DebugConsole.ThrowError(
$"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (not a valid {PropertyInfo.PropertyType})", e);
return false;
}
PropertyInfo.SetValue(parentObject, enumVal);
@@ -315,8 +337,7 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError("Failed to set the value of the property \"" + Name + "\" of \"" + parentObject + "\" to " + value);
DebugConsole.ThrowError("(Type not supported)");
DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (Type \"{PropertyType.Name}\" not supported)");
return false;
}
@@ -349,9 +370,18 @@ namespace Barotrauma
case "rectangle":
PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect((string)value, false));
return true;
case "identifier":
PropertyInfo.SetValue(parentObject, new Identifier((string)value));
return true;
case "localizedstring":
PropertyInfo.SetValue(parentObject, new RawLString((string)value));
return true;
case "stringarray":
PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray((string)value));
break;
return true;
case "identifierarray":
PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray((string)value).ToIdentifiers().ToArray());
return true;
default:
DebugConsole.ThrowError("Failed to set the value of the property \"" + Name + "\" of \"" + parentObject.ToString() + "\" to " + value.ToString());
DebugConsole.ThrowError("(Cannot convert a string to a " + PropertyType.ToString() + ")");
@@ -527,6 +557,35 @@ namespace Barotrauma
return typeName;
}
private readonly ImmutableDictionary<Identifier, Func<object, object>> valueGetters =
new Dictionary<Identifier, Func<object, object>>()
{
{"Voltage".ToIdentifier(), (obj) => obj is Powered p ? p.Voltage : (object) null},
{"Charge".ToIdentifier(), (obj) => obj is PowerContainer p ? p.Charge : (object) null},
{"Overload".ToIdentifier(), (obj) => obj is PowerTransfer p ? p.Overload : (object) null},
{"AvailableFuel".ToIdentifier(), (obj) => obj is Reactor r ? r.AvailableFuel : (object) null},
{"FissionRate".ToIdentifier(), (obj) => obj is Reactor r ? r.FissionRate : (object) null},
{"OxygenFlow".ToIdentifier(), (obj) => obj is Vent v ? v.OxygenFlow : (object) null},
{
"CurrFlow".ToIdentifier(),
(obj) => obj is Pump p ? (object) p.CurrFlow :
obj is OxygenGenerator o ? (object)o.CurrFlow :
null
},
{"CurrentVolume".ToIdentifier(), (obj) => obj is Engine e ? e.CurrentVolume : (object)null},
{"MotionDetected".ToIdentifier(), (obj) => obj is MotionSensor m ? m.MotionDetected : (object)null},
{"Oxygen".ToIdentifier(), (obj) => obj is Character c ? c.Oxygen : (object)null},
{"Health".ToIdentifier(), (obj) => obj is Character c ? c.Health : (object)null},
{"OxygenAvailable".ToIdentifier(), (obj) => obj is Character c ? c.OxygenAvailable : (object)null},
{"PressureProtection".ToIdentifier(), (obj) => obj is Character c ? c.PressureProtection : (object)null},
{"IsDead".ToIdentifier(), (obj) => obj is Character c ? c.IsDead : (object)null},
{"IsHuman".ToIdentifier(), (obj) => obj is Character c ? c.IsHuman : (object)null},
{"IsOn".ToIdentifier(), (obj) => obj is LightComponent l ? l.IsOn : (object)null},
{"Condition".ToIdentifier(), (obj) => obj is Item i ? i.Condition : (object)null},
{"ContainerIdentifier".ToIdentifier(), (obj) => obj is Item i ? i.ContainerIdentifier : (object)null},
{"PhysicsBodyActive".ToIdentifier(), (obj) => obj is Item i ? i.PhysicsBodyActive : (object)null},
}.ToImmutableDictionary();
/// <summary>
/// Try getting the values of some commonly used properties directly without reflection
/// </summary>
@@ -683,7 +742,7 @@ namespace Barotrauma
{
case nameof(Item.ContainerIdentifier):
{
if (parentObject is Item item) { value = item.ContainerIdentifier; return true; }
if (parentObject is Item item) { value = item.ContainerIdentifier.Value; return true; }
}
break;
}
@@ -761,7 +820,7 @@ namespace Barotrauma
return editableProperties;
}
public static Dictionary<string, SerializableProperty> GetProperties(object obj)
public static Dictionary<Identifier, SerializableProperty> GetProperties(object obj)
{
Type objType = obj.GetType();
if (cachedProperties.ContainsKey(objType))
@@ -770,11 +829,11 @@ namespace Barotrauma
}
var properties = TypeDescriptor.GetProperties(obj.GetType()).Cast<PropertyDescriptor>();
Dictionary<string, SerializableProperty> dictionary = new Dictionary<string, SerializableProperty>();
Dictionary<Identifier, SerializableProperty> dictionary = new Dictionary<Identifier, SerializableProperty>();
foreach (var property in properties)
{
var serializableProperty = new SerializableProperty(property);
dictionary.Add(serializableProperty.NameToLowerInvariant, serializableProperty);
dictionary.Add(serializableProperty.Name.ToIdentifier(), serializableProperty);
}
cachedProperties[objType] = dictionary;
@@ -782,16 +841,16 @@ namespace Barotrauma
return dictionary;
}
public static Dictionary<string, SerializableProperty> DeserializeProperties(object obj, XElement element = null)
public static Dictionary<Identifier, SerializableProperty> DeserializeProperties(object obj, XElement element = null)
{
Dictionary<string, SerializableProperty> dictionary = GetProperties(obj);
Dictionary<Identifier, SerializableProperty> dictionary = GetProperties(obj);
foreach (var property in dictionary.Values)
{
//set the value of the property to the default value if there is one
foreach (var ini in property.Attributes.OfType<Serialize>())
{
property.TrySetValue(obj, ini.defaultValue);
property.TrySetValue(obj, ini.DefaultValue);
break;
}
}
@@ -802,7 +861,7 @@ namespace Barotrauma
//and set the value of the matching property if it is initializable
foreach (XAttribute attribute in element.Attributes())
{
if (!dictionary.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
if (!dictionary.TryGetValue(attribute.NameAsIdentifier(), out SerializableProperty property)) { continue; }
if (!property.Attributes.OfType<Serialize>().Any()) { continue; }
property.TrySetValue(obj, attribute.Value);
}
@@ -827,7 +886,7 @@ namespace Barotrauma
bool save = false;
foreach (var attribute in property.Attributes.OfType<Serialize>())
{
if ((attribute.isSaveable && !attribute.defaultValue.Equals(value)) ||
if ((attribute.IsSaveable == IsPropertySaveable.Yes && !attribute.DefaultValue.Equals(value)) ||
(!ignoreEditable && property.Attributes.OfType<Editable>().Any()))
{
save = true;
@@ -881,6 +940,10 @@ namespace Barotrauma
string[] stringArray = (string[])value;
stringValue = stringArray != null ? string.Join(';', stringArray) : "";
break;
case "identifierarray":
Identifier[] identifierArray = (Identifier[])value;
stringValue = identifierArray != null ? string.Join(';', identifierArray) : "";
break;
default:
stringValue = value.ToString();
break;
@@ -888,7 +951,7 @@ namespace Barotrauma
}
element.Attribute(property.Name)?.Remove();
element.SetAttributeValue(property.NameToLowerInvariant, stringValue);
element.SetAttributeValue(property.Name, stringValue);
}
}
@@ -899,9 +962,9 @@ namespace Barotrauma
/// <param name="entity">The entity to upgrade</param>
/// <param name="configElement">The XML element to get the upgrade instructions from (e.g. the config of an item prefab)</param>
/// <param name="savedVersion">The game version the entity was saved with</param>
public static void UpgradeGameVersion(ISerializableEntity entity, XElement configElement, Version savedVersion)
public static void UpgradeGameVersion(ISerializableEntity entity, ContentXElement configElement, Version savedVersion)
{
foreach (XElement subElement in configElement.Elements())
foreach (var subElement in configElement.Elements())
{
if (!subElement.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase)) { continue; }
var upgradeVersion = new Version(subElement.GetAttributeString("gameversion", "0.0.0.0"));
@@ -909,7 +972,7 @@ namespace Barotrauma
foreach (XAttribute attribute in subElement.Attributes())
{
string attributeName = attribute.Name.ToString().ToLowerInvariant();
var attributeName = attribute.NameAsIdentifier();
if (attributeName == "gameversion") { continue; }
if (attributeName == "refreshrect")
@@ -1024,19 +1087,19 @@ namespace Barotrauma
if (entity is Item item2)
{
XElement componentElement = subElement.FirstElement();
var componentElement = subElement.FirstElement();
if (componentElement == null) { continue; }
ItemComponent itemComponent = item2.Components.First(c => c.Name == componentElement.Name.ToString());
if (itemComponent == null) { continue; }
foreach (XAttribute attribute in componentElement.Attributes())
{
string attributeName = attribute.Name.ToString().ToLowerInvariant();
var attributeName = attribute.NameAsIdentifier();
if (itemComponent.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
{
FixValue(property, itemComponent, attribute);
}
}
foreach (XElement element in componentElement.Elements())
foreach (var element in componentElement.Elements())
{
switch (element.Name.ToString().ToLowerInvariant())
{
@@ -0,0 +1,233 @@
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using System.Xml.Schema;
namespace Barotrauma
{
public static class StructSerialization
{
private static readonly ImmutableDictionary<Type, MethodInfo> deserializeMethods;
private static readonly ImmutableDictionary<Type, MethodInfo> serializeMethods;
public class SkipAttribute : Attribute { }
private static bool ShouldSkip(this FieldInfo field)
=> field.GetCustomAttribute<SkipAttribute>() != null;
private static HandlerAttribute? ExtractHandler(this FieldInfo field)
=> field.GetCustomAttribute<HandlerAttribute>();
public class HandlerAttribute : Attribute
{
public readonly Func<string?, object?> Read;
public readonly Func<object?, string?> Write;
public HandlerAttribute(Type handlerType)
{
var readAction =
handlerType.GetMethod(nameof(Read), BindingFlags.Public | BindingFlags.Static)
?? throw new Exception($"Type {handlerType.Name} does not have a static {nameof(Read)} method");
var writeAction =
handlerType.GetMethod(nameof(Write), BindingFlags.Public | BindingFlags.Static)
?? throw new Exception($"Type {handlerType.Name} does not have a static {nameof(Write)} method");
var paramArray = new object?[1];
Read = (s) =>
{
paramArray[0] = s;
return readAction.Invoke(null, paramArray);
};
Write = (o) =>
{
paramArray[0] = o;
return writeAction.Invoke(null, paramArray)?.ToString();
};
}
}
static StructSerialization()
{
deserializeMethods =
typeof(StructSerialization)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m =>
{
if (!m.Name.StartsWith("Deserialize")) { return false; }
var parameters = m.GetParameters();
if (parameters.Length < 1 || parameters.Length > 2 ||
parameters[0].ParameterType != typeof(string))
{
return false;
}
return true;
})
.Select(m => (m.ReturnType, m))
.ToImmutableDictionary();
serializeMethods =
typeof(StructSerialization)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m =>
{
if (!m.Name.StartsWith("Serialize")) { return false; }
var parameters = m.GetParameters();
if (parameters.Length != 1 ||
m.ReturnType != typeof(string))
{
return false;
}
return true;
})
.Select(m => (m.GetParameters()[0].ParameterType, m))
.ToImmutableDictionary();
}
public static void CopyPropertiesFrom<T>(this ref T self, in T other) where T : struct
{
var fields = self.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => !f.IsInitOnly).ToArray();
foreach (var field in fields)
{
if (field.ShouldSkip()) { continue; }
field.SetValue(self, field.GetValue(other));
}
}
public static void DeserializeElement<T>(this ref T self, XElement element) where T : struct
{
var fields = self.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).ToArray();
//box the struct here so we don't end up
//making a copy every time we feed this to reflection
object boxedSelf = self;
foreach (var field in fields)
{
if (field.ShouldSkip()) { continue; }
boxedSelf.TryDeserialize(field, element);
}
//copy the boxed struct into the original
self = (T)boxedSelf;
}
public static void TryDeserialize(this object boxedSelf, FieldInfo field, XElement element)
{
string fieldName = field.Name.ToLowerInvariant();
string valueStr = element.GetAttributeString(fieldName, field.GetValue(boxedSelf)?.ToString() ?? "");
var handler = field.ExtractHandler();
if (handler != null)
{
field.SetValue(boxedSelf, handler.Read(valueStr));
}
else if (deserializeMethods.TryGetValue(field.FieldType, out MethodInfo? deserializeMethod))
{
object?[] parameters = { valueStr };
if (deserializeMethod.GetParameters().Length > 1)
{
Array.Resize(ref parameters, 2);
parameters[1] = field.GetValue(boxedSelf);
}
field.SetValue(boxedSelf, deserializeMethod.Invoke(boxedSelf, parameters));
}
else if (field.FieldType.IsEnum)
{
field.SetValue(boxedSelf, DeserializeEnum(field.FieldType, valueStr, (Enum)field.GetValue(boxedSelf)!));
}
}
public static string DeserializeString(string str)
{
return str;
}
public static bool DeserializeBool(string str, bool defaultValue)
{
if (bool.TryParse(str, out bool result)) { return result; }
return defaultValue;
}
public static float DeserializeFloat(string str, float defaultValue)
{
if (float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out float result)) { return result; }
return defaultValue;
}
public static Int32 DeserializeInt32(string str, Int32 defaultValue)
{
if (Int32.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out Int32 result)) { return result; }
return defaultValue;
}
public static Identifier DeserializeIdentifier(string str)
{
return str.ToIdentifier();
}
public static LanguageIdentifier DeserializeLanguageIdentifier(string str)
{
return str.ToLanguageIdentifier();
}
public static Color DeserializeColor(string str)
{
return XMLExtensions.ParseColor(str);
}
public static Enum DeserializeEnum(Type enumType, string str, Enum defaultValue)
{
if (Enum.TryParse(enumType, str, out object? result)) { return (Enum)result!; }
return defaultValue;
}
public static void SerializeElement<T>(this ref T self, XElement element) where T : struct
{
var fields = self.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).ToArray();
foreach (var field in fields)
{
if (field.ShouldSkip()) { continue; }
self.TrySerialize(field, element);
}
}
public static void TrySerialize<T>(this T self, FieldInfo field, XElement element) where T : struct
{
string fieldName = field.Name.ToLowerInvariant();
object? fieldValue = field.GetValue(self);
string valueStr = fieldValue?.ToString() ?? "";
var handler = field.ExtractHandler();
if (handler != null)
{
valueStr = handler.Write(valueStr) ?? "";
}
else if (serializeMethods.TryGetValue(field.FieldType, out MethodInfo? method))
{
object?[] parameters = { fieldValue };
valueStr = (string)method.Invoke(self, parameters)!;
}
element.SetAttributeValue(fieldName, valueStr);
}
public static string SerializeBool(bool val)
=> val ? "true" : "false";
public static string SerializeInt32(Int32 val)
=> val.ToString(CultureInfo.InvariantCulture);
public static string SerializeFloat(float val)
=> val.ToString(CultureInfo.InvariantCulture);
public static string SerializeColor(Color val)
=> val.ToStringHex();
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -69,8 +70,25 @@ namespace Barotrauma
return doc;
}
public static XDocument TryLoadXml(ContentPath path) => TryLoadXml(path.Value);
public static XDocument TryLoadXml(string filePath)
{
var doc = TryLoadXml(filePath, out var exception);
if (exception != null)
{
DebugConsole.ThrowError($"Couldn't load xml document \"{filePath}\"!", exception);
}
else if (doc is null)
{
DebugConsole.ThrowError($"File \"{filePath}\" could not be loaded: Document or the root element is invalid!");
}
return doc;
}
public static XDocument TryLoadXml(string filePath, out Exception exception)
{
exception = null;
XDocument doc;
try
{
@@ -81,42 +99,16 @@ namespace Barotrauma
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't load xml document \"" + filePath + "\"!", e);
exception = e;
return null;
}
if (doc?.Root == null)
{
DebugConsole.ThrowError("File \"" + filePath + "\" could not be loaded: Document or the root element is invalid!");
return null;
}
return doc;
}
public static XDocument LoadXml(string filePath)
{
XDocument doc = null;
ToolBox.IsProperFilenameCase(filePath);
if (File.Exists(filePath))
{
try
{
using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath));
doc = XDocument.Load(reader);
}
catch
{
return null;
}
if (doc.Root == null) { return null; }
}
return doc;
}
public static object GetAttributeObject(XAttribute attribute)
{
if (attribute == null) { return null; }
@@ -151,8 +143,48 @@ namespace Barotrauma
public static string GetAttributeString(this XElement element, string name, string defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
return GetAttributeString(element.Attribute(name), defaultValue);
if (element?.GetAttribute(name) == null) { return defaultValue; }
string str = GetAttributeString(element.GetAttribute(name), defaultValue);
#if DEBUG
if (!str.IsNullOrEmpty() &&
(str.Contains("%ModDir", StringComparison.OrdinalIgnoreCase)
|| str.CleanUpPathCrossPlatform(correctFilenameCase: false).StartsWith("Content/", StringComparison.OrdinalIgnoreCase)))
{
DebugConsole.ThrowError($"Use {nameof(GetAttributeContentPath)} instead of {nameof(GetAttributeString)}\n{Environment.StackTrace.CleanupStackTrace()}");
if (Debugger.IsAttached) { Debugger.Break(); }
}
#endif
return str;
}
public static string GetAttributeStringUnrestricted(this XElement element, string name, string defaultValue)
{
#warning TODO: remove?
if (element?.GetAttribute(name) == null) { return defaultValue; }
return GetAttributeString(element.GetAttribute(name), defaultValue);
}
public static bool DoesAttributeReferenceFileNameAlone(this XElement element, string name)
{
string texName = element.GetAttributeStringUnrestricted(name, "");
return !texName.IsNullOrEmpty() & !texName.Contains("/") && !texName.Contains("%ModDir", StringComparison.OrdinalIgnoreCase);
}
public static ContentPath GetAttributeContentPath(this XElement element, string name,
ContentPackage contentPackage)
{
if (element?.GetAttribute(name) == null) { return null; }
return ContentPath.FromRaw(contentPackage, GetAttributeString(element.GetAttribute(name), null));
}
public static Identifier GetAttributeIdentifier(this XElement element, string name, string defaultValue)
{
return element.GetAttributeString(name, defaultValue).ToIdentifier();
}
public static Identifier GetAttributeIdentifier(this XElement element, string name, Identifier defaultValue)
{
return element.GetAttributeIdentifier(name, defaultValue.Value);
}
private static string GetAttributeString(XAttribute attribute, string defaultValue)
@@ -163,43 +195,41 @@ namespace Barotrauma
public static string[] GetAttributeStringArray(this XElement element, string name, string[] defaultValue, bool trim = true, bool convertToLowerInvariant = false)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
string stringValue = element.Attribute(name).Value;
string stringValue = element.GetAttribute(name).Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',', '');
if (convertToLowerInvariant)
for (int i = 0; i < splitValue.Length; i++)
{
for (int i = 0; i < splitValue.Length; i++)
{
splitValue[i] = splitValue[i].ToLowerInvariant();
}
}
if (trim)
{
for (int i = 0; i < splitValue.Length; i++)
{
splitValue[i] = splitValue[i].Trim();
}
if (convertToLowerInvariant) { splitValue[i] = splitValue[i].ToLowerInvariant(); }
if (trim) { splitValue[i] = splitValue[i].Trim(); }
}
return splitValue;
}
public static Identifier[] GetAttributeIdentifierArray(this XElement element, string name, Identifier[] defaultValue, bool trim = true)
{
return element.GetAttributeStringArray(name, null, trim: trim, convertToLowerInvariant: false)
?.ToIdentifiers()
?? defaultValue;
}
public static float GetAttributeFloat(this XElement element, float defaultValue, params string[] matchingAttributeName)
{
if (element == null) { return defaultValue; }
foreach (string name in matchingAttributeName)
{
if (element.Attribute(name) == null) { continue; }
if (element.GetAttribute(name) == null) { continue; }
float val;
try
{
string strVal = element.Attribute(name).Value;
string strVal = element.GetAttribute(name).Value;
if (strVal.LastOrDefault() == 'f')
{
strVal = strVal.Substring(0, strVal.Length - 1);
@@ -219,12 +249,12 @@ namespace Barotrauma
public static float GetAttributeFloat(this XElement element, string name, float defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
float val = defaultValue;
try
{
string strVal = element.Attribute(name).Value;
string strVal = element.GetAttribute(name).Value;
if (strVal.LastOrDefault() == 'f')
{
strVal = strVal.Substring(0, strVal.Length - 1);
@@ -286,9 +316,9 @@ namespace Barotrauma
public static float[] GetAttributeFloatArray(this XElement element, string name, float[] defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
string stringValue = element.Attribute(name).Value;
string stringValue = element.GetAttribute(name).Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',');
@@ -315,15 +345,15 @@ namespace Barotrauma
public static int GetAttributeInt(this XElement element, string name, int defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
int val = defaultValue;
try
{
if (!Int32.TryParse(element.Attribute(name).Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val))
if (!Int32.TryParse(element.GetAttribute(name).Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val))
{
val = (int)float.Parse(element.Attribute(name).Value, CultureInfo.InvariantCulture);
val = (int)float.Parse(element.GetAttribute(name).Value, CultureInfo.InvariantCulture);
}
}
catch (Exception e)
@@ -336,13 +366,13 @@ namespace Barotrauma
public static uint GetAttributeUInt(this XElement element, string name, uint defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
uint val = defaultValue;
try
{
val = UInt32.Parse(element.Attribute(name).Value);
val = UInt32.Parse(element.GetAttribute(name).Value);
}
catch (Exception e)
{
@@ -354,13 +384,31 @@ namespace Barotrauma
public static UInt64 GetAttributeUInt64(this XElement element, string name, UInt64 defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
UInt64 val = defaultValue;
try
{
val = UInt64.Parse(element.Attribute(name).Value);
val = UInt64.Parse(element.GetAttribute(name).Value, NumberStyles.Any, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + element + "! ", e);
}
return val;
}
public static Version GetAttributeVersion(this XElement element, string name, Version defaultValue)
{
if (element?.GetAttribute(name) == null) return defaultValue;
Version val = defaultValue;
try
{
val = Version.Parse(element.GetAttribute(name).Value);
}
catch (Exception e)
{
@@ -372,13 +420,13 @@ namespace Barotrauma
public static UInt64 GetAttributeSteamID(this XElement element, string name, UInt64 defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
UInt64 val = defaultValue;
try
{
val = Steam.SteamManager.SteamIDStringToUInt64(element.Attribute(name).Value);
val = Steam.SteamManager.SteamIDStringToUInt64(element.GetAttribute(name).Value);
}
catch (Exception e)
{
@@ -390,9 +438,9 @@ namespace Barotrauma
public static int[] GetAttributeIntArray(this XElement element, string name, int[] defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
string stringValue = element.Attribute(name).Value;
string stringValue = element.GetAttribute(name).Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',');
@@ -414,9 +462,9 @@ namespace Barotrauma
}
public static ushort[] GetAttributeUshortArray(this XElement element, string name, ushort[] defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
string stringValue = element.Attribute(name).Value;
string stringValue = element.GetAttribute(name).Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(',');
@@ -448,8 +496,8 @@ namespace Barotrauma
public static bool GetAttributeBool(this XElement element, string name, bool defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
return element.Attribute(name).GetAttributeBool(defaultValue);
if (element?.GetAttribute(name) == null) { return defaultValue; }
return element.GetAttribute(name).GetAttributeBool(defaultValue);
}
public static bool GetAttributeBool(this XAttribute attribute, bool defaultValue)
@@ -472,45 +520,45 @@ namespace Barotrauma
public static Point GetAttributePoint(this XElement element, string name, Point defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
return ParsePoint(element.Attribute(name).Value);
if (element?.GetAttribute(name) == null) { return defaultValue; }
return ParsePoint(element.GetAttribute(name).Value);
}
public static Vector2 GetAttributeVector2(this XElement element, string name, Vector2 defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
return ParseVector2(element.Attribute(name).Value);
if (element?.GetAttribute(name) == null) { return defaultValue; }
return ParseVector2(element.GetAttribute(name).Value);
}
public static Vector3 GetAttributeVector3(this XElement element, string name, Vector3 defaultValue)
{
if (element == null || element.Attribute(name) == null) { return defaultValue; }
return ParseVector3(element.Attribute(name).Value);
if (element == null || element.GetAttribute(name) == null) { return defaultValue; }
return ParseVector3(element.GetAttribute(name).Value);
}
public static Vector4 GetAttributeVector4(this XElement element, string name, Vector4 defaultValue)
{
if (element == null || element.Attribute(name) == null) { return defaultValue; }
return ParseVector4(element.Attribute(name).Value);
if (element == null || element.GetAttribute(name) == null) { return defaultValue; }
return ParseVector4(element.GetAttribute(name).Value);
}
public static Color GetAttributeColor(this XElement element, string name, Color defaultValue)
{
if (element == null || element.Attribute(name) == null) { return defaultValue; }
return ParseColor(element.Attribute(name).Value);
if (element == null || element.GetAttribute(name) == null) { return defaultValue; }
return ParseColor(element.GetAttribute(name).Value);
}
public static Color? GetAttributeColor(this XElement element, string name)
{
if (element == null || element.Attribute(name) == null) { return null; }
return ParseColor(element.Attribute(name).Value);
if (element == null || element.GetAttribute(name) == null) { return null; }
return ParseColor(element.GetAttribute(name).Value);
}
public static Color[] GetAttributeColorArray(this XElement element, string name, Color[] defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
if (element?.GetAttribute(name) == null) { return defaultValue; }
string stringValue = element.Attribute(name).Value;
string stringValue = element.GetAttribute(name).Value;
if (string.IsNullOrEmpty(stringValue)) { return defaultValue; }
string[] splitValue = stringValue.Split(';');
@@ -531,10 +579,31 @@ namespace Barotrauma
return colorValue;
}
#if CLIENT
public static KeyOrMouse GetAttributeKeyOrMouse(this XElement element, string name, KeyOrMouse defaultValue)
{
string strValue = element.GetAttributeString(name, defaultValue?.ToString() ?? "");
if (Enum.TryParse(strValue, true, out Microsoft.Xna.Framework.Input.Keys key))
{
return key;
}
else if (Enum.TryParse(strValue, out MouseButton mouseButton))
{
return mouseButton;
}
else if (int.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int mouseButtonInt) &&
(Enum.GetValues(typeof(MouseButton)) as MouseButton[]).Contains((MouseButton)mouseButtonInt))
{
return (MouseButton)mouseButtonInt;
}
return defaultValue;
}
#endif
public static Rectangle GetAttributeRect(this XElement element, string name, Rectangle defaultValue)
{
if (element == null || element.Attribute(name) == null) { return defaultValue; }
return ParseRect(element.Attribute(name).Value, false);
if (element == null || element.GetAttribute(name) == null) { return defaultValue; }
return ParseRect(element.GetAttribute(name).Value, false);
}
//TODO: nested tuples and and n-uples where n!=2 are unsupported
@@ -703,16 +772,10 @@ namespace Barotrauma
if (stringColor.StartsWith("gui.", StringComparison.OrdinalIgnoreCase))
{
#if CLIENT
if (GUI.Style != null)
Identifier colorName = stringColor.Substring(4).ToIdentifier();
if (GUIStyle.Colors.TryGetValue(colorName, out GUIColor guiColor))
{
string colorName = stringColor.Substring(4);
var property = GUI.Style.GetType().GetProperties().FirstOrDefault(
p => p.PropertyType == typeof(Color) &&
p.Name.Equals(colorName, StringComparison.OrdinalIgnoreCase));
if (property != null)
{
return (Color)property?.GetValue(GUI.Style);
}
return guiColor.Value;
}
#endif
return Color.White;
@@ -727,7 +790,7 @@ namespace Barotrauma
if (strComponents.Length == 1)
{
bool hexFailed = true;
bool altParseFailed = true;
stringColor = stringColor.Trim();
if (stringColor.Length > 0 && stringColor[0] == '#')
{
@@ -744,11 +807,40 @@ namespace Barotrauma
components[2] = ((float)((colorInt & 0x0000ff00) >> 8)) / 255.0f;
components[3] = ((float)(colorInt & 0x000000ff)) / 255.0f;
hexFailed = false;
altParseFailed = false;
}
}
else if (stringColor.Length > 0 && stringColor[0] == '{')
{
stringColor = stringColor.Substring(1, stringColor.Length-2);
string[] mgComponents = stringColor.Split(' ');
if (mgComponents.Length == 4)
{
altParseFailed = false;
string[] expectedPrefixes = {"R:", "G:", "B:", "A:"};
for (int i = 0; i < 4; i++)
{
if (mgComponents[i].StartsWith(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase))
{
string strToParse = mgComponents[i]
.Remove(expectedPrefixes[i], StringComparison.OrdinalIgnoreCase)
.Trim();
int val = 0;
altParseFailed |= !int.TryParse(strToParse, out val);
components[i] = ((float) val) / 255f;
}
else
{
altParseFailed = true;
break;
}
}
}
}
if (hexFailed)
if (altParseFailed)
{
if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringColor + "\" to Color"); }
return Color.White;
@@ -807,18 +899,27 @@ namespace Barotrauma
return floatArray;
}
public static Identifier VariantOf(this XElement element) =>
element.GetAttributeIdentifier("inherit", element.GetAttributeIdentifier("variantof", ""));
public static string[] ParseStringArray(string stringArrayValues)
{
return string.IsNullOrEmpty(stringArrayValues) ? new string[0] : stringArrayValues.Split(';');
return string.IsNullOrEmpty(stringArrayValues) ? Array.Empty<string>() : stringArrayValues.Split(';');
}
public static Identifier[] ParseIdentifierArray(string stringArrayValues)
{
return ParseStringArray(stringArrayValues).ToIdentifiers().ToArray();
}
public static bool IsOverride(this XElement element) => element.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase);
public static bool IsCharacterVariant(this XElement element) => element.Name.ToString().Equals("charactervariant", StringComparison.OrdinalIgnoreCase);
public static bool IsOverride(this XElement element) => element.NameAsIdentifier() == "override";
public static XElement FirstElement(this XElement element) => element.Elements().FirstOrDefault();
public static XAttribute GetAttribute(this XElement element, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => element.GetAttribute(a => a.Name.ToString().Equals(name, comparisonMethod));
public static XAttribute GetAttribute(this XElement element, Identifier name) => element.GetAttribute(name.Value, StringComparison.OrdinalIgnoreCase);
public static XAttribute GetAttribute(this XElement element, Func<XAttribute, bool> predicate) => element.Attributes().FirstOrDefault(predicate);
/// <summary>
@@ -830,5 +931,31 @@ namespace Barotrauma
/// Returns all child elements that match the name using the provided comparison method.
/// </summary>
public static IEnumerable<XElement> GetChildElements(this XContainer container, string name, StringComparison comparisonMethod = StringComparison.OrdinalIgnoreCase) => container.Elements().Where(e => e.Name.ToString().Equals(name, comparisonMethod));
public static IEnumerable<XElement> GetChildElements(this XContainer container, params string[] names)
{
return names.SelectMany(name => container.GetChildElements(name));
}
public static bool ComesAfter(this XElement element, XElement other)
{
if (element.Parent != other.Parent) { return false; }
foreach (var child in element.Parent.Elements())
{
if (child == element) { return false; }
if (child == other) { return true; }
}
return false;
}
public static Identifier NameAsIdentifier(this XElement elem)
{
return elem.Name.LocalName.ToIdentifier();
}
public static Identifier NameAsIdentifier(this XAttribute attr)
{
return attr.Name.LocalName.ToIdentifier();
}
}
}