Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/Gap.cs
2 using FarseerPhysics;
3 using FarseerPhysics.Dynamics;
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Xml.Linq;
9 using MoonSharp.Interpreter;
10 
11 namespace Barotrauma
12 {
14  {
15  public static List<Gap> GapList = new List<Gap>();
16 
17  const float MaxFlowForce = 500.0f;
18 
19  public static bool ShowGaps = true;
20 
21  const float OutsideColliderRaycastIntervalLowPrio = 1.5f;
22  const float OutsideColliderRaycastIntervalHighPrio = 0.1f;
23 
24  public bool IsHorizontal
25  {
26  get;
27  private set;
28  }
29 
34  public bool IsDiagonal { get; }
35 
36  public readonly float GlowEffectT;
37 
38  //a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
39  private float open;
40 
41  //the force of the water flow which is exerted on physics bodies
42  private Vector2 flowForce;
43  private Hull flowTargetHull;
44 
45  private float openedTimer = 1.0f;
46 
47  private float higherSurface;
48  private float lowerSurface;
49 
50  private Vector2 lerpedFlowForce;
51 
52  //if set to true, hull connections of this gap won't be updated when changes are being done to hulls
53  public bool DisableHullRechecks;
54 
55  //can ambient light get through the gap even if it's not open
56  public bool PassAmbientLight;
57 
58  //a collider outside the gap (for example an ice wall next to the sub)
59  //used by ragdolls to prevent them from ending up inside colliders when teleporting out of the sub
60  private Body outsideCollisionBlocker;
61  private float outsideColliderRaycastTimer;
62 
63  private bool wasRoomToRoom;
64 
65  public float Open
66  {
67  get { return open; }
68  set
69  {
70  if (float.IsNaN(value)) { return; }
71  if (value > open)
72  {
73  openedTimer = 1.0f;
74  }
75  if (connectedDoor == null && !IsHorizontal && linkedTo.Any(e => e is Hull))
76  {
77  if (value > open && value >= 1.0f)
78  {
79  InformWaypointsAboutGapState(this, open: true);
80  }
81  else if (value < open && open >= 1.0f)
82  {
83  InformWaypointsAboutGapState(this, open: false);
84  }
85  }
86  open = MathHelper.Clamp(value, 0.0f, 1.0f);
87 
88  static void InformWaypointsAboutGapState(Gap gap, bool open)
89  {
90  foreach (var wp in WayPoint.WayPointList)
91  {
92  if (IsWaypointRightAboveGap(gap, wp))
93  {
94  wp.OnGapStateChanged(open, gap);
95  }
96  }
97  }
98 
99  static bool IsWaypointRightAboveGap(Gap gap, WayPoint wp)
100  {
101  if (wp.SpawnType != SpawnType.Path) { return false; }
102  if (!gap.linkedTo.Contains(wp.CurrentHull)) { return false; }
103  if (wp.Position.Y < gap.Rect.Top) { return false; }
104  if (wp.Position.X > gap.Rect.Right) { return false; }
105  if (wp.Position.X < gap.Rect.Left) { return false; }
106  return true;
107  }
108  }
109  }
110 
111  public float Size => IsHorizontal ? Rect.Height : Rect.Width;
112 
113  public float PressureDistributionSpeed => Size / 100.0f * open;
114 
115  private Door connectedDoor;
117  {
118  get
119  {
120  if (connectedDoor != null && connectedDoor.Item.Removed)
121  {
122  connectedDoor = null;
123  }
124  return connectedDoor;
125  }
126  set { connectedDoor = value; }
127  }
128 
130 
131  public Vector2 LerpedFlowForce
132  {
133  get { return lerpedFlowForce; }
134  }
135 
137  {
138  get { return flowTargetHull; }
139  }
140 
141  public bool IsRoomToRoom
142  {
143  get
144  {
145  return linkedTo.Count == 2;
146  }
147  }
148 
149  public override Rectangle Rect
150  {
151  get
152  {
153  return base.Rect;
154  }
155  set
156  {
157  base.Rect = value;
158 
159  FindHulls();
160  }
161  }
162 
163  public override string Name => "Gap";
164 
165  public readonly Dictionary<Identifier, SerializableProperty> properties;
166  public Dictionary<Identifier, SerializableProperty> SerializableProperties
167  {
168  get { return properties; }
169  }
170 
171  public Gap(Rectangle rectangle)
172  : this(rectangle, Submarine.MainSub)
173  {
174 #if CLIENT
176  {
177  SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { this }, false));
178  }
179 #endif
180  }
181 
182  public Gap(Rectangle rect, Submarine submarine)
183  : this(rect, rect.Width < rect.Height, submarine)
184  { }
185 
186  public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, bool isDiagonal = false, ushort id = Entity.NullEntityID)
187  : base(CoreEntityPrefab.GapPrefab, submarine, id)
188  {
189  this.rect = rect;
190  flowForce = Vector2.Zero;
191  IsHorizontal = isHorizontal;
192  IsDiagonal = isDiagonal;
193  open = 1.0f;
194 
196 
197  FindHulls();
198  GapList.Add(this);
199  InsertToList();
200 
201  GlowEffectT = Rand.Range(0.0f, 1.0f);
202 
203  float blockerSize = ConvertUnits.ToSimUnits(Math.Max(rect.Width, rect.Height)) / 2;
204  outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize,
205  BodyType.Static,
206  Physics.CollisionWall,
207  Physics.CollisionCharacter,
208  findNewContacts: false);
209  outsideCollisionBlocker.UserData = $"CollisionBlocker (Gap {ID})";
210  outsideCollisionBlocker.Enabled = false;
211 #if CLIENT
212  Resized += newRect => IsHorizontal = newRect.Width < newRect.Height;
213 # endif
214 
215  wasRoomToRoom = IsRoomToRoom;
217  DebugConsole.Log("Created gap (" + ID + ")");
218  }
219 
220  public override MapEntity Clone()
221  {
222  return new Gap(rect, IsHorizontal, Submarine);
223  }
224 
225  public override void Move(Vector2 amount, bool ignoreContacts = true)
226  {
227  if (!MathUtils.IsValid(amount))
228  {
229  DebugConsole.ThrowError($"Attempted to move a gap by an invalid amount ({amount})\n{Environment.StackTrace.CleanupStackTrace()}");
230  return;
231  }
232 
233  base.Move(amount, ignoreContacts);
234 
235  if (!DisableHullRechecks) { FindHulls(); }
236  }
237 
238  public static void UpdateHulls()
239  {
240  foreach (Gap g in GapList)
241  {
242  for (int i = g.linkedTo.Count - 1; i >= 0; i--)
243  {
244  if (g.linkedTo[i].Removed)
245  {
246  g.linkedTo.RemoveAt(i);
247  }
248  }
249 
250  if (g.DisableHullRechecks) continue;
251  g.FindHulls();
252  }
253  }
254 
255  public override bool IsMouseOn(Vector2 position)
256  {
257  return ShowGaps && Submarine.RectContains(WorldRect, position) &&
258  !Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -5), position);
259  }
260 
261  public void AutoOrient()
262  {
263  Vector2 searchPosLeft = new Vector2(rect.X, rect.Y - rect.Height / 2);
264  Hull hullLeft = Hull.FindHullUnoptimized(searchPosLeft, null, false);
265  Vector2 searchPosRight = new Vector2(rect.Right, rect.Y - rect.Height / 2);
266  Hull hullRight = Hull.FindHullUnoptimized(searchPosRight, null, false);
267 
268  if (hullLeft != null && hullRight != null && hullLeft != hullRight)
269  {
270  IsHorizontal = true;
271  return;
272  }
273 
274  Vector2 searchPosTop = new Vector2(rect.Center.X, rect.Y);
275  Hull hullTop = Hull.FindHullUnoptimized(searchPosTop, null, false);
276  Vector2 searchPosBottom = new Vector2(rect.Center.X, rect.Y - rect.Height);
277  Hull hullBottom = Hull.FindHullUnoptimized(searchPosBottom, null, false);
278 
279  if (hullTop != null && hullBottom != null && hullTop != hullBottom)
280  {
281  IsHorizontal = false;
282  return;
283  }
284 
285  if ((hullLeft == null) != (hullRight == null))
286  {
287  IsHorizontal = true;
288  }
289  else if ((hullTop == null) != (hullBottom == null))
290  {
291  IsHorizontal = false;
292  }
293  }
294 
295  private void FindHulls()
296  {
297  Hull[] hulls = new Hull[2];
298 
299  foreach (var linked in linkedTo)
300  {
301  if (linked is Hull hull)
302  {
303  hull.ConnectedGaps.Remove(this);
304  }
305  }
306  linkedTo.Clear();
307 
308  int tolerance = 1;
309  Vector2[] searchPos = new Vector2[2];
310  if (IsHorizontal)
311  {
312  searchPos[0] = new Vector2(rect.X - tolerance, rect.Y - rect.Height / 2);
313  searchPos[1] = new Vector2(rect.Right + tolerance, rect.Y - rect.Height / 2);
314  }
315  else
316  {
317  searchPos[0] = new Vector2(rect.Center.X, rect.Y + tolerance);
318  searchPos[1] = new Vector2(rect.Center.X, rect.Y - rect.Height - tolerance);
319  }
320 
321  for (int i = 0; i < 2; i++)
322  {
323  hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false);
324  if (hulls[i] == null) hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false, true);
325  }
326 
327  if (hulls[0] == null && hulls[1] == null) { return; }
328 
329  if (hulls[0] == null && hulls[1] != null)
330  {
331  Hull temp = hulls[0];
332  hulls[0] = hulls[1];
333  hulls[1] = temp;
334  }
335 
336  flowTargetHull = hulls[0];
337 
338  for (int i = 0; i < 2; i++)
339  {
340  if (hulls[i] == null) { continue; }
341  linkedTo.Add(hulls[i]);
342  if (!hulls[i].ConnectedGaps.Contains(this)) hulls[i].ConnectedGaps.Add(this);
343  }
344  }
345 
346  private int updateCount;
347 
348  public override void Update(float deltaTime, Camera cam)
349  {
350  int updateInterval = 4;
351  float flowMagnitude = flowForce.LengthSquared();
352  if (flowMagnitude < 1.0f)
353  {
354  //very sparse updates if there's practically no water moving
355  updateInterval = 8;
356  }
357  else if (linkedTo.Count == 2 && flowMagnitude > 10.0f)
358  {
359  //frequent updates if water is moving between hulls
360  updateInterval = 1;
361  }
362 
363  updateCount++;
364  if (updateCount < updateInterval) { return; }
365  deltaTime *= updateCount;
366  updateCount = 0;
367 
368  flowForce = Vector2.Zero;
369  outsideColliderRaycastTimer -= deltaTime;
370 
371  if (IsRoomToRoom != wasRoomToRoom)
372  {
374  wasRoomToRoom = IsRoomToRoom;
375  }
376 
377  if (open == 0.0f || linkedTo.Count == 0)
378  {
379  lerpedFlowForce = Vector2.Zero;
380  return;
381  }
382 
383  Hull hull1 = (Hull)linkedTo[0];
384  Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
385  if (hull1 == hull2) { return; }
386 
387  UpdateOxygen(hull1, hull2, deltaTime);
388 
389  if (linkedTo.Count == 1)
390  {
391  //gap leading from a room to outside
392  UpdateRoomToOut(deltaTime, hull1);
393  }
394  else if (linkedTo.Count == 2)
395  {
396  //gap leading from a room to another
397  UpdateRoomToRoom(deltaTime, hull1, hull2);
398  }
399 
400  flowForce.X = MathHelper.Clamp(flowForce.X, -MaxFlowForce, MaxFlowForce);
401  flowForce.Y = MathHelper.Clamp(flowForce.Y, -MaxFlowForce, MaxFlowForce);
402  if (openedTimer > 0.0f && flowForce.LengthSquared() > lerpedFlowForce.LengthSquared())
403  {
404  //if the gap has just been opened/created, allow it to exert a large force instantly without any smoothing
405  lerpedFlowForce = flowForce;
406  }
407  else
408  {
409  lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f);
410  }
411 
412  openedTimer -= deltaTime;
413 
414  EmitParticles(deltaTime);
415  }
416 
417  partial void EmitParticles(float deltaTime);
418 
419  void UpdateRoomToRoom(float deltaTime, Hull hull1, Hull hull2)
420  {
421  Vector2 subOffset = Vector2.Zero;
422  if (hull1.Submarine != Submarine)
423  {
424  subOffset = Submarine.Position - hull1.Submarine.Position;
425  }
426  else if (hull2.Submarine != Submarine)
427  {
428  subOffset = hull2.Submarine.Position - Submarine.Position;
429  }
430 
431  if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) { return; }
432 
433  //a variable affecting the water flow through the gap
434  //the larger the gap is, the faster the water flows
435  float sizeModifier = Size / 100.0f * open;
436 
437  //horizontal gap (such as a regular door)
438  if (IsHorizontal)
439  {
440  higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y);
441  float delta = 0.0f;
442 
443  //water level is above the lower boundary of the gap
444  if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size)
445  {
446  int dir = (hull1.Pressure > hull2.Pressure + subOffset.Y) ? 1 : -1;
447 
448  //water flowing from the righthand room to the lefthand room
449  if (dir == -1)
450  {
451  if (!(hull2.WaterVolume > 0.0f)) { return; }
452  lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
453  //delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
454  //delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
455 
456  flowTargetHull = hull1;
457 
458  //make sure not to move more than what the room contains
459  delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 300.0f * sizeModifier * deltaTime, Math.Min(hull2.WaterVolume, hull2.Volume));
460 
461  //make sure not to place more water to the target room than it can hold
462  delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
463  hull1.WaterVolume += delta;
464  hull2.WaterVolume -= delta;
465  if (hull1.WaterVolume > hull1.Volume)
466  {
467  hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + hull2.Pressure+subOffset.Y) / 2);
468  }
469 
470  flowForce = new Vector2(-delta * (float)(Timing.Step / deltaTime), 0.0f);
471  }
472  else if (dir == 1)
473  {
474  if (!(hull1.WaterVolume > 0.0f)) { return; }
475  lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
476 
477  flowTargetHull = hull2;
478 
479  //make sure not to move more than what the room contains
480  delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 300.0f * sizeModifier * deltaTime, Math.Min(hull1.WaterVolume, hull1.Volume));
481 
482  //make sure not to place more water to the target room than it can hold
483  delta = Math.Min(delta, hull2.Volume * Hull.MaxCompress - hull2.WaterVolume);
484  hull1.WaterVolume -= delta;
485  hull2.WaterVolume += delta;
486  if (hull2.WaterVolume > hull2.Volume)
487  {
488  hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure-subOffset.Y) + hull2.Pressure) / 2);
489  }
490 
491  flowForce = new Vector2(delta * (float)(Timing.Step / deltaTime), 0.0f);
492  }
493 
494  if (delta > 1.5f && subOffset == Vector2.Zero)
495  {
496  float avg = (hull1.Surface + hull2.Surface) / 2.0f;
497 
498  if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
499  hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1] < rect.Y)
500  {
501  hull1.WaveVel[hull1.WaveY.Length - 1] = (avg - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f;
502  hull1.WaveVel[hull1.WaveY.Length - 2] = hull1.WaveVel[hull1.WaveY.Length - 1];
503  }
504 
505  if (hull2.WaterVolume < hull2.Volume / Hull.MaxCompress &&
506  hull2.Surface + hull2.WaveY[0] < rect.Y)
507  {
508  hull2.WaveVel[0] = (avg - (hull2.Surface + hull2.WaveY[0])) * 0.1f;
509  hull2.WaveVel[1] = hull2.WaveVel[0];
510  }
511  }
512  }
513 
514  }
515  else
516  {
517  //lower room is full of water
518  if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
519  {
520  float delta = Math.Min(hull2.WaterVolume - hull2.Volume + (hull2.Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier);
521 
522  //make sure not to place more water to the target room than it can hold
523  if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress)
524  {
525  delta -= (hull1.WaterVolume + delta) - (hull1.Volume * Hull.MaxCompress);
526  }
527 
528  delta = Math.Max(delta, 0.0f);
529  hull1.WaterVolume += delta;
530  hull2.WaterVolume -= delta;
531 
532  flowForce = new Vector2(
533  0.0f,
534  Math.Min(Math.Min((hull2.Pressure + subOffset.Y) - hull1.Pressure, 200.0f), delta * (float)(Timing.Step / deltaTime)));
535 
536  flowTargetHull = hull1;
537 
538  if (hull1.WaterVolume > hull1.Volume)
539  {
540  hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + (hull2.Pressure + subOffset.Y)) / 2);
541  }
542 
543  }
544  //there's water in the upper room, drop to lower
545  else if (hull1.WaterVolume > 0)
546  {
547  flowTargetHull = hull2;
548 
549  //make sure the amount of water moved isn't more than what the room contains
550  float delta = Math.Min(hull1.WaterVolume, deltaTime * 25000f * sizeModifier);
551 
552  //make sure not to place more water to the target room than it can hold
553  if (hull2.WaterVolume + delta > hull2.Volume * Hull.MaxCompress)
554  {
555  delta -= (hull2.WaterVolume + delta) - (hull2.Volume * Hull.MaxCompress);
556  }
557  hull1.WaterVolume -= delta;
558  hull2.WaterVolume += delta;
559 
560  flowForce = new Vector2(
561  hull1.WaveY[hull1.GetWaveIndex(rect.X)] - hull1.WaveY[hull1.GetWaveIndex(rect.Right)],
562  MathHelper.Clamp(-delta * (float)(Timing.Step / deltaTime), -200.0f, 0.0f));
563 
564  if (hull2.WaterVolume > hull2.Volume)
565  {
566  hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure - subOffset.Y) + hull2.Pressure) / 2);
567  }
568  }
569  }
570 
571  if (open > 0.0f)
572  {
573  if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress &&
574  hull2.WaterVolume > hull2.Volume / Hull.MaxCompress)
575  {
576  //both hulls full -> distribute pressure
577  float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
578  changePressure(hull1, avgLethality, PressureDistributionSpeed, deltaTime);
579  changePressure(hull2, avgLethality, PressureDistributionSpeed, deltaTime);
580 
581  static void changePressure(Hull hull, float target, float speed, float deltaTime)
582  {
583  float diff = target - hull.LethalPressure;
584  float maxChange = Hull.PressureBuildUpSpeed * speed * deltaTime;
585  hull.LethalPressure += MathHelper.Clamp(diff, -maxChange, maxChange);
586  }
587  }
588  else
589  {
590  //either hull not full -> pressure drops
591  hull1.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime;
592  hull2.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime;
593  }
594  }
595  }
596 
597  void UpdateRoomToOut(float deltaTime, Hull hull1)
598  {
599  //a variable affecting the water flow through the gap
600  //the larger the gap is, the faster the water flows
601  float sizeModifier = Size * open * open;
602 
603  float delta = 500.0f * sizeModifier * deltaTime;
604 
605  //make sure not to place more water to the target room than it can hold
606  delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
607  hull1.WaterVolume += delta;
608 
609  if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 30.0f * deltaTime; }
610 
611  flowTargetHull = hull1;
612 
613  if (IsHorizontal)
614  {
615  //water flowing from right to left
616  if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
617  {
618  flowForce = new Vector2(-delta * (float)(Timing.Step / deltaTime), 0.0f);
619 
620  }
621  else
622  {
623  flowForce = new Vector2(delta * (float)(Timing.Step / deltaTime), 0.0f);
624  }
625 
626  higherSurface = hull1.Surface;
627  lowerSurface = rect.Y;
628 
629  if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
630  hull1.Surface < rect.Y)
631  {
632  //create a wave from the side of the hull the water is leaking from
633  if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
634  {
635  CreateWave(rect, hull1, hull1.WaveY.Length - 1, hull1.WaveY.Length - 2, flowForce, deltaTime);
636  }
637  else
638  {
639  CreateWave(rect, hull1, 0, 1, flowForce, deltaTime);
640  }
641  static void CreateWave(Rectangle rect, Hull hull1, int index1, int index2, Vector2 flowForce, float deltaTime)
642  {
643  float vel = (rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[index1]);
644  vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
645  if (vel > 0.0f)
646  {
647  hull1.WaveVel[index1] += vel * deltaTime;
648  hull1.WaveVel[index2] += vel * deltaTime;
649  }
650  }
651  }
652  else
653  {
654  hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
655  }
656  }
657  else
658  {
659  if (rect.Y > hull1.Rect.Y - hull1.Rect.Height / 2.0f)
660  {
661  flowForce = new Vector2(0.0f, -delta * (float)(Timing.Step / deltaTime));
662  }
663  else
664  {
665  flowForce = new Vector2(0.0f, delta * (float)(Timing.Step / deltaTime));
666  }
667  if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
668  {
669  hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
670  }
671  }
672  }
673 
675  {
676  if (outsideCollisionBlocker == null) { return false; }
677  if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || linkedTo[0] is not Hull)
678  {
679  outsideCollisionBlocker.Enabled = false;
680  return false;
681  }
682 
683  if (outsideColliderRaycastTimer <= 0.0f)
684  {
685  UpdateOutsideColliderState((Hull)linkedTo[0]);
686  outsideColliderRaycastTimer = outsideCollisionBlocker.Enabled ?
687  OutsideColliderRaycastIntervalHighPrio :
688  OutsideColliderRaycastIntervalLowPrio;
689  }
690 
691  return outsideCollisionBlocker.Enabled;
692  }
693 
694  private void UpdateOutsideColliderState(Hull hull)
695  {
696  if (Submarine == null || IsRoomToRoom || Level.Loaded == null) { return; }
697 
698  Vector2 rayDir;
699  if (IsHorizontal)
700  {
701  rayDir = new Vector2(Math.Sign(rect.Center.X - hull.Rect.Center.X), 0);
702  }
703  else
704  {
705  rayDir = new Vector2(0, Math.Sign((rect.Y - rect.Height / 2) - (hull.Rect.Y - hull.Rect.Height / 2)));
706  }
707 
708  Vector2 rayStart = ConvertUnits.ToSimUnits(WorldPosition);
709  Vector2 rayEnd = rayStart + rayDir * 5.0f;
710 
711  var levelCells = Level.Loaded.GetCells(WorldPosition, searchDepth: 1);
712  foreach (var cell in levelCells)
713  {
714  if (cell.IsPointInside(WorldPosition))
715  {
716  outsideCollisionBlocker.Enabled = true;
717  Vector2 colliderPos = rayStart - Submarine.SimPosition;
718  float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2;
719  outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
720  return;
721  }
722  }
723 
724  var blockingBody = Submarine.CheckVisibility(rayStart, rayEnd);
725  if (blockingBody != null)
726  {
727  //if the ray hit the body of the submarine itself (for example, if there's 2 layers of walls) we can ignore it
728  if (blockingBody.UserData == Submarine) { return; }
729  outsideCollisionBlocker.Enabled = true;
730  Vector2 colliderPos = Submarine.LastPickedPosition - Submarine.SimPosition;
731  float colliderRotation = MathUtils.VectorToAngle(Submarine.LastPickedNormal) - MathHelper.PiOver2;
732  outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
733  }
734  else
735  {
736  outsideCollisionBlocker.Enabled = false;
737  }
738  }
739 
740  private void UpdateOxygen(Hull hull1, Hull hull2, float deltaTime)
741  {
742  if (hull1 == null || hull2 == null) { return; }
743 
744  if (IsHorizontal)
745  {
746  //if the water level is above the gap, oxygen doesn't circulate
747  if (Math.Max(hull1.WorldSurface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.WorldSurface + hull2.WaveY[0]) > WorldRect.Y) { return; }
748  }
749 
750  var should = GameMain.LuaCs.Hook.Call<bool?>("gapOxygenUpdate", this, hull1, hull2);
751 
752  if (should != null && should.Value) return;
753 
754  float totalOxygen = hull1.Oxygen + hull2.Oxygen;
755  float totalVolume = hull1.Volume + hull2.Volume;
756 
757  float deltaOxygen = (totalOxygen * hull1.Volume / totalVolume) - hull1.Oxygen;
758  deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed * deltaTime, Hull.OxygenDistributionSpeed * deltaTime);
759 
760  hull1.Oxygen += deltaOxygen;
761  hull2.Oxygen -= deltaOxygen;
762  }
763 
764  public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist, bool allowRoomToRoom = false)
765  {
766  foreach (Gap gap in gaps)
767  {
768  if (gap.Open == 0.0f) { continue; }
769  if (gap.IsRoomToRoom && !allowRoomToRoom) { continue; }
770 
771  if (gap.ConnectedWall != null)
772  {
773  int sectionIndex = gap.ConnectedWall.FindSectionIndex(gap.Position);
774  if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) { continue; }
775  }
776 
777  if (gap.IsHorizontal || gap.IsDiagonal)
778  {
779  if (worldPos.Y < gap.WorldRect.Y && worldPos.Y > gap.WorldRect.Y - gap.WorldRect.Height &&
780  Math.Abs(gap.WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist)
781  {
782  return gap;
783  }
784  }
785  if (!gap.IsHorizontal || gap.IsDiagonal)
786  {
787  if (worldPos.X > gap.WorldRect.X && worldPos.X < gap.WorldRect.Right &&
788  Math.Abs(gap.WorldRect.Y - gap.WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist)
789  {
790  return gap;
791  }
792  }
793  }
794 
795  return null;
796  }
797 
798  public override void ShallowRemove()
799  {
800  base.ShallowRemove();
801  GapList.Remove(this);
802 
803  foreach (Hull hull in Hull.HullList)
804  {
805  hull.ConnectedGaps.Remove(this);
806  }
807  }
808 
809  public override void Remove()
810  {
811  base.Remove();
812  GapList.Remove(this);
813 
814  foreach (Hull hull in Hull.HullList)
815  {
816  hull.ConnectedGaps.Remove(this);
817  }
818 
819  if (outsideCollisionBlocker != null)
820  {
821  GameMain.World.Remove(outsideCollisionBlocker);
822  outsideCollisionBlocker = null;
823  }
824  }
825 
826  public override void OnMapLoaded()
827  {
828  if (!DisableHullRechecks) FindHulls();
829  }
830 
831  public static Gap Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
832  {
833  Rectangle rect;
834  if (element.GetAttribute("rect") != null)
835  {
836  rect = element.GetAttributeRect("rect", Rectangle.Empty);
837  }
838  else
839  {
840  //backwards compatibility
841  rect = new Rectangle(
842  int.Parse(element.GetAttribute("x").Value),
843  int.Parse(element.GetAttribute("y").Value),
844  int.Parse(element.GetAttribute("width").Value),
845  int.Parse(element.GetAttribute("height").Value));
846  }
847 
848  bool isHorizontal = rect.Height > rect.Width;
849 
850  var horizontalAttribute = element.GetAttribute("horizontal");
851  if (horizontalAttribute != null)
852  {
853  isHorizontal = horizontalAttribute.Value.ToString() == "true";
854  }
855 
856  Gap g = new Gap(rect, isHorizontal, submarine, id: idRemap.GetOffsetId(element))
857  {
858  linkedToID = new List<ushort>(),
859  Layer = element.GetAttributeString(nameof(Layer), null)
860  };
861  g.HiddenInGame = element.GetAttributeBool(nameof(HiddenInGame), g.HiddenInGame);
862  return g;
863  }
864 
865  public override XElement Save(XElement parentElement)
866  {
867  XElement element = new XElement("Gap");
868 
869  element.Add(
870  new XAttribute("ID", ID),
871  new XAttribute("horizontal", IsHorizontal ? "true" : "false"),
872  new XAttribute(nameof(HiddenInGame), HiddenInGame),
873  new XAttribute(nameof(Layer), Layer ?? string.Empty));
874 
875  element.Add(new XAttribute("rect",
876  (int)(rect.X - Submarine.HiddenSubPosition.X) + "," +
877  (int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," +
878  rect.Width + "," + rect.Height));
879 
880  parentElement.Add(element);
881 
882  return element;
883  }
884  }
885 }
string? GetAttributeString(string key, string? def)
bool GetAttributeBool(string key, bool def)
Rectangle GetAttributeRect(string key, in Rectangle def)
XAttribute? GetAttribute(string name)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Submarine Submarine
Definition: Entity.cs:53
const ushort NullEntityID
Definition: Entity.cs:14
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
static World World
Definition: GameMain.cs:105
override void Update(float deltaTime, Camera cam)
static Gap FindAdjacent(IEnumerable< Gap > gaps, Vector2 worldPos, float allowedOrthogonalDist, bool allowRoomToRoom=false)
override bool IsMouseOn(Vector2 position)
Dictionary< Identifier, SerializableProperty > SerializableProperties
bool IsDiagonal
"Diagonal" gaps are used on sloped walls to allow characters to pass through them either horizontally...
override void ShallowRemove()
Remove the entity from the entity list without removing links to other entities
Gap(Rectangle rect, bool isHorizontal, Submarine submarine, bool isDiagonal=false, ushort id=Entity.NullEntityID)
static Gap Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
Gap(Rectangle rect, Submarine submarine)
readonly Dictionary< Identifier, SerializableProperty > properties
override XElement Save(XElement parentElement)
override void Move(Vector2 amount, bool ignoreContacts=true)
static readonly List< Hull > HullList
static Hull FindHullUnoptimized(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). The difference to FindHul...
ushort GetOffsetId(XElement element)
Definition: IdRemap.cs:86
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
int FindSectionIndex(Vector2 displayPos, bool world=false, bool clamp=false)
static void StoreCommand(Command command)
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)