58a14fd054
commit bd9a92df11a8d83d0d0087ce64422f2fba3f6f99 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 20:07:36 2019 +0200 v0.8.9.2 commit 4e1d5fa3744fb8cab80cb006a433d2efe7685562 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 19:20:03 2019 +0200 Moved humpback's cargo spawn position a bit to prevent the cargo from falling down the hatch under it. commit f0ec5530b0c7ecbdaaf8bd266d9a57fac2c92fe9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 19:18:41 2019 +0200 Fixed structure BodyOffset not being taken into account when generating the collider for the sub. Caused the button outside Orca's button to be impossible to interact with. commit 1360f5511522280d03d608c8eb6f56fb6608aab5 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 19:10:37 2019 +0200 Halved the status effect for damage to submerged items. This might be a bit too generous, let's see. commit 06afb668bc816a345dbd12899b819843549e908a Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 18:22:00 2019 +0200 Changed mantis loading tip to mud raptor, fixed a glaring error in one of the npc conversations commit d2dfa960dd5fa43426eb4dfd02de6c497ae9d838 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 18:17:17 2019 +0200 Fixed water flow forces occasionally being way too high due to bugged force calculation logic when water is flowing from a hull to the one below it. + Added a hard cap to flow forces to prevent this from happening again. Closes #572 commit 21754b39ded247e8c9837d58c6de9658f56a9772 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 17:24:48 2019 +0200 Entities cannot be placed in the sub editor when the cursor is on a UI element. Fixes an entity being placed when selecting an item from them menu while something else is already selected. Closes #1023 commit 97d0cf92f738a058fa0140ec841549a5d6c746a3 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:59:56 2019 +0200 Fixed a typo in one of the affliction descriptions commit ddccbdc9a438199d3cb796789dd9915cc305ec5b Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:59:18 2019 +0200 Fixed sliders buttons becoming invisible when pressed commit bc28473a1c61698c9447825b1c5a13edd6e7bf6c Merge: 2d212802e f3dfe5990 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 16:45:33 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 2d212802ea945c6cbd1c889d304595b68692e05c Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 16:44:30 2019 +0200 corrected the condition at which railgun and coilgun loaders can be repaired commit f3dfe5990a03cb3c577635dffb25883085d919f1 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:41:34 2019 +0200 Fixed second submarine overlapping with the outpost at the end of the level in combat missions. commit 1f224e9b7cae3f94e9b6937d66c171edbd8ef523 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:35:22 2019 +0200 Fixed sub/mode selection setting tickboxes commit 0eb99486454c1bb47b51b20e4d23f42f4147dd8d Merge: ff90fdb35 80785d627 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:08:18 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit ff90fdb352af6800d0dcf224816cf9e831184f1e Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:08:12 2019 +0200 Don't do chat message similarity tests on order messages (caused spam kicks way too easily when giving orders or reporting things) commit af8a350c7998ae2c729b4354c3c02cbcf75cabde Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:06:42 2019 +0200 Fixed Huskappendage.xml not being copied to the output directory commit df3af76d909099e9c9e59f74b1f5b3b381303a99 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 14:06:19 2019 +0200 Revert "Ignore anything but the content path when checking the filepath cases." Doesn't work correctly if there's a folder named "Content" in some subfolder (e.g. "Mods/SomeMod/Content") or if checking file paths somewhere else than the content folder (Submarines, Data, NewWorkshopItem, Mods...) This reverts commit 123e84ea1ac6f4392c293d0d5c64b857bfc4eb30. commit 80785d627c82948ce1784654070a0da8f732ca33 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:43:36 2019 +0200 added better visual feedback when reactor is in poor condition commit e450872721d8e0b47b3ae5792292ea2071a8eefe Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:40:42 2019 +0200 added a small stun effect to railgun shells commit 05792285eab781990bdc3b658a9217ff38704d1e Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:40:03 2019 +0200 added a little extra fuel, moved the position of guns to hull to prevent players getting caught under them commit 8c85c33023b6cec1c18b8743d1f05fd4fc8c2ea7 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 13:58:55 2019 +0200 Fixed crashing when attempting to use active sonar in the sub editor. Closes #1024 commit d71225d8c58fb285cbf207549b77cabdc7e1df11 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:27:06 2019 +0200 Fixed crashing when selecting an item in a fabricator that's linked to containers, but not set to display the contents of the containers commit a6fa5d82c7b1505932b8283ccfef75798b839d92 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:07:40 2019 +0200 Fixed "can't create and entity event for Hull" error messages when attempting to remove a hull with fire sources in it in MP commit 72737c6d3eb7ca7daf5e5d84fdcbc1f060b79b10 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:05:04 2019 +0200 Filename case fix part 913, fixed rewards mentioned in mission descriptions not matching the actual reward of the mission. commit a148c7843f2805b1757d231bcf028bafdbc22187 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:56:18 2019 +0200 Attempt to forward steam query port when UPnP is enabled commit 3e73d705c221a417f60baf76c8c7d100391f7802 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:44:34 2019 +0200 Removed the "training" button from the main menu (not needed, tutorial is now implemented directly in the SP campaign) commit 1f07c3f1b4de0bb09fe11e9834faeea30e4afade Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:33:01 2019 +0200 Fixed crewmanager UI elements hidden outside the extents of the crew list overlapping with other UI elements commit aa0c5f4e90b57a8ee3f152b9aeba1e73c7c539c0 Merge: 123e84ea1 73f51a405 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Fri Feb 1 16:21:27 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 123e84ea1ac6f4392c293d0d5c64b857bfc4eb30 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Fri Feb 1 16:21:19 2019 +0200 Ignore anything but the content path when checking the filepath cases. commit 73f51a4055ca7fd1cd65570a51d88db1f1272d23 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 15:20:15 2019 +0200 Don't show the "incorrect case" errors in non-linux release builds. It seems that Path.GetFullPath may return paths with an incorrect case on Windows (try changing the case of any of the game's parent folders to repro). commit 5a598129a9d63c5185529107d469404c35be59c5 Author: ezjamsen <ezjames.fi@gmail.com> Date: Fri Feb 1 11:29:41 2019 +0200 Renamed hull areas... again.
1453 lines
48 KiB
C#
1453 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 OnSonar = 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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
}
|