Server LuaCsForBarotrauma
FireSource.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
6 #if CLIENT
7 using Barotrauma.Sounds;
8 using Barotrauma.Lights;
9 using Barotrauma.Particles;
10 #endif
11 using FarseerPhysics;
12 using System.Linq;
13 
14 namespace Barotrauma
15 {
16  partial class FireSource : ISpatialEntity
17  {
18  const float OxygenConsumption = 50.0f;
19  const float GrowSpeed = 20.0f;
20  const float MaxDamageRange = 250.0f;
21 
22  protected Hull hull;
23 
24  protected Vector2 position;
25  protected Vector2 size;
26 
27  private readonly Submarine submarine;
28  public Submarine Submarine => submarine;
29 
30  protected bool removed;
31 
32  private readonly List<Decal> burnDecals = new List<Decal>();
33 
34  public Vector2 Position
35  {
36  get { return position; }
37  set
38  {
39  if (!MathUtils.IsValid(value)) return;
40 
41  position = value;
42  }
43  }
44 
45  public Vector2 WorldPosition
46  {
47  get { return Submarine == null ? position : Submarine.Position + position; }
48  }
49 
50  public Vector2 SimPosition => ConvertUnits.ToSimUnits(Position);
51 
52  public Vector2 Size
53  {
54  get { return size; }
55  set
56  {
57  if (value == size) return;
58 
59  Vector2 sizeChange = value - size;
60 
61  size = value;
62  position.X -= sizeChange.X * 0.5f;
63  LimitSize();
64  }
65  }
66 
67  public virtual float DamageRange
68  {
69  get { return Math.Min((float)Math.Sqrt(size.X) * 10.0f, MaxDamageRange); }
70  }
71 
72  public bool DamagesItems
73  {
74  get;
75  set;
76  } = true;
77 
78  public bool DamagesCharacters
79  {
80  get;
81  set;
82  } = true;
83 
84  public bool Removed
85  {
86  get { return removed; }
87  }
88 
89  public Hull Hull
90  {
91  get { return hull; }
92  }
93 
97  public readonly Character SourceCharacter;
98 
99  public FireSource(Vector2 worldPosition, Hull spawningHull = null, Character sourceCharacter = null, bool isNetworkMessage = false)
100  {
101  hull = Hull.FindHull(worldPosition, spawningHull);
102  if (hull == null || worldPosition.Y < hull.WorldSurface) { return; }
103 
104 #if CLIENT
105  if (!isNetworkMessage && GameMain.Client != null) { return; }
106 #endif
107 
108  hull.AddFireSource(this);
109 
110  position = worldPosition - new Vector2(-5.0f, 5.0f);
111  if (hull.Submarine != null)
112  {
113  submarine = hull.Submarine;
115  }
116 
117  SourceCharacter = sourceCharacter;
118 
119 #if CLIENT
120  lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull?.Submarine);
121 #endif
122 
123  size = new Vector2(10.0f, 10.0f);
124  }
125 
126  protected virtual void LimitSize()
127  {
128  if (hull == null) return;
129 
130  position.X = Math.Max(hull.Rect.X, position.X);
131  position.Y = Math.Min(hull.Rect.Y, position.Y);
132 
133  size.X = Math.Min(hull.Rect.Width - (position.X - hull.Rect.X), size.X);
134  size.Y = Math.Min(hull.Rect.Height - (hull.Rect.Y - position.Y), size.Y);
135  }
136 
137  public static void UpdateAll(List<FireSource> fireSources, float deltaTime)
138  {
139  for (int i = fireSources.Count - 1; i >= 0; i--)
140  {
141  fireSources[i].Update(deltaTime);
142  }
143 
144  //combine overlapping fires
145  for (int i = fireSources.Count - 1; i >= 0; i--)
146  {
147  for (int j = i - 1; j >= 0; j--)
148  {
149  i = Math.Min(i, fireSources.Count - 1);
150  j = Math.Min(j, i - 1);
151 
152  if (!fireSources[i].CheckOverLap(fireSources[j])) { continue; }
153 
154  float leftEdge = Math.Min(fireSources[i].position.X, fireSources[j].position.X);
155 
156  fireSources[j].size.X =
157  Math.Max(fireSources[i].position.X + fireSources[i].size.X, fireSources[j].position.X + fireSources[j].size.X)
158  - leftEdge;
159 
160  fireSources[j].position.X = leftEdge;
161  fireSources[j].burnDecals.AddRange(fireSources[i].burnDecals);
162  fireSources[j].burnDecals.Sort((d1, d2) => { return Math.Sign(d1.WorldPosition.X - d2.WorldPosition.X); });
163  fireSources[i].Remove();
164  }
165  }
166  }
167 
168  public static void UpdateAll(List<DummyFireSource> fireSources, float deltaTime)
169  {
170  for (int i = fireSources.Count - 1; i >= 0; i--)
171  {
172  fireSources[i].Update(deltaTime);
173  }
174 
175  //combine overlapping fires
176  for (int i = fireSources.Count - 1; i >= 0; i--)
177  {
178  for (int j = i - 1; j >= 0; j--)
179  {
180  i = Math.Min(i, fireSources.Count - 1);
181  j = Math.Min(j, i - 1);
182 
183  if (!fireSources[i].CheckOverLap(fireSources[j])) { continue; }
184 
185  float leftEdge = Math.Min(fireSources[i].position.X, fireSources[j].position.X);
186 
187  fireSources[j].size.X =
188  Math.Max(fireSources[i].position.X + fireSources[i].size.X, fireSources[j].position.X + fireSources[j].size.X)
189  - leftEdge;
190 
191  fireSources[j].position.X = leftEdge;
192  fireSources[i].Remove();
193  }
194  }
195  }
196 
197  private bool CheckOverLap(FireSource fireSource)
198  {
199  if (this is DummyFireSource != fireSource is DummyFireSource)
200  {
201  return false;
202  }
203 
204  return !(position.X > fireSource.position.X + fireSource.size.X ||
205  position.X + size.X < fireSource.position.X);
206  }
207 
208  public void Update(float deltaTime)
209  {
210  //the firesource will start to shrink if oxygen percentage is below 10
211  float growModifier = Math.Min((hull.OxygenPercentage / 10.0f) - 1.0f, 1.0f);
212 
213  if (DamagesCharacters) { DamageCharacters(deltaTime); }
214  if (DamagesItems) { DamageItems(deltaTime); }
215 
216  if (hull.WaterVolume > 0.0f)
217  {
218  HullWaterExtinguish(deltaTime);
219  if (removed) { return; }
220  }
221 
222  if (!(this is DummyFireSource))
223  {
224  ReduceOxygen(deltaTime);
225  }
226 
227  AdjustXPos(growModifier, deltaTime);
228 
229  size.X += GrowSpeed * growModifier * deltaTime;
230  size.Y = MathHelper.Clamp(size.Y + GrowSpeed * growModifier * deltaTime, 10.0f, 50.0f);
231 
232  if (size.X > 50.0f)
233  {
234  this.position.Y = MathHelper.Lerp(this.position.Y, hull.Rect.Y - hull.Rect.Height + size.Y, deltaTime);
235  }
236 
237  LimitSize();
238 
239  if (size.X > 256.0f && !(this is DummyFireSource))
240  {
241  if (burnDecals.Count == 0)
242  {
243  var newDecal = hull.AddDecal("burnt", WorldPosition + size / 2, 1f, isNetworkEvent: false);
244  if (newDecal != null) { burnDecals.Add(newDecal); }
245  }
246  else if (WorldPosition.X < burnDecals[0].WorldPosition.X - 256.0f)
247  {
248  var newDecal = hull.AddDecal("burnt", WorldPosition, 1f, isNetworkEvent: false);
249  if (newDecal != null) { burnDecals.Insert(0, newDecal); }
250  }
251  else if (WorldPosition.X + size.X > burnDecals[burnDecals.Count - 1].WorldPosition.X + 256.0f)
252  {
253  var newDecal = hull.AddDecal("burnt", WorldPosition + Vector2.UnitX * size.X, 1f, isNetworkEvent: false);
254  if (newDecal != null) { burnDecals.Add(newDecal); }
255  }
256  }
257 
258  foreach (Decal d in burnDecals)
259  {
260  //prevent the decals from fading out as long as the firesource is alive
261  d.ForceRefreshFadeTimer(Math.Min(d.FadeTimer, d.FadeInTime));
262  }
263 
264  UpdateProjSpecific(growModifier, deltaTime);
265 
266 
267  if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
268  {
269  Remove();
270  }
271  }
272 
273  protected virtual void ReduceOxygen(float deltaTime)
274  {
275  hull.Oxygen -= size.X * deltaTime * OxygenConsumption;
276  }
277 
278  protected virtual void AdjustXPos(float growModifier, float deltaTime)
279  {
280  position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
281  }
282 
283  partial void UpdateProjSpecific(float growModifier, float deltaTime);
284 
285  private void OnChangeHull(Vector2 pos, Hull particleHull)
286  {
287  if (particleHull == hull || particleHull == null) return;
288 
289  //hull already has a firesource roughly at the particles position -> don't create a new one
290  if (particleHull.FireSources.Find(fs => pos.X > fs.position.X - 100.0f && pos.X < fs.position.X + fs.size.X + 100.0f) != null) return;
291 
292  new FireSource(new Vector2(pos.X, particleHull.WorldRect.Y - particleHull.Rect.Height + 5.0f));
293  }
294 
295  private void DamageCharacters(float deltaTime)
296  {
297  if (size.X <= 0.0f) { return; }
298 
299  for (int i = 0; i < Character.CharacterList.Count; i++)
300  {
301  Character c = Character.CharacterList[i];
302  if (c.CurrentHull == null || c.IsDead) { continue; }
303 
304  if (!IsInDamageRange(c, DamageRange)) { continue; }
305 
306  //GetApproximateDistance returns float.MaxValue if there's no path through open gaps between the hulls (e.g. if there's a door/wall in between)
307  if (hull.GetApproximateDistance(Position, c.Position, c.CurrentHull, 10000.0f) > size.X + DamageRange)
308  {
309  continue;
310  }
311 
312  float dmg = (float)Math.Sqrt(Math.Min(500, size.X)) * deltaTime / c.AnimController.Limbs.Count(l => !l.IsSevered && !l.Hidden);
313  foreach (Limb limb in c.AnimController.Limbs)
314  {
315  if (limb.IsSevered) { continue; }
316  c.LastDamageSource = SourceCharacter;
317  c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(),
318  stun: 0.0f,
319  playSound: false,
320  attackImpulse: Vector2.Zero,
321  attacker: SourceCharacter);
322  }
323 #if CLIENT
324  //let clients display the client-side damage immediately, otherwise they may not be able to react to the damage fast enough
325  c.CharacterHealth.DisplayedVitality = c.Vitality;
326 #endif
327  c.ApplyStatusEffects(ActionType.OnFire, deltaTime);
328  }
329  }
330 
331  public bool IsInDamageRange(Character c, float damageRange)
332  {
333  if (c.Position.X < position.X - damageRange || c.Position.X > position.X + size.X + damageRange) return false;
334  if (c.Position.Y < position.Y - size.Y || c.Position.Y > hull.Rect.Y) return false;
335 
336  return true;
337  }
338 
339  public bool IsInDamageRange(Vector2 worldPosition, float damageRange)
340  {
341  if (worldPosition.X < WorldPosition.X - damageRange || worldPosition.X > WorldPosition.X + size.X + damageRange) return false;
342  if (worldPosition.Y < WorldPosition.Y - size.Y || worldPosition.Y > hull.WorldRect.Y) return false;
343 
344  return true;
345  }
346 
347  private void DamageItems(float deltaTime)
348  {
349  if (size.X <= 0.0f) { return; }
350 #if CLIENT
351  if (GameMain.Client != null) { return; }
352 #endif
353  foreach (Item item in Item.ItemList)
354  {
355  if (item.CurrentHull != hull || item.FireProof || item.Condition <= 0.0f) { continue; }
356 
357  //don't apply OnFire effects if the item is inside a fireproof container
358  //(or if it's inside a container that's inside a fireproof container, etc)
359  Item container = item.Container;
360  bool fireProof = false;
361  while (container != null)
362  {
363  if (container.FireProof)
364  {
365  fireProof = true;
366  break;
367  }
368  container = container.Container;
369  }
370  if (fireProof) { continue; }
371 
372  float range = (float)Math.Sqrt(size.X) * 10.0f;
373  if (item.Position.X < position.X - range || item.Position.X > position.X + size.X + range) { continue; }
374  if (item.Position.Y < position.Y - size.Y || item.Position.Y > hull.Rect.Y) { continue; }
375 
376  item.ApplyStatusEffects(ActionType.OnFire, deltaTime);
377  if (item.Condition <= 0.0f && GameMain.NetworkMember is { IsServer: true })
378  {
379  GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFire));
380  }
381  }
382  }
383 
384  private void HullWaterExtinguish(float deltaTime)
385  {
386  //the higher the surface of the water is relative to the firesource, the faster it puts out the fire
387  float extinguishAmount = (hull.Surface - (position.Y - size.Y)) * deltaTime;
388 
389  if (extinguishAmount < 0.0f) return;
390 
391 #if CLIENT
392  float steamCount = Rand.Range(-5.0f, Math.Min(extinguishAmount * 100.0f, 10));
393  for (int i = 0; i < steamCount; i++)
394  {
395  Vector2 spawnPos = new Vector2(
396  WorldPosition.X + Rand.Range(0.0f, size.X),
397  WorldPosition.Y + 10.0f);
398 
399  Vector2 speed = new Vector2((spawnPos.X - (WorldPosition.X + size.X / 2.0f)), (float)Math.Sqrt(size.X) * Rand.Range(20.0f, 25.0f));
400 
401  var particle = GameMain.ParticleManager.CreateParticle("steam",
402  spawnPos, speed, 0.0f, hull);
403  }
404 #endif
405  extinguishAmount = Math.Min(size.X, extinguishAmount);
406 
407  position.X += extinguishAmount / 2.0f;
408  size.X -= extinguishAmount;
409 
410  //evaporate some of the water
411  hull.WaterVolume -= extinguishAmount;
412 
413  if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
414  {
415  Remove();
416  }
417  }
418 
419  public void Extinguish(float deltaTime, float amount)
420  {
421  float extinguishAmount = amount * deltaTime;
422 
423 #if CLIENT
424  float steamCount = Rand.Range(-5.0f, (float)Math.Sqrt(amount));
425  for (int i = 0; i < steamCount; i++)
426  {
427  Vector2 spawnPos = new Vector2(Rand.Range(position.X, position.X + size.X), Rand.Range(position.Y - size.Y, position.Y) + 10.0f);
428 
429  Vector2 speed = new Vector2((spawnPos.X - (position.X + size.X / 2.0f)), (float)Math.Sqrt(size.X) * Rand.Range(20.0f, 25.0f));
430 
431  var particle = GameMain.ParticleManager.CreateParticle("steam",
432  spawnPos, speed, 0.0f, hull);
433  }
434 #endif
435  extinguishAmount = Math.Min(size.X, extinguishAmount);
436 
437  position.X += extinguishAmount / 2.0f;
438  size.X -= extinguishAmount;
439 
440  hull.WaterVolume -= extinguishAmount;
441 
442  if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
443  {
444  Remove();
445  }
446  }
447 
448  public void Extinguish(float deltaTime, float amount, Vector2 worldPosition)
449  {
450  if (IsInDamageRange(worldPosition, 100.0f))
451  {
452  Extinguish(deltaTime, amount);
453  }
454  }
455 
456  public void Remove()
457  {
458 #if CLIENT
459  lightSource?.Remove();
460  lightSource = null;
461 #endif
462  foreach (Decal d in burnDecals)
463  {
464  d.StopFadeIn();
465  }
466  hull?.RemoveFire(this);
467  removed = true;
468  }
469  }
470 }
AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable< Affliction > afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker=null, float damageMultiplier=1, bool allowStacking=true, float penetration=0f, bool shouldImplode=false, bool ignoreDamageOverlay=false, bool recalculateVitality=true)
void ForceRefreshFadeTimer(float val)
Definition: Decal.cs:140
float FadeInTime
Definition: Decal.cs:23
void StopFadeIn()
Definition: Decal.cs:146
float FadeTimer
Definition: Decal.cs:17
Submarine Submarine
Definition: Entity.cs:53
void Extinguish(float deltaTime, float amount)
Definition: FireSource.cs:419
FireSource(Vector2 worldPosition, Hull spawningHull=null, Character sourceCharacter=null, bool isNetworkMessage=false)
Definition: FireSource.cs:99
static void UpdateAll(List< DummyFireSource > fireSources, float deltaTime)
Definition: FireSource.cs:168
Vector2? WorldPosition
Definition: FireSource.cs:46
bool IsInDamageRange(Character c, float damageRange)
Definition: FireSource.cs:331
void Extinguish(float deltaTime, float amount, Vector2 worldPosition)
Definition: FireSource.cs:448
virtual void AdjustXPos(float growModifier, float deltaTime)
Definition: FireSource.cs:278
void Update(float deltaTime)
Definition: FireSource.cs:208
virtual float DamageRange
Definition: FireSource.cs:68
virtual void LimitSize()
Definition: FireSource.cs:126
virtual void ReduceOxygen(float deltaTime)
Definition: FireSource.cs:273
bool IsInDamageRange(Vector2 worldPosition, float damageRange)
Definition: FireSource.cs:339
readonly Character SourceCharacter
Which character caused this fire (if any)?
Definition: FireSource.cs:97
static void UpdateAll(List< FireSource > fireSources, float deltaTime)
Definition: FireSource.cs:137
static NetworkMember NetworkMember
Definition: GameMain.cs:41
void AddFireSource(FireSource fireSource)
float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor=0)
Approximate distance from this hull to the target hull, moving through open gaps without passing thro...
Decal AddDecal(UInt32 decalId, Vector2 worldPosition, float scale, bool isNetworkEvent, int? spriteIndex=null)
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
Rectangle? WorldRect
Definition: MapEntity.cs:105
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26
@ Character
Characters only