From 9f8f4e290e3e74fe81f634da820432f8c037d43f Mon Sep 17 00:00:00 2001 From: Regalis Date: Fri, 26 Feb 2016 22:21:00 +0200 Subject: [PATCH] Fixed propulsion applying force to _every limb except_ the ones it's supposed to, fixed pressure building up in enclosed rooms that are full of water, fixed netlobby displaying multiple votes for clients, progress on file transfer --- .../Source/Characters/AI/EnemyAIController.cs | 44 ++----- Subsurface/Source/Characters/StatusEffect.cs | 4 +- Subsurface/Source/GUI/GUI.cs | 24 +++- .../Items/Components/Holdable/Propulsion.cs | 2 +- .../Items/Components/Machines/Engine.cs | 33 +++-- Subsurface/Source/Map/Gap.cs | 47 ++++++- Subsurface/Source/Map/Hull.cs | 5 - Subsurface/Source/Map/Submarine.cs | 4 +- .../Source/Networking/FileStreamReceiver.cs | 124 ++++++++++++++++-- .../Source/Networking/FileStreamSender.cs | 13 +- Subsurface/Source/Networking/GameClient.cs | 38 ++++-- Subsurface/Source/Networking/GameServer.cs | 21 ++- .../Source/Networking/GameServerSettings.cs | 2 + Subsurface/Source/Networking/ServerLog.cs | 2 +- Subsurface/Source/Properties.cs | 24 ++-- Subsurface/Source/Screens/NetLobbyScreen.cs | 65 +++++---- Subsurface/Source/Screens/NetLobbyVoting.cs | 9 +- Subsurface/Source/Screens/ServerListScreen.cs | 2 +- Subsurface_Solution.v12.suo | Bin 907264 -> 907264 bytes 19 files changed, 330 insertions(+), 133 deletions(-) diff --git a/Subsurface/Source/Characters/AI/EnemyAIController.cs b/Subsurface/Source/Characters/AI/EnemyAIController.cs index 5dcb56769..7dccdc1a4 100644 --- a/Subsurface/Source/Characters/AI/EnemyAIController.cs +++ b/Subsurface/Source/Characters/AI/EnemyAIController.cs @@ -558,27 +558,12 @@ namespace Barotrauma bool wallAttack = (wallAttackPos != Vector2.Zero && state == AiState.Attack); - message.Write(wallAttack); - - //if (wallAttack) - //{ - // Vector2 relativeWallAttackPos = wallAttackPos - Submarine.Loaded.SimPosition; - - // message.WriteRangedSingle(MathHelper.Clamp(relativeWallAttackPos.X, -50.0f, 50.0f), -50.0f, 50.0f, 10); - // message.WriteRangedSingle(MathHelper.Clamp(relativeWallAttackPos.Y, -50.0f, 50.0f), -50.0f, 50.0f, 10); - //} - - //message.Write(Velocity.X); - //message.Write(Velocity.Y); - - //message.Write(Character.AnimController.RefLimb.SimPosition.X); - //message.Write(Character.AnimController.RefLimb.SimPosition.Y); - + //message.Write(wallAttack); message.Write(MathUtils.AngleToByte(steeringManager.WanderAngle)); - //message.WriteRangedSingle(MathHelper.Clamp(updateTargetsTimer,0.0f, UpdateTargetsInterval), 0.0f, UpdateTargetsInterval, 8); - //message.WriteRangedSingle(MathHelper.Clamp(raycastTimer, 0.0f, RaycastInterval), 0.0f, RaycastInterval, 8); - //message.WriteRangedSingle(MathHelper.Clamp(coolDownTimer, 0.0f, attackCoolDown * 2.0f), 0.0f, attackCoolDown * 2.0f, 8); + + coolDownTimer = MathHelper.Clamp(coolDownTimer, 0.0f, 30.0f); + message.WriteRangedSingle(coolDownTimer, 0.0f, 30.0f, 8); message.Write(targetEntity==null ? (ushort)0 : (targetEntity as Entity).ID); } @@ -612,9 +597,8 @@ namespace Barotrauma //targetPosition = new Vector2(message.ReadFloat(), message.ReadFloat()); wanderAngle = MathUtils.ByteToAngle(message.ReadByte()); - //updateTargetsTimer = message.ReadRangedSingle(0.0f, UpdateTargetsInterval, 8); - //raycastTimer = message.ReadRangedSingle(0.0f, RaycastInterval, 8); - //coolDownTimer = message.ReadRangedSingle(0.0f, attackCoolDown*2.0f, 8); + + coolDownTimer = message.ReadRangedSingle(0.0f, 30.0f, 8); targetID = message.ReadUInt16(); } @@ -629,14 +613,10 @@ namespace Barotrauma return; } - //wallAttackPos = newWallAttackPos; - steeringManager.WanderAngle = wanderAngle; - //this.updateTargetsTimer = updateTargetsTimer; - //this.raycastTimer = raycastTimer; - //this.coolDownTimer = coolDownTimer; - if (targetID > 0) targetEntity = Entity.FindEntityByID(targetID) as IDamageable; + if (targetID > 0) targetEntity = Entity.FindEntityByID(targetID) as IDamageable; + updateTargetsTimer = UpdateTargetsInterval; } } @@ -647,14 +627,8 @@ namespace Barotrauma //and if the target attacks the Character, the priority increases) class AITargetMemory { - //private AITarget target; private float priority; - - //public AITarget Target - //{ - // get { return target; } - //} - + public float Priority { get { return priority; } diff --git a/Subsurface/Source/Characters/StatusEffect.cs b/Subsurface/Source/Characters/StatusEffect.cs index 8f15f84fd..89f038175 100644 --- a/Subsurface/Source/Characters/StatusEffect.cs +++ b/Subsurface/Source/Characters/StatusEffect.cs @@ -10,9 +10,9 @@ namespace Barotrauma class StatusEffect { [Flags] - public enum TargetType + public enum TargetType { - This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16, UseTarget=32 + This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16, UseTarget = 32, Hull = 64 } private TargetType targetTypes; diff --git a/Subsurface/Source/GUI/GUI.cs b/Subsurface/Source/GUI/GUI.cs index a3cc951f0..b5e750d23 100644 --- a/Subsurface/Source/GUI/GUI.cs +++ b/Subsurface/Source/GUI/GUI.cs @@ -304,24 +304,34 @@ namespace Barotrauma return texture; } - public static bool DrawButton(SpriteBatch sb, Rectangle rect, string text, bool isHoldable = false) + public static bool DrawButton(SpriteBatch sb, Rectangle rect, string text, Color color, bool isHoldable = false) { - Color color = new Color(200, 200, 200); - bool clicked = false; if (rect.Contains(PlayerInput.MousePosition)) { clicked = PlayerInput.LeftButtonHeld(); - color = clicked ? new Color(100, 100, 100) : new Color(250, 250, 250); + color = clicked ? + new Color((int)(color.R * 0.8f), (int)(color.G * 0.8f), (int)(color.B * 0.8f), color.A) : + new Color((int)(color.R * 1.2f), (int)(color.G * 1.2f), (int)(color.B * 1.2f), color.A); - if (!isHoldable) - clicked = PlayerInput.LeftButtonClicked(); + if (!isHoldable) clicked = PlayerInput.LeftButtonClicked(); } DrawRectangle(sb, rect, color, true); - sb.DrawString(Font, text, new Vector2(rect.X + 10, rect.Y + 10), Color.White); + + Vector2 origin; + try + { + origin = Font.MeasureString(text)/2; + } + catch + { + origin = Vector2.Zero; + } + + sb.DrawString(Font, text, new Vector2(rect.Center.X, rect.Center.Y) , Color.White, 0.0f, origin, 1.0f, SpriteEffects.None, 0.0f); return clicked; } diff --git a/Subsurface/Source/Items/Components/Holdable/Propulsion.cs b/Subsurface/Source/Items/Components/Holdable/Propulsion.cs index 5b29339db..2a2435e52 100644 --- a/Subsurface/Source/Items/Components/Holdable/Propulsion.cs +++ b/Subsurface/Source/Items/Components/Holdable/Propulsion.cs @@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components { foreach (Limb limb in character.AnimController.Limbs) { - if (limb.WearingItems.Find(w => w.WearableComponent.Item != this.item)==null) continue; + if (limb.WearingItems.Find(w => w.WearableComponent.Item == this.item)==null) continue; limb.body.ApplyForce(propulsion); } diff --git a/Subsurface/Source/Items/Components/Machines/Engine.cs b/Subsurface/Source/Items/Components/Machines/Engine.cs index 3b01a68d4..f4b279a0f 100644 --- a/Subsurface/Source/Items/Components/Machines/Engine.cs +++ b/Subsurface/Source/Items/Components/Machines/Engine.cs @@ -48,6 +48,24 @@ namespace Barotrauma.Items.Components : base(item, element) { IsActive = true; + + var button = new GUIButton(new Rectangle(160, 50, 30, 30), "-", GUI.Style, GuiFrame); + button.OnClicked = (GUIButton btn, object obj) => + { + targetForce -= 1.0f; + item.NewComponentEvent(this, true, false); + + return true; + }; + + button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", GUI.Style, GuiFrame); + button.OnClicked = (GUIButton btn, object obj) => + { + targetForce += 1.0f; + item.NewComponentEvent(this, true, false); + + return true; + }; } public float CurrentVolume @@ -80,7 +98,7 @@ namespace Barotrauma.Items.Components voltage = 0.0f; } - + public override void DrawHUD(SpriteBatch spriteBatch, Character character) { //isActive = true; @@ -93,18 +111,7 @@ namespace Barotrauma.Items.Components //GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black, true); spriteBatch.DrawString(GUI.Font, "Force: " + (int)(targetForce) + " %", new Vector2(GuiFrame.Rect.X + 30, GuiFrame.Rect.Y + 30), Color.White); - - if (GUI.DrawButton(spriteBatch, new Rectangle(GuiFrame.Rect.X + 280, GuiFrame.Rect.Y + 80, 40, 40), "-", true)) - { - targetForce -= 1.0f; - item.NewComponentEvent(this, true, false); - } - - if (GUI.DrawButton(spriteBatch, new Rectangle(GuiFrame.Rect.X + 280, GuiFrame.Rect.Y + 30, 40, 40), "+", true)) - { - targetForce += 1.0f; - item.NewComponentEvent(this, true, false); - } + } public override void UpdateBroken(float deltaTime, Camera cam) diff --git a/Subsurface/Source/Map/Gap.cs b/Subsurface/Source/Map/Gap.cs index d483046d2..b89793040 100644 --- a/Subsurface/Source/Map/Gap.cs +++ b/Subsurface/Source/Map/Gap.cs @@ -154,9 +154,12 @@ namespace Barotrauma if (GameMain.DebugDraw) { Vector2 center = new Vector2(WorldRect.X + rect.Width / 2.0f, -(WorldRect.Y - rect.Height/ 2.0f)); - GUI.DrawLine(sb, center, center + flowForce/10.0f, Color.Red); - GUI.DrawLine(sb, center + Vector2.One * 5.0f, center + lerpedFlowForce / 10.0f + Vector2.One * 5.0f, Color.Orange); + + + GUI.DrawLine(sb, center, center + new Vector2(flowForce.X, -flowForce.Y)/10.0f, Color.Red); + + GUI.DrawLine(sb, center + Vector2.One * 5.0f, center + new Vector2(lerpedFlowForce.X, -lerpedFlowForce.Y) / 10.0f + Vector2.One * 5.0f, Color.Orange); } if (!editing || !ShowGaps) return; @@ -288,6 +291,25 @@ namespace Barotrauma } } + + + if (flowTargetHull != null && lerpedFlowForce != Vector2.Zero) + { + foreach (Character character in Character.CharacterList) + { + if (character.AnimController.CurrentHull != flowTargetHull) continue; + + foreach (Limb limb in character.AnimController.Limbs) + { + if (!limb.inWater) continue; + + float dist = Vector2.Distance(limb.WorldPosition, WorldPosition); + if (dist > lerpedFlowForce.Length()) continue; + + limb.body.ApplyForce(lerpedFlowForce / dist/10.0f); + } + } + } } @@ -444,7 +466,7 @@ namespace Barotrauma if (open > 0.0f) { - if (hull1.Volume>hull1.FullVolume && hull2.Volume>hull2.FullVolume) + if (hull1.Volume > hull1.FullVolume - Hull.MaxCompress && hull2.Volume > hull2.FullVolume - Hull.MaxCompress) { float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f; hull1.LethalPressure = avgLethality; @@ -497,21 +519,31 @@ namespace Barotrauma lowerSurface = rect.Y; if (hull1.Volume < hull1.FullVolume - Hull.MaxCompress && - hull1.Surface > -rect.Y) + hull1.Surface < rect.Y) { - float vel = (rect.Y + hull1.Surface) * 0.03f; + if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f) { + float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f; + + hull1.WaveVel[hull1.WaveY.Length - 1] += vel; hull1.WaveVel[hull1.WaveY.Length - 2] += vel; } else { + float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[0])) * 0.1f; + + hull1.WaveVel[0] += vel; hull1.WaveVel[1] += vel; } } + else + { + hull1.LethalPressure += (Submarine.Loaded != null && Submarine.Loaded.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime; + } } else { @@ -523,7 +555,12 @@ namespace Barotrauma { flowForce = new Vector2(0.0f, delta); } + if (hull1.Volume >= hull1.FullVolume - Hull.MaxCompress) + { + hull1.LethalPressure += (Submarine.Loaded != null && Submarine.Loaded.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime; + } } + } private void UpdateOxygen() diff --git a/Subsurface/Source/Map/Hull.cs b/Subsurface/Source/Map/Hull.cs index 37bce6d95..c0eb3cf8f 100644 --- a/Subsurface/Source/Map/Hull.cs +++ b/Subsurface/Source/Map/Hull.cs @@ -433,11 +433,6 @@ namespace Barotrauma update = false; } } - else - { - - LethalPressure += ( Submarine.Loaded!=null && Submarine.Loaded.AtDamageDepth) ? 100.0f*deltaTime : 10.0f * deltaTime; - } } public void Extinquish(float deltaTime, float amount, Vector2 position) diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index dbcc754f9..8c2879ee4 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -590,7 +590,7 @@ namespace Barotrauma { try { - filePaths.AddRange(Directory.GetDirectories(subDirectory)); + filePaths.AddRange(Directory.GetFiles(subDirectory).ToList()); } catch (Exception e) { @@ -604,6 +604,8 @@ namespace Barotrauma //Map savedMap = new Map(mapPath); SavedSubmarines.Add(new Submarine(path)); } + + if (GameMain.NetLobbyScreen!=null) GameMain.NetLobbyScreen.UpdateSubList(); } private XDocument OpenDoc(string file) diff --git a/Subsurface/Source/Networking/FileStreamReceiver.cs b/Subsurface/Source/Networking/FileStreamReceiver.cs index 886d5c8e4..8030dfff3 100644 --- a/Subsurface/Source/Networking/FileStreamReceiver.cs +++ b/Subsurface/Source/Networking/FileStreamReceiver.cs @@ -1,11 +1,14 @@ using Lidgren.Network; using System; using System.IO; +using System.Text.RegularExpressions; namespace Barotrauma.Networking { class FileStreamReceiver : IDisposable { + const int MaxFileSize = 1000000; + public delegate void OnFinished(FileStreamReceiver fileStreamReceiver); private OnFinished onFinished; @@ -18,12 +21,17 @@ namespace Barotrauma.Networking private string filePath; private FileTransferType fileType; - + public string FileName { get; private set; } + + public string FilePath + { + get { return filePath; } + } public ulong FileSize { @@ -35,12 +43,23 @@ namespace Barotrauma.Networking get { return received; } } + public FileTransferType FileType + { + get { return fileType; } + } + public FileTransferStatus Status { get; private set; } + public string ErrorMessage + { + get; + private set; + } + public float BytesPerSecond { get; @@ -49,13 +68,13 @@ namespace Barotrauma.Networking public float Progress { - get { return length / (float)received; } + get { return (float)received / (float)length; } } public FileStreamReceiver(NetClient client, string filePath, FileTransferType fileType, OnFinished onFinished) { - client = client; + this.client = client; this.filePath = filePath; this.fileType = fileType; @@ -78,8 +97,75 @@ namespace Barotrauma.Networking } } + private bool ValidateInitialData(byte type, string fileName, ulong fileSize) + { + if (fileSize > MaxFileSize) + { + ErrorMessage = "File too large (" + MathUtils.GetBytesReadable((long)fileSize) + ")"; + Status = FileTransferStatus.Error; + return false; + } + + if (type != (byte)fileType) + { + ErrorMessage = "Unexpected file type ''"+type+"'' (expected "+fileType+")"; + Status = FileTransferStatus.Error; + return false; + } + + if (!Regex.Match(fileName, @"^[\w\- ]+[\w\-. ]*$").Success) + { + ErrorMessage = "Illegal characters in file name ''"+fileName+"''"; + Status = FileTransferStatus.Error; + return false; + } + + switch (type) + { + case (byte)FileTransferType.Submarine: + if (Path.GetExtension(fileName) != ".sub") + { + ErrorMessage = "Wrong file extension ''" + Path.GetExtension(fileName)+"''! (Expected .sub)"; + + Status = FileTransferStatus.Error; + return false; + } + break; + } + + return true; + } + + public void DeleteFile() + { + string file = Path.Combine(filePath, FileName); + + writeStream.Flush(); + writeStream.Close(); + writeStream.Dispose(); + writeStream = null; + + Status = FileTransferStatus.Canceled; + + if (File.Exists(file)) + { + try + { + File.Delete(file); + } + catch (Exception e) + { + DebugConsole.ThrowError("Couldn't delete file ''" + file + "''!", e); + } + } + } + private void TryReadMessage(NetIncomingMessage inc) { + if (Status == FileTransferStatus.Error || + Status == FileTransferStatus.Finished || + Status == FileTransferStatus.Canceled) return; + //int chunkLen = inc.LengthBytes; if (length == 0) { @@ -90,14 +176,17 @@ namespace Barotrauma.Networking } byte fileTypeByte = inc.ReadByte(); - if (fileTypeByte != (byte)fileType) - { - Status = FileTransferStatus.Error; - return; - } length = inc.ReadUInt64(); FileName = inc.ReadString(); + + if (!ValidateInitialData(fileTypeByte, FileName, length)) + { + Status = FileTransferStatus.Error; + if (onFinished != null) onFinished(this); + return; + } + writeStream = new FileStream(Path.Combine(filePath, FileName), FileMode.Create, FileAccess.Write, FileShare.None); timeStarted = Environment.TickCount; @@ -106,6 +195,15 @@ namespace Barotrauma.Networking return; } + + if (received + (ulong)inc.LengthBytes > length*1.1f) + { + ErrorMessage = "Receiving more data than expected (> " + MathUtils.GetBytesReadable((long)(received + (ulong)inc.LengthBytes)) + ")"; + Status = FileTransferStatus.Error; + if (onFinished != null) onFinished(this); + return; + } + byte[] all = inc.ReadBytes(inc.LengthBytes - inc.PositionInBytes); received += (ulong)all.Length; writeStream.Write(all, 0, all.Length); @@ -117,6 +215,7 @@ namespace Barotrauma.Networking Status = FileTransferStatus.Receiving; + if (received >= length) { Status = FileTransferStatus.Finished; @@ -133,9 +232,12 @@ namespace Barotrauma.Networking protected virtual void Dispose(bool disposing) { - writeStream.Flush(); - writeStream.Close(); - writeStream.Dispose(); + if (writeStream != null) + { + writeStream.Flush(); + writeStream.Close(); + writeStream.Dispose(); + } } } diff --git a/Subsurface/Source/Networking/FileStreamSender.cs b/Subsurface/Source/Networking/FileStreamSender.cs index 24eb282da..86c2caf4b 100644 --- a/Subsurface/Source/Networking/FileStreamSender.cs +++ b/Subsurface/Source/Networking/FileStreamSender.cs @@ -6,7 +6,7 @@ namespace Barotrauma.Networking { enum FileTransferStatus { - NotStarted, Sending, Receiving, Finished, Error + NotStarted, Sending, Receiving, Finished, Error, Canceled } enum FileTransferType @@ -22,6 +22,8 @@ namespace Barotrauma.Networking private byte[] tempBuffer; private NetConnection connection; + float waitTimer; + private FileTransferType fileType; @@ -56,7 +58,7 @@ namespace Barotrauma.Networking chunkLen = connection.Peer.Configuration.MaximumTransmissionUnit - 100; tempBuffer = new byte[chunkLen]; sentOffset = 0; - + FileName = fileName; this.fileType = fileType; @@ -64,10 +66,13 @@ namespace Barotrauma.Networking Status = FileTransferStatus.NotStarted; } - public void Update() + public void Update(float deltaTime) { if (inputStream == null) return; + waitTimer -= deltaTime; + if (waitTimer > 0.0f) return; + if (!connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) return; // send another part of the file! @@ -98,6 +103,8 @@ namespace Barotrauma.Networking connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, 1); sentOffset += sendBytes; + waitTimer = connection.AverageRoundtripTime + 0.05f; + //Program.Output("Sent " + m_sentOffset + "/" + m_inputStream.Length + " bytes to " + m_connection); if (remaining - sendBytes <= 0) diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 29ca1f730..7b938b5d6 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -697,6 +697,25 @@ namespace Barotrauma.Networking { base.Draw(spriteBatch); + if (fileStreamReceiver != null && + (fileStreamReceiver.Status == FileTransferStatus.Receiving || fileStreamReceiver.Status == FileTransferStatus.NotStarted)) + { + Vector2 pos = Screen.Selected == GameMain.NetLobbyScreen ? + new Vector2(GameMain.NetLobbyScreen.SubList.Rect.X, GameMain.NetLobbyScreen.SubList.Rect.Bottom+5) : new Vector2(GameMain.GraphicsWidth / 2 - 200, 10); + + GUI.DrawString(spriteBatch, pos, "Downloading " + fileStreamReceiver.FileName, Color.White); + GUI.DrawString(spriteBatch, pos + Vector2.UnitX*300, + MathUtils.GetBytesReadable((long)fileStreamReceiver.Received) + " / " + MathUtils.GetBytesReadable((long)fileStreamReceiver.FileSize), Color.White); + GUI.DrawProgressBar(spriteBatch, new Vector2(pos.X, -pos.Y - 20), new Vector2(300, 15), fileStreamReceiver.Progress, Color.Green); + + if (GUI.DrawButton(spriteBatch, new Rectangle((int)pos.X + 310, (int)pos.Y + 20, 100, 15), "Cancel", new Color(0.88f, 0.25f, 0.15f, 0.8f))) + { + fileStreamReceiver.DeleteFile(); + fileStreamReceiver.Dispose(); + fileStreamReceiver = null; + } + } + if (!GameMain.DebugDraw) return; int width = 200, height = 300; @@ -711,27 +730,28 @@ namespace Barotrauma.Networking spriteBatch.DrawString(GUI.SmallFont, "Sent bytes: " + client.Statistics.SentBytes, new Vector2(x + 10, y + 75), Color.White); spriteBatch.DrawString(GUI.SmallFont, "Sent packets: " + client.Statistics.SentPackets, new Vector2(x + 10, y + 90), Color.White); - if (fileStreamReceiver!=null) - { - GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 100, 20), "Downloading "+fileStreamReceiver.FileName, Color.White); - GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 100, 20), - MathUtils.GetBytesReadable((long)fileStreamReceiver.Received)+" / "+MathUtils.GetBytesReadable((long)fileStreamReceiver.FileSize), Color.White); - GUI.DrawProgressBar(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 100, 20), new Vector2(200, 15), fileStreamReceiver.Progress, Color.Green); - } } private void OnFileReceived(FileStreamReceiver receiver) { if (receiver.Status == FileTransferStatus.Error) { - + new GUIMessageBox("Error while receiving file from server", receiver.ErrorMessage); + receiver.DeleteFile(); + } else if (receiver.Status == FileTransferStatus.Finished) { new GUIMessageBox("Download finished", "File ''"+receiver.FileName+"'' was downloaded succesfully."); + + switch (receiver.FileType) + { + case FileTransferType.Submarine: + Submarine.Preload(); + break; + } } - receiver.Dispose(); fileStreamReceiver = null; } diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index 94ebb4ee0..b970c6c9d 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -312,9 +312,9 @@ namespace Barotrauma.Networking foreach (Client c in ConnectedClients) { - if (c.FileStreamSender!=null) + if (c.FileStreamSender!=null && Rand.Range(0.0f, 1.0f)<0.01f) { - c.FileStreamSender.Update(); + c.FileStreamSender.Update(deltaTime); if (c.FileStreamSender.Status == FileTransferStatus.Finished || c.FileStreamSender.Status == FileTransferStatus.Error) @@ -516,8 +516,17 @@ namespace Barotrauma.Networking ReadCharacterData(inc); break; case (byte)PacketTypes.RequestFile: - string fileName = inc.ReadString(); + + if (!allowFileTransfers) + { + var outmsg = server.CreateMessage(); + outmsg.Write((byte)PacketTypes.RequestFile); + outmsg.Write(false); + break; + } + byte fileType = inc.ReadByte(); + string fileName = inc.ReadString(); switch (fileType) { @@ -552,11 +561,11 @@ namespace Barotrauma.Networking case (byte)PacketTypes.Vote: Voting.RegisterVote(inc, ConnectedClients); - if (Voting.AllowEndVoting && EndVoteMax > 0 && + if (Voting.AllowEndVoting && EndVoteMax > 0 && ((float)EndVoteCount / (float)EndVoteMax) >= EndVoteRequiredRatio) { - Log("Ending round by votes ("+EndVoteCount+"/"+(EndVoteMax-EndVoteCount)+")", Color.Cyan); - EndButtonHit(null,null); + Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", Color.Cyan); + EndButtonHit(null, null); } break; case (byte)PacketTypes.RequestNetLobbyUpdate: diff --git a/Subsurface/Source/Networking/GameServerSettings.cs b/Subsurface/Source/Networking/GameServerSettings.cs index e945fe803..afeb6fb39 100644 --- a/Subsurface/Source/Networking/GameServerSettings.cs +++ b/Subsurface/Source/Networking/GameServerSettings.cs @@ -45,6 +45,8 @@ namespace Barotrauma.Networking private bool saveServerLogs = true; + private bool allowFileTransfers = true; + public bool AutoRestart { get { return (ConnectedClients.Count == 0) ? false : autoRestart; } diff --git a/Subsurface/Source/Networking/ServerLog.cs b/Subsurface/Source/Networking/ServerLog.cs index 4b02144de..19c0986df 100644 --- a/Subsurface/Source/Networking/ServerLog.cs +++ b/Subsurface/Source/Networking/ServerLog.cs @@ -9,7 +9,7 @@ namespace Barotrauma.Networking { class ServerLog { - const int LinesPerFile = 300; + const int LinesPerFile = 800; public const string SavePath = "ServerLogs"; diff --git a/Subsurface/Source/Properties.cs b/Subsurface/Source/Properties.cs index a2fae550f..dde192783 100644 --- a/Subsurface/Source/Properties.cs +++ b/Subsurface/Source/Properties.cs @@ -207,6 +207,11 @@ namespace Barotrauma return dictionary; } + public static Dictionary InitProperties(object obj) + { + return InitProperties(obj, null); + } + public static Dictionary InitProperties(object obj, XElement element) { var properties = TypeDescriptor.GetProperties(obj.GetType()).Cast(); @@ -226,16 +231,19 @@ namespace Barotrauma } } - //go through all the attributes in the xml element - //and set the value of the matching property if it is initializable - foreach (XAttribute attribute in element.Attributes()) + if (element!=null) { - ObjectProperty property = null; - if (!dictionary.TryGetValue(attribute.Name.ToString().ToLower(), out property)) continue; - if (!property.Attributes.OfType().Any()) continue; - property.TrySetValue(attribute.Value); + //go through all the attributes in the xml element + //and set the value of the matching property if it is initializable + foreach (XAttribute attribute in element.Attributes()) + { + ObjectProperty property = null; + if (!dictionary.TryGetValue(attribute.Name.ToString().ToLower(), out property)) continue; + if (!property.Attributes.OfType().Any()) continue; + property.TrySetValue(attribute.Value); + } } - + return dictionary; } diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index 1cd4d37fe..215131455 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -187,24 +187,7 @@ namespace Barotrauma voteText.UserData = "subvotes"; voteText.Visible = false; - if (Submarine.SavedSubmarines.Count > 0) - { - foreach (Submarine sub in Submarine.SavedSubmarines) - { - GUITextBlock textBlock = new GUITextBlock( - new Rectangle(0, 0, 0, 25), - sub.Name, GUI.Style, - Alignment.Left, Alignment.Left, - subList); - textBlock.Padding = new Vector4(10.0f, 0.0f, 0.0f, 0.0f); - textBlock.UserData = sub; - } - } - else - { - DebugConsole.ThrowError("No saved submarines found!"); - return; - } + UpdateSubList(); columnX += columnWidth + 20; @@ -213,8 +196,7 @@ namespace Barotrauma new GUITextBlock(new Rectangle(columnX, 120, 0, 30), "Game mode: ", GUI.Style, infoFrame); modeList = new GUIListBox(new Rectangle(columnX, 150, columnWidth, infoFrame.Rect.Height - 150 - 80), GUI.Style, infoFrame); modeList.OnSelected = VotableClicked; - - + voteText = new GUITextBlock(new Rectangle(columnX, 120, columnWidth, 30), "Votes: ", GUI.Style, Alignment.TopLeft, Alignment.TopRight, infoFrame); voteText.UserData = "modevotes"; voteText.Visible = false; @@ -533,6 +515,33 @@ namespace Barotrauma return true; } + public void UpdateSubList() + { + if (subList == null) return; + + subList.ClearChildren(); + + if (Submarine.SavedSubmarines.Count > 0) + { + foreach (Submarine sub in Submarine.SavedSubmarines) + { + GUITextBlock textBlock = new GUITextBlock( + new Rectangle(0, 0, 0, 25), + sub.Name, GUI.Style, + Alignment.Left, Alignment.Left, + subList); + textBlock.Padding = new Vector4(10.0f, 0.0f, 0.0f, 0.0f); + textBlock.UserData = sub; + } + } + else + { + DebugConsole.ThrowError("No saved submarines found!"); + return; + } + + } + public bool VotableClicked(GUIComponent component, object userData) { if (GameMain.Client == null) return false; @@ -921,7 +930,7 @@ namespace Barotrauma if (sub == null) { var requestFileBox = new GUIMessageBox("Submarine not found!", "The submarine ''" + subName + "'' has been selected by the server. " - +"Matching file not found in your map folder. Do you want to download the file from the server host?", new string[] { "Yes", "No" }); + +"Matching file not found in your map folder. Do you want to download the file from the server host?", new string[] { "Yes", "No" }, 400, 300); requestFileBox.Buttons[0].UserData = subName; requestFileBox.Buttons[0].OnClicked += requestFileBox.Close; requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => @@ -938,10 +947,20 @@ namespace Barotrauma { if (sub.MD5Hash.Hash != md5Hash) { - new GUIMessageBox("Submarine not found!", + var requestFileBox = new GUIMessageBox("Submarine not found!", "Your version of the map file ''" + sub.Name + "'' doesn't match the server's version!" +"\nYour file: " + sub.Name + "(MD5 hash : " + sub.MD5Hash.Hash + ")" - +"\nServer's file: " + subName + "(MD5 hash : " + md5Hash + ")"); + +"\nServer's file: " + subName + "(MD5 hash : " + md5Hash + ")\n" + +"Do you want to download the file from the server host?", new string[] { "Yes", "No" }, 400, 300); + requestFileBox.Buttons[0].UserData = subName; + requestFileBox.Buttons[0].OnClicked += requestFileBox.Close; + requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => + { + GameMain.Client.RequestFile(userdata.ToString(), FileTransferType.Submarine); + return true; + }; + requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; + return false; } else diff --git a/Subsurface/Source/Screens/NetLobbyVoting.cs b/Subsurface/Source/Screens/NetLobbyVoting.cs index d69bd5f92..acafcfb9e 100644 --- a/Subsurface/Source/Screens/NetLobbyVoting.cs +++ b/Subsurface/Source/Screens/NetLobbyVoting.cs @@ -128,7 +128,7 @@ namespace Barotrauma voteText.UserData = "votes"; } - voteText.Text = votes.ToString(); + voteText.Text = votes == 0 ? "" : votes.ToString(); } } @@ -233,6 +233,11 @@ namespace Barotrauma AllowSubVoting = msg.ReadBoolean(); if (allowSubVoting) { + foreach (Submarine sub in Submarine.SavedSubmarines) + { + SetVoteText(GameMain.NetLobbyScreen.SubList, sub, 0); + } + int votableCount = msg.ReadByte(); for (int i = 0; i < votableCount; i++) { @@ -252,7 +257,7 @@ namespace Barotrauma int votes = msg.ReadByte(); string modeName = msg.ReadString(); GameModePreset mode = GameModePreset.list.Find(m => m.Name == modeName); - SetVoteText(GameMain.NetLobbyScreen.SubList, mode, votes); + SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes); } } diff --git a/Subsurface/Source/Screens/ServerListScreen.cs b/Subsurface/Source/Screens/ServerListScreen.cs index 13a49f10e..26a57b4c6 100644 --- a/Subsurface/Source/Screens/ServerListScreen.cs +++ b/Subsurface/Source/Screens/ServerListScreen.cs @@ -169,7 +169,7 @@ namespace Barotrauma string maxPlayersStr = (arguments.Length > 5) ? arguments[5] : ""; - string hasPassWordStr = (arguments.Length > 5) ? arguments[5] : ""; + string hasPassWordStr = (arguments.Length > 6) ? arguments[6] : ""; var serverFrame = new GUIFrame(new Rectangle(0, 0, 0, 20), (i % 2 == 0) ? Color.Transparent : Color.White * 0.2f, null, serverList); serverFrame.UserData = IP + ":" + port; diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index a02bd522ab502f7e976221c4486133cd075df81a..26a27c602f22cebfec34d68c4979a3770a3e4918 100644 GIT binary patch delta 14405 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 delta 14788 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