6 using FarseerPhysics.Dynamics;
7 using Microsoft.Xna.Framework;
9 using System.Collections.Generic;
10 using System.Collections.Immutable;
11 using System.Diagnostics;
13 using System.Xml.Linq;
20 None = 0, Left = 1, Right = 2
44 public static bool LockX, LockY;
46 public static readonly Vector2
GridSize =
new Vector2(16.0f, 16.0f);
54 private static readonly List<Submarine> loaded =
new List<Submarine>();
56 private readonly Identifier upgradeEventIdentifier;
58 private static List<MapEntity> visibleEntities;
61 get {
return visibleEntities; }
79 private static Vector2 lastPickedPosition;
80 private static float lastPickedFraction;
81 private static Fixture lastPickedFixture;
82 private static Vector2 lastPickedNormal;
84 private Vector2 prevPosition;
86 private float networkUpdateTimer;
96 get {
return lastPickedPosition; }
101 get {
return lastPickedFraction; }
106 get {
return lastPickedFixture; }
111 get {
return lastPickedNormal; }
128 get {
return loaded; }
133 get {
return subBody; }
138 get {
return subBody?.
Body; }
172 return subBody ==
null ? Vector2.Zero : subBody.
Position;
176 private float? realWorldCrushDepth;
181 if (!realWorldCrushDepth.HasValue)
183 realWorldCrushDepth =
float.PositiveInfinity;
187 realWorldCrushDepth = Math.Min(structure.
CrushDepth, realWorldCrushDepth.Value);
190 return realWorldCrushDepth.Value;
256 private bool IsAtOutpostExit(
Submarine outpost)
258 if (outpost.exitPoints.Any())
262 foreach (var exitPoint
in outpost.exitPoints)
264 if (exitPoint.ExitPointSize != Point.Zero)
266 if (
RectsOverlap(worldBorders, exitPoint.ExitPointWorldRect)) {
return true; }
270 if (
RectContains(worldBorders, exitPoint.WorldPosition)) {
return true; }
288 return ConvertUnits.ToSimUnits(
Position);
294 get {
return subBody ==
null ? Vector2.Zero : subBody.
Velocity; }
297 if (subBody ==
null) {
return; }
307 private int? submarineSpecificIDTag;
313 return submarineSpecificIDTag.Value;
322 if (
Level.
Loaded ==
null || subBody ==
null) {
return false; }
327 private readonly List<WayPoint> exitPoints =
new List<WayPoint>();
328 public IReadOnlyList<WayPoint>
ExitPoints {
get {
return exitPoints; } }
332 return "Barotrauma.Submarine (" + (
Info?.
Name ??
"[NULL INFO]") +
", " +
IdOffset +
")";
338 float volume =
Hull.
HullList.Where(h => h.Submarine ==
this).Sum(h => h.Volume);
339 float itemValue =
Item.
ItemList.Where(it => it.Submarine ==
this).Sum(it => it.Prefab.GetMinPrice() ?? 0);
340 float price = volume / 500.0f + itemValue / 100.0f;
341 System.Diagnostics.Debug.Assert(price >= 0);
342 return Math.Max(minPrice, (
int)price);
345 private float ballastFloraTimer;
352 if (ballastFloraTimer < 1f)
354 ballastFloraTimer += deltaTime;
358 ballastFloraTimer = 0;
359 if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) >= probability) {
return; }
361 List<Pump> pumps =
new List<Pump>();
362 List<Item> allItems =
GetItems(
true);
364 bool anyHasTag = allItems.Any(i => i.HasTag(Tags.Ballast));
366 foreach (
Item item
in allItems)
368 if ((!anyHasTag || item.
HasTag(Tags.Ballast)) && item.GetComponent<
Pump>() is { } pump)
374 if (!pumps.Any()) {
return; }
376 Pump randomPump = pumps.GetRandom(Rand.RandSync.Unsynced);
381 randomPump.
Item.CreateServerEvent(randomPump);
390 DockedTo.ForEach(s => s.ShowSonarMarker =
false);
396 public SubmarineTurretAI
TurretAI {
get;
private set; }
409 TurretAI =
new SubmarineTurretAI(
this);
426 private static readonly HashSet<Submarine> checkSubmarineBorders =
new HashSet<Submarine>();
433 checkSubmarineBorders.Clear();
434 return GetDockedBordersRecursive(allowDifferentTeam);
437 private Rectangle GetDockedBordersRecursive(
bool allowDifferentTeam)
440 checkSubmarineBorders.Add(
this);
441 var connectedSubs =
DockedTo.Where(s =>
442 !checkSubmarineBorders.Contains(s) &&
444 (allowDifferentTeam || s.TeamID ==
TeamID));
445 foreach (
Submarine dockedSub
in connectedSubs)
451 if (expectedLocation ==
null) {
continue; }
453 Rectangle dockedSubBorders = dockedSub.GetDockedBordersRecursive(allowDifferentTeam);
454 dockedSubBorders.Location += MathUtils.ToPoint(expectedLocation.Value);
456 dockedBorders.Y = -dockedBorders.Y;
457 dockedSubBorders.Y = -dockedSubBorders.Y;
458 dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders);
459 dockedBorders.Y = -dockedBorders.Y;
462 return dockedBorders;
465 private readonly HashSet<Submarine> connectedSubs;
471 return connectedSubs;
476 connectedSubs.Clear();
477 connectedSubs.Add(
this);
478 GetConnectedSubsRecursive(connectedSubs);
481 private void GetConnectedSubsRecursive(HashSet<Submarine> subs)
485 if (subs.Contains(dockedSub)) {
continue; }
487 dockedSub.GetConnectedSubsRecursive(subs);
494 public Vector2
FindSpawnPos(Vector2 spawnPos, Point? submarineSize =
null,
float subDockingPortOffset = 0.0f,
int verticalMoveDir = 0)
497 Vector2 diffFromDockedBorders =
498 new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
501 int minWidth = Math.Max(submarineSize.HasValue ? submarineSize.Value.X : dockedBorders.Width, 500);
502 int minHeight = Math.Max(submarineSize.HasValue ? submarineSize.Value.Y : dockedBorders.Height, 1000);
506 minHeight += padding;
509 const int maxIterations = 5;
512 Vector2 potentialPos = spawnPos;
513 if (verticalMoveDir != 0)
515 verticalMoveDir = Math.Sign(verticalMoveDir);
517 Vector2 rayEnd =
new Vector2(potentialPos.X, verticalMoveDir > 0 ?
Level.
Loaded.
Size.Y : 0);
519 Vector2 closestPickedPos = rayEnd;
521 for (
float x = -1; x <= 1; x += 0.2f)
523 Vector2 xOffset = Vector2.UnitX * minWidth / 2 * x;
524 xOffset.X += subDockingPortOffset;
526 ConvertUnits.ToSimUnits(potentialPos + xOffset),
527 ConvertUnits.ToSimUnits(rayEnd + xOffset),
528 collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
529 customPredicate: (Fixture f) =>
531 return f.UserData is not VoronoiCell { IsDestructible: true };
535 int offsetFromWall = 10 * -verticalMoveDir;
538 verticalMoveDir > 0 ?
539 Math.Min(closestPickedPos.Y, pickedPos) :
540 Math.Max(closestPickedPos.Y, pickedPos);
543 potentialPos.Y = closestPickedPos.Y;
546 Vector2 limits = GetHorizontalLimits(
new Vector2(potentialPos.X, potentialPos.Y - (dockedBorders.Height * 0.5f * verticalMoveDir)),
547 maxHorizontalMoveAmount: minWidth, minHeight, verticalMoveDir, padding);
548 if (limits.Y - limits.X >= minWidth)
550 Vector2 newSpawnPos =
new Vector2(spawnPos.X, potentialPos.Y - (dockedBorders.Height * 0.5f * verticalMoveDir));
551 bool couldMoveInVerticalMoveDir = Math.Sign(newSpawnPos.Y - spawnPos.Y) == Math.Sign(verticalMoveDir);
552 if (!couldMoveInVerticalMoveDir) {
break; }
553 spawnPos = ClampToHorizontalLimits(newSpawnPos, limits);
557 }
while (iterations < maxIterations);
559 Vector2 GetHorizontalLimits(Vector2 spawnPos,
float maxHorizontalMoveAmount,
float minHeight,
int verticalMoveDir,
int padding)
561 Vector2 refPos = spawnPos - Vector2.UnitY * minHeight * 0.5f * Math.Sign(verticalMoveDir);
563 float minX =
float.MinValue, maxX =
float.MaxValue;
568 if ((e.
Point1.Y < refPos.Y - minHeight * 0.5f && e.Point2.Y < refPos.Y - minHeight * 0.5f) ||
569 (e.
Point1.Y > refPos.Y + minHeight * 0.5f && e.Point2.Y > refPos.Y + minHeight * 0.5f))
576 minX = Math.Max(minX, Math.Max(e.
Point1.X, e.Point2.X));
580 maxX = Math.Min(maxX, Math.Min(e.
Point1.X, e.Point2.X));
585 foreach (var ruin
in Level.Loaded.Ruins)
587 if (Math.Abs(ruin.Area.Center.Y - refPos.Y) > (minHeight + ruin.Area.Height) * 0.5f) {
continue; }
588 if (ruin.Area.Center.X < refPos.X)
590 minX = Math.Max(minX, ruin.Area.Right + padding);
594 maxX = Math.Min(maxX, ruin.Area.X - padding);
598 minX += subDockingPortOffset;
599 maxX += subDockingPortOffset;
602 Math.Max(Math.Max(minX, spawnPos.X - maxHorizontalMoveAmount - padding), 0),
603 Math.Min(Math.Min(maxX, spawnPos.X + maxHorizontalMoveAmount + padding), Level.Loaded.Size.X));
606 Vector2 ClampToHorizontalLimits(Vector2 spawnPos, Vector2 limits)
608 if (limits.X < 0.0f && limits.Y > Level.Loaded.Size.X)
612 else if (limits.X < 0)
615 spawnPos.X = limits.Y - minWidth * 0.5f - 100.0f + subDockingPortOffset;
617 else if (limits.Y > Level.Loaded.Size.X)
620 spawnPos.X = limits.X + minWidth * 0.5f + 100.0f + subDockingPortOffset;
625 spawnPos.X = (limits.X + limits.Y) / 2 + subDockingPortOffset;
630 spawnPos.Y = MathHelper.Clamp(spawnPos.Y, dockedBorders.Height / 2 + 10, Level.Loaded.Size.Y - dockedBorders.Height / 2 - padding * 2);
631 return spawnPos - diffFromDockedBorders;
636 DrawPosition = interpolate ?
637 Timing.Interpolate(prevPosition, Position) :
639 if (!interpolate) { prevPosition = Position; }
648 position.X = MathF.Round(position.X / GridSize.X) * GridSize.X;
649 position.Y = MathF.Round(position.Y / GridSize.Y) * GridSize.Y;
653 position.X = MathF.Floor(position.X / GridSize.X) * GridSize.X;
654 position.Y = MathF.Ceiling(position.Y / GridSize.Y) * GridSize.Y;
659 position.X += sub.Position.X % GridSize.X;
660 position.Y += sub.Position.Y % GridSize.Y;
667 List<MapEntity> entities = onlyHulls ?
672 entities.RemoveAll(e =>
676 if (item.GetComponent<
Turret>() !=
null) { return false; }
677 if (item.body !=
null && !item.body.Enabled) { return true; }
679 if (e.IsHidden) { return true; }
683 if (entities.Count == 0) {
return Rectangle.Empty; }
685 float minX = entities[0].Rect.X, minY = entities[0].Rect.Y - entities[0].Rect.Height;
686 float maxX = entities[0].Rect.Right, maxY = entities[0].Rect.Y;
688 for (
int i = 1; i < entities.Count; i++)
690 if (entities[i] is
Item item)
692 var turret = item.GetComponent<
Turret>();
695 minX = Math.Min(minX, entities[i].Rect.X + turret.TransformedBarrelPos.X * 2f);
696 minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height - turret.TransformedBarrelPos.Y * 2f);
697 maxX = Math.Max(maxX, entities[i].Rect.Right + turret.TransformedBarrelPos.X * 2f);
698 maxY = Math.Max(maxY, entities[i].Rect.Y - turret.TransformedBarrelPos.Y * 2f);
701 minX = Math.Min(minX, entities[i].Rect.X);
702 minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height);
703 maxX = Math.Max(maxX, entities[i].Rect.Right);
704 maxY = Math.Max(maxY, entities[i].Rect.Y);
707 return new Rectangle((
int)minX, (
int)minY, (
int)(maxX - minX), (
int)(maxY - minY));
723 return new Rectangle((
int)pos.X, (
int)pos.Y, (
int)size.X, (
int)size.Y);
726 public static RectangleF
AbsRectF(Vector2 pos, Vector2 size)
739 return new RectangleF(pos.X, pos.Y, size.X, size.Y);
742 public static bool RectContains(Rectangle rect, Vector2 pos,
bool inclusive =
false)
746 return (pos.X >= rect.X && pos.X <= rect.X + rect.Width
747 && pos.Y <= rect.Y && pos.Y >= rect.Y - rect.Height);
751 return (pos.X > rect.X && pos.X < rect.X + rect.Width
752 && pos.Y < rect.Y && pos.Y > rect.Y - rect.Height);
756 public static bool RectsOverlap(Rectangle rect1, Rectangle rect2,
bool inclusive =
true)
760 return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X ||
761 rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y);
765 return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
766 rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y);
770 public static bool RectsOverlap(RectangleF rect1, RectangleF rect2,
bool inclusive =
true)
774 return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X ||
775 rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y);
778 return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
779 rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y);
782 public static Body
PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable<Body> ignoredBodies =
null, Category? collisionCategory =
null,
bool ignoreSensors =
true, Predicate<Fixture> customPredicate =
null,
bool allowInsideFixture =
false)
784 if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.0001f)
789 float closestFraction = 1.0f;
790 Vector2 closestNormal = Vector2.Zero;
791 Fixture closestFixture =
null;
792 Body closestBody =
null;
793 if (allowInsideFixture)
795 var aabb =
new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f);
798 if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) {
return true; }
800 fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
801 if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
803 closestFraction = 0.0f;
804 closestNormal = Vector2.Normalize(rayEnd - rayStart);
805 closestFixture = fixture;
806 if (fixture.Body !=
null) { closestBody = fixture.Body; }
809 if (closestFraction <= 0.0f)
811 lastPickedPosition = rayStart;
812 lastPickedFraction = closestFraction;
813 lastPickedFixture = closestFixture;
814 lastPickedNormal = closestNormal;
821 if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) {
return -1; }
823 if (fraction < closestFraction)
825 closestFraction = fraction;
826 closestNormal = normal;
827 closestFixture = fixture;
828 if (fixture.Body !=
null) closestBody = fixture.Body;
831 }, rayStart, rayEnd, collisionCategory ?? Category.All);
833 lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
834 lastPickedFraction = closestFraction;
835 lastPickedFixture = closestFixture;
836 lastPickedNormal = closestNormal;
841 private static readonly Dictionary<Body, float> bodyDist =
new Dictionary<Body, float>();
842 private static readonly List<Body> bodies =
new List<Body>();
846 if (!bodyDist.ContainsKey(body)) {
return 0.0f; }
847 return bodyDist[body];
855 public static IEnumerable<Body>
PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable<Body> ignoredBodies =
null, Category? collisionCategory =
null,
bool ignoreSensors =
true, Predicate<Fixture> customPredicate =
null,
bool allowInsideFixture =
false)
857 if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
859 rayEnd += Vector2.UnitX * 0.001f;
862 float closestFraction = 1.0f;
867 if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) {
return -1; }
869 if (fixture.Body !=
null)
871 bodies.Add(fixture.Body);
872 bodyDist[fixture.Body] = fraction;
874 if (fraction < closestFraction)
876 lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
877 lastPickedFraction = fraction;
878 lastPickedNormal = normal;
879 lastPickedFixture = fixture;
883 }, rayStart, rayEnd, collisionCategory ?? Category.All);
885 if (allowInsideFixture)
887 var aabb =
new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f);
890 if (bodies.Contains(fixture.Body) || fixture.Body ==
null) {
return true; }
891 if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) {
return true; }
893 fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
894 if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) {
return true; }
896 closestFraction = 0.0f;
897 lastPickedPosition = rayStart;
898 lastPickedFraction = 0.0f;
899 lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
900 lastPickedFixture = fixture;
901 bodies.Add(fixture.Body);
902 bodyDist[fixture.Body] = 0.0f;
907 bodies.Sort((b1, b2) => {
return bodyDist[b1].CompareTo(bodyDist[b2]); });
911 private static bool CheckFixtureCollision(Fixture fixture, IEnumerable<Body> ignoredBodies =
null, Category? collisionCategory =
null,
bool ignoreSensors =
true, Predicate<Fixture> customPredicate =
null)
913 if (fixture ==
null ||
914 (ignoreSensors && fixture.IsSensor) ||
915 fixture.CollisionCategories == Category.None ||
916 fixture.CollisionCategories == Physics.CollisionItem)
921 if (customPredicate !=
null && !customPredicate(fixture))
926 if (collisionCategory !=
null &&
927 !fixture.CollisionCategories.HasFlag((Category)collisionCategory) &&
928 !((Category)collisionCategory).HasFlag(fixture.CollisionCategories))
933 if (ignoredBodies !=
null && ignoredBodies.Contains(fixture.Body))
938 if (fixture.Body.UserData is Structure structure)
940 if (structure.IsPlatform && collisionCategory !=
null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform))
958 public static Body
CheckVisibility(Vector2 rayStart, Vector2 rayEnd,
bool ignoreLevel =
false,
bool ignoreSubs =
false,
bool ignoreSensors =
true,
bool ignoreDisabledWalls =
true,
bool ignoreBranches =
true,
959 Predicate<Fixture> blocksVisibilityPredicate =
null)
961 Body closestBody =
null;
962 float closestFraction = 1.0f;
963 Fixture closestFixture =
null;
964 Vector2 closestNormal = Vector2.Zero;
966 if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
968 lastPickedPosition = rayEnd;
974 if (fixture ==
null) {
return -1; }
975 if (ignoreSensors && fixture.IsSensor) { return -1; }
976 if (ignoreLevel && fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; }
977 if (!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)
978 && !fixture.CollisionCategories.HasFlag(Physics.CollisionWall)
979 && !fixture.CollisionCategories.HasFlag(Physics.CollisionRepairableWall)) { return -1; }
980 if (ignoreSubs && fixture.Body.UserData is
Submarine) { return -1; }
981 if (ignoreBranches && fixture.Body.UserData is VineTile) { return -1; }
982 if (fixture.Body.UserData as
string ==
"ruinroom") { return -1; }
984 if (fixture.UserData is
Hull) { return -1; }
985 if (fixture.Body.UserData is
Structure structure)
987 if (structure.IsPlatform || structure.StairDirection != Direction.None) { return -1; }
988 if (ignoreDisabledWalls)
990 int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point));
991 if (sectionIndex > -1 && structure.SectionBodyDisabled(sectionIndex)) { return -1; }
994 if (blocksVisibilityPredicate !=
null && !blocksVisibilityPredicate(fixture))
998 if (fraction < closestFraction)
1000 closestBody = fixture.Body;
1001 closestFraction = fraction;
1002 closestFixture = fixture;
1003 closestNormal = normal;
1005 return closestFraction;
1007 , rayStart, rayEnd);
1010 lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
1011 lastPickedFraction = closestFraction;
1012 lastPickedFixture = closestFixture;
1013 lastPickedNormal = closestNormal;
1019 private bool flippedX;
1020 public bool FlippedX
1022 get {
return flippedX; }
1025 public void FlipX(List<Submarine> parents =
null)
1027 if (parents ==
null) { parents =
new List<Submarine>(); }
1030 flippedX = !flippedX;
1034 List<Item> bodyItems =
Item.
ItemList.FindAll(it => it.Submarine ==
this && it.body !=
null);
1039 if (e is
Item)
continue;
1045 Vector2 relative1 = linkedSub.
Position - SubBody.Position;
1046 relative1.X = -relative1.X;
1047 linkedSub.Rect =
new Rectangle((relative1 + SubBody.Position).ToPoint(), linkedSub.Rect.Size);
1049 else if (!parents.Contains(sub))
1052 relative1.X = -relative1.X;
1053 sub.
SetPosition(relative1 + SubBody.Position,
new List<Submarine>(parents));
1063 foreach (
MapEntity mapEntity
in subEntities)
1065 mapEntity.
Move(-HiddenSubPosition);
1073 SetPosition(pos,
new List<Submarine>(parents.Where(p => p !=
this)));
1075 if (entityGrid !=
null)
1084 foreach (
MapEntity mapEntity
in subEntities)
1086 mapEntity.
Move(HiddenSubPosition);
1089 for (
int i = 0; i < 2; i++)
1094 if ((item.GetComponent<
DockingPort>() !=
null) == (i == 0)) {
continue; }
1095 if (bodyItems.Contains(item))
1098 if (Position == Vector2.Zero) { item.
Move(-HiddenSubPosition); }
1116 Lights.ConvexHull.RecalculateAll(
this);
1122 foreach (var faction
in FactionPrefab.Prefabs)
1124 SetLayerEnabled(faction.Identifier, faction.Identifier == factionIdentifier);
1132 if (me.
Layer == layer) {
return true; }
1146 public void SetLayerEnabled(Identifier layer,
bool enabled,
bool sendNetworkEvent =
false)
1150 if (
string.IsNullOrEmpty(entity.
Layer) || entity.
Submarine !=
this || entity.
Layer != layer) {
continue; }
1157 wp.SpawnType = wp.SpawnType.RemoveFlag(
SpawnType.Disabled);
1161 wp.SpawnType = wp.SpawnType.AddFlag(
SpawnType.Disabled);
1164 else if (entity is
Item item)
1166 foreach (var connectionPanel
in item.GetComponents<
ConnectionPanel>())
1168 foreach (var connection
in connectionPanel.Connections)
1170 foreach (var wire
in connection.Wires)
1180 foreach (var lightComponent
in item.GetComponents<
LightComponent>())
1182 lightComponent.Light.Enabled =
false;
1189 if (sendNetworkEvent)
1191 GameMain.Server.CreateEntityEvent(
this,
new SetLayerEnabledEventData(layer, enabled));
1198 RefreshConnectedSubs();
1204 TurretAI?.Update(deltaTime);
1206 if (subBody?.Body ==
null) {
return; }
1233 subBody.
Update(deltaTime);
1235 for (
int i = 0; i < 2; i++)
1237 if (MainSubs[i] ==
null) {
continue; }
1238 if (
this != MainSubs[i] && MainSubs[i].DockedTo.Contains(
this)) {
return; }
1242 networkUpdateTimer -= MathHelper.Clamp(Velocity.Length() * 10.0f, 0.1f, 5.0f) * deltaTime;
1244 if (networkUpdateTimer < 0.0f)
1246 networkUpdateTimer = 1.0f;
1252 if (subBody !=
null) { subBody.
ApplyForce(force); }
1259 if (item.
Submarine !=
this) {
continue; }
1260 var steering = item.GetComponent<
Steering>();
1261 if (steering ==
null || item.
Connections ==
null) {
continue; }
1264 List<Item> connectedItems =
new List<Item>();
1268 connectedItems.AddRange(item.GetConnectedComponentsRecursive<
Engine>(c).
Select(engine => engine.Item));
1269 connectedItems.AddRange(item.GetConnectedComponentsRecursive<
Pump>(c).
Select(pump => pump.Item));
1274 if (connectedItems.Count(it => it.Submarine != item.
Submarine) > connectedItems.Count / 2)
1279 steering.MaintainPos =
true;
1280 steering.PosToMaintain = WorldPosition;
1281 steering.AutoPilot =
true;
1283 steering.UnsentChanges =
true;
1292 float neutralBallastLevel = 0.5f;
1293 int selectedSteeringValue = 0;
1296 if (item.
Submarine !=
this) {
continue; }
1297 var steering = item.GetComponent<
Steering>();
1298 if (steering ==
null) {
continue; }
1301 int steeringValue = 1;
1302 Connection connectionX = item.GetComponent<
ConnectionPanel>()?.Connections.Find(
static c => c.Name ==
"velocity_x_out");
1303 Connection connectionY = item.GetComponent<
ConnectionPanel>()?.Connections.Find(
static c => c.Name ==
"velocity_y_out");
1304 if (connectionX !=
null)
1306 foreach (
Engine engine
in steering.
Item.GetConnectedComponentsRecursive<
Engine>(connectionX))
1311 if (connectionY !=
null)
1313 foreach (
Pump pump
in steering.
Item.GetConnectedComponentsRecursive<
Pump>(connectionY))
1319 if (steeringValue > selectedSteeringValue)
1321 neutralBallastLevel = steering.NeutralBallastLevel;
1325 HashSet<Hull> ballastHulls =
new HashSet<Hull>();
1328 if (item.
Submarine !=
this) {
continue; }
1329 var pump = item.GetComponent<
Pump>();
1330 if (pump ==
null || item.
CurrentHull ==
null) {
continue; }
1331 if (!item.
HasTag(Tags.Ballast) && !item.
CurrentHull.
RoomName.Contains(
"ballast", StringComparison.OrdinalIgnoreCase)) {
continue; }
1336 float waterVolume = 0.0f;
1337 float volume = 0.0f;
1338 float excessWater = 0.0f;
1341 if (hull.
Submarine !=
this) {
continue; }
1344 if (!ballastHulls.Contains(hull)) { excessWater += hull.
WaterVolume; }
1347 neutralBallastLevel -= excessWater / volume;
1349 neutralBallastLevel *= 0.9f;
1351 foreach (
Hull hull
in ballastHulls)
1359 prevPosition = position;
1362 public void SetPosition(Vector2 position, List<Submarine> checkd =
null,
bool forceUndockFromStaticSubmarines =
true)
1364 if (!MathUtils.IsValid(position)) {
return; }
1366 if (checkd ==
null) { checkd =
new List<Submarine>(); }
1367 if (checkd.Contains(
this)) {
return; }
1372 UpdateTransform(interpolate:
false);
1374 foreach (
Submarine dockedSub
in DockedTo)
1378 if (ConnectedDockingPorts.TryGetValue(dockedSub, out
DockingPort port))
1380 port.Undock(applyEffects:
false);
1384 Vector2? expectedLocation = CalculateDockOffset(
this, dockedSub);
1385 if (expectedLocation ==
null) {
continue; }
1386 dockedSub.
SetPosition(position + expectedLocation.Value, checkd, forceUndockFromStaticSubmarines);
1394 if (myPort ==
null) {
return null; }
1396 if (theirPort ==
null) {
return null; }
1402 if (amount == Vector2.Zero || !MathUtils.IsValid(amount))
return;
1411 float closestDist = 0.0f;
1416 if (ignoreRespawnShuttle)
1420 if (teamType.HasValue && sub.
TeamID != teamType) {
continue; }
1421 float dist = Vector2.DistanceSquared(worldPosition, sub.
WorldPosition);
1422 if (closest ==
null || dist < closestDist)
1438 public List<Gap>
GetGaps(
bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs,
Gap.
GapList);
1443 public List<T> GetEntities<T>(
bool includingConnectedSubs, List<T> list) where T :
MapEntity
1445 return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
1451 var connectedSubs = GetConnectedSubs().Where(sub => sub.
Info?.
Type == Info.Type);
1454 if (!connectedSubs.Contains(item.
Submarine)) {
continue; }
1455 if (!item.
HasTag(Tags.CargoContainer)) {
continue; }
1456 if (item.
HasTag(Tags.DisallowCargo)) {
continue; }
1459 if (itemContainer ==
null) {
continue; }
1461 for (
int i = 0; i < itemContainer.Inventory.Capacity; i++)
1463 if (itemContainer.Inventory.GetItemAt(i) ==
null) { emptySlots++; }
1465 containers.Add((itemContainer, emptySlots));
1470 public IEnumerable<T> GetEntities<T>(
bool includingConnectedSubs, IEnumerable<T> list) where T :
MapEntity
1472 return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
1477 if (entity ==
null) {
return false; }
1478 if (entity.
Submarine ==
this) {
return true; }
1479 if (entity.
Submarine ==
null) {
return false; }
1480 if (includingConnectedSubs)
1497 subBorders.Inflate(inflate, inflate);
1498 if (subBorders.Contains(position)) {
return sub; }
1513 worldBorders.Inflate(inflate, inflate);
1514 if (worldBorders.Contains(worldPosition)) {
return sub; }
1522 Vector4 bounds =
new Vector4(
float.MaxValue,
float.MinValue,
float.MinValue,
float.MaxValue);
1523 foreach (XElement element
in submarineElement.Elements())
1525 if (element.Name ==
"Structure")
1527 string name = element.GetAttributeString(
"name",
"");
1528 Identifier identifier = element.GetAttributeIdentifier(
"identifier",
"");
1530 if (prefab ==
null || !prefab.
Body) {
continue; }
1532 var rect = element.GetAttributeRect(
"rect",
Rectangle.Empty);
1533 bounds =
new Vector4(
1534 Math.Min(rect.X, bounds.X),
1535 Math.Max(rect.Y, bounds.Y),
1536 Math.Max(rect.Right, bounds.Z),
1537 Math.Min(rect.Y - rect.Height, bounds.W));
1539 else if (element.Name ==
"LinkedSubmarine")
1541 Point dimensions = element.GetAttributePoint(
"dimensions", Point.Zero);
1542 Point pos = element.GetAttributeVector2(
"pos", Vector2.Zero).ToPoint();
1543 bounds =
new Vector4(
1544 Math.Min(pos.X - dimensions.X / 2, bounds.X),
1545 Math.Max(pos.Y + dimensions.Y / 2, bounds.Y),
1546 Math.Max(pos.X + dimensions.X / 2, bounds.Z),
1547 Math.Min(pos.Y - dimensions.Y / 2, bounds.W));
1551 if (bounds.X ==
float.MaxValue || bounds.Y ==
float.MinValue || bounds.Z ==
float.MinValue || bounds.W ==
float.MaxValue)
1557 return new Rectangle((
int)bounds.X, (
int)bounds.Y, (
int)(bounds.Z - bounds.X), (
int)(bounds.Y - bounds.W));
1562 Stopwatch sw = Stopwatch.StartNew();
1564 connectedSubs =
new HashSet<Submarine>(2)
1569 upgradeEventIdentifier =
new Identifier($
"Submarine{ID}");
1578 ConnectedDockingPorts =
new Dictionary<Submarine, DockingPort>();
1581 HiddenSubPosition = HiddenSubStartPosition;
1587 for (
int i = 0; i < loaded.Count; i++)
1593 -HiddenSubPosition.X,
1594 HiddenSubPosition.Y + sub.
Borders.Height + 5000.0f);
1599 List<MapEntity> newEntities =
new List<MapEntity>();
1600 if (loadEntities ==
null)
1602 if (Info.SubmarineElement !=
null)
1604 newEntities =
MapEntity.
LoadAll(
this, Info.SubmarineElement, Info.FilePath, IdOffset);
1609 newEntities = loadEntities(
this);
1610 newEntities.ForEach(me => me.
Submarine =
this);
1613 if (newEntities !=
null)
1615 foreach (var e
in newEntities)
1617 if (linkedRemap !=
null) { e.ResolveLinks(linkedRemap); }
1618 e.unresolvedLinkedToID =
null;
1622 Vector2 center = Vector2.Zero;
1623 var matchingHulls =
Hull.
HullList.FindAll(h => h.Submarine ==
this);
1625 if (matchingHulls.Any())
1627 Vector2 topLeft =
new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
1628 Vector2 bottomRight =
new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
1629 foreach (
Hull hull
in matchingHulls)
1631 if (hull.
Rect.X < topLeft.X) topLeft.X = hull.
Rect.X;
1632 if (hull.
Rect.Y > topLeft.Y) topLeft.Y = hull.
Rect.Y;
1634 if (hull.
Rect.Right > bottomRight.X) bottomRight.X = hull.
Rect.Right;
1635 if (hull.
Rect.Y - hull.
Rect.Height < bottomRight.Y) bottomRight.Y = hull.
Rect.Y - hull.
Rect.Height;
1638 center = (topLeft + bottomRight) / 2.0f;
1639 center.X -= center.X % GridSize.X;
1640 center.Y -= center.Y % GridSize.Y;
1646 Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition);
1651 ShowSonarMarker =
false;
1655 bool indestructible =
1663 if (me is
Item item)
1665 item.AllowStealing =
true;
1668 item.SpawnedInCurrentOutpost =
true;
1669 item.AllowStealing =
1671 item.RootContainer is {
Prefab: { AllowStealingContainedItems:
true } };
1673 if (item.GetComponent<
Repairable>() !=
null && indestructible)
1675 item.Indestructible =
true;
1684 connectionPanel.Locked =
true;
1693 holdable.CanBePicked =
false;
1694 holdable.CanBeSelected =
false;
1700 structure.Indestructible =
true;
1706 ShowSonarMarker =
false;
1710 if (entityGrid !=
null)
1731 linkedSub.LinkDummyToMainSubmarine();
1735 exitPoints.Add(wayPoint);
1739 foreach (
Hull hull
in matchingHulls)
1741 if (
string.IsNullOrEmpty(hull.
RoomName))
1749 foreach (Identifier layer
in Info.LayersHiddenByDefault)
1751 SetLayerEnabled(layer, enabled:
false);
1759 Lights.ConvexHull.RecalculateAll(
this);
1763 if (showErrorMessages &&
1764 !
string.IsNullOrEmpty(Info.FilePath) &&
1766 (Info.GameVersion ==
null || Info.GameVersion <
new Version(
"0.8.9.0")))
1768 DebugConsole.ThrowError(
"The submarine \"" + Info.Name +
"\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. "
1769 +
"The game automatically adjusts the lights make them look better with the new formula, but it's recommended to open the submarine in the submarine editor and make sure everything looks right after the automatic conversion.");
1776 light.LightColor =
new Color(light.LightColor, light.LightColor.A / 255.0f * 0.5f);
1780 GenerateOutdoorNodes();
1788 string debugMsg = $
"Loading {Info?.Name ?? "unknown
"} took {sw.ElapsedMilliseconds} ms.";
1789 DebugConsole.Log(debugMsg);
1790 System.Diagnostics.Debug.WriteLine(debugMsg);
1795 return (ushort)(ReservedIDStart -
Submarine.loaded.Count);
1800 if (unloadPrevious) { Unload(); }
1801 return new Submarine(info,
false, linkedRemap: linkedRemap);
1804 private void ResetCrushDepth()
1806 realWorldCrushDepth =
null;
1822 this.realWorldCrushDepth = realWorldCrushDepth;
1827 if (moveAmount.LengthSquared() < 0.00001f) {
return; }
1830 if (entity is
Item item)
1832 item.GetComponent<
Wire>()?.MoveNodes(moveAmount);
1834 entity.
Move(moveAmount);
1840 float fuel = GetItems(
true).Where(i => i.HasTag(Tags.Fuel)).Sum(i => i.Condition);
1841 Info.LowFuel = fuel < 200;
1842 return !Info.LowFuel;
1847 element.Add(
new XAttribute(
"name", Info.Name));
1848 element.Add(
new XAttribute(
"description", Info.Description ??
""));
1849 element.Add(
new XAttribute(
"checkval", Rand.Int(
int.MaxValue)));
1850 element.Add(
new XAttribute(
"price", Info.Price));
1851 element.Add(
new XAttribute(
"tier", Info.Tier));
1852 element.Add(
new XAttribute(
"initialsuppliesspawned", Info.InitialSuppliesSpawned));
1853 element.Add(
new XAttribute(
"noitems", Info.NoItems));
1854 element.Add(
new XAttribute(
"lowfuel", !CheckFuel()));
1855 element.Add(
new XAttribute(
"type", Info.Type.ToString()));
1856 element.Add(
new XAttribute(
"ismanuallyoutfitted", Info.IsManuallyOutfitted));
1857 if (Info.IsPlayer && !Info.HasTag(
SubmarineTag.Shuttle))
1859 element.Add(
new XAttribute(
"class", Info.SubmarineClass.ToString()));
1861 element.Add(
new XAttribute(
"tags", Info.Tags.ToString()));
1862 element.Add(
new XAttribute(
"gameversion",
GameMain.
Version.ToString()));
1865 element.Add(
new XAttribute(
"dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
1866 var cargoContainers = GetCargoContainers();
1867 int cargoCapacity = cargoContainers.Sum(c => c.container.Capacity);
1872 cargoCapacity += linkedSub.CargoCapacity;
1876 element.Add(
new XAttribute(
"cargocapacity", cargoCapacity));
1877 element.Add(
new XAttribute(
"recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
1878 element.Add(
new XAttribute(
"recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
1879 element.Add(
new XAttribute(
"recommendedcrewexperience", Info.RecommendedCrewExperience.ToString()));
1880 element.Add(
new XAttribute(
"requiredcontentpackages",
string.Join(
", ", Info.RequiredContentPackages)));
1881 if (Info.LayersHiddenByDefault.Any())
1883 element.Add(
new XAttribute(
"layerhiddenbydefault",
string.Join(
", ", Info.LayersHiddenByDefault)));
1886 if (Info.WreckInfo !=
null)
1888 bool hasThalamus =
false;
1891 var prefabsOnSub = GetItems(
true).Select(i => i.Prefab).Distinct().ToImmutableHashSet();
1895 foreach (Identifier entity
in wreckAiEntities)
1897 if (
WreckAI.IsThalamus(prefab, entity))
1903 if (hasThalamus) {
break; }
1911 Info.OutpostModuleInfo?.Save(element);
1913 if (Info.GetExtraSubmarineInfo is { } extraSubInfo)
1915 extraSubInfo.Save(element);
1921 foreach (var (requiredTag, swapTo) in connectedItemsToSwap)
1923 List<Item> itemsToSwap =
new List<Item>();
1924 itemsToSwap.AddRange(item.
linkedTo.Where(lt => (lt as
Item)?.HasTag(requiredTag) ??
false).Cast<
Item>());
1927 foreach (
Connection c
in connectionPanel.Connections)
1929 foreach (var connectedComponent
in item.GetConnectedComponentsRecursive<
ItemComponent>(c))
1931 if (!itemsToSwap.Contains(connectedComponent.Item) && connectedComponent.Item.HasTag(requiredTag))
1933 itemsToSwap.Add(connectedComponent.Item);
1939 if (itemPrefab ==
null)
1941 DebugConsole.ThrowError($
"Failed to swap an item connected to \"{item.Name}\" into \"{swapTo}\".");
1944 foreach (
Item itemToSwap
in itemsToSwap)
1952 Dictionary<int, MapEntity> savedEntities =
new Dictionary<int, MapEntity>();
1959 GameAnalyticsManager.AddErrorEventOnce(
1960 "Submarine.SaveToXElement:Removed" + e.
Name,
1961 GameAnalyticsManager.ErrorSeverity.Error,
1962 $
"Attempted to save a removed entity (\"{e.Name}\"). Duplicate ID: {savedEntities.ContainsKey(e.ID)}");
1963 DebugConsole.ThrowError($
"Error while saving the submarine. Attempted to save a removed entity (\"{e.Name} ({e.ID})\"). The entity will not be saved to avoid corrupting the submarine file.");
1966 if (savedEntities.TryGetValue(e.
ID, out
MapEntity duplicateEntity))
1968 GameAnalyticsManager.AddErrorEventOnce(
1969 "Submarine.SaveToXElement:DuplicateId" + e.
Name,
1970 GameAnalyticsManager.ErrorSeverity.Error,
1971 $
"Attempted to save an entity with a duplicate ID ({e.Name}, {duplicateEntity.Name}).");
1972 DebugConsole.ThrowError($
"Error while saving the submarine. The entity \"{e.Name}\" has the same ID as \"{duplicateEntity.Name}\" ({e.ID}). The entity will not be saved to avoid corrupting the submarine file.");
1978 if (item.FindParentInventory(inv => inv is
CharacterInventory) !=
null) {
continue; }
1986 if (item.RootContainer !=
null && item.RootContainer.Submarine !=
this) {
continue; }
1994 savedEntities.Add(e.
ID, e);
1996 Info.CheckSubsLeftBehind(element);
1999 public bool TrySaveAs(
string filePath, System.IO.MemoryStream previewImage =
null)
2004 FilePath = filePath,
2008 Name = Path.GetFileNameWithoutExtension(filePath)
2012 Info.PreviewImage =
null;
2019 newInfo.SaveAs(filePath, previewImage);
2023 DebugConsole.ThrowError($
"Saving submarine \"{filePath}\" failed!", e);
2029 public static bool Unloading
2039 DebugConsole.AddWarning($
"Called {nameof(Submarine.Unload)} when already unloading.");
2049 depthSortedDamageable.Clear();
2051 var _loaded =
new List<Submarine>(loaded);
2059 visibleEntities =
null;
2068 foreach (
Item item
in items)
2070 DebugConsole.ThrowError(
"Error while unloading submarines - item \"" + item.
Name +
"\" (ID:" + item.
ID +
") not removed");
2077 DebugConsole.ThrowError(
"Error while removing \"" + item.
Name +
"\"!", e);
2107 outdoorNodes?.Clear();
2108 outdoorNodes =
null;
2109 obstructedNodes.Clear();
2113 if (entityGrid !=
null)
2119 visibleEntities =
null;
2124 if (MainSub ==
this) { MainSub =
null; }
2125 if (MainSubs[1] ==
this) { MainSubs[1] =
null; }
2127 ConnectedDockingPorts?.Clear();
2132 loaded.Remove(
this);
2140 private List<PathNode> outdoorNodes;
2141 private List<PathNode> OutdoorNodes
2145 if (outdoorNodes ==
null)
2147 GenerateOutdoorNodes();
2149 return outdoorNodes;
2153 private void GenerateOutdoorNodes()
2155 var waypoints = WayPoint.WayPointList.FindAll(wp => wp.SpawnType ==
SpawnType.Path && wp.Submarine ==
this && wp.CurrentHull ==
null);
2156 outdoorNodes = PathNode.GenerateNodes(waypoints, removeOrphans:
false);
2159 private readonly Dictionary<Submarine, HashSet<PathNode>> obstructedNodes =
new Dictionary<Submarine, HashSet<PathNode>>();
2167 foreach (var node
in OutdoorNodes)
2169 if (node ==
null || node.Waypoint ==
null) {
continue; }
2170 var wp = node.Waypoint;
2171 if (wp.IsObstructed) {
continue; }
2172 foreach (var connection
in node.connections)
2174 var connectedWp = connection.Waypoint;
2175 if (connectedWp.IsObstructed) {
continue; }
2176 Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition);
2177 Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition);
2178 var body = PickBody(start, end,
null, Physics.CollisionLevel, allowInsideFixture:
false);
2181 connectedWp.IsObstructed =
true;
2182 wp.IsObstructed =
true;
2194 if (otherSub ==
null) {
return; }
2195 if (otherSub ==
this) {
return; }
2197 foreach (var node
in OutdoorNodes)
2199 if (node ==
null || node.Waypoint ==
null) {
continue; }
2200 var wp = node.Waypoint;
2201 if (wp.IsObstructed) {
continue; }
2202 foreach (var connection
in node.connections)
2204 var connectedWp = connection.Waypoint;
2205 if (connectedWp.IsObstructed || connectedWp.Ladders !=
null) {
continue; }
2206 bool isObstructed = wp.CurrentHull is
Hull h && h.
Submarine !=
this;
2209 Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.
SimPosition;
2210 Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
2211 var body = PickBody(start, end,
null, Physics.CollisionWall, allowInsideFixture:
true);
2214 if (body.UserData is
Structure wall && !wall.
IsPlatform || body.UserData is
Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall))
2216 isObstructed =
true;
2222 connectedWp.IsObstructed =
true;
2223 wp.IsObstructed =
true;
2224 if (!obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
2226 nodes =
new HashSet<PathNode>();
2227 obstructedNodes.Add(otherSub, nodes);
2230 nodes.Add(connection);
2242 if (obstructedNodes.TryGetValue(otherSub, out HashSet<PathNode> nodes))
2244 nodes.ForEach(n => n.Waypoint.IsObstructed =
false);
2246 obstructedNodes.Remove(otherSub);
2254 var connectedSubs = GetConnectedSubs().Where(s => s.Info.Type ==
SubmarineType.Player).ToHashSet();
2255 Item selectedContainer =
null;
2258 if (potentialContainer.
Removed) {
continue; }
2260 if (potentialContainer.
IsHidden) {
continue; }
2261 if (allowConnectedSubs)
2263 if (!connectedSubs.Contains(potentialContainer.
Submarine)) {
continue; }
2267 if (potentialContainer.
Submarine !=
this) {
continue; }
2269 if (potentialContainer == item) {
continue; }
2270 if (potentialContainer.
Condition <= 0) {
continue; }
2271 if (potentialContainer.
OwnInventory ==
null) {
continue; }
2273 var container = potentialContainer.GetComponent<
ItemContainer>();
2274 if (container ==
null) {
continue; }
2276 if (!container.ShouldBeContained(item, out _)) {
continue; }
2277 if (!item.
Prefab.
IsContainerPreferred(item, container, out
bool isPreferencesDefined, out
bool isSecondary, checkTransferConditions: checkTransferConditions) || !isPreferencesDefined || onlyPrimary && isSecondary) {
continue; }
2278 if (potentialContainer.
Submarine ==
this && !isSecondary)
2281 return potentialContainer;
2283 selectedContainer = potentialContainer;
2286 return selectedContainer;
2291 return targetWorldPos.HasValue ?
2292 GetRelativeSimPositionFromWorldPosition(targetWorldPos.Value, from.
Submarine, to.
Submarine) :
2298 Vector2 worldPos = targetWorldPos;
2303 return GetRelativeSimPosition(ConvertUnits.ToSimUnits(worldPos), fromSub, toSub);
2308 Vector2 targetPos = targetSimPos;
2309 if (fromSub ==
null && toSub !=
null)
2314 else if (fromSub !=
null && toSub ==
null)
2319 else if (fromSub != toSub)
2321 if (fromSub !=
null && toSub !=
null)
BeaconStationInfo(SubmarineInfo submarineInfo, XElement element)
UpgradeManager UpgradeManager
static readonly List< Character > CharacterList
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
static GameSession?? GameSession
static SubEditorScreen SubEditorScreen
static Lights.LightManager LightManager
static readonly Version Version
static GameScreen GameScreen
static NetworkMember NetworkMember
static void UpdateHulls()
static List< Gap > GapList
static readonly List< EntityGrid > EntityGrids
static EntityGrid GenerateEntityGrid(Rectangle worldRect)
static readonly List< Hull > HullList
static ushort DetermineNewOffset()
bool CanBePut(Item item)
Can the item be put in the inventory (i.e. is there a suitable free slot or a stack the item can be p...
ItemInventory OwnInventory
List< Connection > Connections
Inventory ParentInventory
override void Move(Vector2 amount, bool ignoreContacts=true)
static void UpdateHulls()
goes through every item and re-checks which hull they are in
override Vector2? Position
override void FlipX(bool relativeToSub)
Flip the entity horizontally
ItemPrefab PendingItemSwap
bool HasTag(Identifier tag)
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
static readonly List< Item > ItemList
Entity GetRootInventoryOwner()
bool AllowRotatingInEditor
static ItemPrefab Find(string name, Identifier identifier)
SwappableItem SwappableItem
bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement=false, bool checkTransferConditions=false)
The base class for components holding the different functionalities of the item
virtual bool Select(Character character)
static readonly Dictionary< int, GridInfo > Grids
static readonly HashSet< Connection > ChangedConnections
void InfectBallast(Identifier identifier, bool allowMultiplePerShip=false)
LevelGenerationParams GenerationParams
float GetRealWorldDepth(float worldPositionY)
Calculate the "real" depth in meters from the surface of Europa (the value you see on the nav termina...
List< VoronoiCell > GetAllCells()
float RealWorldCrushDepth
The crush depth of a non-upgraded submarine in "real world units" (meters from the surface of Europa)...
Vector2 StartExitPosition
virtual void FlipX(bool relativeToSub)
Flip the entity horizontally
static void MapLoaded(List< MapEntity > entities, bool updateHulls)
static readonly List< MapEntity > MapEntityList
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
virtual void Move(Vector2 amount, bool ignoreContacts=true)
bool IsLayerHidden
Is the layer this entity is in currently hidden? If it is, the entity is not updated and should do no...
virtual XElement Save(XElement parentElement)
readonly List< MapEntity > linkedTo
static List< MapEntity > LoadAll(Submarine submarine, XElement parentElement, string filePath, int idOffset)
OutpostModuleInfo(SubmarineInfo submarineInfo, XElement element)
static void RemoveAllRoundSounds()
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
new StructurePrefab Prefab
static List< Structure > WallList
static StructurePrefab FindPrefab(string name, Identifier identifier)
bool IndestructibleInOutposts
Rectangle VisibleBorders
Extents of all the visible items/structures/hulls (including ones without a physics body)
Rectangle Borders
Extents of the solid items/structures (ones with a physics body) and hulls
void ApplyForce(Vector2 force)
readonly PhysicsBody Body
void SetPosition(Vector2 position)
void Update(float deltaTime)
List< Vector2 > HullVertices
static bool RectsOverlap(RectangleF rect1, RectangleF rect2, bool inclusive=true)
int??? SubmarineSpecificIDTag
readonly Dictionary< Submarine, DockingPort > ConnectedDockingPorts
void SetPrevTransform(Vector2 position)
Item FindContainerFor(Item item, bool onlyPrimary, bool checkTransferConditions=false, bool allowConnectedSubs=false)
override ushort DetermineID(ushort id, Submarine submarine)
List< Item > GetItems(bool alsoFromConnectedSubs)
static Submarine FindContainingInLocalCoordinates(Vector2 position, float inflate=500.0f)
Finds the sub whose borders contain the position. Note that this method uses the "actual" position of...
static Vector2 LastPickedNormal
static void RepositionEntities(Vector2 moveAmount, IEnumerable< MapEntity > entities)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
static readonly Submarine[] MainSubs
override Vector2? WorldPosition
override string ToString()
void SaveToXElement(XElement element)
Rectangle CalculateDimensions(bool onlyHulls=true)
void EnableMaintainPosition()
IEnumerable< Submarine > DockedTo
static Submarine FindContaining(Vector2 worldPosition, float inflate=500.0f)
Finds the sub whose world borders contain the position.
static Vector2 GetRelativeSimPosition(ISpatialEntity from, ISpatialEntity to, Vector2? targetWorldPos=null)
Rectangle? VisibleBorders
Extents of all the visible items/structures/hulls (including ones without a physics body)
void SetLayerEnabled(Identifier layer, bool enabled, bool sendNetworkEvent=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).
void Update(float deltaTime)
static IEnumerable< MapEntity > VisibleEntities
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
static ? Vector2 CalculateDockOffset(Submarine sub, Submarine dockedSub)
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
static RectangleF AbsRectF(Vector2 pos, Vector2 size)
static Vector2 GetRelativeSimPositionFromWorldPosition(Vector2 targetWorldPos, Submarine fromSub, Submarine toSub)
static bool LayerExistsInAnySub(Identifier layer)
void RefreshOutdoorNodes()
List< Structure > GetWalls(bool alsoFromConnectedSubs)
void SetCrushDepth(float realWorldCrushDepth)
Normally crush depth is determined by the crush depths of the walls and upgrades applied on them....
static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive=true)
List< WayPoint > GetWaypoints(bool alsoFromConnectedSubs)
static Submarine Load(SubmarineInfo info, bool unloadPrevious, IdRemap linkedRemap=null)
void EnableFactionSpecificEntities(Identifier factionIdentifier)
void FlipX(List< Submarine > parents=null)
static List< Submarine > Loaded
void RefreshConnectedSubs()
Vector2 HiddenSubPosition
void AttemptBallastFloraInfection(Identifier identifier, float deltaTime, float probability)
static IEnumerable< Body > PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
Returns a list of physics bodies the ray intersects with, sorted according to distance (the closest b...
void DisableObstructedWayPoints(Submarine otherSub)
Temporarily disables waypoints obstructed by the other sub.
void EnableObstructedWaypoints(Submarine otherSub)
Only affects temporarily disabled waypoints.
IReadOnlyList< WayPoint > ExitPoints
override Vector2 SimPosition
void UpdateTransform(bool interpolate=true)
static readonly Vector2 GridSize
static readonly Vector2 HiddenSubStartPosition
void ApplyForce(Vector2 force)
bool IsConnectedTo(Submarine otherSub)
Returns true if the sub is same as the other, or connected to it via docking ports.
float RealWorldCrushDepth
List< Vector2 >? HullVertices
static Fixture LastPickedFixture
List<(ItemContainer container, int freeSlots)> GetCargoContainers()
static Rectangle GetBorders(XElement submarineElement)
Rectangle? Borders
Extents of the solid items/structures (ones with a physics body) and hulls
bool LayerExists(Identifier layer)
bool ImmuneToBallastFlora
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
static Vector2 VectorToWorldGrid(Vector2 position, Submarine sub=null, bool round=false)
static float LastPickedFraction
static float LastPickedBodyDist(Body body)
bool CreateTurretAI()
Creates an AI that operates all the turrets on a sub, same as Thalamus but only operates the turrets.
static Rectangle AbsRect(Vector2 pos, Vector2 size)
List< Gap > GetGaps(bool alsoFromConnectedSubs)
float? RealWorldDepth
How deep down the sub is from the surface of Europa in meters (affected by level type,...
static Vector2 GetRelativeSimPosition(Vector2 targetSimPos, Submarine fromSub, Submarine toSub)
void Translate(Vector2 amount)
List< Hull > GetHulls(bool alsoFromConnectedSubs)
override Vector2? Position
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
void DisableObstructedWayPoints()
Permanently disables obstructed waypoints obstructed by the level.
void SetPosition(Vector2 position, List< Submarine > checkd=null, bool forceUndockFromStaticSubmarines=true)
Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize=null, float subDockingPortOffset=0.0f, int verticalMoveDir=0)
Attempt to find a spawn position close to the specified position where the sub doesn't collide with w...
bool TrySaveAs(string filePath, System.IO.MemoryStream previewImage=null)
SubmarineTurretAI TurretAI
static Vector2 LastPickedPosition
static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts=false, bool ignoreOutsideLevel=true, bool ignoreRespawnShuttle=false, CharacterTeamType? teamType=null)
If has value, the sub must match the team type.
OutpostGenerationParams OutpostGenerationParams
List<(Identifier requiredTag, Identifier swapTo)> ConnectedItemsToSwap
readonly NamedEvent< UpgradeManager > OnUpgradesChanged
static List< WayPoint > WayPointList
static readonly PrefabCollection< WreckAIConfig > Prefabs
static void RemoveThalamusItems(Submarine wreck)
static WreckAI Create(Submarine wreck)
override void Update(float deltaTime)
HasThalamus WreckContainsThalamus
WreckInfo(SubmarineInfo submarineInfo, XElement element)
Interface for entities that handle ServerNetObject.ENTITY_POSITION