Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Sprite/Sprite.cs
1 using Microsoft.Xna.Framework;
2 using System.Xml.Linq;
3 using System.Linq;
5 using System;
6 using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
7 #if CLIENT
8 using Microsoft.Xna.Framework.Graphics;
9 #endif
10 
11 namespace Barotrauma
12 {
13  public partial class Sprite
14  {
18  public ContentXElement SourceElement { get; private set; }
19 
20  //the area in the texture that is supposed to be drawn
21  private Rectangle sourceRect;
22 
23  //the offset used when drawing the sprite
24  protected Vector2 offset;
25 
26  public bool LazyLoad
27  {
28  get;
29  private set;
30  }
31 
32  protected Vector2 origin;
33 
34  //the size of the drawn sprite, if larger than the source,
35  //the sprite is tiled to fill the target size
36  public Vector2 size = Vector2.One;
37 
38  public float rotation;
39 
40 #if CLIENT
41  public SpriteEffects effects = SpriteEffects.None;
42 #endif
43 
44  protected float depth;
45 
47  {
48  get { return sourceRect; }
49  set { sourceRect = value; }
50  }
51 
52  public float Depth
53  {
54  get { return depth; }
55  set { depth = MathHelper.Clamp(value, 0.001f, 0.999f); }
56  }
57 
61  public Vector2 Origin
62  {
63  get { return origin; }
64  set
65  {
66  origin = value;
67  _relativeOrigin = new Vector2(origin.X / sourceRect.Width, origin.Y / sourceRect.Height);
68  }
69  }
70 
71  private Vector2 _relativeOrigin;
75  public Vector2 RelativeOrigin
76  {
77  get => _relativeOrigin;
78  set
79  {
80  _relativeOrigin = value;
81  origin = new Vector2(_relativeOrigin.X * sourceRect.Width, _relativeOrigin.Y * sourceRect.Height);
82  }
83  }
84 
85  public Vector2 RelativeSize { get; private set; }
86 
87  public ContentPath FilePath { get; private set; }
88 
89  public string FullPath => FilePath.FullPath;
90 
91  public bool Compress { get; private set; }
92 
93  public override string ToString()
94  {
95  return FilePath + ": " + sourceRect;
96  }
97 
101  public Identifier EntityIdentifier { get; set; }
102  public string Name { get; set; }
103 
104  partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn);
105 
106  partial void CalculateSourceRect();
107 
108  static partial void AddToList(Sprite sprite);
109 
110  public Sprite(ContentXElement element, string path = "", string file = "", bool lazyLoad = false, float sourceRectScale = 1)
111  {
112  if (element is null)
113  {
114  DebugConsole.ThrowError($"Sprite: xml element null in {file}. Failed to create the sprite!");
115  return;
116  }
117  this.LazyLoad = lazyLoad;
118  SourceElement = element;
119  if (!ParseTexturePath(path, file)) { return; }
120  Name = SourceElement.GetAttributeString("name", null);
121  Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
122  var overrideElement = GetLocalizationOverrideElement();
123  if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
124  {
125  sourceVector = overrideElement.GetAttributeVector4("sourcerect", Vector4.Zero);
126  }
127  if ((overrideElement ?? SourceElement).Attribute("sheetindex") != null)
128  {
129  Point sheetElementSize = (overrideElement ?? SourceElement).GetAttributePoint("sheetelementsize", Point.Zero);
130  Point sheetIndex = (overrideElement ?? SourceElement).GetAttributePoint("sheetindex", Point.Zero);
131  sourceVector = new Vector4(sheetIndex.X * sheetElementSize.X, sheetIndex.Y * sheetElementSize.Y, sheetElementSize.X, sheetElementSize.Y);
132  }
133  Compress = SourceElement.GetAttributeBool("compress", true);
134  bool shouldReturn = false;
135  if (!lazyLoad)
136  {
137  LoadTexture(ref sourceVector, ref shouldReturn);
138  }
139  if (shouldReturn) { return; }
140  sourceRect = new Rectangle((int)(sourceVector.X * sourceRectScale), (int)(sourceVector.Y * sourceRectScale), (int)(sourceVector.Z * sourceRectScale), (int)(sourceVector.W * sourceRectScale));
141  size = SourceElement.GetAttributeVector2("size", Vector2.One);
142  RelativeSize = size;
143  size.X *= sourceRect.Width;
144  size.Y *= sourceRect.Height;
145  RelativeOrigin = SourceElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
146  Depth = SourceElement.GetAttributeFloat("depth", 0.001f);
147 #if CLIENT
149  AddToList(this);
150 #endif
151  }
152 
153  internal void LoadParams(SpriteParams spriteParams, bool isFlipped)
154  {
155  SourceElement = spriteParams.Element;
156  sourceRect = spriteParams.SourceRect;
157  RelativeOrigin = spriteParams.Origin;
158  if (isFlipped)
159  {
160  Origin = new Vector2(sourceRect.Width - origin.X, origin.Y);
161  }
162  depth = spriteParams.Depth;
163  }
164 
165  public Sprite(string newFile, Vector2 newOrigin)
166  {
167  Init(newFile, newOrigin: newOrigin);
168  AddToList(this);
169  }
170 
171  public Sprite(string newFile, Rectangle? sourceRectangle, Vector2? origin = null, float rotation = 0)
172  {
173  Init(newFile, sourceRectangle: sourceRectangle, newOrigin: origin, newRotation: rotation);
174  AddToList(this);
175  }
176 
177  private void Init(string newFile, Rectangle? sourceRectangle = null, Vector2? newOrigin = null, Vector2? newOffset = null, float newRotation = 0)
178  {
179  FilePath = ContentPath.FromRaw(newFile);
180  Vector4 sourceVector = Vector4.Zero;
181  bool shouldReturn = false;
182  LoadTexture(ref sourceVector, ref shouldReturn);
183  if (shouldReturn) return;
184  if (sourceRectangle.HasValue)
185  {
186  sourceRect = sourceRectangle.Value;
187  }
188  else
189  {
190  CalculateSourceRect();
191  }
192  offset = newOffset ?? Vector2.Zero;
193  if (newOrigin.HasValue)
194  {
195  RelativeOrigin = newOrigin.Value;
196  }
197  size = new Vector2(sourceRect.Width, sourceRect.Height);
198  rotation = newRotation;
199  }
200 
206  public static Identifier GetIdentifier(XElement sourceElement)
207  {
208  if (sourceElement == null) { return "".ToIdentifier(); }
209  var parentElement = sourceElement.Parent;
210  return $"{sourceElement}{parentElement?.ToString() ?? ""}".ToIdentifier();
211  }
212 
213  static partial void RemoveFromList(Sprite sprite);
214 
215  public void Remove()
216  {
217  RemoveFromList(this);
218  DisposeTexture();
219  }
220 
221  ~Sprite()
222  {
223  Remove();
224  }
225 
226  partial void DisposeTexture();
227 
231  public void ReloadXML()
232  {
233  if (SourceElement == null) { return; }
234  string path = SourceElement.ParseContentPathFromUri();
235  if (string.IsNullOrWhiteSpace(path))
236  {
237  DebugConsole.NewMessage($"[Sprite] Could not parse the content path from the source element ({SourceElement}) uri: {SourceElement.BaseUri}", Color.Yellow);
238  return;
239  }
240  var doc = XMLExtensions.TryLoadXml(path);
241  if (doc == null) { return; }
242  if (string.IsNullOrWhiteSpace(Name) && string.IsNullOrWhiteSpace(EntityIdentifier.Value)) { return; }
243  var spriteElements = doc.Descendants("sprite").Concat(doc.Descendants("Sprite"));
244  var sourceElements = spriteElements.Where(e => e.GetAttributeString("name", null) == Name);
245  if (sourceElements.None())
246  {
247  // Try parents by first comparing the entity id and then the name, if no match was found.
248  sourceElements = spriteElements.Where(e => e.Parent?.GetAttributeString("identifier", null) == EntityIdentifier);
249  if (sourceElements.None())
250  {
251  sourceElements = spriteElements.Where(e => e.Parent?.GetAttributeString("name", null) == Name);
252  }
253  }
254  if (sourceElements.Multiple())
255  {
256  DebugConsole.NewMessage($"[Sprite] Multiple matching elements found by name ({Name}) or identifier ({EntityIdentifier})!: {SourceElement}", Color.Yellow);
257  }
258  else if (sourceElements.None())
259  {
260  DebugConsole.NewMessage($"[Sprite] Cannot find matching source element by comparing the name attribute ({Name}) or identifier ({EntityIdentifier})! Cannot reload the xml for sprite element \"{SourceElement.ToString()}\"!", Color.Yellow);
261  }
262  else
263  {
264  SourceElement = sourceElements.Single().FromPackage(SourceElement.ContentPackage);
265  }
266  if (SourceElement != null)
267  {
268  sourceRect = SourceElement.GetAttributeRect("sourcerect", Rectangle.Empty);
269  var overrideElement = GetLocalizationOverrideElement();
270  if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
271  {
272  sourceRect = overrideElement.GetAttributeRect("sourcerect", Rectangle.Empty);
273  }
274  if ((overrideElement ?? SourceElement).Attribute("sheetindex") != null)
275  {
276  Point sheetElementSize = (overrideElement ?? SourceElement).GetAttributePoint("sheetelementsize", Point.Zero);
277  Point sheetIndex = (overrideElement ?? SourceElement).GetAttributePoint("sheetindex", Point.Zero);
278  sourceRect = new Rectangle(sheetIndex.X * sheetElementSize.X, sheetIndex.Y * sheetElementSize.Y, sheetElementSize.X, sheetElementSize.Y);
279  }
280  size = SourceElement.GetAttributeVector2("size", Vector2.One);
281  size.X *= sourceRect.Width;
282  size.Y *= sourceRect.Height;
283  RelativeOrigin = SourceElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
284  Depth = SourceElement.GetAttributeFloat("depth", 0.001f);
285 #if CLIENT
287 #endif
288  }
289  }
290 
291  public bool ParseTexturePath(string path = "", string file = "")
292  {
293 #if SERVER
294  // Server doesn't care about texture paths at all
295  return true;
296 #endif
297  if (file == "")
298  {
299  file = SourceElement.GetAttributeStringUnrestricted("texture", "");
300  var overrideElement = GetLocalizationOverrideElement();
301  if (overrideElement != null)
302  {
303  string overrideFile = overrideElement.GetAttributeStringUnrestricted("texture", "");
304  if (!string.IsNullOrEmpty(overrideFile)) { file = overrideFile; }
305  }
306  }
307  if (file == "")
308  {
309  DebugConsole.ThrowError("Sprite " + SourceElement.Element + " doesn't have a texture specified!",
310  contentPackage: SourceElement.ContentPackage);
311  return false;
312  }
313  if (!string.IsNullOrEmpty(path))
314  {
315  if (!path.EndsWith("/")) path += "/";
316  }
317  FilePath = ContentPath.FromRaw(SourceElement.ContentPackage, (path + file).CleanUpPathCrossPlatform(correctFilenameCase: true));
318  return true;
319  }
320 
321  private XElement GetLocalizationOverrideElement()
322  {
323  foreach (var subElement in SourceElement.Elements())
324  {
325  if (subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
326  {
327  LanguageIdentifier language = subElement.GetAttributeIdentifier("language", "").ToLanguageIdentifier();
328  if (GameSettings.CurrentConfig.Language == language)
329  {
330  return subElement;
331  }
332  }
333  }
334  return null;
335  }
336  }
337 }
338 
static ContentPath FromRaw(string? rawValue)
string? GetAttributeString(string key, string? def)
Vector4 GetAttributeVector4(string key, in Vector4 def)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
string GetAttributeStringUnrestricted(string key, string def)
IEnumerable< ContentXElement > Elements()
readonly XElement Element
Vector2 GetAttributeVector2(string key, in Vector2 def)
bool GetAttributeBool(string key, bool def)
Rectangle GetAttributeRect(string key, in Rectangle def)
Sprite(string newFile, Rectangle? sourceRectangle, Vector2? origin=null, float rotation=0)
static Identifier GetIdentifier(XElement sourceElement)
Creates a supposedly unique identifier from the parent element. If the parent element is not found,...
Identifier EntityIdentifier
Identifier of the Map Entity so that we can link the sprite to its owner.
bool ParseTexturePath(string path="", string file="")
Sprite(string newFile, Vector2 newOrigin)
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
ContentXElement SourceElement
Reference to the xml element from where the sprite was created. Can be null if the sprite was not def...
void ReloadXML()
Works only if there is a name attribute defined for the sprite. For items and structures,...