339 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
|