(965c31410a) Unstable v0.10.4.0
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
// ReSharper disable ArrangeThisQualifier
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class PropertyReference
|
||||
{
|
||||
public object? OriginalValue { get; private set; }
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
private readonly string Multiplier;
|
||||
|
||||
private readonly char[] prefixCharacters = { '=', '/', '*', 'x', '-', '+' };
|
||||
|
||||
private readonly Upgrade upgrade;
|
||||
|
||||
private PropertyReference(string name, string multiplier, Upgrade upgrade)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Multiplier = multiplier;
|
||||
this.upgrade = upgrade;
|
||||
}
|
||||
|
||||
public void SetOriginalValue(object value)
|
||||
{
|
||||
OriginalValue ??= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the new value of the property
|
||||
/// </summary>
|
||||
/// <param name="level">level of the upgrade</param>
|
||||
/// <param name="sourceElement">Optional XElement reference, only used for error logging.</param>
|
||||
/// <returns></returns>
|
||||
public float CalculateUpgrade(int level, XElement? sourceElement = null)
|
||||
{
|
||||
if (OriginalValue is float || OriginalValue is int || OriginalValue is double)
|
||||
{
|
||||
var value = (float) OriginalValue;
|
||||
|
||||
if (Multiplier[^1] != '%')
|
||||
{
|
||||
float multiplier = ParseValue();
|
||||
switch (Multiplier[0])
|
||||
{
|
||||
case '*':
|
||||
case 'x':
|
||||
return value * (multiplier * level);
|
||||
case '/':
|
||||
return value / (multiplier * level);
|
||||
case '-':
|
||||
return value - (multiplier * level);
|
||||
case '+':
|
||||
return value + (multiplier * level);
|
||||
case '=':
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float multiplier = UpgradePrefab.ParsePercentage(Multiplier, Name, sourceElement, upgrade.Prefab.SupressWarnings);
|
||||
return ApplyPercentage(value, multiplier, level);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Original value of \"{Name}\" in the upgrade \"{upgrade.Prefab.Name}\" is not a integer, float or a double but {OriginalValue?.GetType()} with a value of ({OriginalValue}). \n" +
|
||||
"The value has been assumed to be '0', did you forget a Convert.ChangeType()?");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the OriginalValue to a value stored in the save XML element
|
||||
/// </summary>
|
||||
/// <param name="savedElement"></param>
|
||||
public void ApplySavedValue(XElement? savedElement)
|
||||
{
|
||||
if (savedElement == null) { return; }
|
||||
|
||||
foreach (var savedValue in savedElement.Elements())
|
||||
{
|
||||
if (string.Equals(savedValue.Name.ToString(), Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OriginalValue = savedValue.GetAttributeFloat("value", 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively apply a percentage to a value certain amount of times
|
||||
/// </summary>
|
||||
/// <param name="value">original value</param>
|
||||
/// <param name="amount">percentage increase/decrease</param>
|
||||
/// <param name="times">how many times to apply the percentage change</param>
|
||||
/// <returns></returns>
|
||||
private static float ApplyPercentage(float value, float amount, int times)
|
||||
{
|
||||
return times <= 0 ? value : ApplyPercentage(value + (value * amount / 100), amount, --times);
|
||||
}
|
||||
|
||||
public static PropertyReference[] ParseAttributes(IEnumerable<XAttribute> attributes, Upgrade upgrade)
|
||||
{
|
||||
return attributes.Select(attribute => new PropertyReference(attribute.Name.ToString(), attribute.Value, upgrade)).ToArray();
|
||||
}
|
||||
|
||||
private float ParseValue()
|
||||
{
|
||||
if (Multiplier.Length > 1)
|
||||
{
|
||||
if (prefixCharacters.Contains(Multiplier[0]))
|
||||
{
|
||||
if (float.TryParse(Multiplier.Substring(1).Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out float value)) { return value; }
|
||||
|
||||
if (OriginalValue is float || OriginalValue is int || OriginalValue is double) { return (float) OriginalValue; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!upgrade.Prefab.SupressWarnings)
|
||||
{
|
||||
DebugConsole.AddWarning($"Multiplier for {Name} is too short or does not contain proper prefix. \n" +
|
||||
$"The value should start with {string.Join(",", prefixCharacters)} and contain a floating point value or another property. \n" +
|
||||
"The value has been assumed to be '1'.");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Upgrade : IDisposable
|
||||
{
|
||||
private ISerializableEntity TargetEntity { get; }
|
||||
|
||||
public Dictionary<ISerializableEntity, PropertyReference[]> TargetComponents { get; }
|
||||
|
||||
public UpgradePrefab Prefab { get; }
|
||||
|
||||
public string Identifier => Prefab.Identifier;
|
||||
|
||||
public int Level { get; set; }
|
||||
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
private readonly XElement sourceElement;
|
||||
|
||||
public Upgrade(ISerializableEntity targetEntity, UpgradePrefab prefab, int level, XContainer? saveElement = null)
|
||||
{
|
||||
this.TargetEntity = targetEntity;
|
||||
this.sourceElement = prefab.SourceElement;
|
||||
this.Prefab = prefab;
|
||||
this.Level = level;
|
||||
|
||||
var targetProperties = new Dictionary<ISerializableEntity, PropertyReference[]>();
|
||||
|
||||
List<XElement>? saveElements = saveElement?.Elements().ToList();
|
||||
|
||||
foreach (XElement subElement in prefab.SourceElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "decorativesprite":
|
||||
case "sprite":
|
||||
case "price":
|
||||
break;
|
||||
case "item":
|
||||
case "structure":
|
||||
case "base":
|
||||
case "root":
|
||||
case "this":
|
||||
XElement? savedRootElement = saveElements?.Find(e => string.Equals(e.Name.ToString(), "This", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var rootProperties = PropertyReference.ParseAttributes(subElement.Attributes(), this);
|
||||
targetProperties.Add(targetEntity, rootProperties);
|
||||
|
||||
foreach (var propertyRef in rootProperties)
|
||||
{
|
||||
propertyRef.ApplySavedValue(savedRootElement);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (targetEntity is Item item)
|
||||
{
|
||||
ISerializableEntity[]? itemComponents = FindItemComponent(item, subElement.Name.ToString());
|
||||
|
||||
if (itemComponents != null && itemComponents.Any())
|
||||
{
|
||||
foreach (ISerializableEntity sEntity in itemComponents)
|
||||
{
|
||||
XElement? savedElement = saveElements?.Find(e => string.Equals(e.Name.ToString(), sEntity.Name, StringComparison.OrdinalIgnoreCase));
|
||||
PropertyReference[] properties = PropertyReference.ParseAttributes(subElement.Attributes(), this);
|
||||
|
||||
foreach (PropertyReference propertyRef in properties)
|
||||
{
|
||||
propertyRef.ApplySavedValue(savedElement);
|
||||
}
|
||||
|
||||
targetProperties.Add(sEntity, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TargetComponents = targetProperties;
|
||||
|
||||
if (saveElement != null)
|
||||
{
|
||||
ResetNonAffectedProperties(saveElement);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds saved properties in the XML element and resets properties that are not managed by the upgrade anymore to their default values
|
||||
/// </summary>
|
||||
/// <param name="saveElement">XML save element</param>
|
||||
private void ResetNonAffectedProperties(XContainer saveElement)
|
||||
{
|
||||
foreach (var element in saveElement.Elements().Elements())
|
||||
{
|
||||
if (TargetComponents.SelectMany(pair => pair.Value)
|
||||
.Select(@ref => @ref.Name)
|
||||
.Any(@string => string.Equals(@string, element.Name.ToString(), StringComparison.OrdinalIgnoreCase))) { continue; }
|
||||
|
||||
string value = element.GetAttributeString("value", string.Empty);
|
||||
string name = element.Name.ToString();
|
||||
string componentName = element.Parent.Name.ToString();
|
||||
|
||||
DebugConsole.AddWarning($"Upgrade \"{Prefab.Name}\" in {TargetEntity.Name} does not affect the property \"{name}\" but the save file suggest it has done so before (has it been overriden?). \n" +
|
||||
$"The property has been reset to the original value of {value} and will be ignored from now on.");
|
||||
|
||||
if (string.Equals(componentName, "This", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (TargetEntity.SerializableProperties.TryGetValue(name, out SerializableProperty? property))
|
||||
{
|
||||
property?.SetValue(TargetEntity, Convert.ChangeType(value, property!.GetValue(TargetEntity).GetType(), NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
}
|
||||
else if (TargetEntity is Item item)
|
||||
{
|
||||
ISerializableEntity[]? foundComponents = FindItemComponent(item, componentName);
|
||||
if (foundComponents == null) { continue; }
|
||||
|
||||
foreach (var serializableEntity in foundComponents)
|
||||
{
|
||||
if (serializableEntity.SerializableProperties.TryGetValue(name, out SerializableProperty? property))
|
||||
{
|
||||
property?.SetValue(serializableEntity, Convert.ChangeType(value, property!.GetValue(serializableEntity).GetType(), NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an item component matching the XML element
|
||||
/// </summary>
|
||||
/// <param name="item">Target item</param>
|
||||
/// <param name="name">XML ItemComponent element</param>
|
||||
/// <returns>Array of matching ItemComponents or null</returns>
|
||||
private static ISerializableEntity[]? FindItemComponent(Item item, string name)
|
||||
{
|
||||
Type? type = Type.GetType($"Barotrauma.Items.Components.{name.ToLowerInvariant()}", false, true);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
int count = item.Components.Count(ic => ic.GetType() == type);
|
||||
if (count == 0) { return null; }
|
||||
|
||||
IEnumerable<ItemComponent> itemComponents = item.Components.Where(ic => ic.GetType() == type);
|
||||
return itemComponents.Cast<ISerializableEntity>().ToArray();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
{
|
||||
var upgrade = new XElement("Upgrade", new XAttribute("identifier", Identifier), new XAttribute("level", Level));
|
||||
|
||||
foreach (var targetComponent in TargetComponents)
|
||||
{
|
||||
var (key, value) = targetComponent;
|
||||
|
||||
string name = key is ItemComponent ? key.Name : "This";
|
||||
|
||||
XElement subElement = new XElement(name);
|
||||
foreach (PropertyReference propertyRef in value)
|
||||
{
|
||||
if (propertyRef.OriginalValue != null)
|
||||
{
|
||||
subElement.Add(new XElement(propertyRef.Name,
|
||||
new XAttribute("value", propertyRef.OriginalValue)));
|
||||
}
|
||||
else if (!Prefab.SupressWarnings)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to save upgrade \"{Prefab.Name}\" on {TargetEntity.Name} because property reference \"{propertyRef.Name}\" is missing original values. \n" +
|
||||
"Upgrades should always call Upgrade.ApplyUpgrade() or manually set the original value in a property reference after they have been added. \n" +
|
||||
"If you are not a developer submit a bug report at https://github.com/Regalis11/Barotrauma/issues/.");
|
||||
}
|
||||
}
|
||||
|
||||
upgrade.Add(subElement);
|
||||
}
|
||||
|
||||
element.Add(upgrade);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the upgrade to the target item and components
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should be called every time a new upgrade is added unless you set the original values of PropertyReference manually.
|
||||
/// Do note that <see cref="MapEntity.AddUpgrade"/> calls this method automatically.
|
||||
/// </remarks>
|
||||
public void ApplyUpgrade()
|
||||
{
|
||||
foreach (var keyValuePair in TargetComponents)
|
||||
{
|
||||
var (entity, properties) = keyValuePair;
|
||||
|
||||
foreach (PropertyReference propertyReference in properties)
|
||||
{
|
||||
if (entity.SerializableProperties.TryGetValue(propertyReference.Name, out SerializableProperty? property) && property != null)
|
||||
{
|
||||
object? originalValue = property!.GetValue(entity);
|
||||
propertyReference.SetOriginalValue(originalValue);
|
||||
object newValue = Convert.ChangeType(propertyReference.CalculateUpgrade(Level, sourceElement), originalValue.GetType(), NumberFormatInfo.InvariantInfo);
|
||||
property!.SetValue(entity, newValue);
|
||||
#if SERVER
|
||||
// if (TargetEntity is IServerSerializable clientSerializable && !IsEqual(originalValue, newValue))
|
||||
// {
|
||||
// GameMain.Server.CreateEntityEvent(clientSerializable, new object[] { NetEntityEvent.Type.ChangeProperty, property });
|
||||
// }
|
||||
//
|
||||
// static bool IsEqual(object item1, object item2)
|
||||
// {
|
||||
// if (item1 is float float1 && item2 is float float2)
|
||||
// {
|
||||
// return MathUtils.NearlyEqual(float1, float2);
|
||||
// }
|
||||
//
|
||||
// return item1 == item2;
|
||||
// }
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the closest matching property name and suggest it in the error message
|
||||
string matchingString = string.Empty;
|
||||
int closestMatch = int.MaxValue;
|
||||
foreach (var (propertyName, _) in entity.SerializableProperties)
|
||||
{
|
||||
int match = ToolBox.LevenshteinDistance(propertyName, propertyReference.Name);
|
||||
if (match < closestMatch)
|
||||
{
|
||||
matchingString = propertyName;
|
||||
closestMatch = match;
|
||||
}
|
||||
}
|
||||
|
||||
DebugConsole.ThrowError($"The upgrade \"{Prefab.Name}\" cannot be applied to {entity.Name} because it does not contain the property \"{propertyReference.Name}\" and has been ignored. \n" +
|
||||
$"Did you mean \"{matchingString}\"?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
TargetComponents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal readonly struct UpgradePrice
|
||||
{
|
||||
public readonly int BasePrice;
|
||||
|
||||
public readonly int IncreaseLow;
|
||||
|
||||
public readonly int IncreaseHigh;
|
||||
|
||||
public readonly UpgradePrefab Prefab;
|
||||
|
||||
public UpgradePrice(UpgradePrefab prefab, XElement element)
|
||||
{
|
||||
Prefab = prefab;
|
||||
|
||||
IncreaseLow = UpgradePrefab.ParsePercentage(element.GetAttributeString("increaselow", string.Empty),
|
||||
"IncreaseLow", element, suppressWarnings: prefab.SupressWarnings);
|
||||
|
||||
IncreaseHigh = UpgradePrefab.ParsePercentage(element.GetAttributeString("increasehigh", string.Empty),
|
||||
"IncreaseHigh", element, suppressWarnings: prefab.SupressWarnings);
|
||||
|
||||
BasePrice = element.GetAttributeInt("baseprice", -1);
|
||||
|
||||
if (BasePrice == -1)
|
||||
{
|
||||
if (prefab.SupressWarnings)
|
||||
{
|
||||
DebugConsole.AddWarning($"Price attribute \"baseprice\" is not defined for {prefab?.Identifier}.\n " +
|
||||
"The value has been assumed to be '1000'.");
|
||||
BasePrice = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetBuyprice(int level, Location? location = null)
|
||||
{
|
||||
int price = BasePrice;
|
||||
for (int i = 1; i <= level; i++)
|
||||
{
|
||||
price += (int)(price * MathHelper.Lerp( IncreaseLow, IncreaseHigh, i / (float)Prefab.MaxLevel) / 100);
|
||||
}
|
||||
return location?.GetAdjustedMechanicalCost(price) ?? price;
|
||||
}
|
||||
}
|
||||
|
||||
internal class UpgradeCategory
|
||||
{
|
||||
public static readonly List<UpgradeCategory> Categories = new List<UpgradeCategory>();
|
||||
|
||||
public readonly string[] ItemTags;
|
||||
public readonly string Identifier;
|
||||
public readonly bool IsWallUpgrade;
|
||||
public readonly string Name;
|
||||
|
||||
public UpgradeCategory(XElement element)
|
||||
{
|
||||
ItemTags = element.GetAttributeStringArray("items", new string[] { });
|
||||
Identifier = element.GetAttributeString("identifier", string.Empty);
|
||||
Name = element.GetAttributeString("name", string.Empty);
|
||||
IsWallUpgrade = element.GetAttributeBool("wallupgrade", false);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
Name = TextManager.Get($"UpgradeCategory.{Identifier}", true) ?? string.Empty;
|
||||
}
|
||||
|
||||
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
string[] identifierArray = itemPrefab.AllowedUpgrades.Split(",");
|
||||
if (identifierArray.Contains(Identifier))
|
||||
{
|
||||
ItemTags = ItemTags.Concat(new[] { itemPrefab.Identifier }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
Categories.Add(this);
|
||||
}
|
||||
|
||||
public bool CanBeApplied(Item item, UpgradePrefab? upgradePrefab = null)
|
||||
{
|
||||
if (IsWallUpgrade) { return false; }
|
||||
|
||||
if (upgradePrefab != null && item.disallowedUpgrades.Contains(upgradePrefab.Identifier)) { return false; }
|
||||
|
||||
return item.prefab.GetAllowedUpgrades().Contains(Identifier) ||
|
||||
ItemTags.Any(tag => item.Prefab.Tags.Contains(tag) || item.Prefab.Identifier.Equals(tag, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public bool CanBeApplied(XElement element)
|
||||
{
|
||||
if (string.Equals("Structure", element.Name.ToString(), StringComparison.OrdinalIgnoreCase)) { return IsWallUpgrade; }
|
||||
|
||||
string identifier = element.GetAttributeString("identifier", string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(identifier)) { return false; }
|
||||
|
||||
ItemPrefab? item = ItemPrefab.Find(null, identifier);
|
||||
if (item == null) { return false; }
|
||||
|
||||
return item.GetAllowedUpgrades().Contains(Identifier) ||
|
||||
ItemTags.Any(tag => item.Tags.Contains(tag) || item.Identifier.Equals(tag, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public static UpgradeCategory? Find(string idenfitier)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(idenfitier) ? Categories.Find(category => string.Equals(category.Identifier, idenfitier, StringComparison.OrdinalIgnoreCase)) : null;
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class UpgradePrefab : IPrefab, IDisposable
|
||||
{
|
||||
public static readonly PrefabCollection<UpgradePrefab> Prefabs = new PrefabCollection<UpgradePrefab>();
|
||||
|
||||
public int MaxLevel { get; }
|
||||
|
||||
public string OriginalName { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public string Identifier { get; }
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public UpgradeCategory[] UpgradeCategories { get; }
|
||||
|
||||
public UpgradePrice Price { get; }
|
||||
|
||||
public ContentPackage? ContentPackage { get; private set; }
|
||||
|
||||
private bool IsOverride { get; }
|
||||
|
||||
public XElement SourceElement { get; }
|
||||
|
||||
private bool Disposed { get; set; }
|
||||
|
||||
public bool SupressWarnings { get; }
|
||||
|
||||
public bool HideInMenus { get; }
|
||||
|
||||
public IEnumerable<string> TargetItems => UpgradeCategories.SelectMany(u => u.ItemTags);
|
||||
|
||||
public bool IsWallUpgrade => UpgradeCategories.All(u => u.IsWallUpgrade);
|
||||
|
||||
private Dictionary<string, string[]> TargetProperties { get; }
|
||||
|
||||
private UpgradePrefab(XElement element, string filePath, bool isOverride)
|
||||
{
|
||||
Name = element.GetAttributeString("name", string.Empty);
|
||||
Description = element.GetAttributeString("description", string.Empty);
|
||||
MaxLevel = element.GetAttributeInt("maxlevel", 1);
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
SupressWarnings = element.GetAttributeBool("supresswarnings", false);
|
||||
HideInMenus = element.GetAttributeBool("hideinmenus", false);
|
||||
FilePath = filePath;
|
||||
SourceElement = element;
|
||||
IsOverride = isOverride;
|
||||
OriginalName = Name;
|
||||
|
||||
var targetProperties = new Dictionary<string, string[]>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
Name = TextManager.Get($"UpgradeName.{Identifier}", returnNull: true) ?? string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
Description = TextManager.Get($"UpgradeDescription.{Identifier}", returnNull: true) ?? string.Empty;
|
||||
}
|
||||
|
||||
DebugConsole.Log(" " + Name);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "price":
|
||||
{
|
||||
Price = new UpgradePrice(this, subElement);
|
||||
break;
|
||||
}
|
||||
#if CLIENT
|
||||
case "decorativesprite":
|
||||
{
|
||||
DecorativeSprites.Add(new DecorativeSprite(subElement));
|
||||
break;
|
||||
}
|
||||
case "sprite":
|
||||
{
|
||||
Sprite = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
#else
|
||||
case "decorativesprite":
|
||||
case "sprite":
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
{
|
||||
IEnumerable<string> properties = subElement.Attributes().Select(attribute => attribute.Name.ToString());
|
||||
targetProperties.Add(subElement.Name.ToString(), properties.ToArray());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TargetProperties = targetProperties;
|
||||
|
||||
string[] categories = element.GetAttributeStringArray("categories", new string[] { });
|
||||
UpgradeCategories = (from category in UpgradeCategory.Categories from identifier in categories where string.Equals(category.Identifier, identifier) select category).ToArray();
|
||||
|
||||
if (!SupressWarnings && !IsOverride)
|
||||
{
|
||||
foreach (UpgradePrefab matchingPrefab in Prefabs.Where(prefab => prefab.TargetItems.Any(s => TargetItems.Contains(s))))
|
||||
{
|
||||
if (matchingPrefab.IsOverride) { continue; }
|
||||
|
||||
var upgradePrefab = matchingPrefab.TargetProperties;
|
||||
string key = string.Empty;
|
||||
|
||||
if (upgradePrefab.Keys.Any(s => TargetProperties.Keys.Any(s1 => s == (key = s1))))
|
||||
{
|
||||
if (upgradePrefab.ContainsKey(key) && upgradePrefab[key].Any(s => TargetProperties[key].Contains(s)))
|
||||
{
|
||||
DebugConsole.AddWarning($"Upgrade \"{Identifier}\" is affecting a property that is also being affected by \"{matchingPrefab.Identifier}\".\n" +
|
||||
"This is unsupported and might yield unexpected results if both upgrades are applied at the same time to the same item.\n" +
|
||||
"Add the attribute suppresswarnings=\"true\" to your XML element to disable this warning if you know what you're doing.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Prefabs.Add(this, isOverride);
|
||||
}
|
||||
|
||||
public static UpgradePrefab? Find(string idenfitier)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(idenfitier) ? Prefabs.Find(prefab => prefab.Identifier == idenfitier) : null;
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
DebugConsole.Log("Loading upgrade module prefabs: ");
|
||||
|
||||
foreach (ContentFile file in files) { LoadFromFile(file); }
|
||||
}
|
||||
|
||||
private static void LoadFromFile(ContentFile file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
|
||||
var rootElement = doc?.Root;
|
||||
if (rootElement == null) { return; }
|
||||
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "upgrademodule":
|
||||
{
|
||||
new UpgradePrefab(rootElement, file.Path, false) { ContentPackage = file.ContentPackage };
|
||||
break;
|
||||
}
|
||||
case "upgradecategory":
|
||||
{
|
||||
new UpgradeCategory(rootElement);
|
||||
break;
|
||||
}
|
||||
case "upgrademodules":
|
||||
{
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var upgradeElement = element.GetChildElement("upgradeprefab");
|
||||
if (upgradeElement != null)
|
||||
{
|
||||
new UpgradePrefab(upgradeElement, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find an upgrade element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "upgrademodule":
|
||||
{
|
||||
new UpgradePrefab(element, file.Path, false) { ContentPackage = file.ContentPackage };
|
||||
break;
|
||||
}
|
||||
case "upgradecategory":
|
||||
{
|
||||
new UpgradeCategory(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "override":
|
||||
{
|
||||
var upgrades = rootElement.GetChildElement("upgrademodules");
|
||||
if (upgrades != null)
|
||||
{
|
||||
foreach (var element in upgrades.Elements())
|
||||
{
|
||||
new UpgradePrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var element in rootElement.GetChildElements("upgrademodule"))
|
||||
{
|
||||
new UpgradePrefab(element, file.Path, true) { ContentPackage = file.ContentPackage };
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}\n " +
|
||||
"Valid elements are: \"UpgradeModule\", \"UpgradeModules\" and \"Override\".");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a integer value from a string that is formatted like a percentage increase / decrease.
|
||||
/// </summary>
|
||||
/// <param name="value">String to parse</param>
|
||||
/// <param name="attribute">What XML attribute the value originates from, only used for warning formatting.</param>
|
||||
/// <param name="sourceElement">What XMLElement the value originates from, only used for warning formatting.</param>
|
||||
/// <param name="suppressWarnings">Whether or not to suppress warnings if both "attribute" and "sourceElement" are defined.</param>
|
||||
/// <returns></returns>
|
||||
/// <example>
|
||||
/// This sample returns -15 as an integer.
|
||||
/// <code>
|
||||
/// XElement element = new XElement("change", new XAttribute("increase", "-15%"));
|
||||
/// ParsePercentage(element.GetAttributeString("increase", string.Empty));
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static int ParsePercentage(string value, string? attribute = null, XElement? sourceElement = null, bool suppressWarnings = false)
|
||||
{
|
||||
string? line = sourceElement?.ToString().Split('\n')[0].Trim();
|
||||
bool doWarnings = !suppressWarnings && attribute != null && sourceElement != null && line != null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
if (doWarnings)
|
||||
{
|
||||
DebugConsole.AddWarning($"Attribute \"{attribute}\" not found at {sourceElement!.Document?.ParseContentPathFromUri()} @ '{line}'.\n " +
|
||||
"Value has been assumed to be '0'.");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!int.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var price))
|
||||
{
|
||||
string str = value;
|
||||
|
||||
if (str.Length > 1 && str[0] == '+') { str = str.Substring(1); }
|
||||
|
||||
if (str.Length > 1 && str[^1] == '%') { str = str.Substring(0, str.Length - 1); }
|
||||
|
||||
if (int.TryParse(str, out price))
|
||||
{
|
||||
return price;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return price;
|
||||
}
|
||||
|
||||
if (doWarnings)
|
||||
{
|
||||
DebugConsole.AddWarning($"Value in attribute \"{attribute}\" is not formatted correctly\n " +
|
||||
$"at {sourceElement!.Document?.ParseContentPathFromUri()} @ '{line}'.\n " +
|
||||
"It should be an integer with optionally a '+' or '-' at the front and/or '%' at the end.\n" +
|
||||
"The value has been assumed to be '0'.");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Prefabs.Remove(this);
|
||||
#if CLIENT
|
||||
Sprite.Remove();
|
||||
Sprite = null;
|
||||
DecorativeSprites.ForEach(sprite => sprite.Remove());
|
||||
DecorativeSprites.Clear();
|
||||
TargetProperties.Clear();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user