Client LuaCsForBarotrauma
ParticleEmitter.cs
1 #nullable enable
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Xml.Linq;
6 
7 namespace Barotrauma.Particles
8 {
10  {
11 
12  private const float MinValue = int.MinValue,
13  MaxValue = int.MaxValue;
14 
15  public string Name => nameof(ParticleEmitter);
16 
17  private float angleMin, angleMax;
18 
19  public float AngleMinRad { get; private set; }
20  public float AngleMaxRad { get; private set; }
21 
22  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, IsPropertySaveable.Yes)]
23  public float AngleMin
24  {
25  get => angleMin;
26  set
27  {
28  angleMin = value;
29  AngleMinRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
30  }
31  }
32 
33  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, IsPropertySaveable.Yes)]
34  public float AngleMax
35  {
36  get => angleMax;
37  set
38  {
39  angleMax = value;
40  AngleMaxRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
41  }
42  }
43 
44  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
45  public float DistanceMin { get; set; }
46 
47  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
48  public float DistanceMax { get; set; }
49 
50  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
51  public float VelocityMin { get; set; }
52 
53  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
54  public float VelocityMax { get; set; }
55 
56  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(1f, IsPropertySaveable.Yes)]
57  public float ScaleMin { get; set; }
58 
59  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(1f, IsPropertySaveable.Yes)]
60  public float ScaleMax { get; set; }
61 
62 
63  [Editable(), Serialize("1,1", IsPropertySaveable.Yes)]
64  public Vector2 ScaleMultiplier { get; set; }
65 
66  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(0f, IsPropertySaveable.Yes)]
67  public float EmitInterval { get; set; }
68 
69  [Editable(ValueStep = 1, MinValueInt = 0, MaxValueInt = 1000), Serialize(0, IsPropertySaveable.Yes, description: "The number of particles to spawn per frame, or every x seconds if EmitInterval is set.")]
70  public int ParticleAmount { get; set; }
71 
72  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 1000.0f, MinValueFloat = 0.0f), Serialize(0f, IsPropertySaveable.Yes)]
73  public float ParticlesPerSecond { get; set; }
74 
75  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 10.0f, MinValueFloat = 0.0f), Serialize(0f, IsPropertySaveable.Yes, description: "If larger than 0, a particle is spawned every x pixels across the ray cast by a hitscan weapon.")]
76  public float EmitAcrossRayInterval { get; set; }
77 
78  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(0f, IsPropertySaveable.Yes, description: "Delay before the emitter becomes active after being created.")]
79  public float InitialDelay { get; set; }
80 
82  public bool HighQualityCollisionDetection { get; set; }
83 
85  public bool CopyEntityAngle { get; set; }
86 
87  [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should the entity heading direction be applied to the particle rotation? Only affects after flipping the texture and when CopyEntityAngle is true.")]
88  public bool CopyEntityDir { get; set; }
89 
90  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Only relevant for status effects. Makes the emitter copy the angle from the target of the effect instead of the entity applying the effect.")]
91  public bool CopyTargetAngle { get; set; }
92 
93  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Only relevant for particles spawned by another particle. Makes the emitter copy the scale of the parent particle.")]
94  public bool CopyParentParticleScale { get; set; }
95 
96  [Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)]
97  public Color ColorMultiplier { get; set; }
98 
100  public float LifeTimeMultiplier { get; set; }
101 
103  public ParticleDrawOrder DrawOrder { get; set; }
104 
105  [Serialize(0f, IsPropertySaveable.Yes)]
106  public float Angle
107  {
108  get => AngleMin;
109  set => AngleMin = AngleMax = value;
110  }
111 
112  [Serialize(0f, IsPropertySaveable.Yes)]
113  public float Distance
114  {
115  get => DistanceMin;
116  set => DistanceMin = DistanceMax = value;
117  }
118 
119  [Serialize(0f, IsPropertySaveable.Yes)]
120  public float Velocity
121  {
122  get => VelocityMin;
123  set => VelocityMin = VelocityMax = value;
124  }
125 
126  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
127 
128  public ParticleEmitterProperties(XElement element)
129  {
131 
132  //backwards compatibility
133  if (element.GetAttributeBool("drawontop", false))
134  {
135  DrawOrder = ParticleDrawOrder.Foreground;
136  }
137  }
138  }
139 
141  {
142  private float emitTimer;
143  private float burstEmitTimer;
144  private float initialDelay;
145 
146  public readonly ParticleEmitterPrefab Prefab;
147 
149  {
150  Prefab = new ParticleEmitterPrefab(element);
151  }
152 
154  {
155  System.Diagnostics.Debug.Assert(prefab != null, "The prefab of a particle emitter cannot be null");
156  Prefab = prefab;
157  }
158 
159  public void Emit(float deltaTime, Vector2 position, Hull? hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab? overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2>? tracerPoints = null)
160  {
161  if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
162 
163  if (initialDelay < Prefab.Properties.InitialDelay)
164  {
165  initialDelay += deltaTime;
166  return;
167  }
168 
169  emitTimer += deltaTime * amountMultiplier;
170  burstEmitTimer -= deltaTime;
171 
172  if (Prefab.Properties.EmitAcrossRayInterval > 0.0f && tracerPoints != null)
173  {
174  Vector2 dir = tracerPoints.Item2 - tracerPoints.Item1;
175  if (dir.LengthSquared() > 0.001f)
176  {
177  float dist = dir.Length();
178  dir /= dist;
179  for (float z = 0.0f; z < dist; z += Prefab.Properties.EmitAcrossRayInterval)
180  {
181  Vector2 pos = tracerPoints.Item1 + dir * z;
182  Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: null);
183  }
184  }
185  }
186 
187  if (Prefab.Properties.ParticlesPerSecond > 0)
188  {
189  float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond;
190  while (emitTimer > emitInterval)
191  {
192  Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
193  emitTimer -= emitInterval;
194  }
195  }
196 
197  if (burstEmitTimer > 0.0f) { return; }
198 
199  burstEmitTimer = Prefab.Properties.EmitInterval;
200  for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++)
201  {
202  Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
203  }
204  }
205 
206  private void Emit(Vector2 position, Hull? hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab? overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2>? tracerPoints = null)
207  {
208  var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab;
209  if (particlePrefab == null)
210  {
211  DebugConsole.AddWarning($"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\".",
212  contentPackage: Prefab.ContentPackage);
213  return;
214  }
215 
216  Vector2 velocity = Vector2.Zero;
217  if (!MathUtils.NearlyEqual(Prefab.Properties.VelocityMax * velocityMultiplier, 0.0f) || !MathUtils.NearlyEqual(Prefab.Properties.DistanceMax, 0.0f))
218  {
219  angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad) * (mirrorAngle ? -1 : 1);
220  Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
221  velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier;
222  position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
223  }
224 
225  var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess,
226  particlePrefab.DrawOrder != ParticleDrawOrder.Default ? particlePrefab.DrawOrder : Prefab.DrawOrder,
227  lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
228 
229  if (particle != null)
230  {
231  particle.Size *= Rand.Range(Prefab.Properties.ScaleMin, Prefab.Properties.ScaleMax) * sizeMultiplier;
232  particle.Size *= Prefab.Properties.ScaleMultiplier;
233  particle.HighQualityCollisionDetection = Prefab.Properties.HighQualityCollisionDetection;
234  if (colorMultiplier.HasValue)
235  {
236  particle.ColorMultiplier = colorMultiplier.Value.ToVector4();
237  }
238  else if (Prefab.Properties.ColorMultiplier != Color.White)
239  {
240  particle.ColorMultiplier = Prefab.Properties.ColorMultiplier.ToVector4();
241  }
242  }
243  }
244 
245  public Rectangle CalculateParticleBounds(Vector2 startPosition)
246  {
247  Rectangle bounds = new Rectangle((int)startPosition.X, (int)startPosition.Y, (int)startPosition.X, (int)startPosition.Y);
248  if (Prefab.ParticlePrefab == null) { return bounds; }
249 
250  for (float angle = Prefab.Properties.AngleMinRad; angle <= Prefab.Properties.AngleMaxRad; angle += 0.1f)
251  {
252  Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.Properties.VelocityMax;
253  Vector2 endPosition = Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity);
254 
255  Vector2 endSize = Prefab.ParticlePrefab.CalculateEndSize();
256  float spriteExtent = 0.0f;
257  foreach (Sprite sprite in Prefab.ParticlePrefab.Sprites)
258  {
259  if (sprite is SpriteSheet spriteSheet)
260  {
261  spriteExtent = Math.Max(spriteExtent, Math.Max(spriteSheet.FrameSize.X * endSize.X, spriteSheet.FrameSize.Y * endSize.Y));
262  }
263  else
264  {
265  spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y));
266  }
267  }
268 
269  bounds = new Rectangle(
270  (int)Math.Min(bounds.X, endPosition.X - Prefab.Properties.DistanceMax - spriteExtent / 2),
271  (int)Math.Min(bounds.Y, endPosition.Y - Prefab.Properties.DistanceMax - spriteExtent / 2),
272  (int)Math.Max(bounds.X, endPosition.X + Prefab.Properties.DistanceMax + spriteExtent / 2),
273  (int)Math.Max(bounds.Y, endPosition.Y + Prefab.Properties.DistanceMax + spriteExtent / 2));
274  }
275 
276  bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y);
277  return bounds;
278  }
279  }
280 
282  {
283  public readonly Identifier ParticlePrefabName;
284 
286  {
287  get
288  {
289  ParticlePrefab.Prefabs.TryGet(ParticlePrefabName, out var prefab);
290  return prefab;
291  }
292  }
293 
295 
296  public readonly ContentPackage? ContentPackage;
297 
301 
303  {
304  if (element == null) { throw new ArgumentNullException(nameof(element)); }
305  Properties = new ParticleEmitterProperties(element!);
306  ParticlePrefabName = element.GetAttributeIdentifier("particle", "");
307  ContentPackage = element.ContentPackage;
308  }
309 
311  {
312  Properties = properties;
314  }
315  }
316 }
ContentPackage? ContentPackage
Identifier GetAttributeIdentifier(string key, string def)
static GameClient Client
Definition: GameMain.cs:188
readonly ParticleEmitterPrefab Prefab
void Emit(float deltaTime, Vector2 position, Hull? hullGuess=null, float angle=0.0f, float particleRotation=0.0f, float velocityMultiplier=1.0f, float sizeMultiplier=1.0f, float amountMultiplier=1.0f, Color? colorMultiplier=null, ParticlePrefab? overrideParticle=null, bool mirrorAngle=false, Tuple< Vector2, Vector2 >? tracerPoints=null)
ParticleEmitter(ContentXElement element)
Rectangle CalculateParticleBounds(Vector2 startPosition)
ParticleEmitter(ParticleEmitterPrefab prefab)
ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties)
ParticleEmitterPrefab(ContentXElement element)
readonly ParticleEmitterProperties Properties
Dictionary< Identifier, SerializableProperty > SerializableProperties
static readonly PrefabCollection< ParticlePrefab > Prefabs
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)