Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/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;
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)
Submarine Submarine
Definition: Entity.cs:53
void Extinguish(float deltaTime, float amount)
FireSource(Vector2 worldPosition, Hull spawningHull=null, Character sourceCharacter=null, bool isNetworkMessage=false)
static void UpdateAll(List< DummyFireSource > fireSources, float deltaTime)
bool IsInDamageRange(Character c, float damageRange)
void Extinguish(float deltaTime, float amount, Vector2 worldPosition)
virtual void AdjustXPos(float growModifier, float deltaTime)
bool IsInDamageRange(Vector2 worldPosition, float damageRange)
readonly Character SourceCharacter
Which character caused this fire (if any)?
static void UpdateAll(List< FireSource > fireSources, float deltaTime)
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static ParticleManager ParticleManager
Definition: GameMain.cs:101
static GameClient Client
Definition: GameMain.cs:188
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)
Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess=null, float collisionIgnoreTimer=0f, Tuple< Vector2, Vector2 > tracerPoints=null)
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19