using Barotrauma.Items.Components; using Barotrauma.Steam; using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; namespace Barotrauma.Networking { class GameClient : NetworkMember { public override bool IsClient { get { return true; } } private NetClient client; private GUIMessageBox reconnectBox, waitInServerQueueBox; //TODO: move these to NetLobbyScreen public GUIButton EndRoundButton; public GUITickBox EndVoteTickBox; private NetStats netStats; protected GUITickBox cameraFollowsSub; private ClientPermissions permissions = ClientPermissions.None; private List permittedConsoleCommands = new List(); private bool connected; private byte myID; private List otherClients; private readonly List serverSubmarines = new List(); private string serverIP; private bool needAuth; private bool requiresPw; private int nonce; private string saltedPw; private Facepunch.Steamworks.Auth.Ticket steamAuthTicket; private UInt16 lastSentChatMsgID = 0; //last message this client has successfully sent private UInt16 lastQueueChatMsgID = 0; //last message added to the queue private List chatMsgQueue = new List(); public UInt16 LastSentEntityEventID; private ClientEntityEventManager entityEventManager; private FileReceiver fileReceiver; //has the client been given a character to control this round public bool HasSpawned; public byte ID { get { return myID; } } public VoipClient VoipClient { get; private set; } public override List ConnectedClients { get { return otherClients; } } public FileReceiver FileReceiver { get { return fileReceiver; } } public bool MidRoundSyncing { get { return entityEventManager.MidRoundSyncing; } } private int ownerKey; public bool IsServerOwner { get { return ownerKey > 0; } } public GameClient(string newName, string ip, int ownerKey=0) { //TODO: gui stuff should probably not be here? this.ownerKey = ownerKey; netStats = new NetStats(); inGameHUD = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null) { CanBeFocused = false }; cameraFollowsSub = new GUITickBox(new RectTransform(new Vector2(0.05f, 0.05f), inGameHUD.RectTransform, anchor: Anchor.TopCenter) { AbsoluteOffset = new Point(0, 5), MaxSize = new Point(25, 25) }, TextManager.Get("CamFollowSubmarine")) { Selected = Camera.FollowSub, OnSelected = (tbox) => { Camera.FollowSub = tbox.Selected; return true; } }; chatBox = new ChatBox(inGameHUD, isSinglePlayer: false); chatBox.OnEnterMessage += EnterChatMessage; chatBox.InputBox.OnTextChanged += TypingChatMessage; var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight) { AbsoluteSpacing = 5, CanBeFocused = false }; EndRoundButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) }, TextManager.Get("EndRound")) { OnClicked = (btn, userdata) => { if (!permissions.HasFlag(ClientPermissions.ManageRound)) return false; RequestRoundEnd(); return true; }, Visible = false }; EndVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.4f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) }, TextManager.Get("EndRound")) { UserData = TextManager.Get("EndRound"), OnSelected = ToggleEndRoundVote, Visible = false }; ShowLogButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) }, TextManager.Get("ServerLog")) { OnClicked = (GUIButton button, object userData) => { if (serverSettings.ServerLog.LogFrame == null) { serverSettings.ServerLog.CreateLogFrame(); } else { serverSettings.ServerLog.LogFrame = null; GUI.KeyboardDispatcher.Subscriber = null; } return true; } }; GameMain.DebugDraw = false; Hull.EditFire = false; Hull.EditWater = false; newName = newName.Replace(":", "").Replace(";", ""); name = newName; entityEventManager = new ClientEntityEventManager(this); fileReceiver = new FileReceiver(); fileReceiver.OnFinished += OnFileReceived; fileReceiver.OnTransferFailed += OnTransferFailed; characterInfo = new CharacterInfo(Character.HumanConfigFile, name, null) { Job = null }; otherClients = new List(); serverSettings = new ServerSettings("Server", 0, 0, 0, false, false); ConnectToServer(ip); //ServerLog = new ServerLog(""); ChatMessage.LastID = 0; GameMain.NetLobbyScreen = new NetLobbyScreen(); } private void ConnectToServer(string hostIP) { chatBox.InputBox.Enabled = false; if (GameMain.NetLobbyScreen?.TextBox != null) { GameMain.NetLobbyScreen.TextBox.Enabled = false; } string[] address = hostIP.Split(':'); if (address.Length == 1) { serverIP = hostIP; Port = NetConfig.DefaultPort; } else { serverIP = string.Join(":", address.Take(address.Length - 1)); if (!int.TryParse(address[address.Length - 1], out int port)) { DebugConsole.ThrowError("Invalid port: " + address[address.Length - 1] + "!"); Port = NetConfig.DefaultPort; } else { Port = port; } } myCharacter = Character.Controlled; ChatMessage.LastID = 0; // Create new instance of configs. Parameter is "application Id". It has to be same on client and server. NetPeerConfiguration = new NetPeerConfiguration("barotrauma"); NetPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); client = new NetClient(NetPeerConfiguration); NetPeer = client; client.Start(); System.Net.IPEndPoint IPEndPoint = null; try { IPEndPoint = new System.Net.IPEndPoint(NetUtility.Resolve(serverIP), Port); } catch { new GUIMessageBox(TextManager.Get("CouldNotConnectToServer"), TextManager.Get("InvalidIPAddress").Replace("[serverip]", serverIP).Replace("[port]", Port.ToString())); return; } NetOutgoingMessage outmsg = client.CreateMessage(); WriteAuthRequest(outmsg); // Connect client, to ip previously requested from user try { client.Connect(IPEndPoint, outmsg); } catch (Exception e) { DebugConsole.ThrowError("Couldn't connect to " + hostIP + ". Error message: " + e.Message); Disconnect(); chatBox.InputBox.Enabled = true; if (GameMain.NetLobbyScreen?.TextBox != null) { GameMain.NetLobbyScreen.TextBox.Enabled = true; } GameMain.ServerListScreen.Select(); return; } updateInterval = new TimeSpan(0, 0, 0, 0, 150); CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo"); } private bool RetryConnection(GUIButton button, object obj) { if (client != null) client.Shutdown(TextManager.Get("Disconnecting")); ConnectToServer(serverIP); return true; } private bool ReturnToServerList(GUIButton button, object obj) { Disconnect(); Submarine.Unload(); GameMain.Client = null; GameMain.GameSession = null; GameMain.ServerListScreen.Select(); return true; } private bool connectCancelled; private bool CancelConnect(GUIButton button, object obj) { connectCancelled = true; return true; } // Before main looping starts, we loop here and wait for approval message private IEnumerable WaitForStartingInfo() { requiresPw = false; needAuth = true; saltedPw = ""; connectCancelled = false; // When this is set to true, we are approved and ready to go bool CanStart = false; DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20); DateTime reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200); // Loop until we are approved //TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly) string connectingText = TextManager.Get("Connecting"); while (!CanStart && !connectCancelled) { if (reconnectBox == null) { reconnectBox = new GUIMessageBox(connectingText, TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP), new string[] { TextManager.Get("Cancel") }); reconnectBox.Buttons[0].OnClicked += CancelConnect; reconnectBox.Buttons[0].OnClicked += reconnectBox.Close; } reconnectBox.Header.Text = connectingText + new string('.', ((int)Timing.TotalTime % 3 + 1)); if (DateTime.Now > reqAuthTime) { if (needAuth) { //request auth again NetOutgoingMessage reqAuthMsg = client.CreateMessage(); WriteAuthRequest(reqAuthMsg); client.SendMessage(reqAuthMsg, NetDeliveryMethod.Unreliable); } else { //request init again NetOutgoingMessage outmsg = client.CreateMessage(); outmsg.Write((byte)ClientPacketHeader.REQUEST_INIT); if (requiresPw) { outmsg.Write(saltedPw); } outmsg.Write(GameMain.Version.ToString()); var mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent); outmsg.Write((UInt16)mpContentPackages.Count()); foreach (ContentPackage contentPackage in mpContentPackages) { outmsg.Write(contentPackage.Name); outmsg.Write(contentPackage.MD5hash.Hash); } outmsg.Write(name); client.SendMessage(outmsg, NetDeliveryMethod.Unreliable); DebugConsole.Log("Sending init request (" + (requiresPw ? "password required" : "no password required") + ")"); } reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 1); } yield return CoroutineStatus.Running; if (DateTime.Now > timeOut) break; NetIncomingMessage inc; // If new messages arrived if ((inc = client.ReadMessage()) == null) continue; string pwMsg = TextManager.Get("PasswordRequired"); try { switch (inc.MessageType) { case NetIncomingMessageType.Data: DecompressIncomingMessage(inc); ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); switch (header) { case ServerPacketHeader.AUTH_RESPONSE: DebugConsole.Log("Received auth response (needauth: " + needAuth + ")"); if (needAuth) { if (inc.ReadBoolean()) { DebugConsole.Log(" password required."); //requires password nonce = inc.ReadInt32(); requiresPw = true; } else { DebugConsole.Log(" password not required."); requiresPw = false; reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200); } needAuth = false; //got auth! } break; case ServerPacketHeader.AUTH_FAILURE: //failed to authenticate, can still use same nonce DebugConsole.Log("Received auth failure message"); pwMsg = inc.ReadString(); requiresPw = true; break; case ServerPacketHeader.UPDATE_LOBBY: DebugConsole.Log("Recived lobby update"); //server accepted client ReadLobbyUpdate(inc); CanStart = true; break; } break; case NetIncomingMessageType.StatusChanged: NetConnectionStatus connectionStatus = (NetConnectionStatus)inc.ReadByte(); if (connectionStatus == NetConnectionStatus.Disconnected) { ReadDisconnectMessage(inc, false); connectCancelled = true; } break; } } catch (Exception e) { DebugConsole.ThrowError("Error while connecting to the server", e); break; } if (requiresPw && !CanStart && !connectCancelled) { if (reconnectBox != null) { reconnectBox.Close(null, null); reconnectBox = null; } var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }); var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 0.1f), msgBox.InnerFrame.RectTransform, Anchor.Center)) { IgnoreLayoutGroups = true, UserData = "password" }; var okButton = msgBox.Buttons[0]; var cancelButton = msgBox.Buttons[1]; while (GUIMessageBox.MessageBoxes.Contains(msgBox)) { while (client.ReadMessage() != null) { switch (inc.MessageType) { case NetIncomingMessageType.StatusChanged: NetConnectionStatus connectionStatus = (NetConnectionStatus)inc.ReadByte(); if (connectionStatus == NetConnectionStatus.Disconnected) { ReadDisconnectMessage(inc, false); msgBox.Close(null, null); connectCancelled = true; } break; } } if (DateTime.Now > reqAuthTime) { //request auth again to prevent timeout NetOutgoingMessage reqAuthMsg = client.CreateMessage(); WriteAuthRequest(reqAuthMsg); client.SendMessage(reqAuthMsg, NetDeliveryMethod.Unreliable); reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 3); } okButton.Enabled = !string.IsNullOrWhiteSpace(passwordBox.Text); if (okButton.Selected) { saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(passwordBox.Text))); saltedPw = saltedPw + Convert.ToString(nonce); saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(saltedPw))); timeOut = DateTime.Now + new TimeSpan(0, 0, 20); reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 1); msgBox.Close(null, null); break; } else if (cancelButton.Selected) { msgBox.Close(null, null); connectCancelled = true; } else { yield return CoroutineStatus.Running; } } } } if (reconnectBox != null) { reconnectBox.Close(null, null); reconnectBox = null; } if (connectCancelled) yield return CoroutineStatus.Success; if (client.ConnectionStatus != NetConnectionStatus.Connected) { steamAuthTicket?.Cancel(); steamAuthTicket = null; var reconnect = new GUIMessageBox(TextManager.Get("ConnectionFailed"), TextManager.Get("CouldNotConnectToServer"), new string[] { TextManager.Get("Retry"), TextManager.Get("Cancel") }); DebugConsole.NewMessage("Failed to connect to the server - connection status: " + client.ConnectionStatus.ToString(), Color.Orange); reconnect.Buttons[0].OnClicked += RetryConnection; reconnect.Buttons[0].OnClicked += reconnect.Close; reconnect.Buttons[1].OnClicked += ReturnToServerList; reconnect.Buttons[1].OnClicked += reconnect.Close; } else { if (Screen.Selected != GameMain.GameScreen) { GameMain.NetLobbyScreen.Select(); } connected = true; chatBox.InputBox.Enabled = true; if (GameMain.NetLobbyScreen?.TextBox != null) { GameMain.NetLobbyScreen.TextBox.Enabled = true; } } yield return CoroutineStatus.Success; } private void WriteAuthRequest(NetOutgoingMessage outmsg) { if (SteamManager.IsInitialized && SteamManager.USE_STEAM) { outmsg.Write((byte)ClientPacketHeader.REQUEST_STEAMAUTH); } else { DebugConsole.Log("Sending auth request"); outmsg.Write((byte)ClientPacketHeader.REQUEST_AUTH); } outmsg.Write(ownerKey); if (SteamManager.IsInitialized && SteamManager.USE_STEAM) { if (steamAuthTicket == null) { steamAuthTicket = SteamManager.GetAuthSessionTicket(); } DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 3); while ((steamAuthTicket.Data == null || steamAuthTicket.Data.Length == 0) && DateTime.Now < timeOut) { System.Threading.Thread.Sleep(10); } outmsg.Write(SteamManager.GetSteamID()); outmsg.Write(steamAuthTicket.Data.Length); outmsg.Write(steamAuthTicket.Data); DebugConsole.Log("Sending Steam auth request"); DebugConsole.Log(" Steam ID: " + SteamManager.GetSteamID()); DebugConsole.Log(" Ticket data: " + ToolBox.LimitString(string.Concat(steamAuthTicket.Data.Select(b => b.ToString("X2"))), 16)); DebugConsole.Log(" Msg length: " + outmsg.LengthBytes); } } public override void Update(float deltaTime) { #if DEBUG if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return; #endif foreach (Client c in ConnectedClients) { c.UpdateSoundPosition(); } if (VoipCapture.Instance != null) { if (VoipCapture.Instance.LastEnqueueAudio > DateTime.Now - new TimeSpan(0, 0, 0, 0, milliseconds: 100)) { var myClient = ConnectedClients.Find(c => c.ID == ID); if (Screen.Selected == GameMain.NetLobbyScreen) { GameMain.NetLobbyScreen.SetPlayerSpeaking(myClient); } else { GameMain.GameSession?.CrewManager?.SetPlayerSpeaking(myClient); } } } if (gameStarted) SetRadioButtonColor(); if (ShowNetStats && client?.ServerConnection != null) { netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes); netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes); netStats.AddValue(NetStats.NetStatType.ResentMessages, client.ServerConnection.Statistics.ResentMessages); netStats.Update(deltaTime); } UpdateHUD(deltaTime); base.Update(deltaTime); if (!connected) return; if (reconnectBox != null) { reconnectBox.Close(null, null); reconnectBox = null; } try { CheckServerMessages(); } catch (Exception e) { string errorMsg = "Error while reading a message from server. {" + e + "}\n" + e.StackTrace; GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); DebugConsole.ThrowError("Error while reading a message from server.", e); new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("MessageReadError").Replace("[message]", e.Message).Replace("[targetsite]", e.TargetSite.ToString())); Disconnect(); GameMain.MainMenuScreen.Select(); return; } if (gameStarted && Screen.Selected == GameMain.GameScreen) { EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned; if (respawnManager != null) { respawnManager.Update(deltaTime); } if (updateTimer > DateTime.Now) { return; } SendIngameUpdate(); } else { if (updateTimer > DateTime.Now) { return; } SendLobbyUpdate(); } if (serverSettings.VoiceChatEnabled) { VoipClient?.SendToServer(); } // Update current time updateTimer = DateTime.Now + updateInterval; } private CoroutineHandle startGameCoroutine; private void DecompressIncomingMessage(NetIncomingMessage inc) { byte[] data = inc.Data; if (data[data.Length - 1] == 1) { using (MemoryStream stream = new MemoryStream()) { stream.Write(data, 0, inc.LengthBytes-1); stream.Position = 0; using (MemoryStream decompressed = new MemoryStream()) { using (DeflateStream deflate = new DeflateStream(stream, CompressionMode.Decompress, false)) { deflate.CopyTo(decompressed); } byte[] newData = decompressed.ToArray(); inc.Data = newData; inc.LengthBytes = newData.Length; inc.Position = 0; } } } } /// /// Check for new incoming messages from server /// private void CheckServerMessages() { // Create new incoming message holder NetIncomingMessage inc; if (startGameCoroutine != null && CoroutineManager.IsCoroutineRunning(startGameCoroutine)) return; while ((inc = client.ReadMessage()) != null) { switch (inc.MessageType) { case NetIncomingMessageType.Data: DecompressIncomingMessage(inc); ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); switch (header) { case ServerPacketHeader.UPDATE_LOBBY: ReadLobbyUpdate(inc); break; case ServerPacketHeader.UPDATE_INGAME: ReadIngameUpdate(inc); break; case ServerPacketHeader.VOICE: VoipClient.Read(inc); break; case ServerPacketHeader.QUERY_STARTGAME: string subName = inc.ReadString(); string subHash = inc.ReadString(); bool usingShuttle = inc.ReadBoolean(); string shuttleName = inc.ReadString(); string shuttleHash = inc.ReadString(); NetOutgoingMessage readyToStartMsg = client.CreateMessage(); readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; readyToStartMsg.Write( GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList) && GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox)); WriteCharacterInfo(readyToStartMsg); client.SendMessage(readyToStartMsg, NetDeliveryMethod.ReliableUnordered); break; case ServerPacketHeader.STARTGAME: startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false); break; case ServerPacketHeader.ENDGAME: string endMessage = inc.ReadString(); bool missionSuccessful = inc.ReadBoolean(); Character.TeamType winningTeam = (Character.TeamType)inc.ReadByte(); if (missionSuccessful && GameMain.GameSession?.Mission != null) { GameMain.GameSession.WinningTeam = winningTeam; GameMain.GameSession.Mission.Completed = true; } CoroutineManager.StartCoroutine(EndGame(endMessage)); break; case ServerPacketHeader.CAMPAIGN_SETUP_INFO: UInt16 saveCount = inc.ReadUInt16(); List saveFiles = new List(); for (int i = 0; i < saveCount; i++) { saveFiles.Add(inc.ReadString()); } GameMain.NetLobbyScreen.CampaignSetupUI = MultiPlayerCampaign.StartCampaignSetup(serverSubmarines, saveFiles); break; case ServerPacketHeader.PERMISSIONS: ReadPermissions(inc); break; case ServerPacketHeader.ACHIEVEMENT: ReadAchievement(inc); break; case ServerPacketHeader.CHEATS_ENABLED: bool cheatsEnabled = inc.ReadBoolean(); inc.ReadPadBits(); if (cheatsEnabled == DebugConsole.CheatsEnabled) { continue; } else { DebugConsole.CheatsEnabled = cheatsEnabled; SteamAchievementManager.CheatsEnabled = cheatsEnabled; if (cheatsEnabled) { new GUIMessageBox(TextManager.Get("CheatsEnabledTitle"), TextManager.Get("CheatsEnabledDescription")); } } break; case ServerPacketHeader.FILE_TRANSFER: fileReceiver.ReadMessage(inc); break; } break; case NetIncomingMessageType.StatusChanged: NetConnectionStatus connectionStatus = (NetConnectionStatus)inc.ReadByte(); DebugConsole.NewMessage("Connection status changed: " + connectionStatus.ToString(), Color.Orange); if (connectionStatus == NetConnectionStatus.Disconnected) { ReadDisconnectMessage(inc, true); } break; } } } private void ReadDisconnectMessage(NetIncomingMessage inc, bool allowReconnect) { steamAuthTicket?.Cancel(); steamAuthTicket = null; string disconnectMsg = inc.ReadString(); string[] splitMsg = disconnectMsg.Split('/'); DisconnectReason disconnectReason = DisconnectReason.Unknown; if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason); DebugConsole.NewMessage("Received a disconnect message (" + disconnectMsg + ")"); if (disconnectReason == DisconnectReason.ServerFull) { //already waiting for a slot to free up, do nothing if (CoroutineManager.IsCoroutineRunning("WaitInServerQueue")) return; reconnectBox?.Close(); reconnectBox = null; var queueBox = new GUIMessageBox( TextManager.Get("DisconnectReason.ServerFull"), TextManager.Get("ServerFullQuestionPrompt"), new string[] { TextManager.Get("Cancel"), TextManager.Get("ServerQueue") }); queueBox.Buttons[0].OnClicked += queueBox.Close; queueBox.Buttons[1].OnClicked += queueBox.Close; queueBox.Buttons[1].OnClicked += (btn, userdata) => { reconnectBox?.Close(); reconnectBox = null; CoroutineManager.StartCoroutine(WaitInServerQueue(), "WaitInServerQueue"); return true; }; return; } else { //disconnected/denied for some other reason than the server being full // -> stop queuing and show a message box waitInServerQueueBox?.Close(); waitInServerQueueBox = null; CoroutineManager.StopCoroutines("WaitInServerQueue"); } if (allowReconnect && disconnectReason == DisconnectReason.Unknown) { DebugConsole.NewMessage("Attempting to reconnect..."); string msg = TextManager.GetServerMessage(disconnectMsg); msg = string.IsNullOrWhiteSpace(msg) ? TextManager.Get("ConnectionLostReconnecting") : msg + '\n' + TextManager.Get("ConnectionLostReconnecting"); reconnectBox = new GUIMessageBox( TextManager.Get("ConnectionLost"), msg, new string[0]); connected = false; ConnectToServer(serverIP); } else { string msg = ""; if (disconnectReason == DisconnectReason.Unknown) { DebugConsole.NewMessage("Do not attempt reconnect (not allowed)."); msg = disconnectMsg; } else { DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection)."); msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString()); for (int i = 1; i < splitMsg.Length; i++) { msg += TextManager.GetServerMessage(splitMsg[i]); } } var msgBox = new GUIMessageBox(TextManager.Get(allowReconnect ? "ConnectionLost" : "CouldNotConnectToServer"), msg); msgBox.Buttons[0].OnClicked += ReturnToServerList; } } private IEnumerable WaitInServerQueue() { waitInServerQueueBox = new GUIMessageBox( TextManager.Get("ServerQueuePleaseWait"), TextManager.Get("WaitingInServerQueue"), new string[] { TextManager.Get("Cancel") }); waitInServerQueueBox.Buttons[0].OnClicked += (btn, userdata) => { CoroutineManager.StopCoroutines("WaitInServerQueue"); waitInServerQueueBox?.Close(); waitInServerQueueBox = null; return true; }; while (!connected) { if (!CoroutineManager.IsCoroutineRunning("WaitForStartingInfo")) { ConnectToServer(serverIP); yield return new WaitForSeconds(2.0f); } yield return new WaitForSeconds(0.5f); } waitInServerQueueBox?.Close(); waitInServerQueueBox = null; yield return CoroutineStatus.Success; } private void ReadAchievement(NetIncomingMessage inc) { string achievementIdentifier = inc.ReadString(); SteamAchievementManager.UnlockAchievement(achievementIdentifier); } private void ReadPermissions(NetIncomingMessage inc) { List permittedConsoleCommands = new List(); byte clientID = inc.ReadByte(); ClientPermissions permissions = ClientPermissions.None; List permittedCommands = new List(); Client.ReadPermissions(inc, out permissions, out permittedCommands); Client targetClient = ConnectedClients.Find(c => c.ID == clientID); if (targetClient != null) { targetClient.SetPermissions(permissions, permittedCommands); } if (clientID == myID) { SetMyPermissions(permissions, permittedCommands.Select(command => command.names[0])); } } private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable permittedConsoleCommands) { if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) { if (newPermissions == permissions) return; } permissions = newPermissions; this.permittedConsoleCommands = new List(permittedConsoleCommands); //don't show the "permissions changed" popup if the client owns the server if (ownerKey == 0) { GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions"); string msg = ""; if (newPermissions == ClientPermissions.None) { msg = TextManager.Get("PermissionsRemoved"); } else { msg = TextManager.Get("CurrentPermissions") + '\n'; foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { if (!newPermissions.HasFlag(permission) || permission == ClientPermissions.None) continue; msg += " - " + TextManager.Get("ClientPermission." + permission) + "\n"; } } GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("PermissionsChanged"), msg, GUIMessageBox.DefaultWidth, 0) { UserData = "permissions" }; if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands)) { int listBoxWidth = (int)(msgBox.InnerFrame.Rect.Width) / 2 - 30; new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.1f), msgBox.InnerFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.05f, 0.15f) }, TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUI.SmallFont); var commandList = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.55f), msgBox.InnerFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.05f, 0.25f) }); foreach (string permittedCommand in permittedConsoleCommands) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), commandList.Content.RectTransform, minSize: new Point(0, 15)), permittedCommand, font: GUI.SmallFont) { CanBeFocused = false }; } } } GameMain.NetLobbyScreen.UpdatePermissions(); } private IEnumerable StartGame(NetIncomingMessage inc) { if (Character != null) Character.Remove(); HasSpawned = false; GameMain.LightManager.LightingEnabled = true; //enable spectate button in case we fail to start the round now //(for example, due to a missing sub file or an error) GameMain.NetLobbyScreen.ShowSpectateButton(); entityEventManager.Clear(); LastSentEntityEventID = 0; EndVoteTickBox.Selected = false; int seed = inc.ReadInt32(); string levelSeed = inc.ReadString(); int levelEqualityCheckVal = inc.ReadInt32(); float levelDifficulty = inc.ReadFloat(); byte losMode = inc.ReadByte(); int missionTypeIndex = inc.ReadByte(); string subName = inc.ReadString(); string subHash = inc.ReadString(); bool usingShuttle = inc.ReadBoolean(); string shuttleName = inc.ReadString(); string shuttleHash = inc.ReadString(); string modeIdentifier = inc.ReadString(); int missionIndex = inc.ReadInt16(); bool respawnAllowed = inc.ReadBoolean(); bool loadSecondSub = inc.ReadBoolean(); bool disguisesAllowed = inc.ReadBoolean(); bool isTraitor = inc.ReadBoolean(); string traitorTargetName = isTraitor ? inc.ReadString() : null; serverSettings.ReadMonsterEnabled(inc); GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier); MultiPlayerCampaign campaign = GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ? GameMain.GameSession?.GameMode as MultiPlayerCampaign : null; if (gameMode == null) { DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!"); yield return CoroutineStatus.Success; } GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; GameMain.LightManager.LosMode = (LosMode)losMode; serverSettings.AllowDisguises = disguisesAllowed; if (campaign == null) { if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList)) { yield return CoroutineStatus.Success; } if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox)) { yield return CoroutineStatus.Success; } } Rand.SetSyncedSeed(seed); if (campaign == null) { GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionIndex < 0 ? null : MissionPrefab.List[missionIndex]); GameMain.GameSession.StartRound(levelSeed, levelDifficulty, loadSecondSub); } else { if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset(); GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, reloadSub: true, loadSecondSub: false, mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); } if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) { string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ")."; DebugConsole.ThrowError(errorMsg, createMessageBox: true); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); CoroutineManager.StartCoroutine(EndGame("")); yield return CoroutineStatus.Failure; } if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); gameStarted = true; GameMain.GameScreen.Select(); AddChatMessage($"ServerMessage.HowToCommunicate~[chatbutton]={GameMain.Config.KeyBind(InputType.Chat).ToString()}~[radiobutton]={GameMain.Config.KeyBind(InputType.RadioChat).ToString()}", ChatMessageType.Server); yield return CoroutineStatus.Success; } public IEnumerable EndGame(string endMessage) { if (!gameStarted) yield return CoroutineStatus.Success; if (GameMain.GameSession != null) GameMain.GameSession.GameMode.End(endMessage); gameStarted = false; Character.Controlled = null; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; respawnManager = null; float endPreviewLength = 10.0f; if (Screen.Selected == GameMain.GameScreen) { new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); float secondsLeft = endPreviewLength; do { secondsLeft -= CoroutineManager.UnscaledDeltaTime; yield return CoroutineStatus.Running; } while (secondsLeft > 0.0f); } Submarine.Unload(); GameMain.NetLobbyScreen.Select(); myCharacter = null; foreach (Client c in otherClients) { c.InGame = false; c.Character = null; } yield return CoroutineStatus.Success; } private void ReadInitialUpdate(NetIncomingMessage inc) { myID = inc.ReadByte(); VoipClient = new VoipClient(this, client); UInt16 subListCount = inc.ReadUInt16(); serverSubmarines.Clear(); for (int i = 0; i < subListCount; i++) { string subName = inc.ReadString(); string subHash = inc.ReadString(); var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); if (matchingSub != null) { serverSubmarines.Add(matchingSub); } else { serverSubmarines.Add(new Submarine(Path.Combine(Submarine.SavePath, subName) + ".sub", subHash, false)); } } GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, serverSubmarines); GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, serverSubmarines); gameStarted = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean(); ReadPermissions(inc); if (gameStarted) { new GUIMessageBox(TextManager.Get("PleaseWait"), TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled")); GameMain.NetLobbyScreen.Select(); } } private void ReadClientList(NetIncomingMessage inc) { UInt16 listId = inc.ReadUInt16(); List tempClients = new List(); int clientCount = inc.ReadByte(); for (int i = 0; i < clientCount; i++) { byte id = inc.ReadByte(); string name = inc.ReadString(); UInt16 characterID = inc.ReadUInt16(); bool muted = inc.ReadBoolean(); inc.ReadPadBits(); tempClients.Add(new TempClient { ID = id, Name = name, CharacterID = characterID, Muted = muted }); } if (NetIdUtils.IdMoreRecent(listId, LastClientListUpdateID)) { bool updateClientListId = true; List currentClients = new List(); foreach (TempClient tc in tempClients) { //see if the client already exists var existingClient = ConnectedClients.Find(c => c.ID == tc.ID && c.Name == tc.Name); if (existingClient == null) //if not, create it { existingClient = new Client(tc.Name, tc.ID) { Muted = tc.Muted }; ConnectedClients.Add(existingClient); GameMain.NetLobbyScreen.AddPlayer(existingClient); } existingClient.Character = null; existingClient.Muted = tc.Muted; if (tc.CharacterID > 0) { existingClient.Character = Entity.FindEntityByID(tc.CharacterID) as Character; if (existingClient.Character == null) { updateClientListId = false; } } if (existingClient.ID == myID) { existingClient.SetPermissions(permissions, permittedConsoleCommands); } currentClients.Add(existingClient); } //remove clients that aren't present anymore for (int i = ConnectedClients.Count - 1; i >= 0; i--) { if (!currentClients.Contains(ConnectedClients[i])) { GameMain.NetLobbyScreen.RemovePlayer(ConnectedClients[i]); ConnectedClients[i].Dispose(); ConnectedClients.RemoveAt(i); } } if (updateClientListId) LastClientListUpdateID = listId; } } private void ReadLobbyUpdate(NetIncomingMessage inc) { ServerNetObject objHeader; while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) { switch (objHeader) { case ServerNetObject.SYNC_IDS: bool lobbyUpdated = inc.ReadBoolean(); inc.ReadPadBits(); if (lobbyUpdated) { var prevDispatcher = GUI.KeyboardDispatcher.Subscriber; UInt16 updateID = inc.ReadUInt16(); UInt16 settingsLen = inc.ReadUInt16(); byte[] settingsData = inc.ReadBytes(settingsLen); if (inc.ReadBoolean()) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray); } ReadInitialUpdate(inc); } string selectSubName = inc.ReadString(); string selectSubHash = inc.ReadString(); bool usingShuttle = inc.ReadBoolean(); string selectShuttleName = inc.ReadString(); string selectShuttleHash = inc.ReadString(); bool allowSubVoting = inc.ReadBoolean(); bool allowModeVoting = inc.ReadBoolean(); bool voiceChatEnabled = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean(); YesNoMaybe traitorsEnabled = (YesNoMaybe)inc.ReadRangedInteger(0, 2); int missionTypeIndex = inc.ReadRangedInteger(0, Enum.GetValues(typeof(MissionType)).Length - 1); int modeIndex = inc.ReadByte(); string levelSeed = inc.ReadString(); float levelDifficulty = inc.ReadFloat(); byte botCount = inc.ReadByte(); BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal; bool autoRestartEnabled = inc.ReadBoolean(); float autoRestartTimer = autoRestartEnabled ? inc.ReadFloat() : 0.0f; //ignore the message if we already a more up-to-date one if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID)) { NetBuffer settingsBuf = new NetBuffer(); settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.Position = 0; serverSettings.ClientRead(settingsBuf); GameMain.NetLobbyScreen.LastUpdateID = updateID; serverSettings.ServerLog.ServerName = serverSettings.ServerName; if (!GameMain.NetLobbyScreen.ServerName.Selected) GameMain.NetLobbyScreen.ServerName.Text = serverSettings.ServerName; if (!GameMain.NetLobbyScreen.ServerMessage.Selected) GameMain.NetLobbyScreen.ServerMessage.Text = serverSettings.ServerMessageText; GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; if (!allowSubVoting) GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, GameMain.NetLobbyScreen.SubList); GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox); GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); GameMain.NetLobbyScreen.SetMissionType(missionTypeIndex); if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex); GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.LevelSeed = levelSeed; GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); GameMain.NetLobbyScreen.SetBotCount(botCount); GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); serverSettings.VoiceChatEnabled = voiceChatEnabled; serverSettings.Voting.AllowSubVoting = allowSubVoting; serverSettings.Voting.AllowModeVoting = allowModeVoting; GUI.KeyboardDispatcher.Subscriber = prevDispatcher; } } bool campaignUpdated = inc.ReadBoolean(); inc.ReadPadBits(); if (campaignUpdated) { MultiPlayerCampaign.ClientRead(inc); } else if (GameMain.NetLobbyScreen.SelectedMode.Identifier != "multiplayercampaign") { GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null); } lastSentChatMsgID = inc.ReadUInt16(); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.CHAT_MESSAGE: ChatMessage.ClientRead(inc); break; case ServerNetObject.VOTE: serverSettings.Voting.ClientRead(inc); break; } } } private void ReadIngameUpdate(NetIncomingMessage inc) { List entities = new List(); float sendingTime = inc.ReadFloat() - inc.SenderConnection.RemoteTimeOffset; ServerNetObject? prevObjHeader = null; long prevBitPos = 0; long prevBytePos = 0; long prevBitLength = 0; long prevByteLength = 0; ServerNetObject objHeader; while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) { bool eventReadFailed = false; switch (objHeader) { case ServerNetObject.SYNC_IDS: lastSentChatMsgID = inc.ReadUInt16(); LastSentEntityEventID = inc.ReadUInt16(); break; case ServerNetObject.ENTITY_POSITION: UInt16 id = inc.ReadUInt16(); byte msgLength = inc.ReadByte(); long msgEndPos = inc.Position + msgLength * 8; var entity = Entity.FindEntityByID(id) as IServerSerializable; if (entity != null) { entity.ClientRead(objHeader, inc, sendingTime); } //force to the correct position in case the entity doesn't exist //or the message wasn't read correctly for whatever reason inc.Position = msgEndPos; inc.ReadPadBits(); break; case ServerNetObject.CLIENT_LIST: ReadClientList(inc); break; case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT_INITIAL: if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) { eventReadFailed = true; break; } break; case ServerNetObject.CHAT_MESSAGE: ChatMessage.ClientRead(inc); break; default: List errorLines = new List { "Error while reading update from server (unknown object header \"" + objHeader + "\"!)", "Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)", prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!", "Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)" }; if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL) { foreach (IServerSerializable ent in entities) { if (ent == null) { errorLines.Add(" - NULL"); continue; } Entity e = ent as Entity; errorLines.Add(" - " + e.ToString()); } } foreach (string line in errorLines) { DebugConsole.ThrowError(line); } errorLines.Add("Last console messages:"); for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--) { errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); } GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); DebugConsole.ThrowError("Writing object data to \"crashreport_object.bin\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); FileStream fl = File.Open("crashreport_object.bin", FileMode.Create); BinaryWriter sw = new BinaryWriter(fl); sw.Write(inc.Data, (int)(prevBytePos - prevByteLength), (int)(prevByteLength)); sw.Close(); fl.Close(); throw new Exception("Error while reading update from server: please send us \"crashreport_object.bin\"!"); } prevBitLength = inc.Position - prevBitPos; prevByteLength = inc.PositionInBytes - prevByteLength; prevObjHeader = objHeader; prevBitPos = inc.Position; prevBytePos = inc.PositionInBytes; if (eventReadFailed) { break; } } } private void SendLobbyUpdate() { NetOutgoingMessage outmsg = client.CreateMessage(); outmsg.Write((byte)ClientPacketHeader.UPDATE_LOBBY); outmsg.Write((byte)ClientNetObject.SYNC_IDS); outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); outmsg.Write(ChatMessage.LastID); outmsg.Write(LastClientListUpdateID); var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; if (campaign == null || campaign.LastSaveID == 0) { outmsg.Write((UInt16)0); } else { outmsg.Write(campaign.LastSaveID); outmsg.Write(campaign.CampaignID); outmsg.Write(campaign.LastUpdateID); outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); } chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID)); for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++) { if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > client.Configuration.MaximumTransmissionUnit - 5) { //not enough room in this packet return; } chatMsgQueue[i].ClientWrite(outmsg); } outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); if (outmsg.LengthBytes > client.Configuration.MaximumTransmissionUnit) { DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + client.Configuration.MaximumTransmissionUnit); } client.SendMessage(outmsg, NetDeliveryMethod.Unreliable); } private void SendIngameUpdate() { NetOutgoingMessage outmsg = client.CreateMessage(); outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME); outmsg.Write((byte)ClientNetObject.SYNC_IDS); //outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); outmsg.Write(ChatMessage.LastID); outmsg.Write(entityEventManager.LastReceivedID); outmsg.Write(LastClientListUpdateID); Character.Controlled?.ClientWrite(outmsg); entityEventManager.Write(outmsg, client.ServerConnection); chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID)); for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++) { if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > client.Configuration.MaximumTransmissionUnit - 5) { //not enough room in this packet return; } chatMsgQueue[i].ClientWrite(outmsg); } outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE); if (outmsg.LengthBytes > client.Configuration.MaximumTransmissionUnit) { DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + client.Configuration.MaximumTransmissionUnit); } client.SendMessage(outmsg, NetDeliveryMethod.Unreliable); } public void SendChatMessage(ChatMessage msg) { if (client.ServerConnection == null) return; lastQueueChatMsgID++; msg.NetStateID = lastQueueChatMsgID; chatMsgQueue.Add(msg); } public void SendChatMessage(string message, ChatMessageType type = ChatMessageType.Default) { if (client.ServerConnection == null) return; ChatMessage chatMessage = ChatMessage.Create( gameStarted && myCharacter != null ? myCharacter.Name : name, message, type, gameStarted && myCharacter != null ? myCharacter : null); lastQueueChatMsgID++; chatMessage.NetStateID = lastQueueChatMsgID; chatMsgQueue.Add(chatMessage); } public void RequestFile(FileTransferType fileType, string file, string fileHash) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.FILE_REQUEST); msg.Write((byte)FileTransferMessageType.Initiate); msg.Write((byte)fileType); if (file != null) msg.Write(file); if (fileHash != null) msg.Write(fileHash); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public void CancelFileTransfer(FileReceiver.FileTransferIn transfer) { CancelFileTransfer(transfer.SequenceChannel); } public void CancelFileTransfer(int sequenceChannel) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.FILE_REQUEST); msg.Write((byte)FileTransferMessageType.Cancel); msg.Write((byte)sequenceChannel); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } private void OnFileReceived(FileReceiver.FileTransferIn transfer) { switch (transfer.FileType) { case FileTransferType.Submarine: new GUIMessageBox(TextManager.Get("ServerDownloadFinished"), TextManager.Get("FileDownloadedNotification").Replace("[filename]", transfer.FileName)); var newSub = new Submarine(transfer.FilePath); var existingSubs = Submarine.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.Hash == newSub.MD5Hash.Hash).ToList(); foreach (Submarine existingSub in existingSubs) { existingSub.Dispose(); } Submarine.AddToSavedSubs(newSub); for (int i = 0; i < 2; i++) { IEnumerable subListChildren = (i == 0) ? GameMain.NetLobbyScreen.ShuttleList.ListBox.Content.Children : GameMain.NetLobbyScreen.SubList.Content.Children; var subElement = subListChildren.FirstOrDefault(c => ((Submarine)c.UserData).Name == newSub.Name && ((Submarine)c.UserData).MD5Hash.Hash == newSub.MD5Hash.Hash); if (subElement == null) continue; subElement.GetChild().TextColor = new Color(subElement.GetChild().TextColor, 1.0f); subElement.UserData = newSub; subElement.ToolTip = newSub.Description; GUIButton infoButton = subElement.GetChild(); if (infoButton == null) { int buttonSize = (int)(subElement.Rect.Height * 0.8f); infoButton = new GUIButton(new RectTransform(new Point(buttonSize), subElement.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point((int)(buttonSize * 0.2f), 0) }, "?"); } infoButton.UserData = newSub; infoButton.OnClicked = (component, userdata) => { ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 400)); return true; }; } if (GameMain.NetLobbyScreen.FailedSelectedSub != null && GameMain.NetLobbyScreen.FailedSelectedSub.First == newSub.Name && GameMain.NetLobbyScreen.FailedSelectedSub.Second == newSub.MD5Hash.Hash) { GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.SubList); } if (GameMain.NetLobbyScreen.FailedSelectedShuttle != null && GameMain.NetLobbyScreen.FailedSelectedShuttle.First == newSub.Name && GameMain.NetLobbyScreen.FailedSelectedShuttle.Second == newSub.MD5Hash.Hash) { GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.ShuttleList.ListBox); } break; case FileTransferType.CampaignSave: var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; if (campaign == null) { return; } GameMain.GameSession.SavePath = transfer.FilePath; if (GameMain.GameSession.Submarine == null) { var gameSessionDoc = SaveUtil.LoadGameSessionDoc(GameMain.GameSession.SavePath); string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub"; GameMain.GameSession.Submarine = new Submarine(subPath, ""); } SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession); campaign.LastSaveID = campaign.PendingSaveID; DebugConsole.Log("Campaign save received, save ID " + campaign.LastSaveID); //decrement campaign update ID so the server will send us the latest data //(as there may have been campaign updates after the save file was created) campaign.LastUpdateID--; break; } } private void OnTransferFailed(FileReceiver.FileTransferIn transfer) { if (transfer.FileType == FileTransferType.CampaignSave) { GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null); } } public override void CreateEntityEvent(INetSerializable entity, object[] extraData) { if (!(entity is IClientSerializable)) throw new InvalidCastException("Entity is not IClientSerializable"); entityEventManager.CreateEvent(entity as IClientSerializable, extraData); } public bool HasPermission(ClientPermissions permission) { return permissions.HasFlag(permission); } public bool HasConsoleCommandPermission(string commandName) { if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; } commandName = commandName.ToLowerInvariant(); if (permittedConsoleCommands.Any(c => c.ToLowerInvariant() == commandName)) { return true; } //check aliases foreach (DebugConsole.Command command in DebugConsole.Commands) { if (command.names.Contains(commandName)) { if (command.names.Intersect(permittedConsoleCommands).Any()) { return true; } break; } } return false; } public override void Disconnect() { client.Shutdown(""); steamAuthTicket?.Cancel(); steamAuthTicket = null; foreach (var fileTransfer in FileReceiver.ActiveTransfers) { fileTransfer.Dispose(); } if (HasPermission(ClientPermissions.ServerLog)) { serverSettings.ServerLog?.Save(); } if (GameMain.ServerChildProcess != null) { int checks = 0; while (!GameMain.ServerChildProcess.HasExited) { if (checks > 10) { GameMain.ServerChildProcess.Kill(); } Thread.Sleep(100); checks++; } GameMain.ServerChildProcess = null; } VoipClient?.Dispose(); VoipClient = null; GameMain.Client = null; } public void WriteCharacterInfo(NetOutgoingMessage msg) { msg.Write(characterInfo == null); if (characterInfo == null) return; msg.Write((byte)characterInfo.Gender); msg.Write((byte)characterInfo.Race); msg.Write((byte)characterInfo.HeadSpriteId); msg.Write((byte)characterInfo.HairIndex); msg.Write((byte)characterInfo.BeardIndex); msg.Write((byte)characterInfo.MoustacheIndex); msg.Write((byte)characterInfo.FaceAttachmentIndex); var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; int count = Math.Min(jobPreferences.Count, 3); msg.Write((byte)count); for (int i = 0; i < count; i++) { msg.Write(jobPreferences[i].Identifier); } } public void Vote(VoteType voteType, object data) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY); msg.Write((byte)ClientNetObject.VOTE); serverSettings.Voting.ClientWrite(msg, voteType, data); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public bool VoteForKick(GUIButton button, object userdata) { var votedClient = userdata is Client ? (Client)userdata : otherClients.Find(c => c.Character == userdata); if (votedClient == null) return false; votedClient.AddKickVote(ConnectedClients.First(c => c.ID == ID)); Vote(VoteType.Kick, votedClient); button.Enabled = false; return true; } public override void AddChatMessage(ChatMessage message) { base.AddChatMessage(message); if (string.IsNullOrEmpty(message.Text)) { return; } GameMain.NetLobbyScreen.NewChatMessage(message); chatBox.AddMessage(message); } public override void KickPlayer(string kickedName, string reason) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.Kick); msg.Write(kickedName); msg.Write(reason); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public override void BanPlayer(string kickedName, string reason, bool range = false, TimeSpan? duration = null) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.Ban); msg.Write(kickedName); msg.Write(reason); msg.Write(range); msg.Write(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public override void UnbanPlayer(string playerName, string playerIP) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.Unban); msg.Write(string.IsNullOrEmpty(playerName) ? "" : playerName); msg.Write(string.IsNullOrEmpty(playerIP) ? "" : playerIP); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public void UpdateClientPermissions(Client targetClient) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.ManagePermissions); targetClient.WritePermissions(msg); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public void SendCampaignState() { MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign; if (campaign == null) { DebugConsole.ThrowError("Failed send campaign state to the server (no campaign active).\n" + Environment.StackTrace); return; } NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.ManageCampaign); campaign.ClientWrite(msg); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public void SendConsoleCommand(string command) { if (string.IsNullOrWhiteSpace(command)) { DebugConsole.ThrowError("Cannot send an empty console command to the server!\n" + Environment.StackTrace); return; } NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.ConsoleCommands); msg.Write(command); Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); msg.Write(cursorWorldPos.X); msg.Write(cursorWorldPos.Y); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } /// /// Tell the server to start the round (permission required) /// public void RequestStartRound() { if (!HasPermission(ClientPermissions.ManageRound)) return; NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.ManageRound); msg.Write(false); //indicates round start client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } /// /// Tell the server to select a submarine (permission required) /// public void RequestSelectSub(int subIndex, bool isShuttle) { if (!HasPermission(ClientPermissions.SelectSub)) return; var subList = isShuttle ? GameMain.NetLobbyScreen.ShuttleList.ListBox : GameMain.NetLobbyScreen.SubList; if (subIndex < 0 || subIndex >= subList.Content.CountChildren) { DebugConsole.ThrowError("Submarine index out of bounds (" + subIndex + ")\n" + Environment.StackTrace); return; } NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.SelectSub); msg.Write(isShuttle); msg.WritePadBits(); msg.Write((UInt16)subIndex); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } /// /// Tell the server to select a submarine (permission required) /// public void RequestSelectMode(int modeIndex) { if (!HasPermission(ClientPermissions.SelectMode)) return; if (modeIndex < 0 || modeIndex >= GameMain.NetLobbyScreen.ModeList.Content.CountChildren) { DebugConsole.ThrowError("Gamemode index out of bounds (" + modeIndex + ")\n" + Environment.StackTrace); return; } NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.SelectMode); msg.Write((UInt16)modeIndex); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public void SetupNewCampaign(Submarine sub, string savePath, string mapSeed) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); msg.Write(true); msg.WritePadBits(); msg.Write(savePath); msg.Write(mapSeed); msg.Write(sub.Name); msg.Write(sub.MD5Hash.Hash); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); GameMain.NetLobbyScreen.CampaignSetupUI = null; } public void SetupLoadCampaign(string saveName) { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); msg.Write(false); msg.WritePadBits(); msg.Write(saveName); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); GameMain.NetLobbyScreen.CampaignSetupUI = null; } /// /// Tell the server to end the round (permission required) /// public void RequestRoundEnd() { NetOutgoingMessage msg = client.CreateMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.ManageRound); msg.Write(true); //indicates round end client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } public bool SpectateClicked(GUIButton button, object userData) { if (button != null) button.Enabled = false; NetOutgoingMessage readyToStartMsg = client.CreateMessage(); readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); //assume we have the required sub files to start the round //(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer) readyToStartMsg.Write(true); WriteCharacterInfo(readyToStartMsg); client.SendMessage(readyToStartMsg, NetDeliveryMethod.ReliableUnordered); return false; } public bool SetReadyToStart(GUITickBox tickBox) { if (gameStarted) { tickBox.Visible = false; return false; } Vote(VoteType.StartRound, tickBox.Selected); return true; } public bool ToggleEndRoundVote(GUITickBox tickBox) { if (!gameStarted) return false; if (!serverSettings.Voting.AllowEndVoting || !HasSpawned) { tickBox.Visible = false; return false; } Vote(VoteType.EndRound, tickBox.Selected); return false; } protected CharacterInfo characterInfo; protected Character myCharacter; public CharacterInfo CharacterInfo { get { return characterInfo; } set { characterInfo = value; } } public Character Character { get { return myCharacter; } set { myCharacter = value; } } protected GUIFrame inGameHUD; protected ChatBox chatBox; public GUIButton ShowLogButton; //TODO: move to NetLobbyScreen private float myCharacterFrameOpenState; public GUIFrame InGameHUD { get { return inGameHUD; } } public ChatBox ChatBox { get { return chatBox; } } protected void SetRadioButtonColor() { if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) { chatBox.RadioButton.GetChild().Color = new Color(60, 60, 60, 255); } else { var radioItem = Character.Controlled?.Inventory?.Items.FirstOrDefault(i => i?.GetComponent() != null); chatBox.RadioButton.GetChild().Color = (radioItem != null && Character.Controlled.HasEquippedItem(radioItem) && radioItem.GetComponent().CanTransmit()) ? Color.White : new Color(60, 60, 60, 255); } } public bool TypingChatMessage(GUITextBox textBox, string text) { return chatBox.TypingChatMessage(textBox, text); } public bool EnterChatMessage(GUITextBox textBox, string message) { textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default]; if (string.IsNullOrWhiteSpace(message)) { if (textBox == chatBox.InputBox) textBox.Deselect(); return false; } SendChatMessage(message); textBox.Deselect(); textBox.Text = ""; return true; } public virtual void AddToGUIUpdateList() { if (GUI.DisableHUD || GUI.DisableUpperHUD) return; if (gameStarted && Screen.Selected == GameMain.GameScreen) { inGameHUD.AddToGUIUpdateList(); if (Character.Controlled == null) { GameMain.NetLobbyScreen.MyCharacterFrame.AddToGUIUpdateList(); } } } public void UpdateHUD(float deltaTime) { GUITextBox msgBox = null; if (Screen.Selected == GameMain.GameScreen) { msgBox = chatBox.InputBox; } else if (Screen.Selected == GameMain.NetLobbyScreen) { msgBox = GameMain.NetLobbyScreen.TextBox; } if (gameStarted && Screen.Selected == GameMain.GameScreen) { if (!GUI.DisableHUD && !GUI.DisableUpperHUD) { inGameHUD.UpdateManually(deltaTime); chatBox.Update(deltaTime); cameraFollowsSub.Visible = Character.Controlled == null; if (Character.Controlled == null) { myCharacterFrameOpenState = GameMain.NetLobbyScreen.MyCharacterFrameOpen ? myCharacterFrameOpenState + deltaTime * 5 : myCharacterFrameOpenState - deltaTime * 5; myCharacterFrameOpenState = MathHelper.Clamp(myCharacterFrameOpenState, 0.0f, 1.0f); var myCharFrame = GameMain.NetLobbyScreen.MyCharacterFrame; int padding = GameMain.GraphicsWidth - myCharFrame.Parent.Rect.Right; myCharFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(new Vector2(-myCharFrame.Rect.Width - padding, 0.0f), new Vector2(-padding, 0), myCharacterFrameOpenState).ToPoint(); } } if (Character.Controlled == null || Character.Controlled.IsDead) { GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; } } //tab doesn't autoselect the chatbox when debug console is open, //because tab is used for autocompleting console commands if (msgBox != null) { if ((PlayerInput.KeyHit(InputType.Chat) || PlayerInput.KeyHit(InputType.RadioChat)) && GUI.KeyboardDispatcher.Subscriber == null) { if (msgBox.Selected) { msgBox.Text = ""; msgBox.Deselect(); } else { msgBox.Select(); if (Screen.Selected == GameMain.GameScreen && PlayerInput.KeyHit(InputType.RadioChat)) { msgBox.Text = "r; "; } } } } serverSettings.AddToGUIUpdateList(); if (serverSettings.ServerLog.LogFrame != null) serverSettings.ServerLog.LogFrame.AddToGUIUpdateList(); } public virtual void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { if (!gameStarted || Screen.Selected != GameMain.GameScreen || GUI.DisableHUD || GUI.DisableUpperHUD) return; inGameHUD.DrawManually(spriteBatch); if (EndVoteCount > 0) { if (EndVoteTickBox.Visible) { EndVoteTickBox.Text = (EndVoteTickBox.UserData as string) + " " + EndVoteCount + "/" + EndVoteMax; } else { string endVoteText = TextManager.Get("EndRoundVotes") .Replace("[votes]", EndVoteCount.ToString()) .Replace("[max]", EndVoteMax.ToString()); GUI.DrawString(spriteBatch, EndVoteTickBox.Rect.Center.ToVector2() - GUI.SmallFont.MeasureString(endVoteText) / 2, endVoteText, Color.White, font: GUI.SmallFont); } } else { EndVoteTickBox.Text = EndVoteTickBox.UserData as string; } if (respawnManager != null) { string respawnInfo = ""; if (respawnManager.CurrentState == RespawnManager.State.Waiting && respawnManager.CountdownStarted) { respawnInfo = TextManager.Get(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn"); respawnInfo = respawnInfo.Replace("[time]", ToolBox.SecondsToReadableTime(respawnManager.RespawnTimer)); } else if (respawnManager.CurrentState == RespawnManager.State.Transporting) { respawnInfo = respawnManager.TransportTimer <= 0.0f ? "" : TextManager.Get("RespawnShuttleLeavingIn").Replace("[time]", ToolBox.SecondsToReadableTime(respawnManager.TransportTimer)); } if (respawnManager != null) { GUI.DrawString(spriteBatch, new Vector2(120.0f, 10), respawnInfo, Color.White, null, 0, GUI.SmallFont); } } if (fileReceiver != null && fileReceiver.ActiveTransfers.Count > 0) { Vector2 pos = new Vector2(GameMain.NetLobbyScreen.InfoFrame.Rect.X, GameMain.GraphicsHeight - 35); GUI.DrawRectangle(spriteBatch, new Rectangle( (int)pos.X, (int)pos.Y, fileReceiver.ActiveTransfers.Count * 210 + 10, 32), Color.Black * 0.8f, true); for (int i = 0; i < fileReceiver.ActiveTransfers.Count; i++) { var transfer = fileReceiver.ActiveTransfers[i]; GUI.DrawString(spriteBatch, pos, ToolBox.LimitString(TextManager.Get("DownloadingFile").Replace("[filename]", transfer.FileName), GUI.SmallFont, 200), Color.White, null, 0, GUI.SmallFont); GUI.DrawProgressBar(spriteBatch, new Vector2(pos.X, -pos.Y - 15), new Vector2(135, 15), transfer.Progress, Color.Green); GUI.DrawString(spriteBatch, pos + new Vector2(5, 15), MathUtils.GetBytesReadable((long)transfer.Received) + " / " + MathUtils.GetBytesReadable((long)transfer.FileSize), Color.White, null, 0, GUI.SmallFont); if (GUI.DrawButton(spriteBatch, new Rectangle((int)pos.X + 140, (int)pos.Y + 18, 60, 15), TextManager.Get("Cancel"), new Color(0.47f, 0.13f, 0.15f, 0.08f))) { CancelFileTransfer(transfer); fileReceiver.StopTransfer(transfer); } pos.X += 210; } } if (!ShowNetStats) return; netStats.Draw(spriteBatch, new Rectangle(300, 10, 300, 150)); int width = 200, height = 300; int x = GameMain.GraphicsWidth - width, y = (int)(GameMain.GraphicsHeight * 0.3f); GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black * 0.7f, true); GUI.Font.DrawString(spriteBatch, "Network statistics:", new Vector2(x + 10, y + 10), Color.White); if (client.ServerConnection != null) { GUI.Font.DrawString(spriteBatch, "Ping: " + (int)(client.ServerConnection.AverageRoundtripTime * 1000.0f) + " ms", new Vector2(x + 10, y + 25), Color.White); y += 15; GUI.SmallFont.DrawString(spriteBatch, "Received bytes: " + client.Statistics.ReceivedBytes, new Vector2(x + 10, y + 45), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Received packets: " + client.Statistics.ReceivedPackets, new Vector2(x + 10, y + 60), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Sent bytes: " + client.Statistics.SentBytes, new Vector2(x + 10, y + 75), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Sent packets: " + client.Statistics.SentPackets, new Vector2(x + 10, y + 90), Color.White); } else { GUI.Font.DrawString(spriteBatch, "Disconnected", new Vector2(x + 10, y + 25), Color.White); } } public virtual bool SelectCrewCharacter(Character character, GUIComponent characterFrame) { if (character == null) return false; if (character != myCharacter) { var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == character); if (client == null) return false; if (HasPermission(ClientPermissions.Ban)) { var banButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.15f), characterFrame.RectTransform, Anchor.BottomRight), TextManager.Get("Ban")) { UserData = character.Name, OnClicked = GameMain.NetLobbyScreen.BanPlayer }; } if (HasPermission(ClientPermissions.Kick)) { var kickButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.15f), characterFrame.RectTransform, Anchor.BottomLeft), TextManager.Get("Kick")) { UserData = character.Name, OnClicked = GameMain.NetLobbyScreen.KickPlayer }; } else if (serverSettings.Voting.AllowVoteKick) { var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.15f), characterFrame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.16f) }, TextManager.Get("VoteToKick")) { UserData = character, OnClicked = VoteForKick }; if (GameMain.NetworkMember.ConnectedClients != null) { kickVoteButton.Enabled = !client.HasKickVoteFromID(myID); } } } return true; } public void CreateKickReasonPrompt(string clientName, bool ban, bool rangeBan = false) { var banReasonPrompt = new GUIMessageBox( TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"), "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, 400, 300); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center)); var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { Wrap = true, MaxTextLength = 100 }; GUINumberInput durationInputDays = null, durationInputHours = null; GUITickBox permaBanTickBox = null; if (ban) { new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform), TextManager.Get("BanDuration")); permaBanTickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, TextManager.Get("BanPermanent")) { Selected = true }; var durationContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform), isHorizontal: true) { Visible = false }; permaBanTickBox.OnSelected += (tickBox) => { durationContainer.Visible = !tickBox.Selected; return true; }; durationInputDays = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), GUINumberInput.NumberType.Int) { MinValueInt = 0, MaxValueFloat = 1000 }; new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), TextManager.Get("Days")); durationInputHours = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), GUINumberInput.NumberType.Int) { MinValueInt = 0, MaxValueFloat = 24 }; new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), TextManager.Get("Hours")); } banReasonPrompt.Buttons[0].OnClicked += (btn, userData) => { if (ban) { if (!permaBanTickBox.Selected) { TimeSpan banDuration = new TimeSpan(durationInputDays.IntValue, durationInputHours.IntValue, 0, 0); BanPlayer(clientName, banReasonBox.Text, ban, banDuration); } else { BanPlayer(clientName, banReasonBox.Text, ban); } } else { KickPlayer(clientName, banReasonBox.Text); } return true; }; banReasonPrompt.Buttons[0].OnClicked += banReasonPrompt.Close; banReasonPrompt.Buttons[1].OnClicked += banReasonPrompt.Close; } public void ReportError(ClientNetError error, UInt16 expectedID = 0, UInt16 eventID = 0, UInt16 entityID = 0) { NetOutgoingMessage outMsg = client.CreateMessage(); outMsg.Write((byte)ClientPacketHeader.ERROR); outMsg.Write((byte)error); outMsg.Write(Level.Loaded == null ? 0 : Level.Loaded.EqualityCheckVal); switch (error) { case ClientNetError.MISSING_EVENT: outMsg.Write(expectedID); outMsg.Write(eventID); break; case ClientNetError.MISSING_ENTITY: outMsg.Write(eventID); outMsg.Write(entityID); break; } client.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered); } } }