diff --git a/Subsurface/Content/Items/Artifacts/artifacts.xml b/Subsurface/Content/Items/Artifacts/artifacts.xml index 9b81e1a74..f195a8fdc 100644 --- a/Subsurface/Content/Items/Artifacts/artifacts.xml +++ b/Subsurface/Content/Items/Artifacts/artifacts.xml @@ -16,7 +16,9 @@ - + + + - + diff --git a/Subsurface/Content/LargeFont.xnb b/Subsurface/Content/LargeFont.xnb index 3f9a445ef..9982c5a6a 100644 Binary files a/Subsurface/Content/LargeFont.xnb and b/Subsurface/Content/LargeFont.xnb differ diff --git a/Subsurface/Content/Particles/ParticlePrefabs.xml b/Subsurface/Content/Particles/ParticlePrefabs.xml index e8db4ff60..83cce1985 100644 --- a/Subsurface/Content/Particles/ParticlePrefabs.xml +++ b/Subsurface/Content/Particles/ParticlePrefabs.xml @@ -115,8 +115,8 @@ sizechangemin="0.1,0.1" sizechangemax="0.2,0.2" startrotationmin ="-20.0" startrotationmax="20" startcolor="1.0, 1.0, 1.0" startalpha="1.0" - colorchange="-0.6, -1.0, -4.2, -0.8" - lifetime="5.0" + colorchange="-0.9, -1.5, -6.3, -1.2" + lifetime="2.5" growtime ="0.05" drawtarget="air" collideswithwalls="true" diff --git a/Subsurface/Content/SmallFont.xnb b/Subsurface/Content/SmallFont.xnb index 31fcd8392..ba3695382 100644 Binary files a/Subsurface/Content/SmallFont.xnb and b/Subsurface/Content/SmallFont.xnb differ diff --git a/Subsurface/Content/SpriteFont1.xnb b/Subsurface/Content/SpriteFont1.xnb index 37d6342fc..5723d1238 100644 Binary files a/Subsurface/Content/SpriteFont1.xnb and b/Subsurface/Content/SpriteFont1.xnb differ diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index e0458869c..9db3d668a 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -53,6 +53,8 @@ namespace Barotrauma public readonly bool IsNetworkPlayer; + private bool networkUpdateSent; + private CharacterInventory inventory; public float LastNetworkUpdate; @@ -955,6 +957,17 @@ namespace Barotrauma if (isDead) return; + if (networkUpdateSent) + { + foreach (Key key in keys) + { + key.DequeueHit(); + key.DequeueHeld(); + } + + networkUpdateSent = true; + } + if (needsAir) { bool protectedFromPressure = PressureProtection > 0.0f; @@ -1421,9 +1434,9 @@ namespace Barotrauma return true; case NetworkEventType.EntityUpdate: - message.Write(keys[(int)InputType.Use].DequeueHeld); + message.Write(keys[(int)InputType.Use].GetHeldQueue); - bool secondaryHeld = keys[(int)InputType.Aim].DequeueHeld; + bool secondaryHeld = keys[(int)InputType.Aim].GetHeldQueue; message.Write(secondaryHeld); message.Write(keys[(int)InputType.Left].Held); @@ -1469,6 +1482,8 @@ namespace Barotrauma message.Write(SimPosition.X); message.Write(SimPosition.Y); + networkUpdateSent = true; + return true; default: #if DEBUG diff --git a/Subsurface/Source/Characters/StatusEffect.cs b/Subsurface/Source/Characters/StatusEffect.cs index 89f038175..48b95af00 100644 --- a/Subsurface/Source/Characters/StatusEffect.cs +++ b/Subsurface/Source/Characters/StatusEffect.cs @@ -273,20 +273,21 @@ namespace Barotrauma if (disableDeltaTime) deltaTime = 1.0f; Type type = value.GetType(); - if (type == typeof(float)) + if (type == typeof(float) || + (type == typeof(int) && property.GetValue().GetType() == typeof(float))) { - float floatValue = (float)value * deltaTime; + float floatValue = Convert.ToSingle(value) * deltaTime; if (!setValue) floatValue += (float)property.GetValue(); property.TrySetValue(floatValue); } - else if (type == typeof(int)) + else if (type == typeof(int) && value.GetType()==typeof(int)) { int intValue = (int)((int)value * deltaTime); if (!setValue) intValue += (int)property.GetValue(); property.TrySetValue(intValue); } - else if (type == typeof(bool)) + else if (type == typeof(bool) && value.GetType() == typeof(bool)) { property.TrySetValue((bool)value); } @@ -294,6 +295,11 @@ namespace Barotrauma { property.TrySetValue((string)value); } + else + { + DebugConsole.ThrowError("Couldn't apply value "+value.ToString()+" ("+type+") to property ''"+property.Name+"'' ("+property.GetValue().GetType()+")! " + +"Make sure the type of the value set in the config files matches the type of the property."); + } } public static void UpdateAll(float deltaTime) diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index d5d4b87db..e01da3187 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -381,7 +381,8 @@ namespace Barotrauma DebugConsole.ThrowError("Illegal symbols in filename (../)"); return; } - if (Submarine.SaveCurrent(fileName +".sub")) NewMessage("map saved", Color.Green); + + if (Submarine.SaveCurrent(System.IO.Path.Combine(Submarine.SavePath, fileName +".sub"))) NewMessage("map saved", Color.Green); Submarine.Loaded.CheckForErrors(); break; @@ -493,6 +494,12 @@ namespace Barotrauma DebugConsole.NewMessage("Deleted TutorialSub from the submarine folder", Color.Green); } + if (System.IO.File.Exists(GameServer.SettingsFile)) + { + System.IO.File.Delete(GameServer.SettingsFile); + DebugConsole.NewMessage("Deleted server settings", Color.Green); + } + if (System.IO.File.Exists("crashreport.txt")) { System.IO.File.Delete("crashreport.txt"); diff --git a/Subsurface/Source/GameMain.cs b/Subsurface/Source/GameMain.cs index dd83c7cb0..0d9117ada 100644 --- a/Subsurface/Source/GameMain.cs +++ b/Subsurface/Source/GameMain.cs @@ -157,8 +157,8 @@ namespace Barotrauma Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Character)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Item)); - - //Event.Init("Content/randomevents.xml"); + Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent)); + Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull)); } /// @@ -353,7 +353,7 @@ namespace Barotrauma protected override void OnExiting(object sender, EventArgs args) { if (NetworkMember != null) NetworkMember.Disconnect(); - + base.OnExiting(sender, args); } diff --git a/Subsurface/Source/Items/CharacterInventory.cs b/Subsurface/Source/Items/CharacterInventory.cs index b7c9697d9..3fd75bb56 100644 --- a/Subsurface/Source/Items/CharacterInventory.cs +++ b/Subsurface/Source/Items/CharacterInventory.cs @@ -172,6 +172,29 @@ namespace Barotrauma combined = true; } + //if moving the item between slots in the same inventory + else if (item.ParentInventory == this) + { + int currentIndex = Array.IndexOf(Items, item); + + Item existingItem = Items[index]; + + Items[currentIndex] = null; + Items[index] = null; + //if the item in the slot can be moved to the slot of the moved item + if (TryPutItem(existingItem, currentIndex, false) && + TryPutItem(item, index, false)) + { + new Networking.NetworkEvent(Networking.NetworkEventType.InventoryUpdate, Owner.ID, true, true); + } + else + { + //swapping the items failed -> move them back to where they were + TryPutItem(item, currentIndex, false); + TryPutItem(existingItem, index, false); + } + + } return combined; } diff --git a/Subsurface/Source/Items/Item.cs b/Subsurface/Source/Items/Item.cs index ac9d4ea0f..fe5e3c6cf 100644 --- a/Subsurface/Source/Items/Item.cs +++ b/Subsurface/Source/Items/Item.cs @@ -487,7 +487,8 @@ namespace Barotrauma public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null) { if (condition == 0.0f && effect.type != ActionType.OnBroken) return; - + if (effect.type != type) return; + bool hasTargets = (effect.TargetNames == null); Item[] containedItems = ContainedItems; @@ -501,10 +502,10 @@ namespace Barotrauma List targets = new List(); - if (containedItems!=null) + if (containedItems != null) { if (effect.Targets.HasFlag(StatusEffect.TargetType.Contained)) - { + { foreach (Item containedItem in containedItems) { if (containedItem == null) continue; @@ -528,9 +529,14 @@ namespace Barotrauma } } - if (!hasTargets) return; - + + if (effect.Targets.HasFlag(StatusEffect.TargetType.Hull) && CurrentHull != null) + { + targets.Add(CurrentHull); + } + + if (effect.Targets.HasFlag(StatusEffect.TargetType.This)) { foreach (var pobject in AllPropertyObjects) @@ -550,7 +556,7 @@ namespace Barotrauma // effect.Apply(type, deltaTime, container); // //container.ApplyStatusEffect(effect, type, deltaTime, container); //} - + effect.Apply(type, deltaTime, this, targets); } @@ -575,7 +581,10 @@ namespace Barotrauma public override void Update(Camera cam, float deltaTime) - { + { + + ApplyStatusEffects(ActionType.Always, deltaTime, null); + foreach (ItemComponent ic in components) { if (ic.Parent != null) ic.IsActive = ic.Parent.IsActive; diff --git a/Subsurface/Source/Map/FireSource.cs b/Subsurface/Source/Map/FireSource.cs index 24898c8bf..218cf4709 100644 --- a/Subsurface/Source/Map/FireSource.cs +++ b/Subsurface/Source/Map/FireSource.cs @@ -116,11 +116,13 @@ namespace Barotrauma if (!fireSources[i].CheckOverLap(fireSources[j])) continue; - fireSources[j].position.X = Math.Min(fireSources[i].position.X, fireSources[j].position.X); + float leftEdge = Math.Min(fireSources[i].position.X, fireSources[j].position.X); fireSources[j].size.X = Math.Max(fireSources[i].position.X + fireSources[i].size.X, fireSources[j].position.X + fireSources[j].size.X) - - fireSources[j].position.X; + - leftEdge; + + fireSources[j].position.X = leftEdge; fireSources[i].Remove(); } @@ -140,7 +142,7 @@ namespace Barotrauma public void Update(float deltaTime) { - float count = Rand.Range(0.0f, (float)Math.Sqrt(size.X)/3.0f); + float count = Rand.Range(0.0f, size.X/50.0f); if (fireSoundBasic != null) { @@ -173,10 +175,10 @@ namespace Barotrauma spawnPos, speed, 0.0f, hull); if (particle == null) continue; - + if (Rand.Int(20) == 1) particle.OnChangeHull = OnChangeHull; - particle.Size *= MathHelper.Clamp(size.X/100.0f * Math.Max(hull.Oxygen/hull.FullVolume, 0.4f), 0.5f, 4.0f); + particle.Size *= MathHelper.Clamp(size.X/60.0f * Math.Max(hull.Oxygen/hull.FullVolume, 0.4f), 0.5f, 3.0f); if (size.X < 100.0f) continue; @@ -218,7 +220,7 @@ namespace Barotrauma if (particleHull.FireSources.Find(fs => pos.X > fs.position.X-100.0f && pos.X < fs.position.X+fs.size.X+100.0f)!=null) return; - new FireSource(new Vector2(pos.X, particleHull.Rect.Y-particleHull.Rect.Height + 5.0f)); + new FireSource(new Vector2(pos.X, particleHull.WorldRect.Y-particleHull.Rect.Height + 5.0f)); } private void DamageCharacters(float deltaTime) diff --git a/Subsurface/Source/Map/Hull.cs b/Subsurface/Source/Map/Hull.cs index c0eb3cf8f..de63e87d8 100644 --- a/Subsurface/Source/Map/Hull.cs +++ b/Subsurface/Source/Map/Hull.cs @@ -11,7 +11,7 @@ using Lidgren.Network; namespace Barotrauma { - class Hull : MapEntity + class Hull : MapEntity, IPropertyObject { public static List hullList = new List(); private static EntityGrid entityGrid; @@ -19,7 +19,7 @@ namespace Barotrauma public static bool ShowHulls = true; public static bool EditWater, EditFire; - + public static WaterRenderer renderer; private List fireSources; @@ -36,7 +36,11 @@ namespace Barotrauma //how much excess water the room can contain (= more than the volume of the room) public const float MaxCompress = 10000f; - public readonly Dictionary properties; + public readonly Dictionary properties; + public Dictionary ObjectProperties + { + get { return properties; } + } private float lethalPressure; @@ -161,9 +165,7 @@ namespace Barotrauma fireSources = new List(); - properties = TypeDescriptor.GetProperties(GetType()) - .Cast() - .ToDictionary(pr => pr.Name); + properties = ObjectProperty.GetProperties(this); int arraySize = (rectangle.Width / WaveWidth + 1); waveY = new float[arraySize]; diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index c1cc4d92d..1fd6db4ca 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -511,17 +511,17 @@ namespace Barotrauma return true; } - public static bool SaveCurrent(string fileName) + public static bool SaveCurrent(string filePath) { if (loaded==null) { - loaded = new Submarine(fileName); + loaded = new Submarine(filePath); // return; } - loaded.filePath = SavePath + System.IO.Path.DirectorySeparatorChar + fileName; + loaded.filePath = filePath; - return loaded.SaveAs(SavePath+System.IO.Path.DirectorySeparatorChar+fileName); + return loaded.SaveAs(filePath); } public void CheckForErrors() diff --git a/Subsurface/Source/Networking/FileStreamReceiver.cs b/Subsurface/Source/Networking/FileStreamReceiver.cs index 89f8c32fd..2fc14244b 100644 --- a/Subsurface/Source/Networking/FileStreamReceiver.cs +++ b/Subsurface/Source/Networking/FileStreamReceiver.cs @@ -21,7 +21,7 @@ namespace Barotrauma.Networking private string downloadFolder; - private FileTransferType fileType; + private FileTransferMessageType fileType; public string FileName { @@ -39,7 +39,7 @@ namespace Barotrauma.Networking get { return received; } } - public FileTransferType FileType + public FileTransferMessageType FileType { get { return fileType; } } @@ -67,7 +67,7 @@ namespace Barotrauma.Networking get { return (float)received / (float)length; } } - public FileStreamReceiver(NetClient client, string filePath, FileTransferType fileType, OnFinished onFinished) + public FileStreamReceiver(NetClient client, string filePath, FileTransferMessageType fileType, OnFinished onFinished) { this.client = client; @@ -116,7 +116,7 @@ namespace Barotrauma.Networking switch (type) { - case (byte)FileTransferType.Submarine: + case (byte)FileTransferMessageType.Submarine: if (Path.GetExtension(fileName) != ".sub") { ErrorMessage = "Wrong file extension ''" + Path.GetExtension(fileName) + "''! (Expected .sub)"; @@ -163,9 +163,12 @@ namespace Barotrauma.Networking Status == FileTransferStatus.Finished || Status == FileTransferStatus.Canceled) return; + byte transferMessageType = inc.ReadByte(); + //int chunkLen = inc.LengthBytes; if (length == 0) { + if (transferMessageType != (byte)FileTransferMessageType.Initiate) return; if (!string.IsNullOrWhiteSpace(downloadFolder) && !Directory.Exists(downloadFolder)) { @@ -174,6 +177,7 @@ namespace Barotrauma.Networking byte fileTypeByte = inc.ReadByte(); + length = inc.ReadUInt64(); FileName = inc.ReadString(); @@ -233,7 +237,7 @@ namespace Barotrauma.Networking { switch (fileType) { - case FileTransferType.Submarine: + case FileTransferMessageType.Submarine: string file = Path.Combine(downloadFolder, FileName); Stream stream = null; @@ -255,16 +259,20 @@ namespace Barotrauma.Networking try { - stream.Position = 0; - var doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file); - stream.Close(); - stream.Dispose(); + stream.Position = 0; + var doc = XDocument.Load(stream); } catch { + stream.Close(); + stream.Dispose(); + ErrorMessage = "Failed to parse submarine file ''"+file+"''!"; return false; } + + stream.Close(); + stream.Dispose(); break; } diff --git a/Subsurface/Source/Networking/FileStreamSender.cs b/Subsurface/Source/Networking/FileStreamSender.cs index 8832ba541..6a7ebedde 100644 --- a/Subsurface/Source/Networking/FileStreamSender.cs +++ b/Subsurface/Source/Networking/FileStreamSender.cs @@ -9,13 +9,15 @@ namespace Barotrauma.Networking NotStarted, Sending, Receiving, Finished, Error, Canceled } - enum FileTransferType + enum FileTransferMessageType { - Unknown, Submarine, Cancel + Unknown, Initiate, Submarine, Cancel } class FileStreamSender : IDisposable { + public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0); + private FileStream inputStream; private int sentOffset; private int chunkLen; @@ -24,8 +26,9 @@ namespace Barotrauma.Networking float waitTimer; + DateTime startingTime; - private FileTransferType fileType; + private FileTransferMessageType fileType; public FileTransferStatus Status { @@ -39,6 +42,12 @@ namespace Barotrauma.Networking private set; } + public string FilePath + { + get; + private set; + } + public float Progress { get { return inputStream == null ? 0.0f : (float)sentOffset / (float)inputStream.Length; } @@ -54,30 +63,45 @@ namespace Barotrauma.Networking get { return inputStream == null ? 0 : inputStream.Length; } } - public static FileStreamSender Create(NetConnection conn, string fileName, FileTransferType fileType) + public static FileStreamSender Create(NetConnection conn, string filePath, FileTransferMessageType fileType) { - if (!File.Exists(fileName)) + if (!File.Exists(filePath)) { - DebugConsole.ThrowError("Sending a file failed. File ''"+fileName+"'' not found."); + DebugConsole.ThrowError("Sending a file failed. File ''"+filePath+"'' not found."); return null; } - return new FileStreamSender(conn, fileName, fileType); + FileStreamSender sender = null; + + try + { + sender = new FileStreamSender(conn, filePath, fileType); + } + + catch (Exception e) + { + DebugConsole.ThrowError("Couldn't open file ''"+filePath+"''",e); + } + + return sender; } - private FileStreamSender(NetConnection conn, string fileName, FileTransferType fileType) + private FileStreamSender(NetConnection conn, string filePath, FileTransferMessageType fileType) { connection = conn; - inputStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); + inputStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); chunkLen = connection.Peer.Configuration.MaximumTransmissionUnit - 100; tempBuffer = new byte[chunkLen]; sentOffset = 0; - - FileName = fileName; + + FilePath = filePath; + FileName = Path.GetFileName(filePath); this.fileType = fileType; Status = FileTransferStatus.NotStarted; + + startingTime = DateTime.Now; } public void Update(float deltaTime) @@ -87,6 +111,12 @@ namespace Barotrauma.Networking Status == FileTransferStatus.Error || Status == FileTransferStatus.Finished) return; + if (DateTime.Now > startingTime + MaxTransferDuration) + { + CancelTransfer(); + return; + } + waitTimer -= deltaTime; if (waitTimer > 0.0f) return; @@ -105,6 +135,7 @@ namespace Barotrauma.Networking // first message; send length, chunk length and file name message = connection.Peer.CreateMessage(sendBytes + 8 + 1); message.Write((byte)PacketTypes.FileStream); + message.Write((byte)FileTransferMessageType.Initiate); message.Write((byte)fileType); message.Write((ulong)inputStream.Length); message.Write(Path.GetFileName(inputStream.Name)); @@ -115,12 +146,13 @@ namespace Barotrauma.Networking message = connection.Peer.CreateMessage(sendBytes + 8 + 1); message.Write((byte)PacketTypes.FileStream); + message.Write((byte)fileType); message.Write(tempBuffer, 0, sendBytes); connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, 1); sentOffset += sendBytes; - waitTimer = connection.AverageRoundtripTime + 0.05f; + waitTimer = connection.AverageRoundtripTime; //Program.Output("Sent " + m_sentOffset + "/" + m_inputStream.Length + " bytes to " + m_connection); diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 486858c4d..6ff789457 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -38,6 +38,11 @@ namespace Barotrauma.Networking get { return otherClients; } } + public string ActiveFileTransferName + { + get { return (fileStreamReceiver == null || fileStreamReceiver.Status == FileTransferStatus.Finished) ? "" : fileStreamReceiver.FileName; } + } + public GameClient(string newName) { endRoundButton = new GUITickBox(new Rectangle(GameMain.GraphicsWidth - 170, 20, 20, 20), "End round", Alignment.TopLeft, inGameHUD); @@ -479,7 +484,20 @@ namespace Barotrauma.Networking break; case (byte)PacketTypes.RequestFile: - new GUIMessageBox("Couldn't the file from the server", "Sharing files has been disabled by the server."); + bool accepted = inc.ReadBoolean(); + + if (!accepted) + { + new GUIMessageBox("File transfer canceled", inc.ReadString()); + + if (fileStreamReceiver!=null) + { + fileStreamReceiver.DeleteFile(); + fileStreamReceiver.Dispose(); + fileStreamReceiver = null; + } + } + break; case (byte)PacketTypes.FileStream: if (fileStreamReceiver == null) @@ -710,14 +728,7 @@ namespace Barotrauma.Networking 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; - - NetOutgoingMessage msg = client.CreateMessage(); - msg.Write((byte)PacketTypes.RequestFile); - msg.Write((byte)FileTransferType.Cancel); - client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + CancelFileTransfer(); } } @@ -737,28 +748,6 @@ namespace Barotrauma.Networking } - private void OnFileReceived(FileStreamReceiver receiver) - { - if (receiver.Status == FileTransferStatus.Error) - { - new GUIMessageBox("Error while receiving file from server", receiver.ErrorMessage, 400, 350); - 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; - } - } - - fileStreamReceiver = null; - } public override void Disconnect() { @@ -770,8 +759,13 @@ namespace Barotrauma.Networking GameMain.NetworkMember = null; } - public void RequestFile(string file, FileTransferType fileType) + public void RequestFile(string file, FileTransferMessageType fileType) { + if (fileStreamReceiver!=null) + { + CancelFileTransfer(); + } + NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)PacketTypes.RequestFile); msg.Write((byte)fileType); @@ -783,6 +777,41 @@ namespace Barotrauma.Networking fileStreamReceiver = new FileStreamReceiver(client, Path.Combine(Submarine.SavePath, "Downloaded"), fileType, OnFileReceived); } + private void OnFileReceived(FileStreamReceiver receiver) + { + if (receiver.Status == FileTransferStatus.Error) + { + new GUIMessageBox("Error while receiving file from server", receiver.ErrorMessage, 400, 350); + receiver.DeleteFile(); + + } + else if (receiver.Status == FileTransferStatus.Finished) + { + new GUIMessageBox("Download finished", "File ''" + receiver.FileName + "'' was downloaded succesfully."); + + switch (receiver.FileType) + { + case FileTransferMessageType.Submarine: + Submarine.Preload(); + break; + } + } + + fileStreamReceiver = null; + } + + private void CancelFileTransfer() + { + fileStreamReceiver.DeleteFile(); + fileStreamReceiver.Dispose(); + fileStreamReceiver = null; + + NetOutgoingMessage msg = client.CreateMessage(); + msg.Write((byte)PacketTypes.RequestFile); + msg.Write((byte)FileTransferMessageType.Cancel); + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + } + public void Vote(VoteType voteType, object userData) { NetOutgoingMessage msg = client.CreateMessage(); diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index b408f8d6f..f6e6b2826 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -30,6 +30,7 @@ namespace Barotrauma.Networking private DateTime sparseUpdateTimer; private DateTime refreshMasterTimer; + private RestClient restClient; private bool masterServerResponded; private ServerLog log; @@ -96,6 +97,8 @@ namespace Barotrauma.Networking banList = new BanList(); + LoadSettings(); + //---------------------------------------- @@ -160,7 +163,10 @@ namespace Barotrauma.Networking private void RegisterToMasterServer() { - var client = new RestClient(NetConfig.MasterServerUrl); + if (restClient==null) + { + restClient = new RestClient(NetConfig.MasterServerUrl); + } var request = new RestRequest("masterserver2.php", Method.GET); request.AddParameter("action", "addserver"); @@ -171,7 +177,7 @@ namespace Barotrauma.Networking request.AddParameter("password", string.IsNullOrWhiteSpace(password) ? 0 : 1); // execute the request - RestResponse response = (RestResponse)client.Execute(request); + RestResponse response = (RestResponse)restClient.Execute(request); if (response.StatusCode != System.Net.HttpStatusCode.OK) { @@ -191,7 +197,10 @@ namespace Barotrauma.Networking private IEnumerable RefreshMaster() { - var client = new RestClient(NetConfig.MasterServerUrl); + if (restClient == null) + { + restClient = new RestClient(NetConfig.MasterServerUrl); + } var request = new RestRequest("masterserver2.php", Method.GET); request.AddParameter("action", "refreshserver"); @@ -205,7 +214,7 @@ namespace Barotrauma.Networking sw.Start(); masterServerResponded = false; - var restRequestHandle = client.ExecuteAsync(request, response => MasterServerCallBack(response)); + var restRequestHandle = restClient.ExecuteAsync(request, response => MasterServerCallBack(response)); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 15); while (!masterServerResponded) @@ -312,40 +321,7 @@ namespace Barotrauma.Networking foreach (Client c in ConnectedClients) { - if (c.FileStreamSender != null) - { - var clientNameBox = GameMain.NetLobbyScreen.PlayerList.FindChild(c.name); - var clientInfo = clientNameBox.FindChild(c.FileStreamSender); - - if (clientInfo==null) - { - clientInfo = new GUIFrame(new Rectangle(0,0,180,0), Color.Transparent, Alignment.TopRight, null, clientNameBox); - clientInfo.UserData = c.FileStreamSender; - new GUIProgressBar(new Rectangle(0, 4, 0, clientInfo.Rect.Height-8), Color.Green, GUI.Style, 0.0f, Alignment.Left, clientInfo).IsHorizontal = true; - new GUITextBlock(new Rectangle(0,2,0,0), "", GUI.Style, Alignment.TopLeft, Alignment.Left | Alignment.CenterY, clientInfo, true, GUI.SmallFont); - } - else - { - var progressBar = clientInfo.GetChild(); - progressBar.BarSize = c.FileStreamSender.Progress; - - var progressText = clientInfo.GetChild(); - progressText.Text = c.FileStreamSender.FileName + " " + - MathUtils.GetBytesReadable(c.FileStreamSender.Sent) + " / " + MathUtils.GetBytesReadable(c.FileStreamSender.FileSize); - } - - c.FileStreamSender.Update(deltaTime); - - if (c.FileStreamSender.Status == FileTransferStatus.Finished || - c.FileStreamSender.Status == FileTransferStatus.Error || - c.FileStreamSender.Status == FileTransferStatus.Canceled) - { - clientNameBox.RemoveChild(clientInfo); - - c.FileStreamSender.Dispose(); - c.FileStreamSender = null; - } - } + if (c.FileStreamSender != null) UpdateFileTransfer(c, deltaTime); c.ReliableChannel.Update(deltaTime); } @@ -542,20 +518,16 @@ namespace Barotrauma.Networking if (!allowFileTransfers) { - var outmsg = server.CreateMessage(); - outmsg.Write((byte)PacketTypes.RequestFile); - outmsg.Write(false); - outmsg.Write("File downloads disabled by the server"); - server.SendMessage(outmsg, dataSender.Connection, NetDeliveryMethod.ReliableUnordered); + SendCancelTransferMessage(dataSender, "File transfers have been disabled by the server."); break; } byte fileType = inc.ReadByte(); - string fileName = fileType == (byte)FileTransferType.Cancel ? "" : inc.ReadString(); + string fileName = fileType == (byte)FileTransferMessageType.Cancel ? "" : inc.ReadString(); switch (fileType) { - case (byte)FileTransferType.Submarine: + case (byte)FileTransferMessageType.Submarine: var requestedSubmarine = Submarine.SavedSubmarines.Find(s => s.Name == fileName); @@ -565,11 +537,13 @@ namespace Barotrauma.Networking } else { - var fileStreamSender = FileStreamSender.Create(dataSender.Connection, requestedSubmarine.FilePath, FileTransferType.Submarine); + if (dataSender.FileStreamSender != null) dataSender.FileStreamSender.CancelTransfer(); + + var fileStreamSender = FileStreamSender.Create(dataSender.Connection, requestedSubmarine.FilePath, FileTransferMessageType.Submarine); if (fileStreamSender != null) dataSender.FileStreamSender = fileStreamSender; } break; - case (byte)FileTransferType.Cancel: + case (byte)FileTransferMessageType.Cancel: if (dataSender.FileStreamSender != null) { dataSender.FileStreamSender.CancelTransfer(); @@ -881,6 +855,13 @@ namespace Barotrauma.Networking { GUIMessageBox.CloseAll(); + if (ConnectedClients.Any(c => c.FileStreamSender != null && c.FileStreamSender.FilePath == selectedSub.FilePath)) + { + new GUIMessageBox("Couldn't start a round", + "Can't start a round while sending the selected submarine to clients. Cancel the transfers or wait for them to finish before starting.", 400, 400); + yield return CoroutineStatus.Success; + } + AssignJobs(); roundStartSeed = DateTime.Now.Millisecond; @@ -960,7 +941,6 @@ namespace Barotrauma.Networking GameMain.GameScreen.Select(); - AddChatMessage("Press TAB to chat. Use ''d;'' to talk to dead players and spectators, and ''player name;'' to only send the message to a specific player.", ChatMessageType.Server); yield return CoroutineStatus.Success; @@ -1166,6 +1146,65 @@ namespace Barotrauma.Networking } } + private void UpdateFileTransfer(Client client, float deltaTime) + { + if (client.FileStreamSender == null) return; + + var clientNameBox = GameMain.NetLobbyScreen.PlayerList.FindChild(client.name); + var clientInfo = clientNameBox.FindChild(client.FileStreamSender); + + if (clientInfo == null) + { + clientNameBox.ClearChildren(); + + clientInfo = new GUIFrame(new Rectangle(0, 0, 180, 0), Color.Transparent, Alignment.TopRight, null, clientNameBox); + clientInfo.UserData = client.FileStreamSender; + new GUIProgressBar(new Rectangle(0, 4, 160, clientInfo.Rect.Height - 8), Color.Green, GUI.Style, 0.0f, Alignment.Left, clientInfo).IsHorizontal = true; + new GUITextBlock(new Rectangle(0, 2, 160, 0), "", GUI.Style, Alignment.TopLeft, Alignment.Left | Alignment.CenterY, clientInfo, true, GUI.SmallFont); + + var cancelButton = new GUIButton(new Rectangle(20, 0, 14, 0), "X", Alignment.Right, GUI.Style, clientInfo); + cancelButton.OnClicked = (GUIButton button, object userdata) => + { + (cancelButton.Parent.UserData as FileStreamSender).CancelTransfer(); + return true; + }; + } + else + { + var progressBar = clientInfo.GetChild(); + progressBar.BarSize = client.FileStreamSender.Progress; + + var progressText = clientInfo.GetChild(); + progressText.Text = client.FileStreamSender.FileName + " " + + MathUtils.GetBytesReadable(client.FileStreamSender.Sent) + " / " + MathUtils.GetBytesReadable(client.FileStreamSender.FileSize); + } + + client.FileStreamSender.Update(deltaTime); + + if (client.FileStreamSender.Status != FileTransferStatus.Sending && + client.FileStreamSender.Status != FileTransferStatus.NotStarted) + { + if (client.FileStreamSender.Status == FileTransferStatus.Canceled) + { + SendCancelTransferMessage(client, "File transfer was canceled by the server."); + } + + clientNameBox.RemoveChild(clientInfo); + + client.FileStreamSender.Dispose(); + client.FileStreamSender = null; + } + } + + private void SendCancelTransferMessage(Client client, string message) + { + var outmsg = server.CreateMessage(); + outmsg.Write((byte)PacketTypes.RequestFile); + outmsg.Write(false); + outmsg.Write(message); + server.SendMessage(outmsg, client.Connection, NetDeliveryMethod.ReliableUnordered); + } + public void NewTraitor(Character traitor, Character target) { Log(traitor.Name + " is the traitor and the target is " + target.Name, Color.Cyan); @@ -1590,6 +1629,16 @@ namespace Barotrauma.Networking { banList.Save(); + + if (registeredToMaster && restClient != null) + { + var request = new RestRequest("masterserver2.php", Method.GET); + request.AddParameter("action", "removeserver"); + + restClient.Execute(request); + restClient = null; + } + if (saveServerLogs) { Log("Shutting down server...", Color.Cyan); diff --git a/Subsurface/Source/Networking/GameServerSettings.cs b/Subsurface/Source/Networking/GameServerSettings.cs index afeb6fb39..778717b80 100644 --- a/Subsurface/Source/Networking/GameServerSettings.cs +++ b/Subsurface/Source/Networking/GameServerSettings.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Xml.Linq; namespace Barotrauma.Networking { @@ -18,6 +19,8 @@ namespace Barotrauma.Networking partial class GameServer : NetworkMember { + public const string SettingsFile = "serversettings.xml"; + public bool ShowNetStats; private TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 30); @@ -91,6 +94,66 @@ namespace Barotrauma.Networking public float EndVoteRequiredRatio = 0.5f; + private void SaveSettings() + { + XDocument doc = new XDocument(new XElement("serversettings")); + + doc.Root.Add + ( + new XAttribute("AllowSpectating", allowSpectating), + new XAttribute("RandomizeSeed", randomizeSeed), + new XAttribute("EndRoundAtLevelEnd", endRoundAtLevelEnd), + new XAttribute("AllowFileTransfers", allowFileTransfers), + + new XAttribute("SaveServerLogs", saveServerLogs), + new XAttribute("LinesPerLogFile", log.LinesPerFile), + + new XAttribute("SubSelection", subSelectionMode), + new XAttribute("ModeSelection", modeSelectionMode) + ); + + try + { + doc.Save(SettingsFile); + } + catch (Exception e) + { + DebugConsole.ThrowError("Saving server settings failed", e); + } + } + + private void LoadSettings() + { + XDocument doc = null; + if (System.IO.File.Exists(SettingsFile)) + { + doc = ToolBox.TryLoadXml(SettingsFile); + } + else + { + return; + } + + if (doc == null) + { + doc = new XDocument(new XElement("serversettings")); + } + + allowSpectating = ToolBox.GetAttributeBool(doc.Root, "AllowSpectating", true); + randomizeSeed = ToolBox.GetAttributeBool(doc.Root, "RandomizeSeed", true); + endRoundAtLevelEnd = ToolBox.GetAttributeBool(doc.Root, "EndRoundAtLevelEnd", true); + allowFileTransfers = ToolBox.GetAttributeBool(doc.Root, "AllowFileTransfers", true); + + saveServerLogs = ToolBox.GetAttributeBool(doc.Root, "SaveServerLogs", true); + log.LinesPerFile = ToolBox.GetAttributeInt(doc.Root, "LinesPerLogFile", 800); + + subSelectionMode = SelectionMode.Manual; + Enum.TryParse(ToolBox.GetAttributeString(doc.Root, "SubSelection", "Manual"), out subSelectionMode); + + modeSelectionMode = SelectionMode.Manual; + Enum.TryParse(ToolBox.GetAttributeString(doc.Root, "ModeSelection", "Manual"), out modeSelectionMode); + } + private void CreateSettingsFrame() { settingsFrame = new GUIFrame(new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.Black * 0.5f); @@ -227,6 +290,7 @@ namespace Barotrauma.Networking else { settingsFrame = null; + SaveSettings(); } return false; diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 7f75d07ed..8902102b6 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -172,7 +172,7 @@ namespace Barotrauma.Networking message.Write((byte)msgBytes.Count); foreach (byte[] msgData in msgBytes) { - if (msgData.Length > 255) DebugConsole.ThrowError("too large networkevent (" + msgData.Length + " bytes)"); + if (msgData.Length > 255) DebugConsole.ThrowError("Too large networkevent (" + msgData.Length + " bytes)"); message.Write((byte)msgData.Length); message.Write(msgData); diff --git a/Subsurface/Source/Networking/ServerLog.cs b/Subsurface/Source/Networking/ServerLog.cs index 19c0986df..aef024fc3 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 = 800; + private int linesPerFile = 800; public const string SavePath = "ServerLogs"; @@ -21,6 +21,12 @@ namespace Barotrauma.Networking private Queue lines; + public int LinesPerFile + { + get { return linesPerFile; } + set { linesPerFile = Math.Max(10, linesPerFile); } + } + public ServerLog(string serverName) { this.serverName = serverName; diff --git a/Subsurface/Source/PlayerInput.cs b/Subsurface/Source/PlayerInput.cs index 3b488c34c..179828393 100644 --- a/Subsurface/Source/PlayerInput.cs +++ b/Subsurface/Source/PlayerInput.cs @@ -155,24 +155,28 @@ namespace Barotrauma if (held) heldQueue = true; } - public bool Dequeue + public bool DequeueHit() { - get - { - bool value = hitQueue; - hitQueue = false; - return value; - } + bool value = hitQueue; + hitQueue = false; + return value; } - public bool DequeueHeld + public bool DequeueHeld() { - get - { - bool value = heldQueue; - heldQueue = false; - return value; - } + bool value = heldQueue; + heldQueue = false; + return value; + } + + public bool GetHeldQueue + { + get { return heldQueue; } + } + + public bool GetHitQueue + { + get { return hitQueue; } } diff --git a/Subsurface/Source/Properties.cs b/Subsurface/Source/Properties.cs index dde192783..7453e99d6 100644 --- a/Subsurface/Source/Properties.cs +++ b/Subsurface/Source/Properties.cs @@ -193,7 +193,7 @@ namespace Barotrauma return editableProperties; } - public static Dictionary GetProperties(object obj) + public static Dictionary GetProperties(IPropertyObject obj) { var properties = TypeDescriptor.GetProperties(obj.GetType()).Cast(); @@ -207,12 +207,12 @@ namespace Barotrauma return dictionary; } - public static Dictionary InitProperties(object obj) + public static Dictionary InitProperties(IPropertyObject obj) { return InitProperties(obj, null); } - public static Dictionary InitProperties(object obj, XElement element) + public static Dictionary InitProperties(IPropertyObject obj, XElement element) { var properties = TypeDescriptor.GetProperties(obj.GetType()).Cast(); diff --git a/Subsurface/Source/Screens/EditMapScreen.cs b/Subsurface/Source/Screens/EditMapScreen.cs index 6bb028409..22f711a76 100644 --- a/Subsurface/Source/Screens/EditMapScreen.cs +++ b/Subsurface/Source/Screens/EditMapScreen.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; +using System.IO; using System.Linq; namespace Barotrauma @@ -291,7 +292,14 @@ namespace Barotrauma return false; } - Submarine.SaveCurrent(nameBox.Text + ".sub"); + string savePath = nameBox.Text + ".sub"; + + if (Submarine.Loaded != null) + { + savePath = Path.Combine(Path.GetDirectoryName(Submarine.Loaded.FilePath), savePath); + } + + Submarine.SaveCurrent(savePath); Submarine.Loaded.CheckForErrors(); GUI.AddMessage("Submarine saved to " + Submarine.Loaded.FilePath, Color.Green, 3.0f); diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index bcad8ce5e..bf26560a3 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -512,9 +512,17 @@ namespace Barotrauma var hash = (obj as Submarine).MD5Hash; - + //hash will be null if opening the sub file failed -> don't select the sub - return hash.Hash != null; + if (string.IsNullOrWhiteSpace(hash.Hash)) + { + (component as GUITextBlock).TextColor = Color.DarkRed * 0.8f; + component.CanBeFocused = false; + + return false; + } + + return true; } public void UpdateSubList() @@ -590,7 +598,7 @@ namespace Barotrauma public void AddPlayer(string name) { GUITextBlock textBlock = new GUITextBlock( - new Rectangle(0, 0, 0, 25), name, + new Rectangle(0, 0, playerList.Rect.Width-20, 25), name, GUI.Style, Alignment.Left, Alignment.Left, playerList); @@ -629,7 +637,7 @@ namespace Barotrauma var closeButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Close", Alignment.BottomRight, GUI.Style, playerFrameInner); closeButton.OnClicked = ClosePlayerFrame; - return true; + return false; } private bool ClosePlayerFrame(GUIButton button, object userData) @@ -925,50 +933,47 @@ namespace Barotrauma public bool TrySelectSub(string subName, string md5Hash) { + //already downloading the selected sub file + if (GameMain.Client.ActiveFileTransferName == subName+".sub") return false; + Submarine sub = Submarine.SavedSubmarines.Find(m => m.Name == subName); - if (sub == null) + if (sub == null || sub.MD5Hash.Hash != md5Hash) { - 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" }, 400, 300); + string errorMsg = ""; + if (sub == null) + { + errorMsg = "Submarine ''" + subName + "'' was selected by the server. Matching file not found in your submarine folder. "; + } + else if (sub.MD5Hash.Hash == null) + { + errorMsg = "Couldn't load submarine ''" + subName + "''. The file may be corrupted. "; + } + else + { + errorMsg = "Your version of the submarine file ''" + sub.Name + "'' doesn't match the server's version! " + + "Your MD5 hash: " + sub.MD5Hash.Hash + " \n" + + "Server's MD5 hash: " + md5Hash + ". "; + } + + string downloadMsg = GameMain.Client.ActiveFileTransferName == "" ? + "Do you want to download the file from the server host?" : + "Do you want to download the file and cancel downloading ''" + GameMain.Client.ActiveFileTransferName + "''?"; + + var requestFileBox = new GUIMessageBox("Submarine not found!", errorMsg+downloadMsg, 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); + GameMain.Client.RequestFile(userdata.ToString(), FileTransferMessageType.Submarine); return true; }; requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; - - + return false; } - else - { - if (sub.MD5Hash.Hash != md5Hash) - { - var requestFileBox = new GUIMessageBox("Submarine not found!", - "Your version of the map file ''" + sub.Name + "'' doesn't match the server's version! " - +"Your file: " + sub.Name + "(MD5 hash: " + sub.MD5Hash.Hash + ") " - +"Server'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 - { - subList.Select(sub, true); - //map.Load(); - return true; - } - } + subList.Select(sub, true); + return true; } public void WriteData(NetOutgoingMessage msg) @@ -1011,7 +1016,7 @@ namespace Barotrauma public void ReadData(NetIncomingMessage msg) { - string mapName="", md5Hash=""; + string mapName = "", md5Hash = ""; int modeIndex = 0; //float durationScroll = 0.0f; diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index b209e5e1c..a1f5ff7d2 100644 Binary files a/Subsurface_Solution.v12.suo and b/Subsurface_Solution.v12.suo differ diff --git a/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.spritefont b/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.spritefont index 285dd2a0d..f8b3c56dc 100644 --- a/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.spritefont +++ b/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.spritefont @@ -53,7 +53,7 @@ with. - ~ + Ұ diff --git a/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.xnb b/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.xnb index 37d6342fc..5723d1238 100644 Binary files a/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.xnb and b/Subsurface_content/Subsurface_content/bin/PSM/Content/SpriteFont1.xnb differ diff --git a/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont b/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont index b3a30a69a..20619a92c 100644 --- a/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont +++ b/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont @@ -30,7 +30,7 @@ with. - ~ + Ұ diff --git a/Subsurface_content/Subsurface_contentContent/SmallFont.spritefont b/Subsurface_content/Subsurface_contentContent/SmallFont.spritefont index 2ddab2ef8..90f94d0fe 100644 --- a/Subsurface_content/Subsurface_contentContent/SmallFont.spritefont +++ b/Subsurface_content/Subsurface_contentContent/SmallFont.spritefont @@ -30,7 +30,7 @@ with. - ~ + Ұ diff --git a/Subsurface_content/Subsurface_contentContent/SpriteFont1.spritefont b/Subsurface_content/Subsurface_contentContent/SpriteFont1.spritefont index 285dd2a0d..f8b3c56dc 100644 --- a/Subsurface_content/Subsurface_contentContent/SpriteFont1.spritefont +++ b/Subsurface_content/Subsurface_contentContent/SpriteFont1.spritefont @@ -53,7 +53,7 @@ with. - ~ + Ұ