Client LuaCsForBarotrauma
DecorativeSprite.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Xml.Linq;
6 
7 namespace Barotrauma
8 {
10  {
11  public class State
12  {
13  public float RotationState;
14  public float OffsetState;
15  public float ScaleState;
16  public Vector2 RandomOffsetMultiplier = new Vector2(Rand.Range(-1.0f, 1.0f), Rand.Range(-1.0f, 1.0f));
17  public float RandomRotationFactor = Rand.Range(0.0f, 1.0f);
18  public float RandomScaleFactor = Rand.Range(0.0f, 1.0f);
19  public bool IsActive = true;
20  }
21 
22  public string Name => $"Decorative Sprite";
23  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
24 
25  public Sprite Sprite { get; private set; }
26 
27  public enum AnimationType
28  {
29  None,
30  Sine,
31  Noise,
32  Circle
33  }
34 
36  public float BlinkFrequency { get; private set; }
37 
38  private float blinkTimer = 0.0f;
39 
41  public Vector2 Offset { get; private set; }
42 
44  public Vector2 RandomOffset { get; private set; }
45 
47  public AnimationType OffsetAnim { get; private set; }
48 
50  public float OffsetAnimSpeed { get; private set; }
51 
53  public AnimationType ScaleAnim { get; private set; }
54 
56  public Vector2 ScaleAnimAmount { get; private set; }
57 
59  public float ScaleAnimSpeed { get; private set; }
60 
61  private float rotationSpeedRadians;
62  private float absRotationSpeedRadians;
63 
65  public float RotationSpeed
66  {
67  get
68  {
69  return MathHelper.ToDegrees(rotationSpeedRadians);
70  }
71  private set
72  {
73  rotationSpeedRadians = MathHelper.ToRadians(value);
74  absRotationSpeedRadians = Math.Abs(rotationSpeedRadians);
75  }
76  }
77 
78  private float rotationRadians;
80  public float Rotation
81  {
82  get
83  {
84  return MathHelper.ToDegrees(rotationRadians);
85  }
86  private set
87  {
88  rotationRadians = MathHelper.ToRadians(value);
89  }
90  }
91 
92  private Vector2 randomRotationRadians;
94  public Vector2 RandomRotation
95  {
96  get
97  {
98  return new Vector2(MathHelper.ToDegrees(randomRotationRadians.X), MathHelper.ToDegrees(randomRotationRadians.Y));
99  }
100  private set
101  {
102  randomRotationRadians = new Vector2(MathHelper.ToRadians(value.X), MathHelper.ToRadians(value.Y));
103  }
104  }
105 
106  private float scale;
108  public float Scale
109  {
110  get { return scale; }
111  private set { scale = MathHelper.Clamp(value, 0.0f, 10.0f); }
112  }
113 
114  [Serialize("0,0", IsPropertySaveable.Yes), Editable]
115  public Vector2 RandomScale
116  {
117  get;
118  private set;
119  }
120 
122  public AnimationType RotationAnim { get; private set; }
123 
127  [Serialize(0, IsPropertySaveable.No, description: "If > 0, only one sprite of the same group is used (chosen randomly)"), Editable(ReadOnly = true)]
128  public int RandomGroupID { get; private set; }
129 
130  [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable()]
131  public Color Color { get; set; }
132 
136  internal List<PropertyConditional> IsActiveConditionals { get; private set; } = new List<PropertyConditional>();
140  internal List<PropertyConditional> AnimationConditionals { get; private set; } = new List<PropertyConditional>();
141 
142  public DecorativeSprite(ContentXElement element, string path = "", string file = "", bool lazyLoad = false)
143  {
144  Sprite = new Sprite(element, path, file, lazyLoad: lazyLoad);
146  // load property conditionals
147  foreach (var subElement in element.Elements())
148  {
149  //choose which list the new conditional should be placed to
150  List<PropertyConditional> conditionalList;
151  switch (subElement.Name.ToString().ToLowerInvariant())
152  {
153  case "conditional":
154  case "isactiveconditional":
155  conditionalList = IsActiveConditionals;
156  break;
157  case "animationconditional":
158  conditionalList = AnimationConditionals;
159  break;
160  default:
161  continue;
162  }
163  conditionalList.AddRange(PropertyConditional.FromXElement(subElement));
164  }
165  }
166 
167  public Vector2 GetOffset(ref float offsetState, Vector2 randomOffsetMultiplier, float rotation = 0.0f)
168  {
169  Vector2 offset = Offset;
170  if (OffsetAnimSpeed > 0.0f)
171  {
172  switch (OffsetAnim)
173  {
174  case AnimationType.Sine:
175  offsetState %= (MathHelper.TwoPi / OffsetAnimSpeed);
176  offset *= MathF.Sin(offsetState * OffsetAnimSpeed);
177  break;
178  case AnimationType.Circle:
179  offsetState %= (MathHelper.TwoPi / OffsetAnimSpeed);
180  offset *= new Vector2(MathF.Cos(offsetState * OffsetAnimSpeed), MathF.Sin(offsetState * OffsetAnimSpeed));
181  break;
182  case AnimationType.Noise:
183  offset *= GetNoiseVector(ref offsetState, OffsetAnimSpeed);
184  break;
185  }
186  }
187  offset += new Vector2(
188  RandomOffset.X * randomOffsetMultiplier.X,
189  RandomOffset.Y * randomOffsetMultiplier.Y);
190  if (Math.Abs(rotation) > 0.01f)
191  {
192  Matrix transform = Matrix.CreateRotationZ(rotation);
193  offset = Vector2.Transform(offset, transform);
194  }
195  return offset;
196  }
197 
198  public float GetRotation(ref float rotationState, float randomRotationFactor)
199  {
200  switch (RotationAnim)
201  {
202  case AnimationType.Sine:
203  rotationState %= MathHelper.TwoPi / absRotationSpeedRadians;
204  return
205  rotationRadians * MathF.Sin(rotationState * rotationSpeedRadians)
206  + MathHelper.Lerp(randomRotationRadians.X, randomRotationRadians.Y, randomRotationFactor);
207  case AnimationType.Noise:
208  rotationState %= 1.0f / absRotationSpeedRadians;
209  return
210  rotationRadians * (PerlinNoise.GetPerlin(rotationState * absRotationSpeedRadians, rotationState * absRotationSpeedRadians) - 0.5f)
211  + MathHelper.Lerp(randomRotationRadians.X, randomRotationRadians.Y, randomRotationFactor);
212  default:
213  return
214  rotationRadians +
215  rotationState * rotationSpeedRadians
216  + MathHelper.Lerp(randomRotationRadians.X, randomRotationRadians.Y, randomRotationFactor);
217  }
218  }
219 
220  public Vector2 GetScale(ref float scaleState, float randomScaleModifier)
221  {
222  Vector2 currentScale = Vector2.One *
223  (RandomScale == Vector2.Zero ? scale : MathHelper.Lerp(RandomScale.X, RandomScale.Y, randomScaleModifier));
224  if (ScaleAnimSpeed > 0.0f)
225  {
226  switch (ScaleAnim)
227  {
228  case AnimationType.Sine:
229  scaleState %= (MathHelper.TwoPi / ScaleAnimSpeed);
230  currentScale *= Vector2.One + ScaleAnimAmount * MathF.Sin(scaleState * ScaleAnimSpeed);
231  break;
232  case AnimationType.Noise:
233  currentScale *= Vector2.One + ScaleAnimAmount * GetNoiseVector(ref scaleState, ScaleAnimSpeed);
234  break;
235  }
236  }
237  return currentScale;
238  }
239 
240  private static Vector2 GetNoiseVector(ref float state, float speed)
241  {
242  //multiply speed by a magic constant, because otherwise a speed of 1 would already be very fast (looping through the noise texture once per second)
243  //just makes the values more intuitive / closer to what constitutes as "fast" on the other types of animations
244  float modifiedSpeed = speed * 0.1f;
245  // wrap around the edge of the noise (t == 1)
246  state %= 1.0f / modifiedSpeed;
247  float t = state * modifiedSpeed;
248  Vector2 noiseValue = new Vector2(
249  PerlinNoise.GetPerlin(t, t),
250  //sample the y coordinate from a different position in the noise texture
251  PerlinNoise.GetPerlin(t + 0.5f, t + 0.5f));
252  //move the value so it's in the range of -0.5 and 0.5, as opposed to 0-1.
253  return noiseValue - new Vector2(0.5f, 0.5f);
254  }
255 
256  public static void UpdateSpriteStates(ImmutableDictionary<int, ImmutableArray<DecorativeSprite>> spriteGroups, Dictionary<DecorativeSprite, State> animStates,
257  int entityID, float deltaTime, Func<PropertyConditional, bool> checkConditional)
258  {
259  foreach (int spriteGroup in spriteGroups.Keys)
260  {
261  for (int i = 0; i < spriteGroups[spriteGroup].Length; i++)
262  {
263  var decorativeSprite = spriteGroups[spriteGroup][i];
264  if (decorativeSprite == null) { continue; }
265  if (spriteGroup > 0)
266  {
267  int activeSpriteIndex = entityID % spriteGroups[spriteGroup].Length;
268  if (i != activeSpriteIndex)
269  {
270  animStates[decorativeSprite].IsActive = false;
271  continue;
272  }
273  }
274 
275  //check if the sprite is active (whether it should be drawn or not)
276  var spriteState = animStates[decorativeSprite];
277  spriteState.IsActive = true;
278  foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
279  {
280  if (!checkConditional(conditional))
281  {
282  spriteState.IsActive = false;
283  break;
284  }
285  }
286  if (!spriteState.IsActive) { continue; }
287  if (decorativeSprite.BlinkFrequency > 0.0f)
288  {
289  decorativeSprite.blinkTimer += deltaTime * decorativeSprite.BlinkFrequency;
290  decorativeSprite.blinkTimer %= 1.0f;
291  if (decorativeSprite.blinkTimer > 0.5f)
292  {
293  spriteState.IsActive = false;
294  continue;
295  }
296  }
297  //check if the sprite should be animated
298  bool animate = true;
299  foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
300  {
301  if (!checkConditional(conditional)) { animate = false; break; }
302  }
303  if (!animate) { continue; }
304  spriteState.ScaleState += deltaTime;
305  spriteState.OffsetState += deltaTime;
306  spriteState.RotationState += deltaTime;
307  }
308  }
309  }
310 
311  public void Remove()
312  {
313  Sprite?.Remove();
314  Sprite = null;
315  }
316  }
317 }
Vector2 GetOffset(ref float offsetState, Vector2 randomOffsetMultiplier, float rotation=0.0f)
Vector2 GetScale(ref float scaleState, float randomScaleModifier)
DecorativeSprite(ContentXElement element, string path="", string file="", bool lazyLoad=false)
Dictionary< Identifier, SerializableProperty > SerializableProperties
static void UpdateSpriteStates(ImmutableDictionary< int, ImmutableArray< DecorativeSprite >> spriteGroups, Dictionary< DecorativeSprite, State > animStates, int entityID, float deltaTime, Func< PropertyConditional, bool > checkConditional)
float GetRotation(ref float rotationState, float randomRotationFactor)
int RandomGroupID
If > 0, only one sprite of the same group is used (chosen randomly)
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)