Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/ClientSource/Sprite/DeformAnimations/SpriteDeformation.cs
T
2025-03-12 12:56:27 +00:00

271 lines
10 KiB
C#

using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.SpriteDeformations
{
abstract class SpriteDeformationParams : ISerializableEntity
{
/// <summary>
/// A negative value means that the deformation is used only by one sprite only (default).
/// A positive value means that this deformation is or could be used for multiple sprites.
/// This behaviour is not automatic, and has to be implemented for any particular case separately (currently only used in Limbs).
/// </summary>
[Serialize(-1, IsPropertySaveable.Yes), Editable(minValue: -1, maxValue: 100)]
public int Sync
{
get;
private set;
}
[Serialize("", IsPropertySaveable.No)]
public string Type
{
get;
set;
}
[Serialize(SpriteDeformation.DeformationBlendMode.Add, IsPropertySaveable.Yes), Editable]
public SpriteDeformation.DeformationBlendMode BlendMode
{
get;
set;
}
public string Name => $"Deformation ({Type})";
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2, ValueStep = 0.01f)]
public float Strength { get; private set; }
[Serialize(90f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 90)]
public float MaxRotation { get; private set; }
[Serialize(false, IsPropertySaveable.Yes), Editable]
public bool UseMovementSine { get; set; }
[Serialize(false, IsPropertySaveable.Yes), Editable]
public bool StopWhenHostIsDead { get; set; }
[Serialize(false, IsPropertySaveable.Yes), Editable]
public bool OnlyInWater { get; set; }
/// <summary>
/// Only used if UseMovementSine is enabled. Multiplier for Pi.
/// </summary>
[Serialize(0f, IsPropertySaveable.Yes), Editable]
public float SineOffset { get; set; }
public virtual float Frequency { get; set; } = 1;
public Dictionary<Identifier, SerializableProperty> SerializableProperties
{
get;
set;
}
/// <summary>
/// Defined in the shader.
/// </summary>
public static readonly Point ShaderMaxResolution = new Point(15, 15);
private Point _resolution;
[Serialize("2,2", IsPropertySaveable.Yes)]
public Point Resolution
{
get { return _resolution; }
set
{
if (_resolution == value) { return; }
_resolution = value.Clamp(new Point(2, 2), ShaderMaxResolution);
}
}
public SpriteDeformationParams(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (element != null && string.IsNullOrEmpty(Type))
{
Type = element.GetAttributeString("typename", string.Empty);
}
}
}
abstract class SpriteDeformation
{
public enum DeformationBlendMode
{
Add,
Multiply,
Override
}
public virtual float Phase { get; set; }
protected Vector2[,] Deformation { get; set; }
public SpriteDeformationParams Params { get; set; }
private static readonly string[] deformationTypes = new string[] { "Inflate", "Custom", "Noise", "BendJoint", "ReactToTriggerers" };
public static IEnumerable<string> DeformationTypes
{
get { return deformationTypes; }
}
public Point Resolution
{
get { return Params.Resolution; }
set { SetResolution(value); }
}
public string TypeName => Params.Type;
public int Sync => Params.Sync;
public static SpriteDeformation Load(string deformationType, string parentDebugName)
{
return Load(null, deformationType, parentDebugName);
}
public static SpriteDeformation Load(XElement element, string parentDebugName)
{
return Load(element, null, parentDebugName);
}
private static SpriteDeformation Load(XElement element, string deformationType, string parentDebugName)
{
string typeName = deformationType;
if (element != null)
{
typeName = element.GetAttributeString("typename", null) ?? element.GetAttributeString("type", "");
}
var resolution = element.GetAttributePoint(nameof(Resolution), new Point(0, 0));
if (resolution.X < 2|| resolution.Y < 2)
{
DebugConsole.AddWarning($"Potential error in sprite deformation ({parentDebugName}): resolution must be at least 2x2.");
}
SpriteDeformation newDeformation = null;
switch (typeName.ToLowerInvariant())
{
case "inflate":
newDeformation = new Inflate(element);
break;
case "custom":
newDeformation = new CustomDeformation(element);
break;
case "noise":
newDeformation = new NoiseDeformation(element);
break;
case "jointbend":
case "bendjoint":
newDeformation = new JointBendDeformation(element);
break;
case "reacttotriggerers":
return new PositionalDeformation(element);
default:
if (Enum.TryParse(typeName, out PositionalDeformation.ReactionType reactionType))
{
newDeformation = new PositionalDeformation(element)
{
Type = reactionType
};
}
else
{
DebugConsole.ThrowError("Could not load sprite deformation animation in " + parentDebugName + " - \"" + typeName + "\" is not a valid deformation type.");
}
break;
}
if (newDeformation != null)
{
newDeformation.Params.Type = typeName;
}
return newDeformation;
}
protected SpriteDeformation(XElement element, SpriteDeformationParams deformationParams)
{
this.Params = deformationParams;
SerializableProperty.DeserializeProperties(deformationParams, element);
Deformation = new Vector2[deformationParams.Resolution.X, deformationParams.Resolution.Y];
}
public void SetResolution(Point resolution)
{
Params.Resolution = resolution;
Deformation = new Vector2[Params.Resolution.X, Params.Resolution.Y];
}
/// <param name="flippedHorizontally">Is the sprite flipped horizontally?</param>
/// <param name="inverseY">Should the y-coordinate of customdeformations be inverted? Legacy fix for mirroring deformable light sprites.</param>
protected abstract void GetDeformation(out Vector2[,] deformation, out float multiplier, bool flippedHorizontally, bool inverseY);
public abstract void Update(float deltaTime);
private static readonly List<int> yValues = new List<int>();
public static Vector2[,] GetDeformation(IEnumerable<SpriteDeformation> animations, Vector2 scale, bool flippedHorizontally, bool inverseY = false)
{
foreach (SpriteDeformation animation in animations)
{
if (animation.Params.Resolution.X != animation.Deformation.GetLength(0) ||
animation.Params.Resolution.Y != animation.Deformation.GetLength(1))
{
animation.Deformation = new Vector2[animation.Params.Resolution.X, animation.Params.Resolution.Y];
}
}
Point resolution = animations.First().Resolution;
if (animations.Any(a => a.Resolution != resolution))
{
DebugConsole.ThrowError("All animations must have the same resolution! Using the lowest resolution.");
resolution = animations.OrderBy(anim => anim.Resolution.X + anim.Resolution.Y).First().Resolution;
animations.ForEach(a => a.Resolution = resolution);
}
Vector2[,] deformation = new Vector2[resolution.X, resolution.Y];
foreach (SpriteDeformation animation in animations)
{
yValues.Clear();
for (int y = 0; y < resolution.Y; y++)
{
yValues.Add(y);
}
if (inverseY && animation is CustomDeformation)
{
yValues.Reverse();
}
animation.GetDeformation(out Vector2[,] animDeformation, out float multiplier, flippedHorizontally, inverseY);
for (int x = 0; x < resolution.X; x++)
{
for (int y = 0; y < resolution.Y; y++)
{
switch (animation.Params.BlendMode)
{
case DeformationBlendMode.Override:
deformation[x, yValues[y]] = animDeformation[x, y] * scale * multiplier;
break;
case DeformationBlendMode.Add:
deformation[x, yValues[y]] += animDeformation[x, y] * scale * multiplier;
break;
case DeformationBlendMode.Multiply:
deformation[x, yValues[y]] *= animDeformation[x, y] * multiplier;
break;
}
}
}
}
return deformation;
}
public virtual void Save(XElement element)
{
SerializableProperty.SerializeProperties(Params, element);
}
}
}