Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs
2024-06-18 16:50:02 +03:00

339 lines
13 KiB
C#

using Microsoft.Xna.Framework;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Extensions;
using System;
using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
#endif
namespace Barotrauma
{
public partial class Sprite
{
/// <summary>
/// Reference to the xml element from where the sprite was created. Can be null if the sprite was not defined in xml!
/// </summary>
public ContentXElement SourceElement { get; private set; }
//the area in the texture that is supposed to be drawn
private Rectangle sourceRect;
//the offset used when drawing the sprite
protected Vector2 offset;
public bool LazyLoad
{
get;
private set;
}
protected Vector2 origin;
//the size of the drawn sprite, if larger than the source,
//the sprite is tiled to fill the target size
public Vector2 size = Vector2.One;
public float rotation;
#if CLIENT
public SpriteEffects effects = SpriteEffects.None;
#endif
protected float depth;
public Rectangle SourceRect
{
get { return sourceRect; }
set { sourceRect = value; }
}
public float Depth
{
get { return depth; }
set { depth = MathHelper.Clamp(value, 0.001f, 0.999f); }
}
/// <summary>
/// In pixels
/// </summary>
public Vector2 Origin
{
get { return origin; }
set
{
origin = value;
_relativeOrigin = new Vector2(origin.X / sourceRect.Width, origin.Y / sourceRect.Height);
}
}
private Vector2 _relativeOrigin;
/// <summary>
/// 0 - 1
/// </summary>
public Vector2 RelativeOrigin
{
get => _relativeOrigin;
set
{
_relativeOrigin = value;
origin = new Vector2(_relativeOrigin.X * sourceRect.Width, _relativeOrigin.Y * sourceRect.Height);
}
}
public Vector2 RelativeSize { get; private set; }
public ContentPath FilePath { get; private set; }
public string FullPath => FilePath.FullPath;
public bool Compress { get; private set; }
public override string ToString()
{
return FilePath + ": " + sourceRect;
}
/// <summary>
/// Identifier of the Map Entity so that we can link the sprite to its owner.
/// </summary>
public Identifier EntityIdentifier { get; set; }
public string Name { get; set; }
partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn);
partial void CalculateSourceRect();
static partial void AddToList(Sprite sprite);
public Sprite(ContentXElement element, string path = "", string file = "", bool lazyLoad = false, float sourceRectScale = 1)
{
if (element is null)
{
DebugConsole.ThrowError($"Sprite: xml element null in {file}. Failed to create the sprite!");
return;
}
this.LazyLoad = lazyLoad;
SourceElement = element;
if (!ParseTexturePath(path, file)) { return; }
Name = SourceElement.GetAttributeString("name", null);
Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
var overrideElement = GetLocalizationOverrideElement();
if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
{
sourceVector = overrideElement.GetAttributeVector4("sourcerect", Vector4.Zero);
}
if ((overrideElement ?? SourceElement).Attribute("sheetindex") != null)
{
Point sheetElementSize = (overrideElement ?? SourceElement).GetAttributePoint("sheetelementsize", Point.Zero);
Point sheetIndex = (overrideElement ?? SourceElement).GetAttributePoint("sheetindex", Point.Zero);
sourceVector = new Vector4(sheetIndex.X * sheetElementSize.X, sheetIndex.Y * sheetElementSize.Y, sheetElementSize.X, sheetElementSize.Y);
}
Compress = SourceElement.GetAttributeBool("compress", true);
bool shouldReturn = false;
if (!lazyLoad)
{
LoadTexture(ref sourceVector, ref shouldReturn);
}
if (shouldReturn) { return; }
sourceRect = new Rectangle((int)(sourceVector.X * sourceRectScale), (int)(sourceVector.Y * sourceRectScale), (int)(sourceVector.Z * sourceRectScale), (int)(sourceVector.W * sourceRectScale));
size = SourceElement.GetAttributeVector2("size", Vector2.One);
RelativeSize = size;
size.X *= sourceRect.Width;
size.Y *= sourceRect.Height;
RelativeOrigin = SourceElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
Depth = SourceElement.GetAttributeFloat("depth", 0.001f);
#if CLIENT
Identifier = GetIdentifier(SourceElement);
AddToList(this);
#endif
}
internal void LoadParams(SpriteParams spriteParams, bool isFlipped)
{
SourceElement = spriteParams.Element;
sourceRect = spriteParams.SourceRect;
RelativeOrigin = spriteParams.Origin;
if (isFlipped)
{
Origin = new Vector2(sourceRect.Width - origin.X, origin.Y);
}
depth = spriteParams.Depth;
}
public Sprite(string newFile, Vector2 newOrigin)
{
Init(newFile, newOrigin: newOrigin);
AddToList(this);
}
public Sprite(string newFile, Rectangle? sourceRectangle, Vector2? origin = null, float rotation = 0)
{
Init(newFile, sourceRectangle: sourceRectangle, newOrigin: origin, newRotation: rotation);
AddToList(this);
}
private void Init(string newFile, Rectangle? sourceRectangle = null, Vector2? newOrigin = null, Vector2? newOffset = null, float newRotation = 0)
{
FilePath = ContentPath.FromRaw(newFile);
Vector4 sourceVector = Vector4.Zero;
bool shouldReturn = false;
LoadTexture(ref sourceVector, ref shouldReturn);
if (shouldReturn) return;
if (sourceRectangle.HasValue)
{
sourceRect = sourceRectangle.Value;
}
else
{
CalculateSourceRect();
}
offset = newOffset ?? Vector2.Zero;
if (newOrigin.HasValue)
{
RelativeOrigin = newOrigin.Value;
}
size = new Vector2(sourceRect.Width, sourceRect.Height);
rotation = newRotation;
}
/// <summary>
/// Creates a supposedly unique identifier from the parent element. If the parent element is not found, uses the sprite element.
/// TODO: If there are multiple elements with exactly the same data, the ids will fail. -> Is there a better way to identify the sprites?
/// ALSO TODO: delete :)
/// </summary>
public static Identifier GetIdentifier(XElement sourceElement)
{
if (sourceElement == null) { return "".ToIdentifier(); }
var parentElement = sourceElement.Parent;
return $"{sourceElement}{parentElement?.ToString() ?? ""}".ToIdentifier();
}
static partial void RemoveFromList(Sprite sprite);
public void Remove()
{
RemoveFromList(this);
DisposeTexture();
}
~Sprite()
{
Remove();
}
partial void DisposeTexture();
/// <summary>
/// Works only if there is a name attribute defined for the sprite. For items and structures, the entity id or name is used if the sprite's name attribute is not defined.
/// </summary>
public void ReloadXML()
{
if (SourceElement == null) { return; }
string path = SourceElement.ParseContentPathFromUri();
if (string.IsNullOrWhiteSpace(path))
{
DebugConsole.NewMessage($"[Sprite] Could not parse the content path from the source element ({SourceElement}) uri: {SourceElement.BaseUri}", Color.Yellow);
return;
}
var doc = XMLExtensions.TryLoadXml(path);
if (doc == null) { return; }
if (string.IsNullOrWhiteSpace(Name) && string.IsNullOrWhiteSpace(EntityIdentifier.Value)) { return; }
var spriteElements = doc.Descendants("sprite").Concat(doc.Descendants("Sprite"));
var sourceElements = spriteElements.Where(e => e.GetAttributeString("name", null) == Name);
if (sourceElements.None())
{
// Try parents by first comparing the entity id and then the name, if no match was found.
sourceElements = spriteElements.Where(e => e.Parent?.GetAttributeString("identifier", null) == EntityIdentifier);
if (sourceElements.None())
{
sourceElements = spriteElements.Where(e => e.Parent?.GetAttributeString("name", null) == Name);
}
}
if (sourceElements.Multiple())
{
DebugConsole.NewMessage($"[Sprite] Multiple matching elements found by name ({Name}) or identifier ({EntityIdentifier})!: {SourceElement}", Color.Yellow);
}
else if (sourceElements.None())
{
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);
}
else
{
SourceElement = sourceElements.Single().FromPackage(SourceElement.ContentPackage);
}
if (SourceElement != null)
{
sourceRect = SourceElement.GetAttributeRect("sourcerect", Rectangle.Empty);
var overrideElement = GetLocalizationOverrideElement();
if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
{
sourceRect = overrideElement.GetAttributeRect("sourcerect", Rectangle.Empty);
}
if ((overrideElement ?? SourceElement).Attribute("sheetindex") != null)
{
Point sheetElementSize = (overrideElement ?? SourceElement).GetAttributePoint("sheetelementsize", Point.Zero);
Point sheetIndex = (overrideElement ?? SourceElement).GetAttributePoint("sheetindex", Point.Zero);
sourceRect = new Rectangle(sheetIndex.X * sheetElementSize.X, sheetIndex.Y * sheetElementSize.Y, sheetElementSize.X, sheetElementSize.Y);
}
size = SourceElement.GetAttributeVector2("size", Vector2.One);
size.X *= sourceRect.Width;
size.Y *= sourceRect.Height;
RelativeOrigin = SourceElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
Depth = SourceElement.GetAttributeFloat("depth", 0.001f);
#if CLIENT
Identifier = GetIdentifier(SourceElement);
#endif
}
}
public bool ParseTexturePath(string path = "", string file = "")
{
#if SERVER
// Server doesn't care about texture paths at all
return true;
#endif
if (file == "")
{
file = SourceElement.GetAttributeStringUnrestricted("texture", "");
var overrideElement = GetLocalizationOverrideElement();
if (overrideElement != null)
{
string overrideFile = overrideElement.GetAttributeStringUnrestricted("texture", "");
if (!string.IsNullOrEmpty(overrideFile)) { file = overrideFile; }
}
}
if (file == "")
{
DebugConsole.ThrowError("Sprite " + SourceElement.Element + " doesn't have a texture specified!",
contentPackage: SourceElement.ContentPackage);
return false;
}
if (!string.IsNullOrEmpty(path))
{
if (!path.EndsWith("/")) path += "/";
}
FilePath = ContentPath.FromRaw(SourceElement.ContentPackage, (path + file).CleanUpPathCrossPlatform(correctFilenameCase: true));
return true;
}
private XElement GetLocalizationOverrideElement()
{
foreach (var subElement in SourceElement.Elements())
{
if (subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
{
LanguageIdentifier language = subElement.GetAttributeIdentifier("language", "").ToLanguageIdentifier();
if (GameSettings.CurrentConfig.Language == language)
{
return subElement;
}
}
}
return null;
}
}
}