Server LuaCsForBarotrauma
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
175  if (SubEditorScreen.IsSubEditor())
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:28
override MapEntity Clone()
Definition: Gap.cs:220
float PressureDistributionSpeed
Definition: Gap.cs:113
bool RefreshOutsideCollider()
Definition: Gap.cs:674
override void Update(float deltaTime, Camera cam)
Definition: Gap.cs:348
void AutoOrient()
Definition: Gap.cs:261
static Gap FindAdjacent(IEnumerable< Gap > gaps, Vector2 worldPos, float allowedOrthogonalDist, bool allowRoomToRoom=false)
Definition: Gap.cs:764
bool IsHorizontal
Definition: Gap.cs:25
override bool IsMouseOn(Vector2 position)
Definition: Gap.cs:255
override void OnMapLoaded()
Definition: Gap.cs:826
static void UpdateHulls()
Definition: Gap.cs:238
Vector2 LerpedFlowForce
Definition: Gap.cs:132
Structure ConnectedWall
Definition: Gap.cs:129
Dictionary< Identifier, SerializableProperty > SerializableProperties
Definition: Gap.cs:167
bool PassAmbientLight
Definition: Gap.cs:56
Gap(Rectangle rectangle)
Definition: Gap.cs:171
override string Name
Definition: Gap.cs:163
bool IsDiagonal
"Diagonal" gaps are used on sloped walls to allow characters to pass through them either horizontally...
Definition: Gap.cs:34
Door ConnectedDoor
Definition: Gap.cs:117
float Size
Definition: Gap.cs:111
override void ShallowRemove()
Remove the entity from the entity list without removing links to other entities
Definition: Gap.cs:798
Gap(Rectangle rect, bool isHorizontal, Submarine submarine, bool isDiagonal=false, ushort id=Entity.NullEntityID)
Definition: Gap.cs:186
float Open
Definition: Gap.cs:66
override void Remove()
Definition: Gap.cs:809
static Gap Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
Definition: Gap.cs:831
bool DisableHullRechecks
Definition: Gap.cs:53
readonly float GlowEffectT
Definition: Gap.cs:36
static List< Gap > GapList
Definition: Gap.cs:15
Hull FlowTargetHull
Definition: Gap.cs:137
Gap(Rectangle rect, Submarine submarine)
Definition: Gap.cs:182
readonly Dictionary< Identifier, SerializableProperty > properties
Definition: Gap.cs:165
override XElement Save(XElement parentElement)
Definition: Gap.cs:865
override void Move(Vector2 amount, bool ignoreContacts=true)
Definition: Gap.cs:225
override Rectangle Rect
Definition: Gap.cs:150
bool IsRoomToRoom
Definition: Gap.cs:142
static bool ShowGaps
Definition: Gap.cs:19
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
Rectangle? WorldRect
Definition: MapEntity.cs:105
List< ushort > linkedToID
Definition: MapEntity.cs:19
override Vector2 Position
Definition: MapEntity.cs:214
readonly List< MapEntity > linkedTo
Definition: MapEntity.cs:52
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
int FindSectionIndex(Vector2 displayPos, bool world=false, bool clamp=false)
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)
static List< WayPoint > WayPointList
Definition: WayPoint.cs:18
SpawnType SpawnType
Definition: WayPoint.cs:79