Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
T
Joonas Rikkonen 74086415fc 1ec6577...e08c5e7
commit e08c5e722fdff38d408428d138919bb8ea90321b
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Feb 5 11:13:25 2019 +0200

    Changed default radio chat keybind to T. OemTilde works as an apostrophe on some keyboard layouts, making it difficult to type in chat messages without deselecting the chatbox. Closes #1044

commit e0ca9ffebe06b3f711b28a3ccfd486d84c47f9b0
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Feb 5 11:03:14 2019 +0200

    Fixed content package compatibility check. Closes #1059

commit f54bc180e8aef383cf9c893cd44c103840c066ac
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Tue Feb 5 10:48:03 2019 +0200

    Remove the spacing from the crew selection listbox and disable focusing from the background. An alternative fix for #1039.

commit d037815054ecc8269b0af3297700d2b98e1e5bc5
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Tue Feb 5 10:40:52 2019 +0200

    Enforce vsync during the splash screen. Should fix the crashes due to insufficient memory mentioned in #1060. Does not fix the issue on releasing the used video resources.

commit 90f1d8fe168356ba3c797c0c58fadea2aff57458
Author: ezjamsen <ezjames.fi@gmail.com>
Date:   Mon Feb 4 23:05:54 2019 +0200

    small updates to subs

commit 56dbdf6ea6e8e14035f943eb232eed7aa250f8f6
Author: ezjamsen <ezjames.fi@gmail.com>
Date:   Mon Feb 4 23:05:26 2019 +0200

    adjusted default deterioration values on prefabs

commit eaa4818cb63d52e1af519c8ca5c4f01f20143cb2
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 21:59:03 2019 +0200

    Fixed servers occasionally starting the round multiple times when automatically starting the game via autorestart or clients being ready. Happened because "initiatedStartGame" (which prevents starting the round again) was not set until the InitiateStartGame coroutine is run for the first time, causing the automatic restart logic to run one extra time. Closes #1056

commit afd83b6ef40333a95e5eeec400d5f981cbdb1889
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 21:36:35 2019 +0200

    Fixed some typos in affliction & mission descriptions. Closes #1057

