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  private const float MinValue = int.MinValue,
12  MaxValue = int.MaxValue;
13 
14  public string Name => nameof(ParticleEmitter);
15 
16  private float angleMin, angleMax;
17 
18  public float AngleMinRad { get; private set; }
19  public float AngleMaxRad { get; private set; }
20 
21  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, IsPropertySaveable.Yes)]
22  public float AngleMin
23  {
24  get => angleMin;
25  set
26  {
27  angleMin = value;
28  AngleMinRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
29  }
30  }
31 
32  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 360, MinValueFloat = -360f), Serialize(0f, IsPropertySaveable.Yes)]
33  public float AngleMax
34  {
35  get => angleMax;
36  set
37  {
38  angleMax = value;
39  AngleMaxRad = MathHelper.ToRadians(MathHelper.Clamp(value, -360.0f, 360.0f));
40  }
41  }
42 
43  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
44  public float DistanceMin { get; set; }
45 
46  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
47  public float DistanceMax { get; set; }
48 
49  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
50  public float VelocityMin { get; set; }
51 
52  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = MaxValue, MinValueFloat = MinValue), Serialize(0f, IsPropertySaveable.Yes)]
53  public float VelocityMax { get; set; }
54 
55  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(1f, IsPropertySaveable.Yes)]
56  public float ScaleMin { get; set; }
57 
58  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(1f, IsPropertySaveable.Yes)]
59  public float ScaleMax { get; set; }
60 
61 
62  [Editable(), Serialize("1,1", IsPropertySaveable.Yes)]
63  public Vector2 ScaleMultiplier { get; set; }
64 
65  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 100.0f, MinValueFloat = 0.0f), Serialize(0f, IsPropertySaveable.Yes)]
66  public float EmitInterval { get; set; }
67 
68  [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.")]
69  public int ParticleAmount { get; set; }
70 
71  [Editable(ValueStep = 1, DecimalCount = 2, MaxValueFloat = 1000.0f, MinValueFloat = 0.0f), Serialize(0f, IsPropertySaveable.Yes)]
72  public float ParticlesPerSecond { get; set; }
73 
74  [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.")]
75  public float EmitAcrossRayInterval { get; set; }
76 
77  [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.")]
78  public float InitialDelay { get; set; }
79 
81  public bool HighQualityCollisionDetection { get; set; }
82 
84  public bool CopyEntityAngle { get; set; }
85 
86  [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.")]
87  public bool CopyEntityDir { get; set; }
88 
89  [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.")]
90  public bool CopyTargetAngle { get; set; }
91 
92  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Only relevant for particles spawned by another particle. Makes the emitter copy the scale of the parent particle.")]
93  public bool CopyParentParticleScale { get; set; }
94 
95  [Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)]
96  public Color ColorMultiplier { get; set; }
97 
99  public float LifeTimeMultiplier { get; set; }
100 
101  [Editable, Serialize(false, IsPropertySaveable.Yes)]
102  public bool DrawOnTop { get; set; }
103 
104  [Serialize(0f, IsPropertySaveable.Yes)]
105  public float Angle
106  {
107  get => AngleMin;
108  set => AngleMin = AngleMax = value;
109  }
110 
111  [Serialize(0f, IsPropertySaveable.Yes)]
112  public float Distance
113  {
114  get => DistanceMin;
115  set => DistanceMin = DistanceMax = value;
116  }
117 
118  [Serialize(0f, IsPropertySaveable.Yes)]
119  public float Velocity
120  {
121  get => VelocityMin;
122  set => VelocityMin = VelocityMax = value;
123  }
124 
125  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
126 
127  public ParticleEmitterProperties(XElement element)
128  {
130  }
131  }
132 
134  {
135  private float emitTimer;
136  private float burstEmitTimer;
137  private float initialDelay;
138 
139  public readonly ParticleEmitterPrefab Prefab;
140 
142  {
143  Prefab = new ParticleEmitterPrefab(element);
144  }
145 
147  {
148  System.Diagnostics.Debug.Assert(prefab != null, "The prefab of a particle emitter cannot be null");
149  Prefab = prefab;
150  }
151 
152  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)
153  {
154  if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
155 
156  if (initialDelay < Prefab.Properties.InitialDelay)
157  {
158  initialDelay += deltaTime;
159  return;
160  }
161 
162  emitTimer += deltaTime * amountMultiplier;
163  burstEmitTimer -= deltaTime;
164 
165  if (Prefab.Properties.EmitAcrossRayInterval > 0.0f && tracerPoints != null)
166  {
167  Vector2 dir = tracerPoints.Item2 - tracerPoints.Item1;
168  if (dir.LengthSquared() > 0.001f)
169  {
170  float dist = dir.Length();
171  dir /= dist;
172  for (float z = 0.0f; z < dist; z += Prefab.Properties.EmitAcrossRayInterval)
173  {
174  Vector2 pos = tracerPoints.Item1 + dir * z;
175  Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: null);
176  }
177  }
178  }
179 
180  if (Prefab.Properties.ParticlesPerSecond > 0)
181  {
182  float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond;
183  while (emitTimer > emitInterval)
184  {
185  Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
186  emitTimer -= emitInterval;
187  }
188  }
189 
190  if (burstEmitTimer > 0.0f) { return; }
191 
192  burstEmitTimer = Prefab.Properties.EmitInterval;
193  for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++)
194  {
195  Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
196  }
197  }
198 
199  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)
200  {
201  var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab;
202  if (particlePrefab == null)
203  {
204  DebugConsole.AddWarning($"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\".",
205  contentPackage: Prefab.ContentPackage);
206  return;
207  }
208 
209  Vector2 velocity = Vector2.Zero;
210  if (!MathUtils.NearlyEqual(Prefab.Properties.VelocityMax * velocityMultiplier, 0.0f) || !MathUtils.NearlyEqual(Prefab.Properties.DistanceMax, 0.0f))
211  {
212  angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad) * (mirrorAngle ? -1 : 1);
213  Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
214  velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier;
215  position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
216  }
217 
218  var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, particlePrefab.DrawOnTop || Prefab.DrawOnTop, lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
219 
220  if (particle != null)
221  {
222  particle.Size *= Rand.Range(Prefab.Properties.ScaleMin, Prefab.Properties.ScaleMax) * sizeMultiplier;
223  particle.Size *= Prefab.Properties.ScaleMultiplier;
224  particle.HighQualityCollisionDetection = Prefab.Properties.HighQualityCollisionDetection;
225  if (colorMultiplier.HasValue)
226  {
227  particle.ColorMultiplier = colorMultiplier.Value.ToVector4();
228  }
229  else if (Prefab.Properties.ColorMultiplier != Color.White)
230  {
231  particle.ColorMultiplier = Prefab.Properties.ColorMultiplier.ToVector4();
232  }
233  }
234  }
235 
236  public Rectangle CalculateParticleBounds(Vector2 startPosition)
237  {
238  Rectangle bounds = new Rectangle((int)startPosition.X, (int)startPosition.Y, (int)startPosition.X, (int)startPosition.Y);
239  if (Prefab.ParticlePrefab == null) { return bounds; }
240 
241  for (float angle = Prefab.Properties.AngleMinRad; angle <= Prefab.Properties.AngleMaxRad; angle += 0.1f)
242  {
243  Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.Properties.VelocityMax;
244  Vector2 endPosition = Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity);
245 
246  Vector2 endSize = Prefab.ParticlePrefab.CalculateEndSize();
247  float spriteExtent = 0.0f;
248  foreach (Sprite sprite in Prefab.ParticlePrefab.Sprites)
249  {
250  if (sprite is SpriteSheet spriteSheet)
251  {
252  spriteExtent = Math.Max(spriteExtent, Math.Max(spriteSheet.FrameSize.X * endSize.X, spriteSheet.FrameSize.Y * endSize.Y));
253  }
254  else
255  {
256  spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y));
257  }
258  }
259 
260  bounds = new Rectangle(
261  (int)Math.Min(bounds.X, endPosition.X - Prefab.Properties.DistanceMax - spriteExtent / 2),
262  (int)Math.Min(bounds.Y, endPosition.Y - Prefab.Properties.DistanceMax - spriteExtent / 2),
263  (int)Math.Max(bounds.X, endPosition.X + Prefab.Properties.DistanceMax + spriteExtent / 2),
264  (int)Math.Max(bounds.Y, endPosition.Y + Prefab.Properties.DistanceMax + spriteExtent / 2));
265  }
266 
267  bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y);
268  return bounds;
269  }
270  }
271 
273  {
274  public readonly Identifier ParticlePrefabName;
275 
277  {
278  get
279  {
280  ParticlePrefab.Prefabs.TryGet(ParticlePrefabName, out var prefab);
281  return prefab;
282  }
283  }
284 
286 
287  public readonly ContentPackage? ContentPackage;
288 
289  public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab is { DrawOnTop: true };
290 
292  {
293  if (element == null) { throw new ArgumentNullException(nameof(element)); }
294  Properties = new ParticleEmitterProperties(element!);
295  ParticlePrefabName = element.GetAttributeIdentifier("particle", "");
296  ContentPackage = element.ContentPackage;
297  }
298 
300  {
301  Properties = properties;
303  }
304  }
305 }
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)