diff --git a/BarotraumaClient/BarotraumaClient.csproj b/BarotraumaClient/BarotraumaClient.csproj
index 51cad3291..6e8bfaaec 100644
--- a/BarotraumaClient/BarotraumaClient.csproj
+++ b/BarotraumaClient/BarotraumaClient.csproj
@@ -64,7 +64,11 @@
+
+
+
+
@@ -73,6 +77,7 @@
+
@@ -80,10 +85,13 @@
+
+
+
@@ -125,33 +133,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -161,6 +181,7 @@
+
@@ -180,6 +201,7 @@
+
diff --git a/BarotraumaClient/Source/Characters/AI/AITarget.cs b/BarotraumaClient/Source/Characters/AI/AITarget.cs
new file mode 100644
index 000000000..05721d77b
--- /dev/null
+++ b/BarotraumaClient/Source/Characters/AI/AITarget.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Barotrauma
+{
+ partial class AITarget
+ {
+ public static bool ShowAITargets;
+
+ public void Draw(SpriteBatch spriteBatch)
+ {
+ if (!ShowAITargets) return;
+
+ var rangeSprite = GUI.SubmarineIcon;
+
+ if (soundRange > 0.0f)
+ rangeSprite.Draw(spriteBatch,
+ new Vector2(WorldPosition.X, -WorldPosition.Y),
+ Color.Cyan * 0.1f, rangeSprite.Origin,
+ 0.0f, soundRange / rangeSprite.size.X);
+
+ if (sightRange > 0.0f)
+ rangeSprite.Draw(spriteBatch,
+ new Vector2(WorldPosition.X, -WorldPosition.Y),
+ Color.Orange * 0.1f, rangeSprite.Origin,
+ 0.0f, sightRange / rangeSprite.size.X);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs b/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs
new file mode 100644
index 000000000..451477959
--- /dev/null
+++ b/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Xml.Linq;
+using FarseerPhysics;
+using Lidgren.Network;
+using Microsoft.Xna.Framework;
+using FarseerPhysics.Dynamics;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Barotrauma
+{
+ partial class EnemyAIController : AIController
+ {
+ public override void DebugDraw(SpriteBatch spriteBatch)
+ {
+ if (Character.IsDead) return;
+
+ Vector2 pos = Character.WorldPosition;
+ pos.Y = -pos.Y;
+
+ if (selectedAiTarget != null)
+ {
+ GUI.DrawLine(spriteBatch, pos, new Vector2(selectedAiTarget.WorldPosition.X, -selectedAiTarget.WorldPosition.Y), Color.Red);
+
+ if (wallAttackPos != Vector2.Zero)
+ {
+ GUI.DrawRectangle(spriteBatch, ConvertUnits.ToDisplayUnits(new Vector2(wallAttackPos.X, -wallAttackPos.Y)) - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false);
+ }
+
+ GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 20.0f, Color.Red);
+ }
+
+ if (selectedAiTarget != null)
+ {
+ GUI.DrawLine(spriteBatch,
+ new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
+ new Vector2(selectedAiTarget.WorldPosition.X, -selectedAiTarget.WorldPosition.Y), Color.Red);
+ }
+
+ GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red);
+
+ GUI.Font.DrawString(spriteBatch, "updatetargets: " + updateTargetsTimer, pos - Vector2.UnitY * 100.0f, Color.Red);
+ GUI.Font.DrawString(spriteBatch, "cooldown: " + coolDownTimer, pos - Vector2.UnitY * 120.0f, Color.Red);
+
+
+ IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager;
+ if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return;
+
+ GUI.DrawLine(spriteBatch,
+ new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
+ new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y),
+ Color.LightGreen);
+
+
+ for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++)
+ {
+ GUI.DrawLine(spriteBatch,
+ new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y),
+ new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y),
+ Color.LightGreen);
+
+ GUI.SmallFont.DrawString(spriteBatch,
+ pathSteering.CurrentPath.Nodes[i].ID.ToString(),
+ new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10),
+ Color.LightGreen);
+ }
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/BarotraumaClient/Source/Characters/AI/HumanAIController.cs
new file mode 100644
index 000000000..4daacaff6
--- /dev/null
+++ b/BarotraumaClient/Source/Characters/AI/HumanAIController.cs
@@ -0,0 +1,40 @@
+using Microsoft.Xna.Framework;
+using System;
+
+namespace Barotrauma
+{
+ partial class HumanAIController : AIController
+ {
+ public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
+ {
+ if (selectedAiTarget != null)
+ {
+ GUI.DrawLine(spriteBatch,
+ new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
+ new Vector2(selectedAiTarget.WorldPosition.X, -selectedAiTarget.WorldPosition.Y), Color.Red);
+ }
+
+ IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager;
+ if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return;
+
+ GUI.DrawLine(spriteBatch,
+ new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
+ new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y),
+ Color.LightGreen);
+
+
+ for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++)
+ {
+ GUI.DrawLine(spriteBatch,
+ new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y),
+ new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y),
+ Color.LightGreen);
+
+ GUI.SmallFont.DrawString(spriteBatch,
+ pathSteering.CurrentPath.Nodes[i].ID.ToString(),
+ new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10),
+ Color.LightGreen);
+ }
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs
new file mode 100644
index 000000000..a5e45a18f
--- /dev/null
+++ b/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using FarseerPhysics;
+using FarseerPhysics.Dynamics;
+using FarseerPhysics.Dynamics.Contacts;
+using FarseerPhysics.Dynamics.Joints;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Barotrauma
+{
+ partial class Ragdoll
+ {
+ public virtual void Draw(SpriteBatch spriteBatch)
+ {
+ if (simplePhysicsEnabled) return;
+
+ Collider.UpdateDrawPosition();
+
+ foreach (Limb limb in Limbs)
+ {
+ limb.Draw(spriteBatch);
+ }
+ }
+
+ public void DebugDraw(SpriteBatch spriteBatch)
+ {
+ if (!GameMain.DebugDraw || !character.Enabled) return;
+ if (simplePhysicsEnabled) return;
+
+ foreach (Limb limb in Limbs)
+ {
+
+ if (limb.pullJoint != null)
+ {
+ Vector2 pos = ConvertUnits.ToDisplayUnits(limb.pullJoint.WorldAnchorA);
+ if (currentHull != null) pos += currentHull.Submarine.DrawPosition;
+ pos.Y = -pos.Y;
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Red, true, 0.01f);
+ }
+
+ limb.body.DebugDraw(spriteBatch, inWater ? Color.Cyan : Color.White);
+ }
+
+ Collider.DebugDraw(spriteBatch, frozen ? Color.Red : (inWater ? Color.SkyBlue : Color.Gray));
+ GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.ToString(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange);
+
+ foreach (RevoluteJoint joint in limbJoints)
+ {
+ Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA);
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
+
+ pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
+ }
+
+ foreach (Limb limb in Limbs)
+ {
+ if (limb.body.TargetPosition != null)
+ {
+ Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)limb.body.TargetPosition);
+ if (currentHull != null) pos += currentHull.Submarine.DrawPosition;
+ pos.Y = -pos.Y;
+
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 10, (int)pos.Y - 10, 20, 20), Color.Cyan, false, 0.01f);
+ GUI.DrawLine(spriteBatch, pos, new Vector2(limb.WorldPosition.X, -limb.WorldPosition.Y), Color.Cyan);
+ }
+ }
+
+ if (character.MemState.Count > 1)
+ {
+ Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemState[0].Position);
+ if (currentHull != null) prevPos += currentHull.Submarine.DrawPosition;
+ prevPos.Y = -prevPos.Y;
+
+ for (int i = 1; i < character.MemState.Count; i++)
+ {
+ Vector2 currPos = ConvertUnits.ToDisplayUnits(character.MemState[i].Position);
+ if (currentHull != null) currPos += currentHull.Submarine.DrawPosition;
+ currPos.Y = -currPos.Y;
+
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 3, (int)currPos.Y - 3, 6, 6), Color.Cyan * 0.6f, true, 0.01f);
+ GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.6f, 0, 3);
+
+ prevPos = currPos;
+ }
+ }
+
+ if (ignorePlatforms)
+ {
+ GUI.DrawLine(spriteBatch,
+ new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y),
+ new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y + 50),
+ Color.Orange, 0, 5);
+ }
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/BarotraumaClient/Source/Characters/CharacterNetworking.cs
new file mode 100644
index 000000000..7c4ae474e
--- /dev/null
+++ b/BarotraumaClient/Source/Characters/CharacterNetworking.cs
@@ -0,0 +1,294 @@
+using Barotrauma.Networking;
+using Lidgren.Network;
+using Microsoft.Xna.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Barotrauma
+{
+ partial class Character
+ {
+ public virtual void ClientWrite(NetBuffer msg, object[] extraData = null)
+ {
+ if (GameMain.Server != null) return;
+
+ if (extraData != null)
+ {
+ switch ((NetEntityEvent.Type)extraData[0])
+ {
+ case NetEntityEvent.Type.InventoryState:
+ msg.WriteRangedInteger(0, 2, 0);
+ inventory.ClientWrite(msg, extraData);
+ break;
+ case NetEntityEvent.Type.Repair:
+ msg.WriteRangedInteger(0, 2, 1);
+ msg.Write(AnimController.Anim == AnimController.Animation.CPR);
+ break;
+ case NetEntityEvent.Type.Status:
+ msg.WriteRangedInteger(0, 2, 2);
+ break;
+ }
+ msg.WritePadBits();
+ }
+ else
+ {
+ msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
+
+ if (memInput.Count > 60)
+ {
+ memInput.RemoveRange(60, memInput.Count - 60);
+ }
+
+ msg.Write(LastNetworkUpdateID);
+ byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
+ msg.Write(inputCount);
+ for (int i = 0; i < inputCount; i++)
+ {
+ msg.WriteRangedInteger(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
+ if (memInput[i].states.HasFlag(InputNetFlags.Aim))
+ {
+ msg.Write(memInput[i].intAim);
+ }
+ if (memInput[i].states.HasFlag(InputNetFlags.Select) || memInput[i].states.HasFlag(InputNetFlags.Use))
+ {
+ msg.Write(memInput[i].interact);
+ }
+ }
+ }
+ }
+
+ public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ {
+ if (GameMain.Server != null) return;
+
+ switch (type)
+ {
+ case ServerNetObject.ENTITY_POSITION:
+ bool facingRight = AnimController.Dir > 0.0f;
+
+ lastRecvPositionUpdateTime = (float)NetTime.Now;
+
+ AnimController.Frozen = false;
+ Enabled = true;
+
+ UInt16 networkUpdateID = 0;
+ if (msg.ReadBoolean())
+ {
+ networkUpdateID = msg.ReadUInt16();
+ }
+ else
+ {
+ bool aimInput = msg.ReadBoolean();
+ keys[(int)InputType.Aim].Held = aimInput;
+ keys[(int)InputType.Aim].SetState(false, aimInput);
+
+ bool useInput = msg.ReadBoolean();
+ keys[(int)InputType.Use].Held = useInput;
+ keys[(int)InputType.Use].SetState(false, useInput);
+
+ bool hasAttackLimb = msg.ReadBoolean();
+ if (hasAttackLimb)
+ {
+ bool attackInput = msg.ReadBoolean();
+ keys[(int)InputType.Attack].Held = attackInput;
+ keys[(int)InputType.Attack].SetState(false, attackInput);
+ }
+
+ if (aimInput)
+ {
+ double aimAngle = ((double)msg.ReadUInt16() / 65535.0) * 2.0 * Math.PI;
+ cursorPosition = (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position)
+ + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;
+
+ TransformCursorPos();
+ }
+ facingRight = msg.ReadBoolean();
+ }
+
+ bool entitySelected = msg.ReadBoolean();
+ Entity selectedEntity = null;
+
+ AnimController.Animation animation = AnimController.Animation.None;
+ if (entitySelected)
+ {
+ ushort entityID = msg.ReadUInt16();
+ selectedEntity = FindEntityByID(entityID);
+ if (selectedEntity is Character)
+ {
+ bool doingCpr = msg.ReadBoolean();
+ if (doingCpr && selectedCharacter != null)
+ {
+ animation = AnimController.Animation.CPR;
+ }
+ }
+ }
+
+ Vector2 pos = new Vector2(
+ msg.ReadFloat(),
+ msg.ReadFloat());
+
+
+ int index = 0;
+ if (GameMain.NetworkMember.Character == this && AllowInput)
+ {
+ var posInfo = new CharacterStateInfo(pos, networkUpdateID, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
+ while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
+ index++;
+
+ memState.Insert(index, posInfo);
+ }
+ else
+ {
+ var posInfo = new CharacterStateInfo(pos, sendingTime, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
+ while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
+ index++;
+
+ memState.Insert(index, posInfo);
+ }
+
+ break;
+ case ServerNetObject.ENTITY_EVENT:
+
+ int eventType = msg.ReadRangedInteger(0, 2);
+ switch (eventType)
+ {
+ case 0:
+ inventory.ClientRead(type, msg, sendingTime);
+ break;
+ case 1:
+ byte ownerID = msg.ReadByte();
+ ResetNetState();
+ if (ownerID == GameMain.Client.ID)
+ {
+ if (controlled != null)
+ {
+ LastNetworkUpdateID = controlled.LastNetworkUpdateID;
+ }
+
+ controlled = this;
+ IsRemotePlayer = false;
+ GameMain.Client.Character = this;
+ }
+ else if (controlled == this)
+ {
+ controlled = null;
+ IsRemotePlayer = ownerID > 0;
+ }
+ break;
+ case 2:
+ ReadStatus(msg);
+ break;
+ }
+
+ break;
+ }
+ }
+ public static Character ReadSpawnData(NetBuffer inc, bool spawn = true)
+ {
+ if (GameMain.Server != null) return null;
+
+ bool noInfo = inc.ReadBoolean();
+ ushort id = inc.ReadUInt16();
+ string configPath = inc.ReadString();
+
+ Vector2 position = new Vector2(inc.ReadFloat(), inc.ReadFloat());
+
+ bool enabled = inc.ReadBoolean();
+
+ DebugConsole.Log("Received spawn data for " + configPath);
+
+ Character character = null;
+ if (noInfo)
+ {
+ if (!spawn) return null;
+
+ character = Character.Create(configPath, position, null, true);
+ character.ID = id;
+ }
+ else
+ {
+ bool hasOwner = inc.ReadBoolean();
+ int ownerId = hasOwner ? inc.ReadByte() : -1;
+
+
+ string newName = inc.ReadString();
+ byte teamID = inc.ReadByte();
+
+ bool hasAi = inc.ReadBoolean();
+ bool isFemale = inc.ReadBoolean();
+ int headSpriteID = inc.ReadByte();
+ string jobName = inc.ReadString();
+
+ JobPrefab jobPrefab = null;
+ Dictionary skillLevels = new Dictionary();
+ if (!string.IsNullOrEmpty(jobName))
+ {
+ jobPrefab = JobPrefab.List.Find(jp => jp.Name == jobName);
+ int skillCount = inc.ReadByte();
+ for (int i = 0; i < skillCount; i++)
+ {
+ string skillName = inc.ReadString();
+ int skillLevel = inc.ReadRangedInteger(0, 100);
+
+ skillLevels.Add(skillName, skillLevel);
+ }
+ }
+
+ if (!spawn) return null;
+
+
+ CharacterInfo ch = new CharacterInfo(configPath, newName, isFemale ? Gender.Female : Gender.Male, jobPrefab);
+ ch.HeadSpriteId = headSpriteID;
+
+ System.Diagnostics.Debug.Assert(skillLevels.Count == ch.Job.Skills.Count);
+ if (ch.Job != null)
+ {
+ foreach (KeyValuePair skill in skillLevels)
+ {
+ Skill matchingSkill = ch.Job.Skills.Find(s => s.Name == skill.Key);
+ if (matchingSkill == null)
+ {
+ DebugConsole.ThrowError("Skill \"" + skill.Key + "\" not found in character \"" + newName + "\"");
+ continue;
+ }
+ matchingSkill.Level = skill.Value;
+ }
+ }
+
+ character = Create(configPath, position, ch, GameMain.Client.ID != ownerId, hasAi);
+ character.ID = id;
+ character.TeamID = teamID;
+
+ if (GameMain.Client.ID == ownerId)
+ {
+ GameMain.Client.Character = character;
+ Controlled = character;
+
+ GameMain.LightManager.LosEnabled = true;
+
+ character.memInput.Clear();
+ character.memState.Clear();
+ character.memLocalState.Clear();
+ }
+ else
+ {
+ var ownerClient = GameMain.Client.ConnectedClients.Find(c => c.ID == ownerId);
+ if (ownerClient != null)
+ {
+ ownerClient.Character = character;
+ }
+ }
+
+ if (configPath == Character.HumanConfigFile)
+ {
+ GameMain.GameSession.CrewManager.characters.Add(character);
+ }
+ }
+
+ character.Enabled = Controlled == character || enabled;
+
+ return character;
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Events/Missions/Mission.cs b/BarotraumaClient/Source/Events/Missions/Mission.cs
new file mode 100644
index 000000000..834118cd1
--- /dev/null
+++ b/BarotraumaClient/Source/Events/Missions/Mission.cs
@@ -0,0 +1,25 @@
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Xml.Linq;
+
+namespace Barotrauma
+{
+ partial class Mission
+ {
+ public void ShowMessage(int index)
+ {
+ if (index >= headers.Count && index >= messages.Count) return;
+
+ string header = index < headers.Count ? headers[index] : "";
+ string message = index < messages.Count ? messages[index] : "";
+
+ GameServer.Log("Mission info: " + header + " - " + message, ServerLog.MessageType.ServerMessage);
+
+ new GUIMessageBox(header, message);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Events/Missions/MissionMode.cs b/BarotraumaClient/Source/Events/Missions/MissionMode.cs
new file mode 100644
index 000000000..05387054d
--- /dev/null
+++ b/BarotraumaClient/Source/Events/Missions/MissionMode.cs
@@ -0,0 +1,18 @@
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma
+{
+ partial class MissionMode : GameMode
+ {
+ public override void MsgBox()
+ {
+ if (mission == null) return;
+
+ var missionMsg = new GUIMessageBox(mission.Name, mission.Description, 400, 400);
+ missionMsg.UserData = "missionstartmessage";
+
+ Networking.GameServer.Log("Mission: " + mission.Name, Networking.ServerLog.MessageType.ServerMessage);
+ Networking.GameServer.Log(mission.Description, Networking.ServerLog.MessageType.ServerMessage);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/GameSession/GameModes/TraitorManager.cs b/BarotraumaClient/Source/GameSession/GameModes/TraitorManager.cs
new file mode 100644
index 000000000..c0ad48d03
--- /dev/null
+++ b/BarotraumaClient/Source/GameSession/GameModes/TraitorManager.cs
@@ -0,0 +1,15 @@
+using Barotrauma.Networking;
+using System.Collections.Generic;
+
+namespace Barotrauma
+{
+ partial class TraitorManager
+ {
+ public static void CreateStartPopUp(string targetName)
+ {
+ new GUIMessageBox("You are the Traitor!",
+ "Your secret task is to assassinate " + targetName + "! Discretion is an utmost concern; sinking the submarine and killing the entire crew "
+ + "will arouse suspicion amongst the Fleet. If possible, make the death look like an accident.", 400, 350);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Items/CharacterInventory.cs b/BarotraumaClient/Source/Items/CharacterInventory.cs
index c509c3201..101eb54c1 100644
--- a/BarotraumaClient/Source/Items/CharacterInventory.cs
+++ b/BarotraumaClient/Source/Items/CharacterInventory.cs
@@ -78,5 +78,90 @@ namespace Barotrauma
return UseItemOnSelf(slotIndex);
}
+
+ protected override void CreateSlots()
+ {
+ if (slots == null) slots = new InventorySlot[capacity];
+
+ int rectWidth = 40, rectHeight = 40;
+
+ Rectangle slotRect = new Rectangle(0, 0, rectWidth, rectHeight);
+ for (int i = 0; i < capacity; i++)
+ {
+ if (slots[i] == null) slots[i] = new InventorySlot(slotRect);
+
+ slots[i].Disabled = false;
+
+ slotRect.X = (int)(SlotPositions[i].X + DrawOffset.X);
+ slotRect.Y = (int)(SlotPositions[i].Y + DrawOffset.Y);
+
+ slots[i].Rect = slotRect;
+
+ slots[i].Color = limbSlots[i] == InvSlotType.Any ? Color.White * 0.2f : Color.White * 0.4f;
+ }
+
+ MergeSlots();
+ }
+
+ public void DrawOwn(SpriteBatch spriteBatch)
+ {
+ if (slots == null) CreateSlots();
+
+ Rectangle slotRect = new Rectangle(0, 0, 40, 40);
+
+ for (int i = 0; i < capacity; i++)
+ {
+ slotRect.X = (int)(SlotPositions[i].X + DrawOffset.X);
+ slotRect.Y = (int)(SlotPositions[i].Y + DrawOffset.Y);
+
+ if (i == 1) //head
+ {
+ spriteBatch.Draw(icons, new Vector2(slotRect.Center.X, slotRect.Center.Y),
+ new Rectangle(0, 0, 56, 128), Color.White * 0.7f, 0.0f,
+ new Vector2(28.0f, 64.0f), Vector2.One,
+ SpriteEffects.None, 0.1f);
+ }
+ else if (i == 3 || i == 4)
+ {
+ spriteBatch.Draw(icons, new Vector2(slotRect.Center.X, slotRect.Center.Y),
+ new Rectangle(92, 41 * (4 - i), 36, 40), Color.White * 0.7f, 0.0f,
+ new Vector2(18.0f, 20.0f), Vector2.One,
+ SpriteEffects.None, 0.1f);
+ }
+ else if (i == 5)
+ {
+ spriteBatch.Draw(icons, new Vector2(slotRect.Center.X, slotRect.Center.Y),
+ new Rectangle(57, 0, 31, 32), Color.White * 0.7f, 0.0f,
+ new Vector2(15.0f, 16.0f), Vector2.One,
+ SpriteEffects.None, 0.1f);
+ }
+ }
+
+ base.Draw(spriteBatch);
+
+ if (character == Character.Controlled)
+ {
+ for (int i = 0; i < capacity; i++)
+ {
+ if (selectedSlot != i &&
+ Items[i] != null && Items[i].CanUseOnSelf && character.HasSelectedItem(Items[i]))
+ {
+ useOnSelfButton[i - 3].Draw(spriteBatch);
+ }
+ }
+ }
+
+ if (selectedSlot > -1)
+ {
+ DrawSubInventory(spriteBatch, selectedSlot);
+
+ if (selectedSlot > -1 &&
+ !slots[selectedSlot].IsHighlighted &&
+ (draggingItem == null || draggingItem.Container != Items[selectedSlot]))
+ {
+ selectedSlot = -1;
+ }
+ }
+ }
}
}
diff --git a/BarotraumaClient/Source/Items/Components/Machines/Steering.cs b/BarotraumaClient/Source/Items/Components/Machines/Steering.cs
index ef47d035a..c2ec43737 100644
--- a/BarotraumaClient/Source/Items/Components/Machines/Steering.cs
+++ b/BarotraumaClient/Source/Items/Components/Machines/Steering.cs
@@ -155,39 +155,6 @@ namespace Barotrauma.Items.Components
}
}
- public void SetDestinationLevelStart()
- {
- AutoPilot = true;
-
- MaintainPos = false;
- posToMaintain = null;
-
- levelEndTickBox.Selected = false;
-
- if (!levelStartTickBox.Selected)
- {
- levelStartTickBox.Selected = true;
- UpdatePath();
- }
- }
-
- public void SetDestinationLevelEnd()
- {
- AutoPilot = false;
-
- MaintainPos = false;
- posToMaintain = null;
-
- levelStartTickBox.Selected = false;
-
- if (!levelEndTickBox.Selected)
- {
- levelEndTickBox.Selected = true;
- UpdatePath();
- }
- }
-
-
private bool SelectDestination(GUITickBox tickBox)
{
unsentChanges = true;
diff --git a/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/BarotraumaClient/Source/Items/Components/Signal/Connection.cs
new file mode 100644
index 000000000..85b8f4723
--- /dev/null
+++ b/BarotraumaClient/Source/Items/Components/Signal/Connection.cs
@@ -0,0 +1,284 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace Barotrauma.Items.Components
+{
+ partial class Connection
+ {
+ private static Texture2D panelTexture;
+ private static Sprite connector;
+ private static Sprite wireVertical;
+
+ public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character)
+ {
+
+ int width = 400, height = 200;
+ int x = GameMain.GraphicsWidth / 2 - width / 2, y = GameMain.GraphicsHeight - height;
+
+ Rectangle panelRect = new Rectangle(x, y, width, height);
+
+ spriteBatch.Draw(panelTexture, panelRect, new Rectangle(0, 512 - height, width, height), Color.White);
+
+ //GUI.DrawRectangle(spriteBatch, panelRect, Color.Black, true);
+
+ bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition);
+
+ int totalWireCount = 0;
+ foreach (Connection c in panel.Connections)
+ {
+ totalWireCount += c.Wires.Count(w => w != null);
+ }
+
+ Wire equippedWire = null;
+
+ //if the Character using the panel has a wire item equipped
+ //and the wire hasn't been connected yet, draw it on the panel
+ for (int i = 0; i < character.SelectedItems.Length; i++)
+ {
+ Item selectedItem = character.SelectedItems[i];
+
+ if (selectedItem == null) continue;
+
+ Wire wireComponent = selectedItem.GetComponent();
+ if (wireComponent != null) equippedWire = wireComponent;
+ }
+
+ Vector2 rightPos = new Vector2(x + width - 130, y + 50);
+ Vector2 leftPos = new Vector2(x + 130, y + 50);
+
+ Vector2 rightWirePos = new Vector2(x + width - 5, y + 30);
+
+ Vector2 leftWirePos = new Vector2(x + 5, y + 30);
+
+ int wireInterval = (height - 20) / Math.Max(totalWireCount, 1);
+
+ foreach (Connection c in panel.Connections)
+ {
+ //if dragging a wire, let the Inventory know so that the wire can be
+ //dropped or dragged from the panel to the players inventory
+ if (draggingConnected != null)
+ {
+ int linkIndex = c.FindWireIndex(draggingConnected.Item);
+ if (linkIndex > -1)
+ {
+ Inventory.draggingItem = c.Wires[linkIndex].Item;
+ }
+ }
+
+ //outputs are drawn at the right side of the panel, inputs at the left
+ if (c.IsOutput)
+ {
+ c.Draw(spriteBatch, panel.Item, rightPos,
+ new Vector2(rightPos.X - GUI.SmallFont.MeasureString(c.Name).X - 20, rightPos.Y + 3),
+ rightWirePos,
+ mouseInRect, equippedWire,
+ wireInterval);
+
+ rightPos.Y += 30;
+ rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
+ }
+ else
+ {
+ c.Draw(spriteBatch, panel.Item, leftPos,
+ new Vector2(leftPos.X + 20, leftPos.Y - 12),
+ leftWirePos,
+ mouseInRect, equippedWire,
+ wireInterval);
+
+ leftPos.Y += 30;
+ leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
+ //leftWireX -= wireInterval;
+ }
+ }
+
+ if (draggingConnected != null)
+ {
+ DrawWire(spriteBatch, draggingConnected, draggingConnected.Item, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height), mouseInRect, null);
+
+ if (!PlayerInput.LeftButtonHeld())
+ {
+ if (GameMain.Client != null)
+ {
+ panel.Item.CreateClientEvent(panel);
+ }
+ else if (GameMain.Server != null)
+ {
+ panel.Item.CreateServerEvent(panel);
+ }
+
+ draggingConnected = null;
+ }
+ }
+
+ //if the Character using the panel has a wire item equipped
+ //and the wire hasn't been connected yet, draw it on the panel
+ if (equippedWire != null)
+ {
+ if (panel.Connections.Find(c => c.Wires.Contains(equippedWire)) == null)
+ {
+ DrawWire(spriteBatch, equippedWire, equippedWire.Item,
+ new Vector2(x + width / 2, y + height - 100),
+ new Vector2(x + width / 2, y + height), mouseInRect, null);
+
+ if (draggingConnected == equippedWire) Inventory.draggingItem = equippedWire.Item;
+ }
+ }
+
+ //stop dragging a wire item if cursor is outside the panel
+ if (mouseInRect) Inventory.draggingItem = null;
+
+
+ spriteBatch.Draw(panelTexture, panelRect, new Rectangle(0, 0, width, height), Color.White);
+
+ }
+
+ private void Draw(SpriteBatch spriteBatch, Item item, Vector2 position, Vector2 labelPos, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval)
+ {
+ //spriteBatch.DrawString(GUI.SmallFont, Name, new Vector2(labelPos.X, labelPos.Y-10), Color.White);
+ GUI.DrawString(spriteBatch, labelPos, Name, IsPower ? Color.Red : Color.White, Color.Black, 0, GUI.SmallFont);
+
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)position.X - 10, (int)position.Y - 10, 20, 20), Color.White);
+ spriteBatch.Draw(panelTexture, position - new Vector2(16.0f, 16.0f), new Rectangle(64, 256, 32, 32), Color.White);
+
+ for (int i = 0; i < MaxLinked; i++)
+ {
+ if (Wires[i] == null || Wires[i].Hidden || draggingConnected == Wires[i]) continue;
+
+ Connection recipient = Wires[i].OtherConnection(this);
+
+ DrawWire(spriteBatch, Wires[i], (recipient == null) ? Wires[i].Item : recipient.item, position, wirePosition, mouseIn, equippedWire);
+
+ wirePosition.Y += wireInterval;
+ }
+
+ if (draggingConnected != null && Vector2.Distance(position, PlayerInput.MousePosition) < 13.0f)
+ {
+ spriteBatch.Draw(panelTexture, position - new Vector2(21.5f, 21.5f), new Rectangle(106, 250, 43, 43), Color.White);
+
+ if (!PlayerInput.LeftButtonHeld())
+ {
+ //find an empty cell for the new connection
+ int index = FindWireIndex(null);
+
+ if (index > -1 && !Wires.Contains(draggingConnected))
+ {
+ bool alreadyConnected = draggingConnected.IsConnectedTo(item);
+
+ draggingConnected.RemoveConnection(item);
+
+ if (draggingConnected.Connect(this, !alreadyConnected, true)) Wires[index] = draggingConnected;
+ }
+ }
+ }
+
+ int screwIndex = (position.Y % 60 < 30) ? 0 : 1;
+
+ if (Wires.Any(w => w != null && w != draggingConnected))
+ {
+ spriteBatch.Draw(panelTexture, position - new Vector2(16.0f, 16.0f), new Rectangle(screwIndex * 32, 256, 32, 32), Color.White);
+ }
+
+ }
+
+ private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire)
+ {
+ if (draggingConnected == wire)
+ {
+ if (!mouseIn) return;
+ end = PlayerInput.MousePosition;
+ start.X = (start.X + end.X) / 2.0f;
+ }
+
+ int textX = (int)start.X;
+ if (start.X < end.X)
+ textX -= 10;
+ else
+ textX += 10;
+
+ bool canDrag = equippedWire == null || equippedWire == wire;
+
+ float alpha = canDrag ? 1.0f : 0.5f;
+
+ bool mouseOn =
+ canDrag &&
+ ((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
+ PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
+ MathUtils.LineToPointDistance(start, end, PlayerInput.MousePosition) < 6) ||
+ Vector2.Distance(end, PlayerInput.MousePosition) < 20.0f ||
+ new Rectangle((start.X < end.X) ? textX - 100 : textX, (int)start.Y - 5, 100, 14).Contains(PlayerInput.MousePosition));
+
+ string label = wire.Locked ? item.Name + "\n(Locked)" : item.Name;
+
+ GUI.DrawString(spriteBatch,
+ new Vector2(start.X < end.X ? textX - GUI.SmallFont.MeasureString(label).X : textX, start.Y - 5.0f),
+ label,
+ (mouseOn ? Color.Gold : Color.White) * (wire.Locked ? 0.6f : 1.0f), Color.Black * 0.8f,
+ 3, GUI.SmallFont);
+
+ var wireEnd = end + Vector2.Normalize(start - end) * 30.0f;
+
+ float dist = Vector2.Distance(start, wireEnd);
+
+ if (mouseOn)
+ {
+ spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point(18, (int)dist)), wireVertical.SourceRect,
+ Color.Gold,
+ MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, //angle of line (calulated above)
+ new Vector2(6, 0), // point in line about which to rotate
+ SpriteEffects.None,
+ 0.0f);
+ }
+ spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point(12, (int)dist)), wireVertical.SourceRect,
+ wire.Item.Color * alpha,
+ MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, //angle of line (calulated above)
+ new Vector2(6, 0), // point in line about which to rotate
+ SpriteEffects.None,
+ 0.0f);
+
+ connector.Draw(spriteBatch, end, Color.White, new Vector2(10.0f, 10.0f), MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2);
+
+ if (draggingConnected == null && canDrag)
+ {
+ if (mouseOn)
+ {
+ ConnectionPanel.HighlightedWire = wire;
+
+ if (!wire.Locked)
+ {
+ //start dragging the wire
+ if (PlayerInput.LeftButtonHeld()) draggingConnected = wire;
+ }
+ }
+ }
+ }
+
+ public void Save(XElement parentElement)
+ {
+ XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
+
+ Array.Sort(Wires, delegate (Wire wire1, Wire wire2)
+ {
+ if (wire1 == null) return 1;
+ if (wire2 == null) return -1;
+ return wire1.Item.ID.CompareTo(wire2.Item.ID);
+ });
+
+ for (int i = 0; i < MaxLinked; i++)
+ {
+ if (Wires[i] == null) continue;
+
+ //Connection recipient = wires[i].OtherConnection(this);
+
+ //int connectionIndex = recipient.item.Connections.FindIndex(x => x == recipient);
+ newElement.Add(new XElement("link",
+ new XAttribute("w", Wires[i].Item.ID.ToString())));
+ }
+
+ parentElement.Add(newElement);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Items/Components/Turret.cs b/BarotraumaClient/Source/Items/Components/Turret.cs
new file mode 100644
index 000000000..2ec831fc0
--- /dev/null
+++ b/BarotraumaClient/Source/Items/Components/Turret.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml.Linq;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using FarseerPhysics;
+using Barotrauma.Networking;
+using Lidgren.Network;
+
+namespace Barotrauma.Items.Components
+{
+ partial class Turret : Powered, IDrawableComponent, IServerSerializable
+ {
+ public void Draw(SpriteBatch spriteBatch, bool editing = false)
+ {
+ Vector2 drawPos = new Vector2(item.Rect.X, item.Rect.Y);
+ if (item.Submarine != null) drawPos += item.Submarine.DrawPosition;
+ drawPos.Y = -drawPos.Y;
+
+ if (barrelSprite != null)
+ {
+ barrelSprite.Draw(spriteBatch,
+ drawPos + barrelPos, Color.White,
+ rotation + MathHelper.PiOver2, 1.0f,
+ SpriteEffects.None, item.Sprite.Depth + 0.01f);
+ }
+
+ if (!editing) return;
+
+ GUI.DrawLine(spriteBatch,
+ drawPos + barrelPos,
+ drawPos + barrelPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * 60.0f,
+ Color.Green);
+
+ GUI.DrawLine(spriteBatch,
+ drawPos + barrelPos,
+ drawPos + barrelPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * 60.0f,
+ Color.Green);
+
+ GUI.DrawLine(spriteBatch,
+ drawPos + barrelPos,
+ drawPos + barrelPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * 60.0f,
+ Color.LightGreen);
+
+ }
+
+ public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ {
+ UInt16 projectileID = msg.ReadUInt16();
+ Item projectile = Entity.FindEntityByID(projectileID) as Item;
+
+ if (projectile == null)
+ {
+ DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found");
+ return;
+ }
+
+ Launch(projectile);
+ PlaySound(ActionType.OnUse, item.WorldPosition);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Items/DockingPort.cs b/BarotraumaClient/Source/Items/DockingPort.cs
new file mode 100644
index 000000000..699b05976
--- /dev/null
+++ b/BarotraumaClient/Source/Items/DockingPort.cs
@@ -0,0 +1,75 @@
+using Barotrauma.Networking;
+using FarseerPhysics;
+using FarseerPhysics.Dynamics;
+using FarseerPhysics.Dynamics.Joints;
+using FarseerPhysics.Factories;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace Barotrauma.Items.Components
+{
+ partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable
+ {
+ public void Draw(SpriteBatch spriteBatch, bool editing)
+ {
+ if (dockingState == 0.0f) return;
+
+ Vector2 drawPos = item.DrawPosition;
+ drawPos.Y = -drawPos.Y;
+
+ var rect = overlaySprite.SourceRect;
+
+ if (IsHorizontal)
+ {
+ drawPos.Y -= rect.Height / 2;
+
+ if (dockingDir == 1)
+ {
+ spriteBatch.Draw(overlaySprite.Texture,
+ drawPos,
+ new Rectangle(
+ rect.Center.X + (int)(rect.Width / 2 * (1.0f - dockingState)), rect.Y,
+ (int)(rect.Width / 2 * dockingState), rect.Height), Color.White);
+
+ }
+ else
+ {
+ spriteBatch.Draw(overlaySprite.Texture,
+ drawPos - Vector2.UnitX * (rect.Width / 2 * dockingState),
+ new Rectangle(
+ rect.X, rect.Y,
+ (int)(rect.Width / 2 * dockingState), rect.Height), Color.White);
+ }
+ }
+ else
+ {
+ drawPos.X -= rect.Width / 2;
+
+ if (dockingDir == 1)
+ {
+ spriteBatch.Draw(overlaySprite.Texture,
+ drawPos - Vector2.UnitY * (rect.Height / 2 * dockingState),
+ new Rectangle(
+ rect.X, rect.Y,
+ rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White);
+ }
+ else
+ {
+ spriteBatch.Draw(overlaySprite.Texture,
+ drawPos,
+ new Rectangle(
+ rect.X, rect.Y + rect.Height / 2 + (int)(rect.Height / 2 * (1.0f - dockingState)),
+ rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White);
+ }
+ }
+ }
+
+ }
+}
diff --git a/BarotraumaClient/Source/Items/Item.cs b/BarotraumaClient/Source/Items/Item.cs
index e09372970..aa385d788 100644
--- a/BarotraumaClient/Source/Items/Item.cs
+++ b/BarotraumaClient/Source/Items/Item.cs
@@ -264,6 +264,26 @@ namespace Barotrauma
}
return editingHUD;
}
+
+ public virtual void UpdateHUD(Camera cam, Character character)
+ {
+ if (condition <= 0.0f)
+ {
+ FixRequirement.UpdateHud(this, character);
+ return;
+ }
+
+ if (HasInGameEditableProperties)
+ {
+ UpdateEditing(cam);
+ }
+
+ foreach (ItemComponent ic in components)
+ {
+ if (ic.CanBeSelected) ic.UpdateHUD(character);
+ }
+ }
+
public virtual void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character)
{
if (condition <= 0.0f)
diff --git a/BarotraumaClient/Source/Items/Rope.cs b/BarotraumaClient/Source/Items/Rope.cs
new file mode 100644
index 000000000..2db3e76e4
--- /dev/null
+++ b/BarotraumaClient/Source/Items/Rope.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using FarseerPhysics;
+using FarseerPhysics.Collision.Shapes;
+using FarseerPhysics.Common;
+using FarseerPhysics.Dynamics;
+using FarseerPhysics.Dynamics.Joints;
+using FarseerPhysics.Factories;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Barotrauma.Items.Components
+{
+ partial class Rope : ItemComponent, IDrawableComponent
+ {
+ public void Draw(SpriteBatch spriteBatch, bool editing = false)
+ {
+ if (!IsActive) return;
+
+ RevoluteJoint firstJoint = null;
+
+ for (int i = 0; i < ropeBodies.Length - 1; i++)
+ {
+ if (!ropeBodies[i].Enabled) continue;
+
+ if (firstJoint == null) firstJoint = ropeJoints[i];
+
+ DrawSection(spriteBatch, ropeJoints[i].WorldAnchorA, ropeJoints[i + 1].WorldAnchorA, i);
+ }
+
+ if (gunJoint == null || firstJoint == null) return;
+
+ DrawSection(spriteBatch, gunJoint.WorldAnchorA, firstJoint.WorldAnchorA, 0);
+
+ }
+
+ private void DrawSection(SpriteBatch spriteBatch, Vector2 start, Vector2 end, int i)
+ {
+ start.Y = -start.Y;
+ end.Y = -end.Y;
+
+ spriteBatch.Draw(sprite.Texture,
+ ConvertUnits.ToDisplayUnits(start), null, Color.White,
+ MathUtils.VectorToAngle(end - start),
+ new Vector2(0.0f, sprite.size.Y / 2.0f),
+ new Vector2((ConvertUnits.ToDisplayUnits(Vector2.Distance(start, end))) / sprite.Texture.Width, 1.0f),
+ SpriteEffects.None,
+ sprite.Depth + i * 0.00001f);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Map/Levels/Ruins/RuinGenerator.cs b/BarotraumaClient/Source/Map/Levels/Ruins/RuinGenerator.cs
new file mode 100644
index 000000000..5a5e343b7
--- /dev/null
+++ b/BarotraumaClient/Source/Map/Levels/Ruins/RuinGenerator.cs
@@ -0,0 +1,30 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Voronoi2;
+
+namespace Barotrauma.RuinGeneration
+{
+ partial class Ruin
+ {
+ public void Draw(SpriteBatch spriteBatch)
+ {
+ //foreach (BTRoom room in leaves)
+ //{
+ // GUI.DrawRectangle(spriteBatch, room.Rect, Color.White);
+ //}
+
+ //foreach (Corridor corr in corridors)
+ //{
+ // GUI.DrawRectangle(spriteBatch, corr.Rect, Color.Blue);
+ //}
+
+ foreach (Line line in walls)
+ {
+ GUI.DrawLine(spriteBatch, new Vector2(line.A.X, -line.A.Y), new Vector2(line.B.X, -line.B.Y), Color.Red, 0.0f, 10);
+ }
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Map/Levels/WrappingWall.cs b/BarotraumaClient/Source/Map/Levels/WrappingWall.cs
new file mode 100644
index 000000000..c1e2fb655
--- /dev/null
+++ b/BarotraumaClient/Source/Map/Levels/WrappingWall.cs
@@ -0,0 +1,39 @@
+using FarseerPhysics;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Voronoi2;
+
+namespace Barotrauma
+{
+ partial class WrappingWall : IDisposable
+ {
+ private VertexBuffer wallVertices, bodyVertices;
+
+ public VertexBuffer WallVertices
+ {
+ get { return wallVertices; }
+ }
+
+ public VertexBuffer BodyVertices
+ {
+ get { return bodyVertices; }
+ }
+
+ public void SetWallVertices(VertexPositionTexture[] vertices)
+ {
+ wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
+ wallVertices.SetData(vertices);
+ }
+
+ public void SetBodyVertices(VertexPositionColor[] vertices)
+ {
+ bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
+ bodyVertices.SetData(vertices);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Map/Map.cs b/BarotraumaClient/Source/Map/Map.cs
new file mode 100644
index 000000000..14d4da9f0
--- /dev/null
+++ b/BarotraumaClient/Source/Map/Map.cs
@@ -0,0 +1,210 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Voronoi2;
+
+namespace Barotrauma
+{
+ partial class Map
+ {
+ private static Sprite iceTexture;
+ private static Texture2D iceCraters;
+ private static Texture2D iceCrack;
+
+ public void Update(float deltaTime, Rectangle rect, float scale = 1.0f)
+ {
+ Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
+ Vector2 offset = -currentLocation.MapPosition;
+
+ float maxDist = 20.0f;
+ float closestDist = 0.0f;
+ highlightedLocation = null;
+ for (int i = 0; i < locations.Count; i++)
+ {
+ Location location = locations[i];
+ Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
+
+ if (!rect.Contains(pos)) continue;
+
+ float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
+ if (dist < maxDist && (highlightedLocation == null || dist < closestDist))
+ {
+ closestDist = dist;
+ highlightedLocation = location;
+ }
+ }
+
+ foreach (LocationConnection connection in connections)
+ {
+ if (highlightedLocation != currentLocation &&
+ connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation))
+ {
+ if (PlayerInput.LeftButtonClicked() &&
+ selectedLocation != highlightedLocation && highlightedLocation != null)
+ {
+ selectedConnection = connection;
+ selectedLocation = highlightedLocation;
+ GameMain.LobbyScreen.SelectLocation(highlightedLocation, connection);
+ }
+ }
+ }
+ }
+
+ public void Draw(SpriteBatch spriteBatch, Rectangle rect, float scale = 1.0f)
+ {
+ Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
+ Vector2 offset = -currentLocation.MapPosition;
+
+ iceTexture.DrawTiled(spriteBatch, new Vector2(rect.X, rect.Y), new Vector2(rect.Width, rect.Height), Vector2.Zero, Color.White * 0.8f);
+
+ foreach (LocationConnection connection in connections)
+ {
+ Color crackColor = Color.White * Math.Max(connection.Difficulty / 100.0f, 1.5f);
+
+ if (selectedLocation != currentLocation &&
+ (connection.Locations.Contains(selectedLocation) && connection.Locations.Contains(currentLocation)))
+ {
+ crackColor = Color.Red;
+ }
+ else if (highlightedLocation != currentLocation &&
+ (connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(currentLocation)))
+ {
+ crackColor = Color.Red * 0.5f;
+ }
+ else if (!connection.Passed)
+ {
+ crackColor *= 0.2f;
+ }
+
+ for (int i = 0; i < connection.CrackSegments.Count; i++)
+ {
+ var segment = connection.CrackSegments[i];
+
+ Vector2 start = rectCenter + (segment[0] + offset) * scale;
+ Vector2 end = rectCenter + (segment[1] + offset) * scale;
+
+ if (!rect.Contains(start) && !rect.Contains(end))
+ {
+ continue;
+ }
+ else
+ {
+ Vector2? intersection = MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(rect.X, rect.Y + rect.Height, rect.Width, rect.Height));
+ if (intersection != null)
+ {
+ if (!rect.Contains(start))
+ {
+ start = (Vector2)intersection;
+ }
+ else
+ {
+ end = (Vector2)intersection;
+ }
+ }
+ }
+
+ float dist = Vector2.Distance(start, end);
+
+ int width = (int)(MathHelper.Clamp(connection.Difficulty, 2.0f, 20.0f) * scale);
+
+ spriteBatch.Draw(iceCrack,
+ new Rectangle((int)start.X, (int)start.Y, (int)dist + 2, width),
+ new Rectangle(0, 0, iceCrack.Width, 60), crackColor, MathUtils.VectorToAngle(end - start),
+ new Vector2(0, 30), SpriteEffects.None, 0.01f);
+ }
+ }
+
+ rect.Inflate(8, 8);
+ GUI.DrawRectangle(spriteBatch, rect, Color.Black, false, 0.0f, 8);
+ GUI.DrawRectangle(spriteBatch, rect, Color.LightGray);
+
+ for (int i = 0; i < locations.Count; i++)
+ {
+ Location location = locations[i];
+ Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
+
+ Rectangle drawRect = location.Type.Sprite.SourceRect;
+ Rectangle sourceRect = drawRect;
+ drawRect.X = (int)pos.X - drawRect.Width / 2;
+ drawRect.Y = (int)pos.Y - drawRect.Width / 2;
+
+ if (!rect.Intersects(drawRect)) continue;
+
+ Color color = location.Connections.Find(c => c.Locations.Contains(currentLocation)) == null ? Color.White : Color.Green;
+
+ color *= (location.Discovered) ? 0.8f : 0.2f;
+
+ if (location == currentLocation) color = Color.Orange;
+
+ if (drawRect.X < rect.X)
+ {
+ sourceRect.X += rect.X - drawRect.X;
+ sourceRect.Width -= sourceRect.X;
+ drawRect.X = rect.X;
+ }
+ else if (drawRect.Right > rect.Right)
+ {
+ sourceRect.Width -= (drawRect.Right - rect.Right);
+ }
+
+ if (drawRect.Y < rect.Y)
+ {
+ sourceRect.Y += rect.Y - drawRect.Y;
+ sourceRect.Height -= sourceRect.Y;
+ drawRect.Y = rect.Y;
+ }
+ else if (drawRect.Bottom > rect.Bottom)
+ {
+ sourceRect.Height -= drawRect.Bottom - rect.Bottom;
+ }
+
+ drawRect.Width = sourceRect.Width;
+ drawRect.Height = sourceRect.Height;
+
+ spriteBatch.Draw(location.Type.Sprite.Texture, drawRect, sourceRect, color);
+ }
+
+ for (int i = 0; i < 3; i++)
+ {
+ Location location = (i == 0) ? highlightedLocation : selectedLocation;
+ if (i == 2) location = currentLocation;
+
+ if (location == null) continue;
+
+ Vector2 pos = rectCenter + (location.MapPosition + offset) * scale;
+ pos.X = (int)(pos.X + location.Type.Sprite.SourceRect.Width * 0.6f);
+ pos.Y = (int)(pos.Y - 10);
+ GUI.DrawString(spriteBatch, pos, location.Name, Color.White, Color.Black * 0.8f, 3);
+ }
+
+ }
+
+ public void Save(XElement element)
+ {
+ XElement mapElement = new XElement("map");
+
+ mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex));
+ mapElement.Add(new XAttribute("seed", Seed));
+ mapElement.Add(new XAttribute("size", size));
+
+ List discoveredLocations = new List();
+ for (int i = 0; i < locations.Count; i++)
+ {
+ if (locations[i].Discovered) discoveredLocations.Add(i);
+ }
+ mapElement.Add(new XAttribute("discovered", string.Join(",", discoveredLocations)));
+
+ List passedConnections = new List();
+ for (int i = 0; i < connections.Count; i++)
+ {
+ if (connections[i].Passed) passedConnections.Add(i);
+ }
+ mapElement.Add(new XAttribute("passed", string.Join(",", passedConnections)));
+
+ element.Add(mapElement);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Map/Submarine.cs b/BarotraumaClient/Source/Map/Submarine.cs
new file mode 100644
index 000000000..a16c7fa8c
--- /dev/null
+++ b/BarotraumaClient/Source/Map/Submarine.cs
@@ -0,0 +1,228 @@
+using Barotrauma.Networking;
+using FarseerPhysics;
+using FarseerPhysics.Common;
+using FarseerPhysics.Dynamics;
+using Lidgren.Network;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+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
+{
+ partial class Submarine : Entity, IServerSerializable
+ {
+ public static void Draw(SpriteBatch spriteBatch, bool editing = false)
+ {
+ var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
+
+ foreach (MapEntity e in entitiesToRender)
+ {
+ e.Draw(spriteBatch, editing);
+ }
+ }
+
+ public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null)
+ {
+ var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
+
+ foreach (MapEntity e in entitiesToRender)
+ {
+ if (!e.DrawOverWater) continue;
+
+ if (predicate != null)
+ {
+ if (!predicate(e)) continue;
+ }
+
+ e.Draw(spriteBatch, editing, false);
+ }
+
+ if (GameMain.DebugDraw)
+ {
+ foreach (Submarine sub in Submarine.Loaded)
+ {
+ Rectangle worldBorders = sub.Borders;
+ worldBorders.Location += sub.WorldPosition.ToPoint();
+ worldBorders.Y = -worldBorders.Y;
+
+ GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
+
+ if (sub.subBody.MemPos.Count < 2) continue;
+
+ Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.MemPos[0].Position);
+ prevPos.Y = -prevPos.Y;
+
+ for (int i = 1; i < sub.subBody.MemPos.Count; i++)
+ {
+ Vector2 currPos = ConvertUnits.ToDisplayUnits(sub.subBody.MemPos[i].Position);
+ currPos.Y = -currPos.Y;
+
+ GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 10, (int)currPos.Y - 10, 20, 20), Color.Blue * 0.6f, true, 0.01f);
+ GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.5f, 0, 5);
+
+ prevPos = currPos;
+ }
+ }
+ }
+ }
+
+ public static float DamageEffectCutoff;
+
+ public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false)
+ {
+ var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
+
+ foreach (MapEntity e in entitiesToRender)
+ {
+ if (e.DrawDamageEffect)
+ e.DrawDamage(spriteBatch, damageEffect);
+ }
+ if (damageEffect != null)
+ {
+ damageEffect.Parameters["aCutoff"].SetValue(0.0f);
+ damageEffect.Parameters["cCutoff"].SetValue(0.0f);
+
+ DamageEffectCutoff = 0.0f;
+ }
+ }
+
+ public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null)
+ {
+ var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
+
+ foreach (MapEntity e in entitiesToRender)
+ {
+ if (!e.DrawBelowWater) continue;
+
+ if (predicate != null)
+ {
+ if (!predicate(e)) continue;
+ }
+
+ e.Draw(spriteBatch, editing, true);
+ }
+ }
+
+ public bool Save()
+ {
+ return SaveAs(filePath);
+ }
+
+ public bool SaveAs(string filePath)
+ {
+ name = System.IO.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));
+
+ 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 == null ? "" : Description));
+
+ element.Add(new XAttribute("tags", tags.ToString()));
+
+ 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)
+ {
+ Submarine.MainSub = new Submarine(filePath);
+ // return;
+ }
+
+ Submarine.MainSub.filePath = filePath;
+
+ return Submarine.MainSub.SaveAs(filePath);
+ }
+
+ public void CheckForErrors()
+ {
+ List errorMsgs = new List();
+
+ if (!Hull.hullList.Any())
+ {
+ errorMsgs.Add("No hulls found in the submarine. Hulls determine the \"borders\" of an individual room and are required for water and air distribution to work correctly.");
+ }
+
+ foreach (Item item in Item.ItemList)
+ {
+ if (item.GetComponent() == null) continue;
+
+ if (!item.linkedTo.Any())
+ {
+ errorMsgs.Add("The submarine contains vents which haven't been linked to an oxygen generator. Select a vent and click an oxygen generator while holding space to link them.");
+ break;
+ }
+ }
+
+ if (WayPoint.WayPointList.Find(wp => !wp.MoveWithLevel && wp.SpawnType == SpawnType.Path) == null)
+ {
+ errorMsgs.Add("No waypoints found in the submarine. AI controlled crew members won't be able to navigate without waypoints.");
+ }
+
+ if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null)
+ {
+ errorMsgs.Add("The submarine doesn't have spawnpoints for cargo (which are used for determining where to place bought items). "
+ + "To fix this, create a new spawnpoint and change its \"spawn type\" parameter to \"cargo\".");
+ }
+
+ if (errorMsgs.Any())
+ {
+ new GUIMessageBox("Warning", string.Join("\n\n", errorMsgs), 400, 0);
+ }
+
+ foreach (MapEntity e in MapEntity.mapEntityList)
+ {
+ if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000)
+ {
+ var msgBox = new GUIMessageBox(
+ "Warning",
+ "One or more structures have been placed very far from the submarine. Show the structures?",
+ new string[] { "Yes", "No" });
+
+ msgBox.Buttons[0].OnClicked += (btn, obj) =>
+ {
+ GameMain.EditMapScreen.Cam.Position = e.WorldPosition;
+ return true;
+ };
+ msgBox.Buttons[0].OnClicked += msgBox.Close;
+ msgBox.Buttons[1].OnClicked += msgBox.Close;
+
+ break;
+
+ }
+ }
+ }
+
+ }
+}
diff --git a/BarotraumaClient/Source/Networking/EntitySpawner.cs b/BarotraumaClient/Source/Networking/EntitySpawner.cs
new file mode 100644
index 000000000..62165c7a3
--- /dev/null
+++ b/BarotraumaClient/Source/Networking/EntitySpawner.cs
@@ -0,0 +1,44 @@
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Barotrauma
+{
+ partial class EntitySpawner : Entity, IServerSerializable
+ {
+ public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer message, float sendingTime)
+ {
+ if (GameMain.Server != null) return;
+
+ bool remove = message.ReadBoolean();
+
+ if (remove)
+ {
+ ushort entityId = message.ReadUInt16();
+
+ var entity = FindEntityByID(entityId);
+ if (entity != null)
+ {
+ entity.Remove();
+ }
+ }
+ else
+ {
+ switch (message.ReadByte())
+ {
+ case (byte)SpawnableType.Item:
+ Item.ReadSpawnData(message, true);
+ break;
+ case (byte)SpawnableType.Character:
+ Character.ReadSpawnData(message, true);
+ break;
+ default:
+ DebugConsole.ThrowError("Received invalid entity spawn message (unknown spawnable type)");
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/BarotraumaShared/Source/Networking/FileTransfer/FileReceiver.cs b/BarotraumaClient/Source/Networking/FileTransfer/FileReceiver.cs
similarity index 100%
rename from BarotraumaShared/Source/Networking/FileTransfer/FileReceiver.cs
rename to BarotraumaClient/Source/Networking/FileTransfer/FileReceiver.cs
diff --git a/BarotraumaShared/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs
similarity index 100%
rename from BarotraumaShared/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs
rename to BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs
diff --git a/BarotraumaClient/Source/Networking/NetEntityEvent/NetEntityEvent.cs b/BarotraumaClient/Source/Networking/NetEntityEvent/NetEntityEvent.cs
new file mode 100644
index 000000000..15e2c9cde
--- /dev/null
+++ b/BarotraumaClient/Source/Networking/NetEntityEvent/NetEntityEvent.cs
@@ -0,0 +1,24 @@
+using Lidgren.Network;
+using System;
+
+namespace Barotrauma.Networking
+{
+ class ClientEntityEvent : NetEntityEvent
+ {
+ private IClientSerializable serializable;
+
+ public UInt16 CharacterStateID;
+
+ public ClientEntityEvent(IClientSerializable entity, UInt16 id)
+ : base(entity, id)
+ {
+ serializable = entity;
+ }
+
+ public void Write(NetBuffer msg)
+ {
+ msg.Write(CharacterStateID);
+ serializable.ClientWrite(msg, Data);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Physics/PhysicsBody.cs b/BarotraumaClient/Source/Physics/PhysicsBody.cs
new file mode 100644
index 000000000..dd32fa518
--- /dev/null
+++ b/BarotraumaClient/Source/Physics/PhysicsBody.cs
@@ -0,0 +1,92 @@
+using System.Xml.Linq;
+using FarseerPhysics;
+using FarseerPhysics.Dynamics;
+using FarseerPhysics.Factories;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System.Collections.Generic;
+using System;
+
+namespace Barotrauma
+{
+ partial class PhysicsBody
+ {
+ public void Draw(SpriteBatch spriteBatch, Sprite sprite, Color color, float? depth = null, float scale = 1.0f)
+ {
+ if (!Enabled) return;
+
+ UpdateDrawPosition();
+
+ if (sprite == null) return;
+
+ SpriteEffects spriteEffect = (dir == 1.0f) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
+
+ if (GameMain.DebugDraw)
+ {
+ if (!body.Awake) color = Color.Blue;
+
+ if (targetPosition != null)
+ {
+ Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)targetPosition);
+ if (Submarine != null) pos += Submarine.DrawPosition;
+
+ GUI.DrawRectangle(spriteBatch,
+ new Vector2(pos.X - 5, -(pos.Y + 5)),
+ Vector2.One * 10.0f, Color.Red, false, 0, 3);
+ }
+
+ if (offsetFromTargetPos != Vector2.Zero)
+ {
+ Vector2 pos = ConvertUnits.ToDisplayUnits(body.Position);
+ if (Submarine != null) pos += Submarine.DrawPosition;
+
+ GUI.DrawLine(spriteBatch,
+ new Vector2(pos.X, -pos.Y),
+ new Vector2(DrawPosition.X, -DrawPosition.Y),
+ Color.Cyan, 0, 5);
+ }
+ }
+
+ sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), color, -drawRotation, scale, spriteEffect, depth);
+ }
+
+ public void DebugDraw(SpriteBatch spriteBatch, Color color)
+ {
+ if (bodyShapeTexture == null)
+ {
+ switch (BodyShape)
+ {
+ case PhysicsBody.Shape.Rectangle:
+ bodyShapeTexture = GUI.CreateRectangle(
+ (int)ConvertUnits.ToDisplayUnits(width),
+ (int)ConvertUnits.ToDisplayUnits(height));
+ break;
+
+ case PhysicsBody.Shape.Capsule:
+ bodyShapeTexture = GUI.CreateCapsule(
+ (int)ConvertUnits.ToDisplayUnits(radius),
+ (int)ConvertUnits.ToDisplayUnits(Math.Max(height, width)));
+ break;
+ case PhysicsBody.Shape.Circle:
+ bodyShapeTexture = GUI.CreateCircle((int)ConvertUnits.ToDisplayUnits(radius));
+ break;
+ }
+ }
+
+ float rot = -DrawRotation;
+ if (bodyShape == PhysicsBody.Shape.Capsule && width > height)
+ {
+ rot -= MathHelper.PiOver2;
+ }
+
+ spriteBatch.Draw(
+ bodyShapeTexture,
+ new Vector2(DrawPosition.X, -DrawPosition.Y),
+ null,
+ color,
+ rot,
+ new Vector2(bodyShapeTexture.Width / 2, bodyShapeTexture.Height / 2),
+ 1.0f, SpriteEffects.None, 0.0f);
+ }
+ }
+}
diff --git a/BarotraumaClient/Source/Utils/SaveUtil.cs b/BarotraumaClient/Source/Utils/SaveUtil.cs
new file mode 100644
index 000000000..1b5570ded
--- /dev/null
+++ b/BarotraumaClient/Source/Utils/SaveUtil.cs
@@ -0,0 +1,239 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Text;
+using System.Xml.Linq;
+
+namespace Barotrauma
+{
+ public partial class SaveUtil
+ {
+ public static void SaveGame(string fileName)
+ {
+ fileName = Path.Combine(SaveFolder, fileName);
+
+ string tempPath = Path.Combine(SaveFolder, "temp");
+
+ Directory.CreateDirectory(tempPath);
+ try
+ {
+ ClearFolder(tempPath, new string[] { GameMain.GameSession.Submarine.FilePath });
+ }
+ catch
+ {
+
+ }
+
+ try
+ {
+ if (Submarine.MainSub != null && Submarine.Loaded.Contains(Submarine.MainSub))
+ {
+ Submarine.MainSub.FilePath = Path.Combine(tempPath, Submarine.MainSub.Name + ".sub");
+ Submarine.MainSub.SaveAs(Submarine.MainSub.FilePath);
+ }
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("Error saving submarine", e);
+ }
+
+ try
+ {
+ GameMain.GameSession.Save(Path.Combine(tempPath, "gamesession.xml"));
+ }
+
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("Error saving gamesession", e);
+ }
+
+ try
+ {
+ CompressDirectory(tempPath, fileName+".save", null);
+ }
+
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("Error compressing save file", e);
+ }
+ }
+
+ public static void LoadGame(string fileName)
+ {
+ string filePath = Path.Combine(SaveFolder, fileName+".save");
+
+ DecompressToDirectory(filePath, TempPath, null);
+
+ XDocument doc = ToolBox.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
+
+ string subPath = Path.Combine(TempPath, ToolBox.GetAttributeString(doc.Root, "submarine", ""))+".sub";
+ Submarine selectedMap = new Submarine(subPath, "");// Submarine.Load();
+ GameMain.GameSession = new GameSession(selectedMap, fileName, doc);
+
+ //Directory.Delete(tempPath, true);
+ }
+
+ public static XDocument LoadGameSessionDoc(string fileName)
+ {
+ string filePath = Path.Combine(SaveFolder, fileName + ".save");
+
+ string tempPath = Path.Combine(SaveFolder, "temp");
+
+ try
+ {
+ DecompressToDirectory(filePath, tempPath, null);
+ }
+ catch
+ {
+ return null;
+ }
+
+ return ToolBox.TryLoadXml(Path.Combine(tempPath, "gamesession.xml"));
+ }
+
+ public static void DeleteSave(string fileName)
+ {
+ fileName = Path.Combine(SaveFolder, fileName + ".save");
+
+ try
+ {
+ File.Delete(fileName);
+ }
+
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("ERROR: deleting save file \""+fileName+" failed.", e);
+ }
+
+ }
+
+ public static string[] GetSaveFiles()
+ {
+ if (!Directory.Exists(SaveFolder))
+ {
+ DebugConsole.ThrowError("Save folder \"" + SaveFolder + " not found! Attempting to create a new folder");
+ try
+ {
+ Directory.CreateDirectory(SaveFolder);
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("Failed to create the folder \"" + SaveFolder + "\"!", e);
+ }
+ }
+
+ string[] files = Directory.GetFiles(SaveFolder, "*.save");
+
+ for (int i = 0; i < files.Length; i++)
+ {
+ files[i] = Path.GetFileNameWithoutExtension(files[i]);
+ }
+
+ return files;
+ }
+
+ public static string CreateSavePath(string fileName="Save")
+ {
+ if (!Directory.Exists(SaveFolder))
+ {
+ DebugConsole.ThrowError("Save folder \""+SaveFolder+"\" not found. Created new folder");
+ Directory.CreateDirectory(SaveFolder);
+ }
+
+ string extension = ".save";
+ string pathWithoutExtension = Path.Combine(SaveFolder, fileName);
+
+ int i = 0;
+ while (File.Exists(pathWithoutExtension + " " + i + extension))
+ {
+ i++;
+ }
+
+ return fileName + " " + i;
+ }
+
+ public static void CompressStringToFile(string fileName, string value)
+ {
+ // A.
+ // Write string to temporary file.
+ string temp = Path.GetTempFileName();
+ File.WriteAllText(temp, value);
+
+ // B.
+ // Read file into byte array buffer.
+ byte[] b;
+ using (FileStream f = new FileStream(temp, FileMode.Open))
+ {
+ b = new byte[f.Length];
+ f.Read(b, 0, (int)f.Length);
+ }
+
+ // C.
+ // Use GZipStream to write compressed bytes to target file.
+ using (FileStream f2 = new FileStream(fileName, FileMode.Create))
+ using (GZipStream gz = new GZipStream(f2, CompressionMode.Compress, false))
+ {
+ gz.Write(b, 0, b.Length);
+ }
+ }
+
+ public static void CompressFile(string sDir, string sRelativePath, GZipStream zipStream)
+ {
+ //Compress file name
+ char[] chars = sRelativePath.ToCharArray();
+ zipStream.Write(BitConverter.GetBytes(chars.Length), 0, sizeof(int));
+ foreach (char c in chars)
+ zipStream.Write(BitConverter.GetBytes(c), 0, sizeof(char));
+
+ //Compress file content
+ byte[] bytes = File.ReadAllBytes(Path.Combine(sDir, sRelativePath));
+ zipStream.Write(BitConverter.GetBytes(bytes.Length), 0, sizeof(int));
+ zipStream.Write(bytes, 0, bytes.Length);
+ }
+
+ public static void CompressDirectory(string sInDir, string sOutFile, ProgressDelegate progress)
+ {
+ string[] sFiles = Directory.GetFiles(sInDir, "*.*", SearchOption.AllDirectories);
+ int iDirLen = sInDir[sInDir.Length - 1] == Path.DirectorySeparatorChar ? sInDir.Length : sInDir.Length + 1;
+
+ using (FileStream outFile = new FileStream(sOutFile, FileMode.Create, FileAccess.Write, FileShare.None))
+ using (GZipStream str = new GZipStream(outFile, CompressionMode.Compress))
+ foreach (string sFilePath in sFiles)
+ {
+ string sRelativePath = sFilePath.Substring(iDirLen);
+ if (progress != null)
+ progress(sRelativePath);
+ CompressFile(sInDir, sRelativePath, str);
+ }
+ }
+
+ private static void ClearFolder(string FolderName, string[] ignoredFiles = null)
+ {
+ DirectoryInfo dir = new DirectoryInfo(FolderName);
+
+ foreach (FileInfo fi in dir.GetFiles())
+ {
+ bool ignore = false;
+ foreach (string ignoredFile in ignoredFiles)
+ {
+ if (Path.GetFullPath(fi.FullName).Equals(Path.GetFullPath(ignoredFile)))
+ {
+ ignore = true;
+ break;
+ }
+ }
+
+ if (ignore) continue;
+
+ fi.IsReadOnly = false;
+ fi.Delete();
+ }
+
+ foreach (DirectoryInfo di in dir.GetDirectories())
+ {
+ ClearFolder(di.FullName, ignoredFiles);
+ di.Delete();
+ }
+ }
+ }
+}
diff --git a/BarotraumaServer/BarotraumaServer.csproj b/BarotraumaServer/BarotraumaServer.csproj
index 45b1cb6a0..e274e5bb5 100644
--- a/BarotraumaServer/BarotraumaServer.csproj
+++ b/BarotraumaServer/BarotraumaServer.csproj
@@ -1,6 +1,5 @@
-
Debug
AnyCPU
@@ -13,35 +12,55 @@
512
true
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
-
- x86
+
true
- full
- false
- bin\Debug\
+ bin\x86\Debug\
TRACE;DEBUG;WINDOWS;SERVER
- prompt
- 4
-
-
+ full
x86
- pdbonly
- true
- bin\Release\
- TRACE;WINDOWS;SERVER
prompt
- 4
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ bin\x86\Release\
+ TRACE;WINDOWS;SERVER
+ true
+ pdbonly
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
False
- ..\..\..\..\..\..\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\MonoGame.Framework.dll
+ C:\Program Files (x86)\MonoGame\v3.0\Assemblies\Windows\MonoGame.Framework.dll
..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll
+
+ False
+
+
@@ -89,7 +108,21 @@
Lidgren.Network
-
+
+
+ False
+ Microsoft .NET Framework 4.5 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+