commit feb97628582a9a22f0660a8ab2798f8e03fe8fc9
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 18:29:40 2019 +0200

    Fixes to Level.TryGetInterestingPosition. Should fix monsters spawning very close to the sub in some levels (see #1054).
    - Ignore outposts when determining which positions are far enough from the sub.
    - If no position far enough from the sub is found, use the furthest one instead of choosing randomly.

commit 62e5d7b229f334a5cf15cedf22e6795f18d637b9
Merge: 6ffc90da6 73f3dd060
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Mon Feb 4 18:03:44 2019 +0200

    Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev

commit 6ffc90da69d9a5db146435054fdbb2cb18d855be
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Mon Feb 4 18:03:26 2019 +0200

    Allow to focus on the crew selection listbox to enable scrolling when the mouse cursor is not over a content element, but is inside the listbox. Another option would be to remove the spacing from the listbox. Fixes #1039.

commit 73f3dd060ef208199941cb74a72dcda6a7c15f41
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 18:00:57 2019 +0200

    Fixed compiler error in GameServer.ClientWriteIngame

commit 131263490c7093dd526a84757f4edd096a213a74
Merge: 637668488 9753e46a5
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 17:55:39 2019 +0200

    Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev

commit 637668488b27954afa0a8485eeaa3b23cf9461c5
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 17:55:01 2019 +0200

    Changed character culling logic a bit. The characters need to be 22,000 units away from the players to be disabled, and won't be re-enabled until they're within 20,000 units. Using 20,000 as the threshold for both caused characters to be constantly toggled on/off when they're around the maximum distance, which caused ContactManager to constantly have to update contacts.

commit 9753e46a5694b7b3c2bd0c4c006e2c1856c6eddb
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Mon Feb 4 17:54:35 2019 +0200

    Add a debug command for adding more money in the compaign mode.

commit f8a7179d6f8cb7c834b91b942722b1ae05417316
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Mon Feb 4 17:53:03 2019 +0200

    Always disallow zooming when the cursor is over a gui element.

commit eefd956c50650be906acbd10662e24507e48cb83
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Feb 4 17:47:18 2019 +0200

    Don't display outpost markers on the sonar (the ending and starting positions of the level are displayed, no need to show an additional marker on the "outpost submarine").

commit c7883bafc634e0ac2603bc96a87433da0c3481df
Author: ezjamsen <ezjames.fi@gmail.com>
Date:   Mon Feb 4 17:21:50 2019 +0200

    removed references to medical syringes
2019-03-18 21:12:02 +02:00

1454 lines
48 KiB
C#

using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.RuinGeneration;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma
{
public enum Direction : byte
{
None = 0, Left = 1, Right = 2
}
[Flags]
public enum SubmarineTag
{
[Description("Shuttle")]
Shuttle = 1,
[Description("Hide in menus")]
HideInMenus = 2
}
partial class Submarine : Entity, IServerSerializable
{
public byte TeamID = 1;
public static string SavePath = "Submarines";
public static readonly Vector2 HiddenSubStartPosition = new Vector2(-50000.0f, 10000.0f);
//position of the "actual submarine" which is rendered wherever the SubmarineBody is
//should be in an unreachable place
public Vector2 HiddenSubPosition
{
get;
private set;
}
public ushort IdOffset
{
get;
private set;
}
public static bool LockX, LockY;
private static List<Submarine> savedSubmarines = new List<Submarine>();
public static IEnumerable<Submarine> SavedSubmarines
{
get { return savedSubmarines; }
}
public static readonly Vector2 GridSize = new Vector2(16.0f, 16.0f);
public static readonly Submarine[] MainSubs = new Submarine[2];
public static Submarine MainSub
{
get { return MainSubs[0]; }
set { MainSubs[0] = value; }
}
private static List<Submarine> loaded = new List<Submarine>();
private static List<MapEntity> visibleEntities;
public static IEnumerable<MapEntity> VisibleEntities
{
get { return visibleEntities; }
}
private SubmarineBody subBody;
public readonly List<Submarine> DockedTo;
private static Vector2 lastPickedPosition;
private static float lastPickedFraction;
private static Vector2 lastPickedNormal;
private Md5Hash hash;
private string filePath;
private string name;
private SubmarineTag tags;
private Vector2 prevPosition;
private float networkUpdateTimer;
private EntityGrid entityGrid = null;
public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
public string RecommendedCrewExperience;
public HashSet<string> RequiredContentPackages = new HashSet<string>();
//properties ----------------------------------------------------
public string Name
{
get { return name; }
set { name = value; }
}
public bool ShowSonarMarker = true;
public string Description
{
get;
set;
}
public Version GameVersion
{
get;
private set;
}
public bool IsOutpost
{
get;
private set;
}
public static Vector2 LastPickedPosition
{
get { return lastPickedPosition; }
}
public static float LastPickedFraction
{
get { return lastPickedFraction; }
}
public static Vector2 LastPickedNormal
{
get { return lastPickedNormal; }
}
public bool Loading
{
get;
private set;
}
public bool GodMode
{
get;
set;
}
public Md5Hash MD5Hash
{
get
{
if (hash != null) return hash;
XDocument doc = OpenFile(filePath);
hash = new Md5Hash(doc);
return hash;
}
}
public static List<Submarine> Loaded
{
get { return loaded; }
}
public SubmarineBody SubBody
{
get { return subBody; }
}
public PhysicsBody PhysicsBody
{
get { return subBody.Body; }
}
public Rectangle Borders
{
get
{
return subBody == null ? Rectangle.Empty : subBody.Borders;
}
}
public Vector2 Dimensions
{
get;
private set;
}
public override Vector2 Position
{
get { return subBody == null ? Vector2.Zero : subBody.Position - HiddenSubPosition; }
}
public override Vector2 WorldPosition
{
get
{
return subBody == null ? Vector2.Zero : subBody.Position;
}
}
public bool AtEndPosition
{
get
{
if (Level.Loaded == null) { return false; }
if (Level.Loaded.EndOutpost != null && DockedTo.Contains(Level.Loaded.EndOutpost))
{
return true;
}
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.EndPosition) < Level.ExitDistance * Level.ExitDistance);
}
}
public bool AtStartPosition
{
get
{
if (Level.Loaded == null) { return false; }
if (Level.Loaded.StartOutpost != null && DockedTo.Contains(Level.Loaded.StartOutpost))
{
return true;
}
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.StartPosition) < Level.ExitDistance * Level.ExitDistance);
}
}
public new Vector2 DrawPosition
{
get;
private set;
}
public override Vector2 SimPosition
{
get
{
return ConvertUnits.ToSimUnits(Position);
}
}
public Vector2 Velocity
{
get { return subBody == null ? Vector2.Zero : subBody.Velocity; }
set
{
if (subBody == null) return;
subBody.Velocity = value;
}
}
public List<Vector2> HullVertices
{
get { return subBody.HullVertices; }
}
public string FilePath
{
get { return filePath; }
set { filePath = value; }
}
public bool AtDamageDepth
{
get { return subBody != null && subBody.AtDamageDepth; }
}
public override string ToString()
{
return "Barotrauma.Submarine (" + name + ")";
}
public override bool Removed
{
get
{
return !loaded.Contains(this);
}
}
//constructors & generation ----------------------------------------------------
public Submarine(string filePath, string hash = "", bool tryLoad = true) : base(null)
{
this.filePath = filePath;
try
{
name = System.IO.Path.GetFileNameWithoutExtension(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error loading submarine " + filePath + "!", e);
}
if (hash != "")
{
this.hash = new Md5Hash(hash);
}
if (tryLoad)
{
XDocument doc = OpenFile(filePath);
if (doc != null && doc.Root != null)
{
Description = doc.Root.GetAttributeString("description", "");
GameVersion = new Version(doc.Root.GetAttributeString("gameversion", "0.0.0.0"));
Enum.TryParse(doc.Root.GetAttributeString("tags", ""), out tags);
Dimensions = doc.Root.GetAttributeVector2("dimensions", Vector2.Zero);
RecommendedCrewSizeMin = doc.Root.GetAttributeInt("recommendedcrewsizemin", 0);
RecommendedCrewSizeMax = doc.Root.GetAttributeInt("recommendedcrewsizemax", 0);
RecommendedCrewExperience = doc.Root.GetAttributeString("recommendedcrewexperience", "Unknown");
//backwards compatibility (use text tags instead of the actual text)
if (RecommendedCrewExperience == "Beginner")
RecommendedCrewExperience = "CrewExperienceLow";
else if (RecommendedCrewExperience == "Intermediate")
RecommendedCrewExperience = "CrewExperienceMid";
else if (RecommendedCrewExperience == "Experienced")
RecommendedCrewExperience = "CrewExperienceHigh";
string[] contentPackageNames = doc.Root.GetAttributeStringArray("requiredcontentpackages", new string[0]);
foreach (string contentPackageName in contentPackageNames)
{
RequiredContentPackages.Add(contentPackageName);
}
#if CLIENT
string previewImageData = doc.Root.GetAttributeString("previewimage", "");
if (!string.IsNullOrEmpty(previewImageData))
{
try
{
using (MemoryStream mem = new MemoryStream(Convert.FromBase64String(previewImageData)))
{
PreviewImage = new Sprite(TextureLoader.FromStream(mem), null, null);
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e);
GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.");
PreviewImage = null;
}
}
#endif
}
}
DockedTo = new List<Submarine>();
ID = ushort.MaxValue;
FreeID();
}
public bool HasTag(SubmarineTag tag)
{
return tags.HasFlag(tag);
}
public void AddTag(SubmarineTag tag)
{
if (tags.HasFlag(tag)) return;
tags |= tag;
}
public void RemoveTag(SubmarineTag tag)
{
if (!tags.HasFlag(tag)) return;
tags &= ~tag;
}
public void MakeOutpost()
{
IsOutpost = true;
ShowSonarMarker = false;
PhysicsBody.FarseerBody.IsStatic = true;
foreach (MapEntity me in MapEntity.mapEntityList)
{
if (me.Submarine != this) { continue; }
if (me is Item item)
{
item.Indestructible = true;
foreach (ItemComponent ic in item.components)
{
if (ic is ConnectionPanel connectionPanel)
{
//prevent rewiring
connectionPanel.Locked = true;
}
else if (ic is Pickable pickable)
{
//prevent picking up (or deattaching) items
pickable.CanBePicked = false;
pickable.CanBeSelected = false;
}
}
}
else if (me is Structure structure)
{
structure.Indestructible = true;
}
}
}
/// <summary>
/// Returns a rect that contains the borders of this sub and all subs docked to it
/// </summary>
public Rectangle GetDockedBorders()
{
Rectangle dockedBorders = Borders;
dockedBorders.Y -= dockedBorders.Height;
var connectedSubs = GetConnectedSubs();
foreach (Submarine dockedSub in connectedSubs)
{
if (dockedSub == this) continue;
Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition;
Rectangle dockedSubBorders = dockedSub.Borders;
dockedSubBorders.Y -= dockedSubBorders.Height;
dockedSubBorders.Location += MathUtils.ToPoint(diff);
dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders);
}
dockedBorders.Y += dockedBorders.Height;
return dockedBorders;
}
/// <summary>
/// Returns a list of all submarines that are connected to this one via docking ports.
/// </summary>
public List<Submarine> GetConnectedSubs()
{
List<Submarine> connectedSubs = new List<Submarine> { this };
GetConnectedSubsRecursive(connectedSubs);
return connectedSubs;
}
private void GetConnectedSubsRecursive(List<Submarine> subs)
{
foreach (Submarine dockedSub in DockedTo)
{
if (subs.Contains(dockedSub)) continue;
subs.Add(dockedSub);
dockedSub.GetConnectedSubsRecursive(subs);
}
}
public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null)
{
Rectangle dockedBorders = GetDockedBorders();
Vector2 diffFromDockedBorders =
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
- new Vector2(Borders.Center.X, Borders.Y - Borders.Height / 2);
int minWidth = Math.Max(submarineSize.HasValue ? submarineSize.Value.X : dockedBorders.Width, 500);
int minHeight = Math.Max(submarineSize.HasValue ? submarineSize.Value.Y : dockedBorders.Height, 1000);
//a bit of extra padding to prevent the sub from spawning in a super tight gap between walls
minHeight += 500;
float minX = float.MinValue, maxX = float.MaxValue;
foreach (VoronoiCell cell in Level.Loaded.GetAllCells())
{
if (cell.Edges.All(e => e.Point1.Y < Level.Loaded.Size.Y - minHeight && e.Point2.Y < Level.Loaded.Size.Y - minHeight)) { continue; }
//find the closest wall at the left and right side of the spawnpos
if (cell.Site.Coord.X < spawnPos.X)
{
minX = Math.Max(minX, cell.Edges.Max(e => Math.Max(e.Point1.X, e.Point2.X)));
}
else
{
maxX = Math.Min(maxX, cell.Edges.Min(e => Math.Min(e.Point1.X, e.Point2.X)));
}
}
foreach (var ruin in Level.Loaded.Ruins)
{
if (ruin.Area.Y + ruin.Area.Height < Level.Loaded.Size.Y - minHeight) { continue; }
if (ruin.Area.X < spawnPos.X)
{
minX = Math.Max(minX, ruin.Area.Right + 100.0f);
}
else
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
{
//no walls found at either side, just use the initial spawnpos and hope for the best
}
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f;
}
else if (maxX > Level.Loaded.Size.X)
{
//no wall found at right side, spawn to the right from the left-side wall
spawnPos.X = minX + minWidth + 100.0f;
}
else
{
//walls found at both sides, use their midpoint
spawnPos.X = (minX + maxX) / 2;
}
spawnPos.Y = Math.Min(spawnPos.Y, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10);
return spawnPos - diffFromDockedBorders;
}
//drawing ----------------------------------------------------
public static void CullEntities(Camera cam)
{
HashSet<Submarine> visibleSubs = new HashSet<Submarine>();
foreach (Submarine sub in Loaded)
{
if (sub.WorldPosition.Y < Level.MaxEntityDepth) continue;
Rectangle worldBorders = new Rectangle(
sub.Borders.X + (int)sub.WorldPosition.X - 500,
sub.Borders.Y + (int)sub.WorldPosition.Y + 500,
sub.Borders.Width + 1000,
sub.Borders.Height + 1000);
if (RectsOverlap(worldBorders, cam.WorldView))
{
visibleSubs.Add(sub);
}
}
HashSet<Ruin> visibleRuins = new HashSet<Ruin>();
if (Level.Loaded != null)
{
foreach (Ruin ruin in Level.Loaded.Ruins)
{
Rectangle worldBorders = new Rectangle(
ruin.Area.X - 500,
ruin.Area.Y + ruin.Area.Height + 500,
ruin.Area.Width + 1000,
ruin.Area.Height + 1000);
if (RectsOverlap(worldBorders, cam.WorldView))
{
visibleRuins.Add(ruin);
}
}
}
if (visibleEntities == null)
{
visibleEntities = new List<MapEntity>(MapEntity.mapEntityList.Count);
}
else
{
visibleEntities.Clear();
}
Rectangle worldView = cam.WorldView;
foreach (MapEntity entity in MapEntity.mapEntityList)
{
if (entity.Submarine != null)
{
if (!visibleSubs.Contains(entity.Submarine)) { continue; }
}
else if(entity.ParentRuin != null)
{
if (!visibleRuins.Contains(entity.ParentRuin)) { continue; }
}
if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); }
}
}
public void UpdateTransform()
{
DrawPosition = Timing.Interpolate(prevPosition, Position);
}
//math/physics stuff ----------------------------------------------------
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub)
{
Vector2 position = PlayerInput.MousePosition;
position = cam.ScreenToWorld(position);
Vector2 worldGridPos = VectorToWorldGrid(position);
if (sub != null)
{
worldGridPos.X += sub.Position.X % GridSize.X;
worldGridPos.Y += sub.Position.Y % GridSize.Y;
}
return worldGridPos;
}
public static Vector2 VectorToWorldGrid(Vector2 position)
{
position.X = (float)Math.Floor(position.X / GridSize.X) * GridSize.X;
position.Y = (float)Math.Ceiling(position.Y / GridSize.Y) * GridSize.Y;
return position;
}
public Rectangle CalculateDimensions(bool onlyHulls = true)
{
List<MapEntity> entities = onlyHulls ?
Hull.hullList.FindAll(h => h.Submarine == this).Cast<MapEntity>().ToList() :
MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
//ignore items whose body is disabled (wires, items inside cabinets)
entities.RemoveAll(e =>
{
if (e is Item item)
{
if (item.body != null && !item.body.Enabled) { return true; }
}
return false;
});
if (entities.Count == 0) return Rectangle.Empty;
float minX = entities[0].Rect.X, minY = entities[0].Rect.Y - entities[0].Rect.Height;
float maxX = entities[0].Rect.Right, maxY = entities[0].Rect.Y;
for (int i = 1; i < entities.Count; i++)
{
minX = Math.Min(minX, entities[i].Rect.X);
minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height);
maxX = Math.Max(maxX, entities[i].Rect.Right);
maxY = Math.Max(maxY, entities[i].Rect.Y);
}
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
}
public static Rectangle AbsRect(Vector2 pos, Vector2 size)
{
if (size.X < 0.0f)
{
pos.X += size.X;
size.X = -size.X;
}
if (size.Y < 0.0f)
{
pos.Y -= size.Y;
size.Y = -size.Y;
}
return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y);
}
public static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive = false)
{
if (inclusive)
{
return (pos.X >= rect.X && pos.X <= rect.X + rect.Width
&& pos.Y <= rect.Y && pos.Y >= rect.Y - rect.Height);
}
else
{
return (pos.X > rect.X && pos.X < rect.X + rect.Width
&& pos.Y < rect.Y && pos.Y > rect.Y - rect.Height);
}
}
public static bool RectsOverlap(Rectangle rect1, Rectangle rect2, bool inclusive = true)
{
if (inclusive)
{
return !(rect1.X > rect2.X + rect2.Width || rect1.X + rect1.Width < rect2.X ||
rect1.Y < rect2.Y - rect2.Height || rect1.Y - rect1.Height > rect2.Y);
}
else
{
return !(rect1.X >= rect2.X + rect2.Width || rect1.X + rect1.Width <= rect2.X ||
rect1.Y <= rect2.Y - rect2.Height || rect1.Y - rect1.Height >= rect2.Y);
}
}
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
{
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
{
rayEnd += Vector2.UnitX * 0.001f;
}
float closestFraction = 1.0f;
Vector2 closestNormal = Vector2.Zero;
Body closestBody = null;
GameMain.World.RayCast((fixture, point, normal, fraction) =>
{
if (fixture == null ||
(ignoreSensors && fixture.IsSensor) ||
fixture.CollisionCategories == Category.None ||
fixture.CollisionCategories == Physics.CollisionItem) return -1;
if (customPredicate != null && !customPredicate(fixture)) return -1;
if (collisionCategory != null &&
!fixture.CollisionCategories.HasFlag((Category)collisionCategory) &&
!((Category)collisionCategory).HasFlag(fixture.CollisionCategories)) return -1;
if (ignoredBodies != null && ignoredBodies.Contains(fixture.Body)) return -1;
if (fixture.Body.UserData is Structure structure)
{
if (structure.IsPlatform && collisionCategory != null && !((Category)collisionCategory).HasFlag(Physics.CollisionPlatform)) return -1;
}
if (fraction < closestFraction)
{
closestFraction = fraction;
closestNormal = normal;
if (fixture.Body != null) closestBody = fixture.Body;
}
return fraction;
}
, rayStart, rayEnd);
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
lastPickedNormal = closestNormal;
return closestBody;
}
/// <summary>
/// check visibility between two points (in sim units)
/// </summary>
/// <returns>a physics body that was between the points (or null)</returns>
public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true)
{
Body closestBody = null;
float closestFraction = 1.0f;
Vector2 closestNormal = Vector2.Zero;
if (Vector2.Distance(rayStart, rayEnd) < 0.01f)
{
lastPickedPosition = rayEnd;
return null;
}
GameMain.World.RayCast((fixture, point, normal, fraction) =>
{
if (fixture == null ||
(ignoreSensors && fixture.IsSensor) ||
(!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel))) return -1;
if (ignoreLevel && fixture.CollisionCategories == Physics.CollisionLevel) return -1;
if (ignoreSubs && fixture.Body.UserData is Submarine) return -1;
if (fixture.Body.UserData is Structure structure)
{
if (structure.IsPlatform || structure.StairDirection != Direction.None) return -1;
int sectionIndex = structure.FindSectionIndex(ConvertUnits.ToDisplayUnits(point));
if (sectionIndex > -1 && structure.SectionBodyDisabled(sectionIndex)) return -1;
}
if (fraction < closestFraction)
{
closestBody = fixture.Body;
closestFraction = fraction;
closestNormal = normal;
}
return closestFraction;
}
, rayStart, rayEnd);
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
lastPickedFraction = closestFraction;
lastPickedNormal = closestNormal;
return closestBody;
}
//movement ----------------------------------------------------
private bool flippedX;
public bool FlippedX
{
get { return flippedX; }
}
public void FlipX(List<Submarine> parents = null)
{
if (parents == null) parents = new List<Submarine>();
parents.Add(this);
flippedX = !flippedX;
Item.UpdateHulls();
List<Item> bodyItems = Item.ItemList.FindAll(it => it.Submarine == this && it.body != null);
List<MapEntity> subEntities = MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
foreach (MapEntity e in subEntities)
{
if (e is Item) continue;
if (e is LinkedSubmarine)
{
Submarine sub = ((LinkedSubmarine)e).Sub;
if (!parents.Contains(sub))
{
Vector2 relative1 = sub.SubBody.Position - SubBody.Position;
relative1.X = -relative1.X;
sub.SetPosition(relative1 + SubBody.Position);
sub.FlipX(parents);
}
}
else
{
e.FlipX(true);
}
}
foreach (MapEntity mapEntity in subEntities)
{
mapEntity.Move(-HiddenSubPosition);
}
Vector2 pos = new Vector2(subBody.Position.X, subBody.Position.Y);
subBody.Body.Remove();
subBody = new SubmarineBody(this);
SetPosition(pos);
if (entityGrid != null)
{
Hull.EntityGrids.Remove(entityGrid);
entityGrid = null;
}
entityGrid = Hull.GenerateEntityGrid(this);
foreach (MapEntity mapEntity in subEntities)
{
mapEntity.Move(HiddenSubPosition);
}
foreach (Item item in Item.ItemList)
{
if (bodyItems.Contains(item))
{
item.Submarine = this;
if (Position == Vector2.Zero) item.Move(-HiddenSubPosition);
}
else if (item.Submarine != this)
{
continue;
}
item.FlipX(true);
}
Item.UpdateHulls();
Gap.UpdateHulls();
}
public void Update(float deltaTime)
{
//if (PlayerInput.KeyHit(InputType.Crouch) && (this == MainSub)) FlipX();
if (Level.Loaded == null || subBody == null) return;
if (WorldPosition.Y < Level.MaxEntityDepth &&
subBody.Body.Enabled &&
(GameMain.NetworkMember?.RespawnManager == null || this != GameMain.NetworkMember.RespawnManager.RespawnShuttle))
{
subBody.Body.ResetDynamics();
subBody.Body.Enabled = false;
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (e.Submarine == this)
{
Spawner.AddToRemoveQueue(e);
}
}
foreach (Character c in Character.CharacterList)
{
if (c.Submarine == this)
{
c.Kill(CauseOfDeathType.Pressure, null);
c.Enabled = false;
}
}
return;
}
subBody.Body.LinearVelocity = new Vector2(
LockX ? 0.0f : subBody.Body.LinearVelocity.X,
LockY ? 0.0f : subBody.Body.LinearVelocity.Y);
subBody.Update(deltaTime);
for (int i = 0; i < 2; i++)
{
if (MainSubs[i] == null) continue;
if (this != MainSubs[i] && MainSubs[i].DockedTo.Contains(this)) return;
}
//send updates more frequently if moving fast
networkUpdateTimer -= MathHelper.Clamp(Velocity.Length() * 10.0f, 0.1f, 5.0f) * deltaTime;
if (networkUpdateTimer < 0.0f)
{
networkUpdateTimer = 1.0f;
}
}
public void ApplyForce(Vector2 force)
{
if (subBody != null) subBody.ApplyForce(force);
}
public void SetPrevTransform(Vector2 position)
{
prevPosition = position;
}
public void SetPosition(Vector2 position)
{
if (!MathUtils.IsValid(position)) return;
subBody.SetPosition(position);
foreach (Submarine sub in loaded)
{
if (sub != this && sub.Submarine == this)
{
sub.SetPosition(position + sub.WorldPosition);
sub.Submarine = null;
}
}
//Level.Loaded.SetPosition(-position);
//prevPosition = position;
}
public void Translate(Vector2 amount)
{
if (amount == Vector2.Zero || !MathUtils.IsValid(amount)) return;
subBody.SetPosition(subBody.Position + amount);
//Level.Loaded.Move(-amount);
}
public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false)
{
Submarine closest = null;
float closestDist = 0.0f;
foreach (Submarine sub in loaded)
{
if (ignoreOutposts && sub.IsOutpost)
{
continue;
}
float dist = Vector2.DistanceSquared(worldPosition, sub.WorldPosition);
if (closest == null || dist < closestDist)
{
closest = sub;
closestDist = dist;
}
}
return closest;
}
/// <summary>
/// Finds the sub whose borders contain the position
/// </summary>
public static Submarine FindContaining(Vector2 position)
{
foreach (Submarine sub in Submarine.Loaded)
{
Rectangle subBorders = sub.Borders;
subBorders.Location += MathUtils.ToPoint(sub.HiddenSubPosition) - new Microsoft.Xna.Framework.Point(0, sub.Borders.Height);
subBorders.Inflate(500.0f, 500.0f);
if (subBorders.Contains(position)) return sub;
}
return null;
}
//saving/loading ----------------------------------------------------
public static void AddToSavedSubs(Submarine sub)
{
savedSubmarines.Add(sub);
}
public static void RefreshSavedSubs()
{
for (int i = savedSubmarines.Count - 1; i>= 0; i--)
{
savedSubmarines[i].Dispose();
}
System.Diagnostics.Debug.Assert(savedSubmarines.Count == 0);
if (!Directory.Exists(SavePath))
{
try
{
Directory.CreateDirectory(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Directory \"" + SavePath + "\" not found and creating the directory failed.", e);
return;
}
}
List<string> filePaths;
string[] subDirectories;
try
{
filePaths = Directory.GetFiles(SavePath).ToList();
subDirectories = Directory.GetDirectories(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open directory \"" + SavePath + "\"!", e);
return;
}
foreach (string subDirectory in subDirectories)
{
try
{
filePaths.AddRange(Directory.GetFiles(subDirectory).ToList());
}
catch (Exception e)
{
DebugConsole.ThrowError("Couldn't open subdirectory \"" + subDirectory + "\"!", e);
return;
}
}
foreach (string path in filePaths)
{
savedSubmarines.Add(new Submarine(path));
}
}
static readonly string TempFolder = Path.Combine("Submarine", "Temp");
public static XDocument OpenFile(string file)
{
XDocument doc = null;
string extension = "";
try
{
extension = System.IO.Path.GetExtension(file);
}
catch
{
//no file extension specified: try using the default one
file += ".sub";
}
if (string.IsNullOrWhiteSpace(extension))
{
extension = ".sub";
file += ".sub";
}
if (extension == ".sub")
{
Stream stream = null;
try
{
stream = SaveUtil.DecompressFiletoStream(file);
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed!", e);
return null;
}
try
{
stream.Position = 0;
doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file);
stream.Close();
stream.Dispose();
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")");
return null;
}
}
else if (extension == ".xml")
{
try
{
ToolBox.IsProperFilenameCase(file);
doc = XDocument.Load(file, LoadOptions.SetBaseUri);
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (" + e.Message + ")");
return null;
}
}
else
{
DebugConsole.ThrowError("Couldn't load submarine \"" + file + "! (Unrecognized file extension)");
return null;
}
return doc;
}
public void Load(bool unloadPrevious, XElement submarineElement = null, bool showWarningMessages = true)
{
if (unloadPrevious) Unload();
Loading = true;
if (submarineElement == null)
{
XDocument doc = OpenFile(filePath);
if (doc == null || doc.Root == null) return;
submarineElement = doc.Root;
}
GameVersion = GameVersion ?? new Version(submarineElement.GetAttributeString("gameversion", "0.0.0.0"));
Description = submarineElement.GetAttributeString("description", "");
Enum.TryParse(submarineElement.GetAttributeString("tags", ""), out tags);
//place the sub above the top of the level
HiddenSubPosition = HiddenSubStartPosition;
if (GameMain.GameSession != null && GameMain.GameSession.Level != null)
{
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.Level.Size.Y;
}
foreach (Submarine sub in Submarine.loaded)
{
HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f);
}
IdOffset = 0;
foreach (MapEntity me in MapEntity.mapEntityList)
{
IdOffset = Math.Max(IdOffset, me.ID);
}
var newEntities = MapEntity.LoadAll(this, submarineElement, filePath);
Vector2 center = Vector2.Zero;
var matchingHulls = Hull.hullList.FindAll(h => h.Submarine == this);
if (matchingHulls.Any())
{
Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
Vector2 bottomRight = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y);
foreach (Hull hull in matchingHulls)
{
if (hull.Rect.X < topLeft.X) topLeft.X = hull.Rect.X;
if (hull.Rect.Y > topLeft.Y) topLeft.Y = hull.Rect.Y;
if (hull.Rect.Right > bottomRight.X) bottomRight.X = hull.Rect.Right;
if (hull.Rect.Y - hull.Rect.Height < bottomRight.Y) bottomRight.Y = hull.Rect.Y - hull.Rect.Height;
}
center = (topLeft + bottomRight) / 2.0f;
center.X -= center.X % GridSize.X;
center.Y -= center.Y % GridSize.Y;
if (center != Vector2.Zero)
{
foreach (Item item in Item.ItemList)
{
if (item.Submarine != this) continue;
var wire = item.GetComponent<Items.Components.Wire>();
if (wire != null)
{
wire.MoveNodes(-center);
}
}
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
{
if (MapEntity.mapEntityList[i].Submarine != this) continue;
MapEntity.mapEntityList[i].Move(-center);
}
}
}
subBody = new SubmarineBody(this, showWarningMessages);
subBody.SetPosition(HiddenSubPosition);
loaded.Add(this);
if (entityGrid != null)
{
Hull.EntityGrids.Remove(entityGrid);
entityGrid = null;
}
entityGrid = Hull.GenerateEntityGrid(this);
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
{
if (MapEntity.mapEntityList[i].Submarine != this) continue;
MapEntity.mapEntityList[i].Move(HiddenSubPosition);
}
Loading = false;
MapEntity.MapLoaded(newEntities, true);
foreach (Hull hull in matchingHulls)
{
if (string.IsNullOrEmpty(hull.RoomName))
{
hull.RoomName = hull.CreateRoomName();
}
}
#if CLIENT
GameMain.LightManager.OnMapLoaded();
#endif
//if the sub was made using an older version,
//halve the brightness of the lights to make them look (almost) right on the new lighting formula
if (showWarningMessages && Screen.Selected != GameMain.SubEditorScreen && (GameVersion == null || GameVersion < new Version("0.8.9.0")))
{
DebugConsole.ThrowError("The submarine \"" + Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. "
+ "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.");
foreach (Item item in Item.ItemList)
{
if (item.Submarine != this) continue;
if (item.ParentInventory != null || item.body != null) continue;
var lightComponent = item.GetComponent<Items.Components.LightComponent>();
if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f);
}
}
ID = (ushort)(ushort.MaxValue - Submarine.loaded.IndexOf(this));
}
public static Submarine Load(XElement element, bool unloadPrevious)
{
if (unloadPrevious) Unload();
//tryload -> false
Submarine sub = new Submarine(element.GetAttributeString("name", ""), "", false);
sub.Load(unloadPrevious, element);
return sub;
}
public static Submarine Load(string fileName, bool unloadPrevious)
{
return Load(fileName, SavePath, unloadPrevious);
}
public static Submarine Load(string fileName, string folder, bool unloadPrevious)
{
if (unloadPrevious) Unload();
string path = string.IsNullOrWhiteSpace(folder) ? fileName : System.IO.Path.Combine(SavePath, fileName);
Submarine sub = new Submarine(path);
sub.Load(unloadPrevious);
return sub;
}
public bool SaveAs(string filePath, MemoryStream previewImage = null)
{
name = Path.GetFileNameWithoutExtension(filePath);
XDocument doc = new XDocument(new XElement("Submarine"));
SaveToXElement(doc.Root);
hash = new Md5Hash(doc);
doc.Root.Add(new XAttribute("md5hash", hash.Hash));
if (previewImage != null)
{
doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
}
try
{
SaveUtil.CompressStringToFile(filePath, doc.ToString());
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving submarine \"" + filePath + "\" failed!", e);
return false;
}
return true;
}
public void SaveToXElement(XElement element)
{
element.Add(new XAttribute("name", name));
element.Add(new XAttribute("description", Description ?? ""));
element.Add(new XAttribute("tags", tags.ToString()));
element.Add(new XAttribute("gameversion", GameMain.Version.ToString()));
Rectangle dimensions = CalculateDimensions();
element.Add(new XAttribute("dimensions", XMLExtensions.Vector2ToString(dimensions.Size.ToVector2())));
element.Add(new XAttribute("recommendedcrewsizemin", RecommendedCrewSizeMin));
element.Add(new XAttribute("recommendedcrewsizemax", RecommendedCrewSizeMax));
element.Add(new XAttribute("recommendedcrewexperience", RecommendedCrewExperience ?? ""));
element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", RequiredContentPackages)));
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (e.Submarine != this || !e.ShouldBeSaved) continue;
e.Save(element);
}
}
public static bool Unloading
{
get;
private set;
}
public static void Unload()
{
Unloading = true;
#if CLIENT
RemoveAllRoundSounds(); //Sound.OnGameEnd();
if (GameMain.LightManager != null) GameMain.LightManager.ClearLights();
#endif
foreach (Submarine sub in loaded)
{
sub.Remove();
}
loaded.Clear();
visibleEntities = null;
if (GameMain.GameScreen.Cam != null) GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
RemoveAll();
if (Item.ItemList.Count > 0)
{
List<Item> items = new List<Item>(Item.ItemList);
foreach (Item item in items)
{
DebugConsole.ThrowError("Error while unloading submarines - item \"" + item.Name + "\" (ID:" + item.ID + ") not removed");
try
{
item.Remove();
}
catch (Exception e)
{
DebugConsole.ThrowError("Error while removing \"" + item.Name + "\"!", e);
}
}
Item.ItemList.Clear();
}
Ragdoll.RemoveAll();
PhysicsBody.RemoveAll();
GameMain.World.Clear();
Unloading = false;
}
public override void Remove()
{
base.Remove();
subBody = null;
visibleEntities = null;
if (MainSub == this) MainSub = null;
if (MainSubs[1] == this) MainSubs[1] = null;
DockedTo.Clear();
}
public void Dispose()
{
savedSubmarines.Remove(this);
#if CLIENT
PreviewImage?.Remove();
PreviewImage = null;
#endif
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(ID);
//length in bytes
msg.Write((byte)(4 + 4));
msg.Write(PhysicsBody.SimPosition.X);
msg.Write(PhysicsBody.SimPosition.Y);
}
}
}