Client LuaCsForBarotrauma
IImplementsVariants.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Globalization;
6 using System.Linq;
7 using System.Xml.Linq;
8 
9 namespace Barotrauma
10 {
11  public interface IImplementsVariants<T> where T : Prefab
12  {
13  public Identifier VariantOf { get; }
14 
15  public T? ParentPrefab { get; set; }
16 
17  public void InheritFrom(T parent);
18  }
19 
20  public static class VariantExtensions
21  {
22  public delegate void VariantXMLChecker(XElement originalElement, XElement? variantElement, XElement result);
23 
24  public static ContentXElement CreateVariantXML(this ContentXElement variantElement, ContentXElement baseElement, VariantXMLChecker? checker = null)
25  {
26  XElement newElement = new XElement(baseElement);
27 
28  //if the base element is from a different content package, we must make sure the %ModDir% elements inherited from it refer to that content package
29  //otherwise there can be situations in which mod B defines a variant of some item without overriding the sprite,
30  //and then when you enable mod A which overrides that item and replaces it's sprite, mod B would attempt to find the sprite for the variant item from it's own folder (even though it's in mod A's folder).
31  if (baseElement!.ContentPackage != null &&
32  baseElement!.ContentPackage != variantElement.ContentPackage)
33  {
34  foreach (var subElement in newElement.Descendants())
35  {
36  foreach (var attribute in subElement.Attributes())
37  {
38  if (attribute.Value.Contains(ContentPath.ModDirStr))
39  {
40  //make mod dir point to the original content package
41  attribute.SetValue(
42  attribute.Value.Replace(ContentPath.ModDirStr, string.Format(ContentPath.OtherModDirFmt, baseElement!.ContentPackage.Name), StringComparison.OrdinalIgnoreCase));
43  }
44  }
45  }
46  }
47 
48  ReplaceElement(newElement, variantElement);
49 
50  void ReplaceElement(XElement element, XElement replacement)
51  {
52  XElement originalElement = new XElement(element);
53 
54  List<XElement> newElementsFromBase = new List<XElement>(element.Elements());
55  List<XElement> elementsToRemove = new List<XElement>();
56  foreach (XAttribute attribute in replacement.Attributes())
57  {
58  ReplaceAttribute(element, attribute);
59  }
60  foreach (XElement replacementSubElement in replacement.Elements())
61  {
62  int index = replacement.Elements().ToList().FindAll(e => e.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)).IndexOf(replacementSubElement);
63  System.Diagnostics.Debug.Assert(index > -1);
64 
65  int i = 0;
66  bool matchingElementFound = false;
67  bool cleared = false;
68  foreach (var subElement in element.Elements())
69  {
70  if (replacementSubElement.Name.ToString().Equals("clear", StringComparison.OrdinalIgnoreCase))
71  {
72  matchingElementFound = true;
73  newElementsFromBase.Clear();
74  elementsToRemove.AddRange(element.Elements());
75  //add all the other elements defined after <Clear>
76  foreach (var elementAfterClear in replacementSubElement.ElementsAfterSelf())
77  {
78  element.Add(elementAfterClear);
79  }
80  cleared = true;
81  break;
82  }
83  if (!subElement.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)) { continue; }
84  if (i == index)
85  {
86  if (!replacementSubElement.HasAttributes && !replacementSubElement.HasElements)
87  {
88  //if the replacement is empty (no attributes or child elements)
89  //remove the element from the variant
90  elementsToRemove.Add(subElement);
91  }
92  else
93  {
94  ReplaceElement(subElement, replacementSubElement);
95  }
96  matchingElementFound = true;
97  newElementsFromBase.Remove(subElement);
98  break;
99  }
100  i++;
101  }
102  if (!matchingElementFound)
103  {
104  element.Add(replacementSubElement);
105  }
106  //this element cleared all the subelements from the base xml and potentially added new elements after the <Clear>,
107  //no need to handle any other subelements here
108  if (cleared) { break; }
109  }
110  elementsToRemove.ForEach(e => e.Remove());
111  checker?.Invoke(originalElement, replacement, element);
112  foreach (XElement newElement in newElementsFromBase)
113  {
114  checker?.Invoke(newElement, null, newElement);
115  }
116  }
117 
118  void ReplaceAttribute(XElement element, XAttribute newAttribute)
119  {
120  XAttribute? existingAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals(newAttribute.Name.ToString(), StringComparison.OrdinalIgnoreCase));
121  if (existingAttribute == null)
122  {
123  element.Add(newAttribute);
124  return;
125  }
126  float.TryParse(existingAttribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out float value);
127  if (newAttribute.Value.StartsWith('*'))
128  {
129  string multiplierStr = newAttribute.Value.Substring(1, newAttribute.Value.Length - 1);
130  float.TryParse(multiplierStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float multiplier);
131  if (multiplierStr.Contains('.') || existingAttribute.Value.Contains('.'))
132  {
133  existingAttribute.Value = (value * multiplier).ToString("G", CultureInfo.InvariantCulture);
134  }
135  else
136  {
137  existingAttribute.Value = ((int)(value * multiplier)).ToString();
138  }
139  }
140  else if (newAttribute.Value.StartsWith('+'))
141  {
142  string additionStr = newAttribute.Value.Substring(1, newAttribute.Value.Length - 1);
143  float.TryParse(additionStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float addition);
144  if (additionStr.Contains('.') || existingAttribute.Value.Contains('.'))
145  {
146  existingAttribute.Value = (value + addition).ToString("G", CultureInfo.InvariantCulture);
147  }
148  else
149  {
150  existingAttribute.Value = ((int)(value + addition)).ToString();
151  }
152  }
153  else
154  {
155  existingAttribute.Value = newAttribute.Value;
156  }
157  }
158 
159  return newElement.FromPackage(variantElement.ContentPackage);
160  }
161 
162  }
163 }
const string OtherModDirFmt
Definition: ContentPath.cs:15
const string ModDirStr
Definition: ContentPath.cs:14
ContentPackage? ContentPackage