diff --git a/Subsurface/Barotrauma.csproj b/Subsurface/Barotrauma.csproj index 09cabeebc..44af133a5 100644 --- a/Subsurface/Barotrauma.csproj +++ b/Subsurface/Barotrauma.csproj @@ -144,7 +144,7 @@ - + diff --git a/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs b/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs index db0c3801d..ce0f38c7f 100644 --- a/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs +++ b/Subsurface/Source/GameSession/GameModes/SinglePlayerMode.cs @@ -53,7 +53,7 @@ namespace Barotrauma endShiftButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 220, 20, 200, 25), "End shift", Alignment.TopLeft, GUI.Style); endShiftButton.Font = GUI.SmallFont; - endShiftButton.OnClicked = EndShift; + endShiftButton.OnClicked = TryEndShift; for (int i = 0; i < 3; i++) { @@ -236,6 +236,39 @@ namespace Barotrauma Submarine.Unload(); } + private bool TryEndShift(GUIButton button, object obj) + { + int subsNotDocked = Submarine.Loaded.Count(s => s != Submarine.MainSub && !s.DockedTo.Contains(Submarine.MainSub)); + + if (subsNotDocked > 0) + { + string msg = ""; + if (subsNotDocked == 1) + { + msg = "One of of your vessels hasn't been docked to " + Submarine.MainSub.Name + + ". If you leave now, you will permanently lose it." + + " Do you want to leave the vessel behind?"; + } + else + { + msg = "Some of of your vessels hasn't been docked to " + Submarine.MainSub.Name + + ". If you leave now, you will permanently lose them." + + " Do you want to leave the vessels behind?"; + } + + var msgBox = new GUIMessageBox("Warning", msg, new string[] {"Yes", "No"}); + msgBox.Buttons[0].OnClicked += EndShift; + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked += msgBox.Close; + } + else + { + EndShift(button, obj); + } + + return true; + } + private bool EndShift(GUIButton button, object obj) { isRunning = false; diff --git a/Subsurface/Source/Items/Components/DockingPort.cs b/Subsurface/Source/Items/Components/DockingPort.cs index 6e73fe062..83cd58e32 100644 --- a/Subsurface/Source/Items/Components/DockingPort.cs +++ b/Subsurface/Source/Items/Components/DockingPort.cs @@ -17,7 +17,7 @@ namespace Barotrauma.Items.Components class DockingPort : ItemComponent, IDrawableComponent { - private static List list = new List(); + public static List list = new List(); private Sprite overlaySprite; @@ -124,7 +124,7 @@ namespace Barotrauma.Items.Components if (adjacentPort != null) Dock(adjacentPort); } - private void Dock(DockingPort target) + public void Dock(DockingPort target) { if (dockingTarget != null) { @@ -133,9 +133,15 @@ namespace Barotrauma.Items.Components PlaySound(ActionType.OnUse, item.WorldPosition); + item.linkedTo.Add(target.item); + + if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine); + if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine); dockingTarget = target; dockingTarget.dockingTarget = this; + + docked = true; dockingTarget.Docked = true; if (Character.Controlled != null && @@ -296,6 +302,11 @@ namespace Barotrauma.Items.Components PlaySound(ActionType.OnUse, item.WorldPosition); + dockingTarget.item.Submarine.DockedTo.Remove(item.Submarine); + item.Submarine.DockedTo.Remove(dockingTarget.item.Submarine); + + item.linkedTo.Clear(); + docked = false; dockingTarget.Undock(); @@ -432,6 +443,17 @@ namespace Barotrauma.Items.Components list.Remove(this); } + public override void OnMapLoaded() + { + if (!item.linkedTo.Any()) return; + + Item linkedItem = item.linkedTo.First() as Item; + if (linkedItem == null) return; + + DockingPort port = linkedItem.GetComponent(); + if (port != null) Dock(port); + } + public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item sender, float power = 0.0f) { switch (connection.Name) diff --git a/Subsurface/Source/Map/LinkedSubmarine.cs b/Subsurface/Source/Map/LinkedSubmarine.cs new file mode 100644 index 000000000..9488dc9d1 --- /dev/null +++ b/Subsurface/Source/Map/LinkedSubmarine.cs @@ -0,0 +1,308 @@ +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Barotrauma +{ + + class LinkedSubmarinePrefab : MapEntityPrefab + { + public readonly Submarine mainSub; + + public LinkedSubmarinePrefab(Submarine submarine) + { + this.mainSub = submarine; + } + + protected override void CreateInstance(Rectangle rect) + { + System.Diagnostics.Debug.Assert(Submarine.MainSub != null); + + LinkedSubmarine.Create(Submarine.MainSub, mainSub.FilePath, rect.Location.ToVector2()); + } + } + + class LinkedSubmarine : MapEntity + { + private List wallVertices; + + private string filePath; + + private Submarine sub; + + private XElement saveElement; + + public override bool IsLinkable + { + get + { + return true; + } + } + + public LinkedSubmarine(Submarine submarine) + : base(null, submarine) + { + linkedTo = new System.Collections.ObjectModel.ObservableCollection(); + linkedToID = new List(); + + InsertToList(); + } + + public static LinkedSubmarine Create(Submarine mainSub, string filePath, Vector2 position) + { + LinkedSubmarine sl = new LinkedSubmarine(mainSub); + sl.filePath = filePath; + + XDocument doc = Submarine.OpenFile(filePath); + if (doc == null || doc.Root == null) return null; + + sl.GenerateWallVertices(doc.Root); + + //for (int i = 0; i < sl.wallVertices.Count; i++) + //{ + // sl.wallVertices[i] = sl.wallVertices[i] += position; + //} + + sl.Rect = new Rectangle( + (int)sl.wallVertices.Min(v => v.X + position.X), + (int)sl.wallVertices.Max(v => v.Y + position.Y), + (int)sl.wallVertices.Max(v => v.X + position.X), + (int)sl.wallVertices.Min(v => v.Y + position.Y)); + + sl.rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height); + + return sl; + } + + public override bool IsMouseOn(Vector2 position) + { + return Vector2.Distance(position, WorldPosition) < 50.0f; + } + + public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) + { + if (!editing || wallVertices == null) return; + + Color color = (isHighlighted) ? Color.Orange : Color.Green; + if (isSelected) color = Color.Red; + + Vector2 pos = new Vector2(rect.X + rect.Width/2, rect.Y - rect.Height/2); + + for (int i = 0; i < wallVertices.Count; i++) + { + Vector2 startPos = wallVertices[i] + pos; + startPos.Y = -startPos.Y; + + Vector2 endPos = wallVertices[(i + 1) % wallVertices.Count] + pos; + endPos.Y = -endPos.Y; + + GUI.DrawLine(spriteBatch, + startPos, + endPos, + color, 0.0f, 5); + } + + pos.Y = -pos.Y; + GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5); + GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5); + + + foreach (MapEntity e in linkedTo) + { + GUI.DrawLine(spriteBatch, + new Vector2(WorldPosition.X, -WorldPosition.Y), + new Vector2(e.WorldPosition.X, -e.WorldPosition.Y), + Color.Red * 0.3f); + } + } + + public override void DrawEditing(SpriteBatch spriteBatch, Camera cam) + { + if (editingHUD == null || editingHUD.UserData as LinkedSubmarine != this) + { + editingHUD = CreateEditingHUD(); + } + + editingHUD.Draw(spriteBatch); + editingHUD.Update((float)Physics.step); + + if (!PlayerInput.LeftButtonClicked() || !PlayerInput.KeyDown(Keys.Space)) return; + + Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); + + foreach (MapEntity entity in mapEntityList) + { + if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) continue; + if (((Item)entity).GetComponent() == null) continue; + if (linkedTo.Contains(entity)) + { + linkedTo.Remove(entity); + } + else + { + linkedTo.Add(entity); + } + } + } + + + private GUIComponent CreateEditingHUD(bool inGame = false) + { + int width = 450; + int x = GameMain.GraphicsWidth / 2 - width / 2, y = 10; + + editingHUD = new GUIFrame(new Rectangle(x, y, width, 100), GUI.Style); + editingHUD.Padding = new Vector4(10, 10, 0, 0); + editingHUD.UserData = this; + + new GUITextBlock(new Rectangle(0, 0, 100, 20), "Linked submarine", GUI.Style, + Alignment.TopLeft, Alignment.TopLeft, editingHUD, false, GUI.LargeFont); + + y += 20; + + if (!inGame) + { + new GUITextBlock(new Rectangle(0, 0, 0, 20), "Hold space to link to a docking port", + GUI.Style, Alignment.TopRight, Alignment.TopRight, editingHUD).Font = GUI.SmallFont; + y += 25; + + } + return editingHUD; + } + + + private void GenerateWallVertices(XElement rootElement) + { + List points = new List(); + + var wallPrefabs = + MapEntityPrefab.list.FindAll(mp => (mp is StructurePrefab) && ((StructurePrefab)mp).HasBody); + + foreach (XElement element in rootElement.Elements()) + { + if (element.Name != "Structure") continue; + + string name = ToolBox.GetAttributeString(element, "name", ""); + if (!wallPrefabs.Any(wp => wp.Name == name)) continue; + + var rect = ToolBox.GetAttributeVector4(element, "rect", Vector4.Zero); + + points.Add(new Vector2(rect.X, rect.Y)); + points.Add(new Vector2(rect.X + rect.Z, rect.Y)); + points.Add(new Vector2(rect.X, rect.Y - rect.W)); + points.Add(new Vector2(rect.X + rect.Z, rect.Y - rect.W)); + } + + wallVertices = MathUtils.GiftWrap(points); + } + + public override XElement Save(XElement parentElement) + { + XElement saveElement = null; + + if (sub == null) + { + var doc = Submarine.OpenFile(filePath); + saveElement = doc.Root; + + saveElement.Name = "LinkedSubmarine"; + + saveElement.Add(new XAttribute("filepath", filePath)); + + var linkedPort = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent() != null); + if (linkedPort != null) + { + saveElement.Add(new XAttribute("linkedto", linkedPort.ID)); + } + } + else + { + saveElement = new XElement("LinkedSubmarine"); + + sub.SaveToXElement(saveElement); + } + + saveElement.Add(new XAttribute("pos", ToolBox.Vector2ToString(Position - Submarine.HiddenSubPosition))); + + parentElement.Add(saveElement); + + return saveElement; + } + + public static void Load(XElement element, Submarine submarine) + { + Vector2 pos = ToolBox.GetAttributeVector2(element, "pos", Vector2.Zero); + + LinkedSubmarine linkedSub = null; + + if (Screen.Selected == GameMain.EditMapScreen) + { + string filePath = ToolBox.GetAttributeString(element, "filepath", ""); + + linkedSub = Create(submarine, filePath, pos); + } + else + { + linkedSub = new LinkedSubmarine(submarine); + linkedSub.saveElement = element; + + linkedSub.rect.Location = pos.ToPoint(); + } + + string linkedToString = ToolBox.GetAttributeString(element, "linkedto", ""); + if (linkedToString != "") + { + string[] linkedToIds = linkedToString.Split(','); + for (int i = 0; i < linkedToIds.Length; i++) + { + linkedSub.linkedToID.Add((ushort)int.Parse(linkedToIds[i])); + } + } + + } + + public override void OnMapLoaded() + { + if (saveElement == null) return; + + sub = Submarine.Load(saveElement, false); + sub.SetPosition(WorldPosition - Submarine.WorldPosition); + sub.Submarine = Submarine; + + var linkedItem = linkedTo.FirstOrDefault(lt => (lt is Item) && ((Item)lt).GetComponent() != null); + + if (linkedItem != null) + { + var linkedPort = ((Item)linkedItem).GetComponent(); + + DockingPort myPort = null; + float closestDistance = 0.0f; + + foreach (DockingPort port in DockingPort.list) + { + if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) continue; + + float dist = Vector2.Distance(port.Item.WorldPosition, linkedPort.Item.WorldPosition); + if (myPort == null || dist < closestDistance) + { + myPort = port; + closestDistance = dist; + } + } + + if (myPort != null) + { + myPort.Dock(linkedPort); + } + } + } + } +} diff --git a/Subsurface/Source/Map/MapEntity.cs b/Subsurface/Source/Map/MapEntity.cs index 958655ce0..f079fb7d9 100644 --- a/Subsurface/Source/Map/MapEntity.cs +++ b/Subsurface/Source/Map/MapEntity.cs @@ -620,7 +620,6 @@ namespace Barotrauma foreach (ushort i in e.linkedToID) { MapEntity linked = FindEntityByID(i) as MapEntity; - Debug.Assert(linked.Submarine == sub); if (linked != null) e.linkedTo.Add(linked); } diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index 09f5b97c5..cc0d87b10 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -46,10 +46,12 @@ namespace Barotrauma private SubmarineBody subBody; + public readonly List DockedTo; + private static Vector2 lastPickedPosition; private static float lastPickedFraction; - Md5Hash hash; + private Md5Hash hash; private string filePath; private string name; @@ -57,8 +59,7 @@ namespace Barotrauma private Vector2 prevPosition; private float lastNetworkUpdate; - - + //properties ---------------------------------------------------- public string Name @@ -231,6 +232,8 @@ namespace Barotrauma } } + DockedTo = new List(); + ID = ushort.MaxValue; base.Remove(); @@ -538,14 +541,7 @@ namespace Barotrauma name = System.IO.Path.GetFileNameWithoutExtension(filePath); XDocument doc = new XDocument(new XElement("Submarine")); - doc.Root.Add(new XAttribute("name", name)); - doc.Root.Add(new XAttribute("description", Description == null ? "" : Description)); - - foreach (MapEntity e in MapEntity.mapEntityList) - { - if (e.MoveWithLevel ||e.Submarine != this) continue; - e.Save(doc.Root); - } + SaveToXElement(doc.Root); hash = new Md5Hash(doc); doc.Root.Add(new XAttribute("md5hash", hash.Hash)); @@ -563,6 +559,18 @@ namespace Barotrauma return true; } + public void SaveToXElement(XElement element) + { + element.Add(new XAttribute("name", name)); + element.Add(new XAttribute("description", Description == null ? "" : Description)); + + foreach (MapEntity e in MapEntity.mapEntityList) + { + if (e.MoveWithLevel || e.Submarine != this) continue; + e.Save(element); + } + } + public static bool SaveCurrent(string filePath) { if (Submarine.MainSub == null) @@ -853,7 +861,7 @@ namespace Barotrauma GameMain.LightManager.OnMapLoaded(); - ID = (ushort)(ushort.MaxValue - Submarine.loaded.Count); + ID = (ushort)(ushort.MaxValue - Submarine.loaded.IndexOf(this)); } public static Submarine Load(XElement element, bool unloadPrevious) @@ -862,7 +870,7 @@ namespace Barotrauma //tryload -> false - Submarine sub = new Submarine(ToolBox.GetAttributeString(element, "name", "")); + Submarine sub = new Submarine(ToolBox.GetAttributeString(element, "name", ""), "", false); sub.Load(unloadPrevious, element); return sub; @@ -898,27 +906,29 @@ namespace Barotrauma foreach (Submarine sub in loaded) { sub.Remove(); - sub.Clear(); } loaded.Clear(); - } - private void Clear() - { if (GameMain.GameScreen.Cam != null) GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; Entity.RemoveAll(); - - subBody = null; - - PhysicsBody.list.Clear(); + PhysicsBody.list.Clear(); + Ragdoll.list.Clear(); GameMain.World.Clear(); } + public override void Remove() + { + base.Remove(); + + subBody = null; + + DockedTo.Clear(); + } } } diff --git a/Subsurface/Source/Map/SubmarineLink.cs b/Subsurface/Source/Map/SubmarineLink.cs deleted file mode 100644 index 68f671656..000000000 --- a/Subsurface/Source/Map/SubmarineLink.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace Barotrauma -{ - - class LinkedSubmarinePrefab : MapEntityPrefab - { - public readonly Submarine mainSub; - - public LinkedSubmarinePrefab(Submarine submarine) - { - this.mainSub = submarine; - } - - protected override void CreateInstance(Rectangle rect) - { - System.Diagnostics.Debug.Assert(Submarine.MainSub != null); - - LinkedSubmarine.Create(Submarine.MainSub, mainSub.FilePath, rect.Location.ToVector2()); - } - } - - class LinkedSubmarine : MapEntity - { - private List wallVertices; - - private string filePath; - - private XElement saveElement; - - public LinkedSubmarine(Submarine submarine) - : base(null, submarine) - { - InsertToList(); - } - - public static LinkedSubmarine Create(Submarine mainSub, string filePath, Vector2 position) - { - LinkedSubmarine sl = new LinkedSubmarine(mainSub); - sl.filePath = filePath; - - XDocument doc = Submarine.OpenFile(filePath); - if (doc == null || doc.Root == null) return null; - - sl.GenerateWallVertices(doc.Root); - - //for (int i = 0; i < sl.wallVertices.Count; i++) - //{ - // sl.wallVertices[i] = sl.wallVertices[i] += position; - //} - - sl.Rect = new Rectangle( - (int)sl.wallVertices.Min(v => v.X + position.X), - (int)sl.wallVertices.Max(v => v.Y + position.Y), - (int)sl.wallVertices.Max(v => v.X + position.X), - (int)sl.wallVertices.Min(v => v.Y + position.Y)); - - sl.rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height); - - return sl; - } - - public override bool IsMouseOn(Vector2 position) - { - return Vector2.Distance(position, WorldPosition) < 50.0f; - } - - public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) - { - if (!editing || wallVertices == null) return; - - Color color = (isHighlighted) ? Color.Orange : Color.Green; - if (isSelected) color = Color.Red; - - Vector2 pos = new Vector2(rect.X + rect.Width/2, rect.Y - rect.Height/2); - - for (int i = 0; i < wallVertices.Count; i++) - { - Vector2 startPos = wallVertices[i] + pos; - startPos.Y = -startPos.Y; - - Vector2 endPos = wallVertices[(i + 1) % wallVertices.Count] + pos; - endPos.Y = -endPos.Y; - - GUI.DrawLine(spriteBatch, - startPos, - endPos, - color, 0.0f, 5); - } - - pos.Y = -pos.Y; - GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5); - GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5); - - } - - private void GenerateWallVertices(XElement rootElement) - { - List points = new List(); - - var wallPrefabs = - MapEntityPrefab.list.FindAll(mp => (mp is StructurePrefab) && ((StructurePrefab)mp).HasBody); - - foreach (XElement element in rootElement.Elements()) - { - if (element.Name != "Structure") continue; - - string name = ToolBox.GetAttributeString(element, "name", ""); - if (!wallPrefabs.Any(wp => wp.Name == name)) continue; - - var rect = ToolBox.GetAttributeVector4(element, "rect", Vector4.Zero); - - points.Add(new Vector2(rect.X, rect.Y)); - points.Add(new Vector2(rect.X + rect.Z, rect.Y)); - points.Add(new Vector2(rect.X, rect.Y - rect.W)); - points.Add(new Vector2(rect.X + rect.Z, rect.Y - rect.W)); - } - - wallVertices = MathUtils.GiftWrap(points); - } - - public override XElement Save(XElement parentElement) - { - var doc = Submarine.OpenFile(filePath); - - doc.Root.Name = "LinkedSubmarine"; - - doc.Root.Add( - new XAttribute("filepath", filePath), - new XAttribute("pos", ToolBox.Vector2ToString(Position - Submarine.HiddenSubPosition))); - - parentElement.Add(doc.Root); - - return doc.Root; - } - - public static void Load(XElement element, Submarine submarine) - { - Vector2 pos = ToolBox.GetAttributeVector2(element, "pos", Vector2.Zero); - - if (Screen.Selected == GameMain.EditMapScreen) - { - string filePath = ToolBox.GetAttributeString(element, "filepath", ""); - - Create(submarine, filePath, pos); - - return; - } - - var ls = new LinkedSubmarine(submarine); - ls.saveElement = element; - - ls.rect.Location = pos.ToPoint(); - } - - public override void OnMapLoaded() - { - if (saveElement == null) return; - var sub = Submarine.Load(saveElement, false); - sub.SetPosition(WorldPosition - Submarine.WorldPosition); - sub.Submarine = Submarine; - } - } -} diff --git a/Subsurface/Source/Utils/SaveUtil.cs b/Subsurface/Source/Utils/SaveUtil.cs index cf4f714d1..154857dae 100644 --- a/Subsurface/Source/Utils/SaveUtil.cs +++ b/Subsurface/Source/Utils/SaveUtil.cs @@ -23,12 +23,13 @@ namespace Barotrauma string tempPath = Path.Combine(SaveFolder, "temp"); - if (!Directory.Exists(tempPath)) + if (Directory.Exists(tempPath)) { - Directory.CreateDirectory(tempPath); + Directory.Delete(tempPath, true); } + Directory.CreateDirectory(tempPath); - try + try { if (Submarine.MainSub != null) {