3 using FarseerPhysics.Dynamics;
4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
9 using MoonSharp.Interpreter;
15 public static List<Gap>
GapList =
new List<Gap>();
17 const float MaxFlowForce = 500.0f;
21 const float OutsideColliderRaycastIntervalLowPrio = 1.5f;
22 const float OutsideColliderRaycastIntervalHighPrio = 0.1f;
42 private Vector2 flowForce;
43 private Hull flowTargetHull;
45 private float openedTimer = 1.0f;
47 private float higherSurface;
48 private float lowerSurface;
50 private Vector2 lerpedFlowForce;
60 private Body outsideCollisionBlocker;
61 private float outsideColliderRaycastTimer;
63 private bool wasRoomToRoom;
70 if (
float.IsNaN(value)) {
return; }
77 if (value > open && value >= 1.0f)
79 InformWaypointsAboutGapState(
this, open:
true);
81 else if (value < open && open >= 1.0f)
83 InformWaypointsAboutGapState(
this, open:
false);
86 open = MathHelper.Clamp(value, 0.0f, 1.0f);
88 static void InformWaypointsAboutGapState(
Gap gap,
bool open)
92 if (IsWaypointRightAboveGap(gap, wp))
94 wp.OnGapStateChanged(open, gap);
99 static bool IsWaypointRightAboveGap(
Gap gap,
WayPoint wp)
115 private Door connectedDoor;
120 if (connectedDoor !=
null && connectedDoor.
Item.
Removed)
122 connectedDoor =
null;
124 return connectedDoor;
126 set { connectedDoor = value; }
133 get {
return lerpedFlowForce; }
138 get {
return flowTargetHull; }
163 public override string Name =>
"Gap";
165 public readonly Dictionary<Identifier, SerializableProperty>
properties;
171 public Gap(Rectangle rectangle)
175 if (SubEditorScreen.IsSubEditor())
177 SubEditorScreen.StoreCommand(
new AddOrDeleteCommand(
new List<MapEntity> {
this },
false));
190 flowForce = Vector2.Zero;
203 float blockerSize = ConvertUnits.ToSimUnits(Math.Max(
rect.Width,
rect.Height)) / 2;
204 outsideCollisionBlocker =
GameMain.
World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize,
206 Physics.CollisionWall,
207 Physics.CollisionCharacter,
208 findNewContacts:
false);
209 outsideCollisionBlocker.UserData = $
"CollisionBlocker (Gap {ID})";
210 outsideCollisionBlocker.Enabled =
false;
212 Resized += newRect =>
IsHorizontal = newRect.Width < newRect.Height;
217 DebugConsole.Log(
"Created gap (" +
ID +
")");
225 public override void Move(Vector2 amount,
bool ignoreContacts =
true)
227 if (!MathUtils.IsValid(amount))
229 DebugConsole.ThrowError($
"Attempted to move a gap by an invalid amount ({amount})\n{Environment.StackTrace.CleanupStackTrace()}");
233 base.Move(amount, ignoreContacts);
242 for (
int i = g.
linkedTo.Count - 1; i >= 0; i--)
263 Vector2 searchPosLeft =
new Vector2(
rect.X,
rect.Y -
rect.Height / 2);
265 Vector2 searchPosRight =
new Vector2(
rect.Right,
rect.Y -
rect.Height / 2);
268 if (hullLeft !=
null && hullRight !=
null && hullLeft != hullRight)
274 Vector2 searchPosTop =
new Vector2(
rect.Center.X,
rect.Y);
276 Vector2 searchPosBottom =
new Vector2(
rect.Center.X,
rect.Y -
rect.Height);
279 if (hullTop !=
null && hullBottom !=
null && hullTop != hullBottom)
285 if ((hullLeft ==
null) != (hullRight ==
null))
289 else if ((hullTop ==
null) != (hullBottom ==
null))
295 private void FindHulls()
301 if (linked is
Hull hull)
309 Vector2[] searchPos =
new Vector2[2];
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);
317 searchPos[0] =
new Vector2(
rect.Center.X,
rect.Y + tolerance);
318 searchPos[1] =
new Vector2(
rect.Center.X,
rect.Y -
rect.Height - tolerance);
321 for (
int i = 0; i < 2; i++)
324 if (hulls[i] ==
null) hulls[i] = Hull.
FindHullUnoptimized(searchPos[i],
null,
false,
true);
327 if (hulls[0] ==
null && hulls[1] ==
null) {
return; }
329 if (hulls[0] ==
null && hulls[1] !=
null)
331 Hull temp = hulls[0];
336 flowTargetHull = hulls[0];
338 for (
int i = 0; i < 2; i++)
340 if (hulls[i] ==
null) {
continue; }
342 if (!hulls[i].ConnectedGaps.Contains(
this)) hulls[i].
ConnectedGaps.Add(
this);
346 private int updateCount;
350 int updateInterval = 4;
351 float flowMagnitude = flowForce.LengthSquared();
352 if (flowMagnitude < 1.0f)
357 else if (
linkedTo.Count == 2 && flowMagnitude > 10.0f)
364 if (updateCount < updateInterval) {
return; }
365 deltaTime *= updateCount;
368 flowForce = Vector2.Zero;
369 outsideColliderRaycastTimer -= deltaTime;
377 if (open == 0.0f ||
linkedTo.Count == 0)
379 lerpedFlowForce = Vector2.Zero;
385 if (hull1 == hull2) {
return; }
387 UpdateOxygen(hull1, hull2, deltaTime);
392 UpdateRoomToOut(deltaTime, hull1);
397 UpdateRoomToRoom(deltaTime, hull1, hull2);
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())
405 lerpedFlowForce = flowForce;
409 lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f);
412 openedTimer -= deltaTime;
414 EmitParticles(deltaTime);
417 partial
void EmitParticles(
float deltaTime);
419 void UpdateRoomToRoom(
float deltaTime,
Hull hull1,
Hull hull2)
421 Vector2 subOffset = Vector2.Zero;
435 float sizeModifier =
Size / 100.0f * open;
440 higherSurface = Math.Max(hull1.
Surface, hull2.
Surface + subOffset.Y);
456 flowTargetHull = hull1;
470 flowForce =
new Vector2(-delta * (
float)(Timing.Step / deltaTime), 0.0f);
477 flowTargetHull = hull2;
491 flowForce =
new Vector2(delta * (
float)(Timing.Step / deltaTime), 0.0f);
494 if (delta > 1.5f && subOffset == Vector2.Zero)
520 float delta = Math.Min(hull2.
WaterVolume - hull2.
Volume + (hull2.
Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier);
528 delta = Math.Max(delta, 0.0f);
532 flowForce =
new Vector2(
534 Math.Min(Math.Min((hull2.
Pressure + subOffset.Y) - hull1.
Pressure, 200.0f), delta * (
float)(Timing.Step / deltaTime)));
536 flowTargetHull = hull1;
547 flowTargetHull = hull2;
550 float delta = Math.Min(hull1.
WaterVolume, deltaTime * 25000f * sizeModifier);
560 flowForce =
new Vector2(
562 MathHelper.Clamp(-delta * (
float)(Timing.Step / deltaTime), -200.0f, 0.0f));
581 static void changePressure(Hull hull,
float target,
float speed,
float deltaTime)
583 float diff = target - hull.LethalPressure;
584 float maxChange = Hull.PressureBuildUpSpeed * speed * deltaTime;
585 hull.LethalPressure += MathHelper.Clamp(diff, -maxChange, maxChange);
597 void UpdateRoomToOut(
float deltaTime, Hull hull1)
601 float sizeModifier =
Size * open * open;
603 float delta = 500.0f * sizeModifier * deltaTime;
606 delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
607 hull1.WaterVolume += delta;
609 if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 30.0f * deltaTime; }
611 flowTargetHull = hull1;
616 if (
rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
618 flowForce =
new Vector2(-delta * (
float)(Timing.Step / deltaTime), 0.0f);
623 flowForce =
new Vector2(delta * (
float)(Timing.Step / deltaTime), 0.0f);
626 higherSurface = hull1.Surface;
627 lowerSurface =
rect.Y;
629 if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
630 hull1.Surface <
rect.Y)
633 if (
rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
635 CreateWave(
rect, hull1, hull1.WaveY.Length - 1, hull1.WaveY.Length - 2, flowForce, deltaTime);
639 CreateWave(
rect, hull1, 0, 1, flowForce, deltaTime);
641 static void CreateWave(Rectangle
rect, Hull hull1,
int index1,
int index2, Vector2 flowForce,
float deltaTime)
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);
647 hull1.WaveVel[index1] += vel * deltaTime;
648 hull1.WaveVel[index2] += vel * deltaTime;
659 if (
rect.Y > hull1.Rect.Y - hull1.Rect.Height / 2.0f)
661 flowForce =
new Vector2(0.0f, -delta * (
float)(Timing.Step / deltaTime));
665 flowForce =
new Vector2(0.0f, delta * (
float)(Timing.Step / deltaTime));
667 if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
676 if (outsideCollisionBlocker ==
null) {
return false; }
679 outsideCollisionBlocker.Enabled =
false;
683 if (outsideColliderRaycastTimer <= 0.0f)
686 outsideColliderRaycastTimer = outsideCollisionBlocker.Enabled ?
687 OutsideColliderRaycastIntervalHighPrio :
688 OutsideColliderRaycastIntervalLowPrio;
691 return outsideCollisionBlocker.Enabled;
694 private void UpdateOutsideColliderState(
Hull hull)
701 rayDir =
new Vector2(Math.Sign(
rect.Center.X - hull.
Rect.Center.X), 0);
705 rayDir =
new Vector2(0, Math.Sign((
rect.Y -
rect.Height / 2) - (hull.
Rect.Y - hull.
Rect.Height / 2)));
709 Vector2 rayEnd = rayStart + rayDir * 5.0f;
711 var levelCells = Level.Loaded.GetCells(
WorldPosition, searchDepth: 1);
712 foreach (var cell
in levelCells)
716 outsideCollisionBlocker.Enabled =
true;
718 float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2;
719 outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
725 if (blockingBody !=
null)
728 if (blockingBody.UserData ==
Submarine) {
return; }
729 outsideCollisionBlocker.Enabled =
true;
732 outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
736 outsideCollisionBlocker.Enabled =
false;
740 private void UpdateOxygen(Hull hull1, Hull hull2,
float deltaTime)
742 if (hull1 ==
null || hull2 ==
null) {
return; }
747 if (Math.Max(hull1.WorldSurface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.WorldSurface + hull2.WaveY[0]) >
WorldRect.Y) {
return; }
750 var should = GameMain.LuaCs.Hook.Call<
bool?>(
"gapOxygenUpdate",
this, hull1, hull2);
752 if (should !=
null && should.Value)
return;
754 float totalOxygen = hull1.Oxygen + hull2.Oxygen;
755 float totalVolume = hull1.Volume + hull2.Volume;
757 float deltaOxygen = (totalOxygen * hull1.Volume / totalVolume) - hull1.Oxygen;
758 deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed * deltaTime, Hull.OxygenDistributionSpeed * deltaTime);
760 hull1.Oxygen += deltaOxygen;
761 hull2.Oxygen -= deltaOxygen;
764 public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos,
float allowedOrthogonalDist,
bool allowRoomToRoom =
false)
766 foreach (
Gap gap
in gaps)
768 if (gap.
Open == 0.0f) {
continue; }
780 Math.Abs(gap.
WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist)
788 Math.Abs(gap.
WorldRect.Y - gap.
WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist)
800 base.ShallowRemove();
819 if (outsideCollisionBlocker !=
null)
822 outsideCollisionBlocker =
null;
841 rect =
new Rectangle(
848 bool isHorizontal =
rect.Height >
rect.Width;
850 var horizontalAttribute = element.
GetAttribute(
"horizontal");
851 if (horizontalAttribute !=
null)
853 isHorizontal = horizontalAttribute.Value.ToString() ==
"true";
865 public override XElement
Save(XElement parentElement)
867 XElement element =
new XElement(
"Gap");
870 new XAttribute(
"ID",
ID),
871 new XAttribute(
"horizontal",
IsHorizontal ?
"true" :
"false"),
873 new XAttribute(nameof(
Layer),
Layer ??
string.Empty));
875 element.Add(
new XAttribute(
"rect",
880 parentElement.Add(element);
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
const ushort NullEntityID
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
override MapEntity Clone()
float PressureDistributionSpeed
bool RefreshOutsideCollider()
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)
override void OnMapLoaded()
static void UpdateHulls()
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)
readonly float GlowEffectT
static List< Gap > GapList
Gap(Rectangle rect, Submarine submarine)
readonly Dictionary< Identifier, SerializableProperty > properties
override XElement Save(XElement parentElement)
override void Move(Vector2 amount, bool ignoreContacts=true)
readonly List< Gap > ConnectedGaps
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...
int GetWaveIndex(Vector2 position)
ushort GetOffsetId(XElement element)
List< ushort > linkedToID
override Vector2 Position
readonly List< MapEntity > linkedTo
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
bool SectionBodyDisabled(int sectionIndex)
int FindSectionIndex(Vector2 displayPos, bool world=false, bool clamp=false)
static Vector2 LastPickedNormal
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)
Vector2 HiddenSubPosition
override Vector2 SimPosition
override Vector2? Position
static Vector2 LastPickedPosition
static List< WayPoint > WayPointList