From bc4ea098f7684d4e43f42a19f7204283d579bb5d Mon Sep 17 00:00:00 2001 From: Regalis Date: Fri, 14 Aug 2015 01:59:41 +0300 Subject: [PATCH] v0.1.2: Asynchronous master server connections, largefont, traitor mode ends when traitor dies or sub reaches end of level --- Subsurface/Content/UI/style.xml | 7 + Subsurface/Properties/AssemblyInfo.cs | 4 +- Subsurface/Source/Characters/CharacterInfo.cs | 4 +- Subsurface/Source/DebugConsole.cs | 3 + Subsurface/Source/GUI/GUI.cs | 2 +- Subsurface/Source/GUI/GUIListBox.cs | 8 + Subsurface/Source/GUI/GUITextBlock.cs | 3 +- Subsurface/Source/GUI/GUITickBox.cs | 10 ++ Subsurface/Source/Game1.cs | 1 + .../GameSession/GameModes/TraitorMode.cs | 40 +++-- Subsurface/Source/Items/CharacterInventory.cs | 10 +- .../Source/Items/Components/Projectile.cs | 2 +- Subsurface/Source/Items/Item.cs | 2 +- Subsurface/Source/Networking/GameClient.cs | 6 +- Subsurface/Source/Networking/GameServer.cs | 73 ++++++-- Subsurface/Source/Screens/MainMenu.cs | 47 ++--- Subsurface/Source/Screens/ServerListScreen.cs | 170 +++++++++++++++--- .../Source/Sounds/AmbientSoundManager.cs | 2 +- Subsurface/Source/Sounds/Sound.cs | 21 ++- Subsurface/Subsurface.csproj | 2 +- Subsurface/changelog.txt | 16 ++ Subsurface_Solution.v12.suo | Bin 604160 -> 606208 bytes .../LargeFont.spritefont | 42 +++++ 23 files changed, 377 insertions(+), 98 deletions(-) create mode 100644 Subsurface_content/Subsurface_contentContent/LargeFont.spritefont diff --git a/Subsurface/Content/UI/style.xml b/Subsurface/Content/UI/style.xml index 73b0c7357..5fb091a28 100644 --- a/Subsurface/Content/UI/style.xml +++ b/Subsurface/Content/UI/style.xml @@ -54,6 +54,13 @@ + + + + = endTime) - { - string endMessage = traitor.character.Info.Name + " was a traitor! "; - endMessage += (traitor.character.Info.Gender == Gender.Male) ? "His" : "Her"; - endMessage += " task was to assassinate " + target.character.Info.Name + ". The task was unsuccesful."; - End(endMessage); - return; - } - + if (traitor==null || target ==null) { @@ -68,6 +59,35 @@ namespace Subsurface endMessage += " task was to assassinate " + target.character.Info.Name + ". The task was succesful."; End(endMessage); } + else if (traitor.character.IsDead) + { + string endMessage = traitor.character.Info.Name + " was a traitor! "; + endMessage += (traitor.character.Info.Gender == Gender.Male) ? "His" : "Her"; + endMessage += " task was to assassinate " + target.character.Info.Name + ". "; + endMessage += "The task was unsuccessful - the has submarine reached its destination."; + End(endMessage); + return; + } + else if (Level.Loaded.AtEndPosition) + { + string endMessage = traitor.character.Info.Name + " was a traitor! "; + endMessage += (traitor.character.Info.Gender == Gender.Male) ? "His" : "Her"; + endMessage += " task was to assassinate " + target.character.Info.Name + ", but "; + endMessage += (traitor.character.Info.Gender == Gender.Male) ? "he" : "she"; + endMessage += " got " + ((traitor.character.Info.Gender == Gender.Male) ? "himself" : "herself"); + endMessage += " killed before completing it."; + End(endMessage); + return; + } + else if (DateTime.Now >= endTime) + { + string endMessage = traitor.character.Info.Name + " was a traitor! "; + endMessage += (traitor.character.Info.Gender == Gender.Male) ? "His" : "Her"; + endMessage += " task was to assassinate " + target.character.Info.Name + ". The task was unsuccesful."; + End(endMessage); + return; + } + } } } diff --git a/Subsurface/Source/Items/CharacterInventory.cs b/Subsurface/Source/Items/CharacterInventory.cs index cab256dc1..25db5d641 100644 --- a/Subsurface/Source/Items/CharacterInventory.cs +++ b/Subsurface/Source/Items/CharacterInventory.cs @@ -144,11 +144,11 @@ namespace Subsurface //PutItem(item, i, false, false); combined = true; } - else if (items[i].Combine(item)) - { - //PutItem(items[i], i, false, false); - combined = true; - } + //else if (items[i].Combine(item)) + //{ + // //PutItem(items[i], i, false, false); + // combined = true; + //} if (!combined) return false; diff --git a/Subsurface/Source/Items/Components/Projectile.cs b/Subsurface/Source/Items/Components/Projectile.cs index 0e44f3468..122574f22 100644 --- a/Subsurface/Source/Items/Components/Projectile.cs +++ b/Subsurface/Source/Items/Components/Projectile.cs @@ -179,7 +179,7 @@ namespace Subsurface.Items.Components foreach (Item contained in item.ContainedItems) { contained.Condition = 0.0f; - if (contained.body!=null) + if (contained.body != null) { contained.body.SetTransform(item.SimPosition, contained.body.Rotation); } diff --git a/Subsurface/Source/Items/Item.cs b/Subsurface/Source/Items/Item.cs index 608b7fc8d..a6b4e95be 100644 --- a/Subsurface/Source/Items/Item.cs +++ b/Subsurface/Source/Items/Item.cs @@ -178,7 +178,7 @@ namespace Subsurface get { ItemContainer c = GetComponent(); - return (c == null) ? null : c.inventory.items; + return (c == null) ? null : Array.FindAll(c.inventory.items, i=>i!=null); } } diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 42d4cf342..57a8dbd23 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -50,9 +50,8 @@ namespace Subsurface.Networking } - public void ConnectToServer(string hostIP) + public void ConnectToServer(string hostIP, string password = "") { - string[] address = hostIP.Split(':'); if (address.Length==1) { @@ -65,7 +64,7 @@ namespace Subsurface.Networking if (!int.TryParse(address[1], out Port)) { - DebugConsole.ThrowError("Invalid port: address[1]!"); + DebugConsole.ThrowError("Invalid port: "+address[1]+"!"); Port = DefaultPort; } } @@ -85,6 +84,7 @@ namespace Subsurface.Networking client.Start(); outmsg.Write((byte)PacketTypes.Login); + outmsg.Write(password); outmsg.Write(Game1.Version.ToString()); outmsg.Write(Game1.SelectedPackage.Name); outmsg.Write(Game1.SelectedPackage.MD5hash.Hash); diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index d856f7694..a1d961eb4 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -22,17 +22,23 @@ namespace Subsurface.Networking private TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 40); private DateTime refreshMasterTimer; + private bool masterServerResponded; + private bool registeredToMaster; + private string password; + private Client myClient; - public GameServer(string name, int port) + public GameServer(string name, int port, bool isPublic = false, string password="") { var endRoundButton = new GUIButton(new Rectangle(Game1.GraphicsWidth - 290, 20, 150, 25), "End round", Alignment.TopLeft, GUI.style, inGameHUD); endRoundButton.OnClicked = EndButtonHit; this.name = name; + this.password = password; + config = new NetPeerConfiguration("subsurface"); //config.SimulatedLoss = 0.2f; @@ -62,7 +68,11 @@ namespace Subsurface.Networking DebugConsole.ThrowError("Couldn't start the server", e); } - RegisterToMasterServer(); + if (isPublic) + { + RegisterToMasterServer(); + } + updateInterval = new TimeSpan(0, 0, 0, 0, 30); @@ -78,6 +88,7 @@ namespace Subsurface.Networking request.AddParameter("servername", name); request.AddParameter("serverport", Port); request.AddParameter("playercount", PlayerCountToByte(connectedClients.Count, config.MaximumConnections)); + request.AddParameter("password", string.IsNullOrWhiteSpace(password) ? 0 : 1); // execute the request RestResponse response = (RestResponse)client.Execute(request); @@ -98,7 +109,7 @@ namespace Subsurface.Networking refreshMasterTimer = DateTime.Now + refreshMasterInterval; } - private void RefreshMaster() + private IEnumerable RefreshMaster() { var client = new RestClient(NetworkMember.MasterServerUrl); @@ -112,17 +123,49 @@ namespace Subsurface.Networking var sw = new Stopwatch(); sw.Start(); - RestResponse response = (RestResponse)client.Execute(request); + masterServerResponded = false; + var restRequestHandle = client.ExecuteAsync(request, response => MasterServerCallBack(response)); - sw.Stop(); - System.Diagnostics.Debug.WriteLine("took "+sw.ElapsedMilliseconds+" ms"); - - if (response.StatusCode != System.Net.HttpStatusCode.OK) + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); + while (!masterServerResponded) { - DebugConsole.ThrowError("Error while connecting to master server (" +response.StatusCode+": "+response.StatusDescription+")"); + if (DateTime.Now > timeOut) + { + restRequestHandle.Abort(); + DebugConsole.ThrowError("Couldn't connect to master server (request timed out)"); + registeredToMaster = false; + } + System.Diagnostics.Debug.WriteLine("took "+sw.ElapsedMilliseconds+" ms"); + + yield return Status.Running; } + + yield return Status.Success; + + + + + } + + private void MasterServerCallBack(IRestResponse response) + { + masterServerResponded = true; + + if (response.ErrorException != null) + { + DebugConsole.ThrowError("Error while connecting to master server", response.ErrorException); + registeredToMaster = false; + return; + } + + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + DebugConsole.ThrowError("Error while connecting to master server (" + response.StatusCode + ": " + response.StatusDescription + ")"); + registeredToMaster = false; + return; + } } public override void Update(float deltaTime) @@ -159,7 +202,7 @@ namespace Subsurface.Networking if (registeredToMaster && refreshMasterTimer < DateTime.Now) { - RefreshMaster(); + CoroutineManager.StartCoroutine(RefreshMaster()); refreshMasterTimer = DateTime.Now + refreshMasterInterval; } @@ -204,9 +247,10 @@ namespace Subsurface.Networking Client existingClient = connectedClients.Find(c=> c.Connection == inc.SenderConnection); if (existingClient==null) { - string version = "", packageName="", packageHash="", name = ""; + string userPassword = "", version = "", packageName="", packageHash="", name = ""; try { + userPassword = inc.ReadString(); version = inc.ReadString(); packageName = inc.ReadString(); packageHash = inc.ReadString(); @@ -218,7 +262,12 @@ namespace Subsurface.Networking break; } - if (version != Game1.Version.ToString()) + if (userPassword != password) + { + inc.SenderConnection.Deny("Wrong password!"); + break; + } + else if (version != Game1.Version.ToString()) { inc.SenderConnection.Deny("Subsurface version " + Game1.Version + " required to connect to the server (Your version: " + version + ")"); break; diff --git a/Subsurface/Source/Screens/MainMenu.cs b/Subsurface/Source/Screens/MainMenu.cs index c9f9ae9d5..781fcfea7 100644 --- a/Subsurface/Source/Screens/MainMenu.cs +++ b/Subsurface/Source/Screens/MainMenu.cs @@ -18,9 +18,8 @@ namespace Subsurface private GUITextBox saveNameBox, seedBox; - private GUITextBox clientNameBox, ipBox; - - private GUITextBox serverNameBox, portBox; + private GUITextBox serverNameBox, portBox, passwordBox; + private GUITickBox isPublicBox; private Game1 game; @@ -38,10 +37,10 @@ namespace Subsurface menuTabs[(int)Tabs.Main] = new GUIFrame(panelRect, GUI.style); //menuTabs[(int)Tabs.Main].Padding = GUI.style.smallPadding; - GUIButton button = new GUIButton(new Rectangle(0, 0, 0, 30), "Tutorial", Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.Main]); - button.OnClicked = TutorialButtonClicked; + //GUIButton button = new GUIButton(new Rectangle(0, 0, 0, 30), "Tutorial", Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.Main]); + //button.OnClicked = TutorialButtonClicked; - button = new GUIButton(new Rectangle(0, 70, 0, 30), "New Game", Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.Main]); + GUIButton button = new GUIButton(new Rectangle(0, 70, 0, 30), "New Game", Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.Main]); button.UserData = (int)Tabs.NewGame; button.OnClicked = SelectTab; //button.Enabled = false; @@ -112,16 +111,21 @@ namespace Subsurface menuTabs[(int)Tabs.HostServer] = new GUIFrame(panelRect, GUI.style); //menuTabs[(int)Tabs.JoinServer].Padding = GUI.style.smallPadding; - new GUITextBlock(new Rectangle(0, 0, 0, 30), "Host Server", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.HostServer]); + new GUITextBlock(new Rectangle(0, -25, 0, 30), "Host Server", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.HostServer], false, GUI.LargeFont); new GUITextBlock(new Rectangle(0, 30, 0, 30), "Server Name:", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.HostServer]); - serverNameBox = new GUITextBox(new Rectangle(0, 60, 200, 30), Color.White, Color.Black, Alignment.CenterX, Alignment.CenterX, null, menuTabs[(int)Tabs.HostServer]); + serverNameBox = new GUITextBox(new Rectangle(0, 60, 200, 30), null, null, Alignment.CenterX, Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.HostServer]); new GUITextBlock(new Rectangle(0, 100, 0, 30), "Server port:", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.HostServer]); - portBox = new GUITextBox(new Rectangle(0, 130, 200, 30), Color.White, Color.Black, Alignment.CenterX, Alignment.CenterX, null, menuTabs[(int)Tabs.HostServer]); + portBox = new GUITextBox(new Rectangle(0, 130, 200, 30), null, null, Alignment.CenterX, Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.HostServer]); portBox.Text = NetworkMember.DefaultPort.ToString(); portBox.ToolTip = "Server port"; + isPublicBox = new GUITickBox(new Rectangle(portBox.Rect.X - menuTabs[(int)Tabs.HostServer].Rect.X, 200, 20, 20), "Public server", Alignment.TopLeft, menuTabs[(int)Tabs.HostServer]); + + new GUITextBlock(new Rectangle(0, 240, 0, 30), "Password (optional):", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.HostServer]); + passwordBox = new GUITextBox(new Rectangle(0, 270, 200, 30), null, null, Alignment.CenterX, Alignment.CenterX, GUI.style, menuTabs[(int)Tabs.HostServer]); + GUIButton hostButton = new GUIButton(new Rectangle(0, 0, 200, 30), "Start", Alignment.BottomCenter, GUI.style, menuTabs[(int)Tabs.HostServer]); hostButton.OnClicked = HostServerClicked; @@ -177,7 +181,7 @@ namespace Subsurface return false; } - Game1.NetworkMember = new GameServer(name, port); + Game1.NetworkMember = new GameServer(name, port, isPublicBox.Selected, passwordBox.Text); Game1.NetLobbyScreen.IsServer = true; Game1.NetLobbyScreen.Select(); @@ -194,7 +198,7 @@ namespace Subsurface { menuTabs[(int)Tabs.LoadGame].ClearChildren(); - new GUITextBlock(new Rectangle(0, 0, 0, 30), "Load Game", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.LoadGame]); + new GUITextBlock(new Rectangle(0, -25, 0, 30), "Load Game", GUI.style, Alignment.CenterX, Alignment.CenterX, menuTabs[(int)Tabs.LoadGame], false, GUI.LargeFont); string[] saveFiles = SaveUtil.GetSaveFiles(); @@ -376,26 +380,5 @@ namespace Subsurface return true; } - - private bool JoinServer(GUIButton button, object obj) - { - if (string.IsNullOrEmpty(clientNameBox.Text)) return false; - if (string.IsNullOrEmpty(ipBox.Text)) return false; - - Game1.NetworkMember = new GameClient(clientNameBox.Text); - Game1.Client.ConnectToServer(ipBox.Text); - - return true; - //{ - // Game1.NetLobbyScreen.Select(); - // return true; - //} - //else - //{ - // Game1.NetworkMember = null; - // return false; - //} - } - } } diff --git a/Subsurface/Source/Screens/ServerListScreen.cs b/Subsurface/Source/Screens/ServerListScreen.cs index a68e096ad..6534975bd 100644 --- a/Subsurface/Source/Screens/ServerListScreen.cs +++ b/Subsurface/Source/Screens/ServerListScreen.cs @@ -14,6 +14,9 @@ namespace Subsurface { class ServerListScreen : Screen { + //how often the client is allowed to refresh servers + private TimeSpan AllowedRefreshInterval = new TimeSpan(0,0,3); + private GUIFrame menu; private GUIListBox serverList; @@ -22,6 +25,15 @@ namespace Subsurface private GUITextBox clientNameBox, ipBox; + //private RestRequestAsyncHandle restRequestHandle; + private bool masterServerResponded; + + private int[] columnX; + + //a timer for + private DateTime refreshDisableTimer; + private bool waitingForRefresh; + public ServerListScreen() { int width = Math.Min(Game1.GraphicsWidth - 160, 1000); @@ -30,37 +42,56 @@ namespace Subsurface Rectangle panelRect = new Rectangle(0, 0, width, height); menu = new GUIFrame(panelRect, null, Alignment.Center, GUI.style); - - new GUITextBlock(new Rectangle(0, 0, 0, 30), "Join Server", GUI.style, Alignment.CenterX, Alignment.CenterX, menu); + + new GUITextBlock(new Rectangle(0, -25, 0, 30), "Join Server", GUI.style, Alignment.CenterX, Alignment.CenterX, menu, false, GUI.LargeFont); new GUITextBlock(new Rectangle(0, 30, 0, 30), "Your Name:", GUI.style, menu); clientNameBox = new GUITextBox(new Rectangle(0, 60, 200, 30), GUI.style, menu); new GUITextBlock(new Rectangle(0, 100, 0, 30), "Server IP:", GUI.style, menu); ipBox = new GUITextBox(new Rectangle(0, 130, 200, 30), GUI.style, menu); + + + + + int middleX = (int)(width * 0.4f); serverList = new GUIListBox(new Rectangle(middleX,60,0,(int)(height*0.7f)), GUI.style, menu); serverList.OnSelected = SelectServer; - new GUITextBlock(new Rectangle(middleX, 30, 0, 30), "Name", GUI.style, menu); - new GUITextBlock(new Rectangle(middleX, 30, 0, 30), "Players", GUI.style, Alignment.TopLeft, Alignment.TopCenter, menu); - new GUITextBlock(new Rectangle(middleX, 30, 0, 30), "Game running", GUI.style, Alignment.TopLeft, Alignment.TopRight, menu); + float[] columnRelativeX = new float[] { 0.15f, 0.55f, 0.15f, 0.15f }; + columnX = new int[columnRelativeX.Length]; + for (int n = 0; n < columnX.Length; n++) + { + columnX[n] = (int)(columnRelativeX[n] * serverList.Rect.Width); + if (n > 0) columnX[n] += columnX[n - 1]; + } + + new GUITextBlock(new Rectangle(middleX, 30, 0, 30), "Password", GUI.style, menu); + + new GUITextBlock(new Rectangle(middleX + columnX[0], 30, 0, 30), "Name", GUI.style, menu); + new GUITextBlock(new Rectangle(middleX + columnX[1], 30, 0, 30), "Players", GUI.style, menu); + new GUITextBlock(new Rectangle(middleX + columnX[2], 30, 0, 30), "Running", GUI.style, menu); joinButton = new GUIButton(new Rectangle(-170, 0, 150, 30), "Refresh", Alignment.BottomRight, GUI.style, menu); joinButton.OnClicked = RefreshServers; joinButton = new GUIButton(new Rectangle(0,0,150,30), "Join", Alignment.BottomRight, GUI.style, menu); joinButton.OnClicked = JoinServer; - //joinButton.Enabled = false; + + + refreshDisableTimer = DateTime.Now; } public override void Select() { base.Select(); - UpdateServerList(); + + //RefreshServers(null, null); + //UpdateServerList(); } private bool SelectServer(object obj) @@ -75,16 +106,39 @@ namespace Subsurface private bool RefreshServers(GUIButton button, object obj) { - UpdateServerList(); + if (waitingForRefresh) return false; + serverList.ClearChildren(); + + new GUITextBlock(new Rectangle(0, 0, 0, 20), "Refreshing server list...", GUI.style, serverList); + + CoroutineManager.StartCoroutine(WaitForRefresh()); return true; } - private void UpdateServerList() + private IEnumerable WaitForRefresh() + { + waitingForRefresh = true; + if (refreshDisableTimer > DateTime.Now) + { + yield return new WaitForSeconds((float)(refreshDisableTimer - DateTime.Now).TotalSeconds); + } + + //CoroutineManager.StartCoroutine(UpdateServerList()); + CoroutineManager.StartCoroutine(SendMasterServerRequest()); + + waitingForRefresh = false; + + refreshDisableTimer = DateTime.Now + AllowedRefreshInterval; + + yield return Status.Success; + } + + private void UpdateServerList(string masterServerData) { serverList.ClearChildren(); - - string masterServerData = GetMasterServerData(); + + //string masterServerData = GetMasterServerData(); if (string.IsNullOrWhiteSpace(masterServerData)) { @@ -96,6 +150,7 @@ namespace Subsurface if (masterServerData.Substring(0,5).ToLower()=="error") { DebugConsole.ThrowError("Error while connecting to master server ("+masterServerData+")!"); + return; } @@ -112,23 +167,33 @@ namespace Subsurface string gameStarted = (arguments.Length > 3) ? arguments[3] : ""; string playerCountStr = (arguments.Length > 4) ? arguments[4] : ""; + string hasPassWordStr = (arguments.Length > 5) ? arguments[5] : ""; + 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; serverFrame.HoverColor = Color.Gold * 0.2f; serverFrame.SelectedColor = Color.Gold * 0.5f; - var nameText = new GUITextBlock(new Rectangle(0,0,0,0), serverName, GUI.style, serverFrame); + var passwordBox = new GUITickBox(new Rectangle(columnX[0]/2, 0, 20, 20), "", Alignment.TopLeft, serverFrame); + passwordBox.Selected = hasPassWordStr == "1"; + passwordBox.Enabled = false; + passwordBox.UserData = "password"; + + var nameText = new GUITextBlock(new Rectangle(columnX[0], 0, 0, 0), serverName, GUI.style, serverFrame); int playerCount, maxPlayers; playerCount = GameClient.ByteToPlayerCount((byte)int.Parse(playerCountStr), out maxPlayers); - var playerCountText = new GUITextBlock(new Rectangle(0, 0, 0, 0), playerCount+"/"+maxPlayers, GUI.style, Alignment.Left, Alignment.TopCenter, serverFrame); - var gameStartedText = new GUITextBlock(new Rectangle(0, 0, 0, 0), gameStarted=="1" ? "Yes" : "No", GUI.style, Alignment.Left, Alignment.TopRight, serverFrame); + var playerCountText = new GUITextBlock(new Rectangle(columnX[1], 0, 0, 0), playerCount + "/" + maxPlayers, GUI.style, serverFrame); + + var gameStartedBox = new GUITickBox(new Rectangle(columnX[2] + (columnX[3] - columnX[2])/ 2, 0, 20, 20), "", Alignment.TopLeft, serverFrame); + gameStartedBox.Selected = gameStarted == "1"; + gameStartedBox.Enabled = false; } } - private string GetMasterServerData() + private IEnumerable SendMasterServerRequest() { RestClient client = null; try @@ -137,10 +202,11 @@ namespace Subsurface } catch (Exception e) { - DebugConsole.ThrowError("Error while connecting to master server", e); - return ""; + DebugConsole.ThrowError("Error while connecting to master server", e); } + if (client == null) yield return Status.Success; + var request = new RestRequest("masterserver.php", Method.GET); request.AddParameter("gamename", "subsurface"); // adds to POST or URL querystring based on Method @@ -154,17 +220,44 @@ namespace Subsurface //request.AddFile(path); // execute the request - RestResponse response = (RestResponse)client.Execute(request); + masterServerResponded = false; + var restRequestHandle = client.ExecuteAsync(request, response => MasterServerCallBack(response)); + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 8); + while (!masterServerResponded) + { + if (DateTime.Now > timeOut) + { + serverList.ClearChildren(); + restRequestHandle.Abort(); + DebugConsole.ThrowError("Couldn't connect to master server (request timed out)"); + } + yield return Status.Running; + } + + yield return Status.Success; + + } + + private void MasterServerCallBack(IRestResponse response) + { + masterServerResponded = true; + + if (response.ErrorException!=null) + { + serverList.ClearChildren(); + DebugConsole.ThrowError("Error while connecting to master server", response.ErrorException); + return; + } if (response.StatusCode!= System.Net.HttpStatusCode.OK) { + serverList.ClearChildren(); DebugConsole.ThrowError("Error while connecting to master server (" +response.StatusCode+": "+response.StatusDescription+")"); - return ""; + return; } - return response.Content; // raw content as string - + UpdateServerList(response.Content); } private bool JoinServer(GUIButton button, object obj) @@ -183,12 +276,41 @@ namespace Subsurface return false; } - Game1.NetworkMember = new GameClient(clientNameBox.Text); - Game1.Client.ConnectToServer(ip); + CoroutineManager.StartCoroutine(JoinServer(ip)); + return true; } + private IEnumerable JoinServer(string ip) + { + string selectedPassword = ""; + + if ((serverList.Selected.GetChild("password") as GUITickBox).Selected) + { + var msgBox = new GUIMessageBox("Password required", ""); + var passwordBox = new GUITextBox(new Rectangle(0,0,150,20), Alignment.BottomCenter, GUI.style, msgBox); + passwordBox.UserData = "password"; + + var okButton = msgBox.GetChild(); + + while (GUIMessageBox.MessageBoxes.Contains(msgBox)) + { + okButton.Enabled = !string.IsNullOrWhiteSpace(passwordBox.Text); + yield return Status.Running; + } + + selectedPassword = passwordBox.Text; + } + + Game1.NetworkMember = new GameClient(clientNameBox.Text); + Game1.Client.ConnectToServer(ip, selectedPassword); + + Game1.NetLobbyScreen.Select(); + + yield return Status.Success; + } + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) { graphics.Clear(Color.CornflowerBlue); @@ -208,6 +330,8 @@ namespace Subsurface public override void Update(double deltaTime) { + + menu.Update((float)deltaTime); GUI.Update((float)deltaTime); diff --git a/Subsurface/Source/Sounds/AmbientSoundManager.cs b/Subsurface/Source/Sounds/AmbientSoundManager.cs index a984733c5..72ef26434 100644 --- a/Subsurface/Source/Sounds/AmbientSoundManager.cs +++ b/Subsurface/Source/Sounds/AmbientSoundManager.cs @@ -155,7 +155,7 @@ namespace Subsurface if (startDrone!=null) { - if (!SoundManager.IsPlaying(startDrone.AlBufferId)) + if (!startDrone.IsPlaying) { startDrone.Remove(); startDrone = null; diff --git a/Subsurface/Source/Sounds/Sound.cs b/Subsurface/Source/Sounds/Sound.cs index 1244d6c1e..ac74d1272 100644 --- a/Subsurface/Source/Sounds/Sound.cs +++ b/Subsurface/Source/Sounds/Sound.cs @@ -19,6 +19,8 @@ namespace Subsurface private OggSound oggSound; string filePath; + + private int alSourceId; //public float Volume @@ -71,7 +73,8 @@ namespace Subsurface public int Play(float volume = 1.0f) { - return SoundManager.Play(this, volume); + alSourceId = SoundManager.Play(this, volume); + return alSourceId; } public int Play(float baseVolume, float range, Vector2 position) @@ -83,7 +86,9 @@ namespace Subsurface Vector2 relativePos = GetRelativePosition(position); float volume = GetVolume(relativePos, range, baseVolume); - return SoundManager.Play(this, relativePos, volume, volume); + alSourceId = SoundManager.Play(this, relativePos, volume, volume); + + return alSourceId; //if (newIndex == -1) return -1; @@ -96,7 +101,9 @@ namespace Subsurface //bodyPosition.Y = -bodyPosition.Y; - return Play(volume, range, ConvertUnits.ToDisplayUnits(body.Position)); + alSourceId = Play(volume, range, ConvertUnits.ToDisplayUnits(body.Position)); + + return alSourceId; } private float GetVolume(Vector2 relativePosition, float range, float baseVolume) @@ -178,6 +185,14 @@ namespace Subsurface } + public bool IsPlaying + { + get + { + return SoundManager.IsPlaying(alSourceId); + } + } + //public int Loop(float volume = 1.0f) //{ // return SoundManager.Loop(this, volume); diff --git a/Subsurface/Subsurface.csproj b/Subsurface/Subsurface.csproj index 58023277f..ff3e2615a 100644 --- a/Subsurface/Subsurface.csproj +++ b/Subsurface/Subsurface.csproj @@ -38,7 +38,7 @@ 4 - x64 + x86 pdbonly true bin\Windows\Release\ diff --git a/Subsurface/changelog.txt b/Subsurface/changelog.txt index 61b893645..49db66232 100644 --- a/Subsurface/changelog.txt +++ b/Subsurface/changelog.txt @@ -1,3 +1,19 @@ +--------------------------------------------------------------------------------------------------------- +v0.1.2 +--------------------------------------------------------------------------------------------------------- + +Multiplayer: + - a "lobby screen" showing a list of servers that are currently running + - password protected servers + - traitor rounds end when the traitor dies/disconnects or if the submarine reaches the end of the level + +Items: + - fixed the crashing when firing the railgun or activating a detonator + +Other: + - optimized lightning and "line of sight" rendering + - an unfinished tutorial which can currently only be accessed by entering "tutorial" into the + debug console --------------------------------------------------------------------------------------------------------- v0.1.1 diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 2c60509ceb4630c4cb1ac8f2d589d783efbb2740..fd62b540260747e2b7a0afff5df013408bf0e63a 100644 GIT binary patch delta 16251 zcmeHu30#%c*7w=ZxEVbHA`-&!h)50yh=_&+f`l^_nyHzHI1dO1M@q0VGcx5EOU(g? zPUdyMG#+o6Az7){5Qogn49%>(UPYtcGRybB51`o1Zuh>u-|zc=zwgmMYwxx9vxl|T zp4NU&eob(Gew)SN36iP)F`G=^Fq6p}(O+)fyeYUcumpGw(16QGzXDtW{sBw?wC&S= zVp`V8jBlkA_H1b$dtt|Sk{e?scBXi{y5cPqcC(b(0~IU#+Mb{U1^x_1FT6SkiOYcL z2-g6u5pK`yPb&WGJ^ON{Gb^xvs3ck!f!z|w0d$}lFbVJgWV{w&vPY_8#x+N9B3Pq= zL5j(AKWI-N3h)790Tp?U0dc@a#Pfm2fok9-Ks5D5&F)EOSL5y$ao(*28AR6_`Yghw;EC=<<3%CWK+t;;&jK}o{s4r+JG=f51i+mFnEPi4 zpflR`uLMxQO{RIkQ^3=J=ojEB{@h#u#&h-_uFdT+uI?%rH*eY>bbYH!1^DmQXWkF` zKJY<({6IbZ8|b0>a3$zrYPC#mzGQLmy=M^nygubD=sDnv`uLZi7l5xs+`h^!qt#s+ z_2*0KdX{GGX6@~ca({cAkG3PJ=V>=8 zab=1Td|EoqDDn$6h8AsAe0K~;J=)x`JjANSx|p!&85*}t3)r!4!jnN{87j4QVM3Yx zu@v7jEwT-j28xP|+M$v=qfuq5b%(X^7Y}>M3dJ7sh5wGY0SlROk7!=p+aw^9mXF3v%FsqyP%TUA%3TcB(*t*0x!oPjt_7bGhe(l>dqsMlyh_#h9r0gR>tyki zf}JDCen)nWuvOYTf-A{E;6zYX3IeC-BXZ;6-ITfSH9mN*8!Mwa z?g#U)z(Zip11$nw4EinTFTf^*OF&Nm{&ttcL1}Zq@dJeFUjpq497Fg6pcWW`_-fE% zAQR!gfF1&d1IvLefY=f`z> ziq{oi3iniejSV4ecuPl>B(W*9cMH4Uh)-sFnZdr7f+WMzhgHjtFi9#}Ykk$4${^O+ zQY+@df+75n#tI7z%z_ypO^gSjsfpG;4NSn_)mad0P=tVh`#_j5i|`n z7q|!E$AD^}3h^00Q-uElTts*_uo7qsj0Z0rco?t%j{w~O2T+dm00vDbAR-!n7<4U| z%|J~+TVM_FG@{<1Gl4gNc*Hk?z78Y-Zvt+>I`9WWLLDjdODSk>6R`FHKEQI|Iba2_ z7svn>0Yb*Xpr7K^v7lK%K9CKp2W-HbNSgsV1oR`&fdB!oBK{0;4OoVF8c+ZPA^uR; z4-I)R`?)onO$ljoPulvD;!ZWgv<~Jcw(l9}oiOp@?yg-tJ{@&q1#M51+F)sJN_%7E zLcq4yG(=AXuN=I|0jmVKvOO~H9>A=556AcVtO8iv8b_e*lpKSd74kEM3*tc}kSNTWM*u^q0O+i^s{s%zNDW%;-I%P0wRPM`FiYk`nHtcD4s3mj9qNK%DzIm4KoAP?qY{s*ymu0!`rP% z$=!@?UBi6mh?EpDFB;aZ(%QNmtrf|u=*JAHy%8KEotB%-etU8HGpB1?KKlNtlwtFy zIcHuY>KEx<=IXe&|7XMVqXsn}Oa-5@6w1G$dK!Mk}5rQ{_~(QS+q2C#7II z@QB>iktcbfH|Uoq&J@LKWrP+Z>jQF{lHHi4+R&4ijUQJ@IxqH-x0y5Uxf$5qyW--Z=jXKT zKJDADgh?95eB_SIzBb?2@ZBg~R2{`kvM2QLruYKJ>Fq?R1x1aP`%}RIF$s*uB;a^m zl8U$DKvMEDn*EB!zRxFohiye6bCgMbM*b$w8BJTkV>)2gaXz`Da&oiBq-SIf&zYK= z4KgLuHe+h`#PL(c4j-F7IWs3Sdpfa9?!%b9mE`Rc72P%J36BSm@-R{|v%@lSVrcsq z?#HG(o59vmfh^CZkroKMW})V5)<5O77e02Ry)|l->xwsDStUBA4>yfBO)+KA;aPG7 zl}qwWmwu*nQ?_ZWDU%XpImjH^=DMHb&FnX}W&G9S>f;MaDPk$}r77Fh?vA69Qsh2F zZH_MZ*&lOnEwyc!VRdsTnD+wh0O7x|Z*~vRIwO+Ykf&?2R76vN-asFqFVK%VzAXp& zL_j8DTHAn0WDFp}H1cgZJT4QN#sK31!4=O0geL+yz+_+wFcruGrUCDugvr1Y4Rw3F zKMD3+K(x(qQc{>g1?%|z6uFuEkXJZs=bhnH%W!yQvrQS!(?JnE*jnl2=8gmuCLTJw zo_A?llj!1%-O5W#x=5WUz6dA95e2%B`RZ8Tosk*b` zLr>nz`idi{G5(B1Dx2w4pm;yST+RxD-&x?ka`3Z{CLQr(KT(1vH#7DZNiocELDKB` zJn-F0nGv!9ataN(7*hz>Lyt<`5R z47l;*6-n0|Ay`F!kR;09Cq+~K`#8|_^OG`}IVC!COIrHT)Kuf*#pd2IX|%V86em4A z8(YgrD)~Y2qtY1Folb4jf{i&hq-PkmhFBL)9_2AX%Lx_3E z-6+1RTuuq?wDv}OMgE96eqoY>%L*M0*OQE-J#06_u6A#(98V+Pm%V6QkmhTQ_mm;z zJYsX?Q8eji)!!WEKE3a&5B}YpdH-V{b$?!|a_Yxe7blNkWs&mZ%zXHxN^_tztkb%x zoI%U`L{4)qQ%`l4pK$o0vL#or*)VU>g3UKRsA##m8QXvK$impq`akR8%r|TqZzIlR zCNZi9+#b~pW0$g4@q2vl6FHnuBl|K@tHnHuHy9YDFXG{}xRz^Pw((yN?CRD3#>5vN zNqO~ehUVUjuD;BJJ<`T!Ozb&zCT4?(USlYsNE$%ZS-M|~Pd@#wB%~<=>=(x6qm0(Rq;)hpT4isI zwpVgLdT|ZcDePsg>-Ck=)e_#qS9l`p9$ub=KvN)Tjp#^l)2EsgntZ+)(|bvmiwja8 z{l`g0M?&RJl%B~SpiVDvjV}8@OWVfsmM(g4TjpeRqa#I>aLcV!ooY+CukVKXDqyqa zI}kRDN?s9_Sj*bUn3J!4t@}{#Hn6R3?(zq$jWCy+&hw1*aV!4{BjwVaqh%bd?f+>- z7Dr1qO`OR~WSSi<^>O;5x*&dQj8kp2lgdF4OE@RJ>2!B-V8m5}g zLPE|JwIL&5>Rc6m`X0>l<6=1~q${JI|Dfia$YilkS()PZ>UXtWv zE@g|Pn~cqJq{>>cUFuBvXH^d)-XfJVDi37?je$et?P^)E`~z!#QEVCU+-+-iiS4jl z2xZFo#@e&nzQW0qi#~l7KI`fNy$xom-Y`^edL(~>$LOz&)0_9 zA1rOwd~$l$@JZvxjXh3)qHOnd*e^pL4~%K5>b&Uua2j^?e(#!2@u z^Ud!@#CN-DjynF{eD66c?w?w>LdTDn%FVx?XAQIDQp2Jel<*W3@-*-aFdujpSO7c+ zECikh3ISnN6g5Q-rVYF0P$NHI&S%9d#AbJr6wH)1jTb+%O_pA8dO3`yrP38fBNaV` zW;H|ZNhc%=Gh4q5T0SG%bkF6F$3=HcdTS^x$-oI=`^VD2Vqe)}SPJFi%-xO^T}-~> zDYM8n#;#IsEBSeQegFop9|rDHxq?xvK}v*q>YRP8p6v9E=cR&dmn%P-_8Qn`yu%j3RDZDK>@0DozpPZIp)_CsSAovs->p zjNsMsX_umEh+(sAVY~!lXf0l7*q4xPi(FVdLdF)WE%@#3OdBDmQc}7cTl~13#P3B~ z^quLCnal&^(QGZ{4v;JBt|G!3=VNOqcc46wcL39`ae^xR#Ml%)F87sqM}(cH z+lHoT4?wLSn`!naxwi|mBDqcDm#KD?>~xW_Ov=rW=X!|^eR>4!iZTm9IjE zm3I`e4;ePqXXs+(tC6_ojtu9KVW0iM4?5XLeh@%O&&azGus9JeDS{E}cpqy8T$E&W zlOIwK;)UnxOT74y`XQog>Z0qeSjEvw{S2&Uoh-5@u@EKTjwV+lbwABoAmik}K(4xT zZ2T{sVVX8R!#n~-uA#@?mOKAR2NAX5$?2$(G7O7bd@~mj$s&M7WN`g z(vaurjdGe8lA#R#E=Q@XICf#JOnXBPrO*o5rfd?*E2)rUoFmvcT_K0cl-yo9tv!*u z?2!u}QD_Hcl3(j#DDPKKO!R7w)2feAzFLV_%c7Los-e6u?^kKwG1-^)#4|t2>Z>S5 z7E>x2Wgn1zjOruuB}V7mlmH{(xO^VmGjb$VyD5PpUaHel2M#2@C*+Gr`Uq2L?WeN4 zf7uk}kWSK9GPeDp${t4h4$3jc{-g5yGDT`ih!Jv1KE;@c)~?gKQv4^fVjMgn?{lH^ zAIoOLd|IwyB%PE)jGfhTzh;gpibQek6;89eDf1~mRbSpV2Q`*(sA(FPy!Ei;7AekY>An z$_be&wyHi<@`>WvxapEzhg?&Y^KzqRJJQhII~ouLX;jNTWJy)LsH!i@7}-kmF;bQ) zyI65grSPuJ!=>&Td9!A@_^!J_{FPV%T$uC!f$&} zxxi>%hRevhK zu5cr;gSt+m2|p;ajks@>3U?~n%;W0nD@)d}L8|peO#$&L%Dd*Ph8y9&D%3dS1EKm5 zwT8X44%QWTP&p@)^sE|6mamo3Mq#LGL(5t#Gb#LYNWk)63NLPt4Mj~yw63&LNo9_9 zszep7m6%&O!YS2?4wpx&4#RRpsg`KeRZ(^a^&F#Xrzf{4+Y2KjLs8sWn0wHZy16#8VbT&wPL3oh-q-P}xFH}>hW|?lL(iS>@ z$*hMMrw*(AIE^}`Mp5cY)t4@3tAWx}5~ViRON@Od)qF-pW!#tY`|4&4L*!%~aS-4>c-JsncD^>xcpqs#dBQty`e_7(>phms}{aN`dOnDaY}# zzzj8vw*C!ot#}oM7F|}Ss1)8wPjXy9?~6Xu`WTC@t2P%3AB$F1EyKV#+*78j>KSmat8cyLuHJ`GlwJfyxd2|g{v`~9Qy{qjc zWT{;#|E3l|hwYNt*!7~e&W%Rw)K(a0hH7INy*?IYmTtocSoX3uj>6}lZIu<857kUm z6(el5*1?6WuWJzGa1FZgvcU$jUey-Uyc>EPn?qigU(l*Fcb)OAItN>AJ<`Hw?P!V|B0**i;W01_N=YvpcYoEIx`BdwvGrUBx&pj#0@fbqEa% z#oGMz6fKWY%?z|*WRV(T#Lv{qRVq1;V8}e}6eIIhHIB@0YhFg`O05(};RC8>XdAWb zjLz@SqJ&D!6ut}J6x4i)R?U1}JA_&J5p@8?jnG1jU6b?_M&XZXcqw0xb1z=3`WeG_ zY8$|;g0(5TU>ey~8kmJTnB}EvhvEU;%ZOX7LwO@t>0w6pE)9;IbsL0YHs~d2Zi>kiWT}|}E~3QXmp06CD{ZLoU3t@=LZI@2`S`E~0tMvK?$`9|dweLB|r_qZ2b z-lpTv6Z%YJ=1zS9(kH-)1I_yx9d_xZ_fX^tttW*`Scq0+>6>648v5XRT>qX?pPzMC z!}X+&3p?c;#=KK6IR1rU<26qr^Ak{RO*cK9aL9@ zy;QQ7Jmpus9BX7$wSL~6d}g66C?ZhYTs5O~Up~#4{||j66k$_ijeMIvQy~bXCsn`2 zUCG>%`>9W3yQJ9@W;|&0nWdL$_Uaj!Z59d1)MRQM$TAn=I$Xxg#t?_T5qgQ!BjHQK zy?k=1{-#V9%JuGKzQlZu)G{4rQBtUnqQ(1lePpCRt} zN{Bz;nqDrGwFqWazFbFZnsIld`xSjNTDx0Kp(crvzY+JnzS@P(9ak)*UDdBswI9!; zJTsp_N}=jSC0m$=U9gWcnu@ZdpM4Q zNqjn|rF-kt=x1CA<(DLSIvr&;sI~4?58W0>Mpb+Mq-?Ys#(kM1oHO^=Ab&S(MB#bS zC};wIRAwgV{bSWa*N-S>mQ1ZCz>l$J0`DfvCLb)hK6GUQ?@T4J+)Urk;0bNa;u0iR zT#T5;!8Mv~QcVBEil((acmSJ3OTO1ypwLFKrrCID5|9p$Y9{Uu1~*Qnr?TMCOiAED zY#ohD;5{j!2Wq#(aZfklh#iHR$Kpz6+A9h!n0oR-G;$rc6i4vE6d!@xiFOfUF^%B) zl%`hf`8SASlt`%U_y|BBB#b$BwU>@CQ} zd4By##o1Hjgk=q&x@t^zt=ZnKKfe*Q>o~%f@tu-ri>P(jY zNq0h~9+mao6WyFH~`L>&ikz6`X#A*IR4FvNsLH}4DS-rF*!WBbH}JIU3+#+ zNJ!|_F{)QYmoCYPT@$-@Nqi!Y!mD{0B~^1j9#-$8qX+MmTi)N`xEp?VU*4qj?6FQ) z-rYQT;d$5poYo}#f!5siCf?PvDCKRTwX1lJSzKdgW=|TQW6R0N&di)bYsd2TG~pb7 zl{$UNb95W-FGfwB>14Bi;ah~?M))3Y-3`>;98lH;?%C;2Qby_@=>G31qjN;Bj@@El z08vq$6FPS58Dqqc;-j$ShRT8B<4EU|iJx>>@G0IT%ojG(7+7aE*2o;wefTgJCRV(T z$Gd%u8xC>F;bQtdL9&fP%9Grt=1v)%BQBDsjHYd0@kqAK$@i+`r%xU=9**Hg$!q`l zHK7-5kwkHxb+1hwJGSl>XVO}y?A=CZj>;Vi^Uaw$iAu)u4o+3F(p!pY>XMZ`bwXx_ zZG2ri+j1*OZ#phLJ3YgenLU2WbhKw`_N+Q<5DG-TyW5N6VoLh`9<}gIsO#jzqV4^i z?qhL9-Xrt&-Icd9EoXS+oD}pmxA=CiR}9>N<>F&kaU+-XuN&UHSz<$vdN=e4b>1hO z=N3P4pOrKGpL5jRn$5jcWk|XBGSy2|`P(w?XigGBPjm`B6BTrFR^Zl%OEo3jziG1u z12eTRZ-cNqtz5--IWv1a>>PbL!~3U?pMvieR`UQ~-&;-aDi^L$;ZIGvy9Ic<4IDex zDMBLq=dwXC-QVt(w{WL`ZTnl(Q z(dX|xTcWB8DU?z_-~p6g#CwzbYMye}DQhxbu7AoGBG3}}vj>0B?Rhb@yb~%I3N#u~ zbRx+}xoE0~@8Ng)MQ^<^ zDX-U$-%fO1H?o%0iFh|0xc5$H_f!4>_NX}d2aqe5gO@CBYm~pn_cPl6BkxA7Gt{SrAl$tK%Ax-dccYP^>#yLx23u#Fi zZhDcy+N=-uB;q@&BWa&X#36cTgQn7FxSr z@|MA*Bk%CJwD1-FJ>_5LiW-N~V(9EgysZ&l$%`3P9_3a_ImJ8D#VQ_3mk;w-$b5na z7~872#LD*YTHR%6{iq)*Mty(vPcUlUx!HV>5I-{Sti68JgE7UgEwB{OY2!oBr$n=L(KVgyY)!kdxDNgk^@CqWu;5{KyOkD#oq z2pG*T$8dt!R*8~Mo#ZWzSB~@h7=3XD!fknshZ<=oxW7bOKEoLr@ARX|$M~bP=yOam zrJwNW_2qm5uYfQF1BQ?()0C6E56NeEwXlf+6#Y5>!T!hx_x@HV8}XXc_9Hx3v^1T1 ze1y5{&7(YnzWIdr5G_d+1@`^V`{+ZLD|r*T@)2)g8SsqFDD)bY*Idx~ZyU~C%NKHBppF*7=g#dCs<~BNcmWLQ`pW?}~NQt2l PN4bxI-0+zH0SWv!;nx%G delta 16888 zcmeHO3w(@M+Mn}Y&qOYhi-?e9#E2k>h&wR|LW5ANs;P>ITZu>}rK%*Vs-&7^bRJbD zbs4Iv2zk|N8`k!!iniLdR#Rly2?q_x--#zWno?_c^!cJm)#* zIqx}Vii%nkU9goV_A%8x7L(~1Gnqp1e)Zb5Yl4ke(?nn<0`G$U5wr&Q2>2LK!Fz4{ z%paI`Ve6K^?0nyuFD+nCRemcqU~Gw#DWQHpq9pO;o1D=~EPKP*M~R8{L$?0N{u|I0 zzmGi zYXb17Vlwpx?F%FWVL(qnMe+}U6yQO;7XkMHHNaEAXy8fU3eX;D4go^^BcSELKJd$> zX_{%tlh-%5ob$#@kU$6_nkOh^Fy)J|vzZbjn$A1=-O$WMx82P7DEQyf%n^{@x0%J< zWLf|`0z3)`n^?q6jZI7NvKV;0mbnzP1aKz$g*oH>EbMAo@oVPS!FfZZC@X%zFiJ6=k%hdyh%Y*;OijRgRaTBieFIYRcwP@iD1MS?SJBlfat{OaZ0>_XE>_=|C

76WB~ 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 Rc-XC{!DR>J&bUn&_`ixjg@XV9 diff --git a/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont b/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont new file mode 100644 index 000000000..b3a30a69a --- /dev/null +++ b/Subsurface_content/Subsurface_contentContent/LargeFont.spritefont @@ -0,0 +1,42 @@ + + + + + + Verdana + + 16 + + 0 + + true + + + + _ + + + + + + ~ + + + + À + ə + + + +