using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using Lidgren.Network; using Microsoft.Xna.Framework; using RestSharp; using Barotrauma.Items.Components; namespace Barotrauma.Networking { partial class GameServer : NetworkMember { private List connectedClients = new List(); //for keeping track of disconnected clients in case the reconnect shortly after private List disconnectedClients = new List(); private NetStats netStats; private int roundStartSeed; //is the server running private bool started; private NetServer server; private NetPeerConfiguration config; private int MaxPlayers; private DateTime sparseUpdateTimer; private DateTime refreshMasterTimer; private RestClient restClient; private bool masterServerResponded; private IRestResponse masterServerResponse; private ServerLog log; private GUIButton showLogButton; private bool initiatedStartGame; private CoroutineHandle startGameCoroutine; private GUIScrollBar clientListScrollBar; public TraitorManager TraitorManager; private ServerEntityEventManager entityEventManager; private FileSender fileSender; public override List ConnectedClients { get { return connectedClients; } } public ServerEntityEventManager EntityEventManager { get { return entityEventManager; } } public ServerLog ServerLog { get { return log; } } public TimeSpan UpdateInterval { get { return updateInterval; } } public GameServer(string name, int port, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10) { name = name.Replace(":", ""); name = name.Replace(";", ""); AdminAuthPass = ""; this.name = name; this.password = ""; if (password.Length>0) { this.password = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(password))); } config = new NetPeerConfiguration("barotrauma"); netStats = new NetStats(); #if DEBUG config.SimulatedLoss = 0.05f; config.SimulatedRandomLatency = 0.05f; config.SimulatedDuplicatesChance = 0.05f; config.SimulatedMinimumLatency = 0.1f; config.ConnectionTimeout = 60.0f; NetIdUtils.Test(); #endif config.Port = port; Port = port; if (attemptUPnP) { config.EnableUPnP = true; } config.MaximumConnections = maxPlayers*2; //double the lidgren connections for unauthenticated players MaxPlayers = maxPlayers; config.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error | NetIncomingMessageType.UnconnectedData); config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); //---------------------------------------- var endRoundButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170, 20, 150, 20), "End round", Alignment.TopLeft, "", inGameHUD); endRoundButton.OnClicked = (btn, userdata) => { EndGame(); return true; }; log = new ServerLog(name); showLogButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170, 20, 150, 20), "Server Log", Alignment.TopLeft, "", inGameHUD); showLogButton.OnClicked = (GUIButton button, object userData) => { if (log.LogFrame == null) { log.CreateLogFrame(); } else { log.LogFrame = null; GUIComponent.KeyboardDispatcher.Subscriber = null; } return true; }; GUIButton settingsButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170 - 170, 20, 150, 20), "Settings", Alignment.TopLeft, "", inGameHUD); settingsButton.OnClicked = ToggleSettingsFrame; settingsButton.UserData = "settingsButton"; entityEventManager = new ServerEntityEventManager(this); whitelist = new WhiteList(); banList = new BanList(); LoadSettings(); LoadClientPermissions(); //---------------------------------------- CoroutineManager.StartCoroutine(StartServer(isPublic)); } private IEnumerable StartServer(bool isPublic) { bool error = false; try { Log("Starting the server...", Color.Cyan); server = new NetServer(config); netPeer = server; fileSender = new FileSender(this); fileSender.OnEnded += FileTransferChanged; fileSender.OnStarted += FileTransferChanged; server.Start(); } catch (Exception e) { Log("Error while starting the server (" + e.Message + ")", Color.Red); System.Net.Sockets.SocketException socketException = e as System.Net.Sockets.SocketException; if (socketException != null && socketException.SocketErrorCode == System.Net.Sockets.SocketError.AddressAlreadyInUse) { new GUIMessageBox("Starting the server failed", e.Message + ". Are you trying to run multiple servers on the same port?"); } else { new GUIMessageBox("Starting the server failed", e.Message); } error = true; } if (error) { if (server != null) server.Shutdown("Error while starting the server"); GameMain.NetworkMember = null; yield return CoroutineStatus.Success; } if (config.EnableUPnP) { server.UPnP.ForwardPort(config.Port, "barotrauma"); GUIMessageBox upnpBox = new GUIMessageBox("Please wait...", "Attempting UPnP port forwarding", new string[] {"Cancel"} ); upnpBox.Buttons[0].OnClicked = upnpBox.Close; //DateTime upnpTimeout = DateTime.Now + new TimeSpan(0,0,5); while (server.UPnP.Status == UPnPStatus.Discovering && GUIMessageBox.VisibleBox == upnpBox)// && upnpTimeout>DateTime.Now) { yield return null; } upnpBox.Close(null,null); if (server.UPnP.Status == UPnPStatus.NotAvailable) { new GUIMessageBox("Error", "UPnP not available"); } else if (server.UPnP.Status == UPnPStatus.Discovering) { new GUIMessageBox("Error", "UPnP discovery timed out"); } } if (isPublic) { CoroutineManager.StartCoroutine(RegisterToMasterServer()); } updateInterval = new TimeSpan(0, 0, 0, 0, 150); Log("Server started", Color.Cyan); GameMain.NetLobbyScreen.Select(); started = true; yield return CoroutineStatus.Success; } private IEnumerable RegisterToMasterServer() { if (restClient==null) { restClient = new RestClient(NetConfig.MasterServerUrl); } var request = new RestRequest("masterserver3.php", Method.GET); request.AddParameter("action", "addserver"); request.AddParameter("servername", name); request.AddParameter("serverport", Port); request.AddParameter("currplayers", connectedClients.Count); request.AddParameter("maxplayers", MaxPlayers); request.AddParameter("password", string.IsNullOrWhiteSpace(password) ? 0 : 1); masterServerResponded = false; masterServerResponse = null; var restRequestHandle = restClient.ExecuteAsync(request, response => MasterServerCallBack(response)); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 15); while (!masterServerResponded) { if (DateTime.Now > timeOut) { restRequestHandle.Abort(); DebugConsole.NewMessage("Couldn't register to master server (request timed out)", Color.Red); Log("Couldn't register to master server (request timed out)", Color.Red); yield return CoroutineStatus.Success; } yield return CoroutineStatus.Running; } if (masterServerResponse.StatusCode != System.Net.HttpStatusCode.OK) { DebugConsole.ThrowError("Error while connecting to master server (" + masterServerResponse.StatusCode + ": " + masterServerResponse.StatusDescription + ")"); } else if (masterServerResponse != null && !string.IsNullOrWhiteSpace(masterServerResponse.Content)) { DebugConsole.ThrowError("Error while connecting to master server (" + masterServerResponse.Content + ")"); } else { registeredToMaster = true; refreshMasterTimer = DateTime.Now + refreshMasterInterval; } yield return CoroutineStatus.Success; } private IEnumerable RefreshMaster() { if (restClient == null) { restClient = new RestClient(NetConfig.MasterServerUrl); } var request = new RestRequest("masterserver3.php", Method.GET); request.AddParameter("action", "refreshserver"); request.AddParameter("serverport", Port); request.AddParameter("gamestarted", gameStarted ? 1 : 0); request.AddParameter("currplayers", connectedClients.Count); request.AddParameter("maxplayers", MaxPlayers); Log("Refreshing connection with master server...", Color.Cyan); var sw = new Stopwatch(); sw.Start(); masterServerResponded = false; masterServerResponse = null; var restRequestHandle = restClient.ExecuteAsync(request, response => MasterServerCallBack(response)); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 15); while (!masterServerResponded) { if (DateTime.Now > timeOut) { restRequestHandle.Abort(); DebugConsole.NewMessage("Couldn't connect to master server (request timed out)", Color.Red); Log("Couldn't connect to master server (request timed out)", Color.Red); yield return CoroutineStatus.Success; } yield return CoroutineStatus.Running; } if (masterServerResponse.Content == "Error: server not found") { Log("Not registered to master server, re-registering...", Color.Red); CoroutineManager.StartCoroutine(RegisterToMasterServer()); } else if (masterServerResponse.ErrorException != null) { DebugConsole.NewMessage("Error while registering to master server (" + masterServerResponse.ErrorException + ")", Color.Red); Log("Error while registering to master server (" + masterServerResponse.ErrorException + ")", Color.Red); } else if (masterServerResponse.StatusCode != System.Net.HttpStatusCode.OK) { DebugConsole.NewMessage("Error while reporting to master server (" + masterServerResponse.StatusCode + ": " + masterServerResponse.StatusDescription + ")", Color.Red); Log("Error while reporting to master server (" + masterServerResponse.StatusCode + ": " + masterServerResponse.StatusDescription + ")", Color.Red); } else { Log("Master server responded", Color.Cyan); } System.Diagnostics.Debug.WriteLine("took "+sw.ElapsedMilliseconds+" ms"); yield return CoroutineStatus.Success; } private void MasterServerCallBack(IRestResponse response) { masterServerResponse = response; masterServerResponded = true; } public override void AddToGUIUpdateList() { if (started) base.AddToGUIUpdateList(); if (settingsFrame != null) settingsFrame.AddToGUIUpdateList(); if (log.LogFrame != null) log.LogFrame.AddToGUIUpdateList(); } public override void Update(float deltaTime) { if (ShowNetStats) netStats.Update(deltaTime); if (settingsFrame != null) settingsFrame.Update(deltaTime); if (log.LogFrame != null) log.LogFrame.Update(deltaTime); if (!started) return; base.Update(deltaTime); foreach (UnauthenticatedClient unauthClient in unauthenticatedClients) { unauthClient.AuthTimer -= deltaTime; if (unauthClient.AuthTimer <= 0.0f) { unauthClient.Connection.Disconnect("Connection timed out"); } } unauthenticatedClients.RemoveAll(uc => uc.AuthTimer <= 0.0f); fileSender.Update(deltaTime); if (gameStarted) { if (respawnManager != null) respawnManager.Update(deltaTime); entityEventManager.Update(connectedClients); bool isCrewDead = connectedClients.Find(c => c.Character != null && !c.Character.IsDead)==null && (myCharacter == null || myCharacter.IsDead); //restart if all characters are dead or submarine is at the end of the level if ((autoRestart && isCrewDead) || (EndRoundAtLevelEnd && Submarine.MainSub != null && Submarine.MainSub.AtEndPosition && Submarine.MainSubs[1]==null)) { if (AutoRestart && isCrewDead) { Log("Ending round (entire crew dead)", Color.Cyan); } else { Log("Ending round (submarine reached the end of the level)", Color.Cyan); } EndGame(); return; } } else if (initiatedStartGame) { //tried to start up the game and StartGame coroutine is not running anymore // -> something wen't wrong during startup, re-enable start button and reset AutoRestartTimer if (startGameCoroutine != null && !CoroutineManager.IsCoroutineRunning(startGameCoroutine)) { if (autoRestart) AutoRestartTimer = Math.Max(AutoRestartInterval, 5.0f); GameMain.NetLobbyScreen.StartButton.Enabled = true; GameMain.NetLobbyScreen.LastUpdateID++; startGameCoroutine = null; initiatedStartGame = false; } } else if (autoRestart && Screen.Selected == GameMain.NetLobbyScreen && connectedClients.Count>0) { AutoRestartTimer -= deltaTime; if (AutoRestartTimer < 0.0f && GameMain.NetLobbyScreen.StartButton.Enabled) { StartGameClicked(null,null); } } for (int i = disconnectedClients.Count - 1; i >= 0; i-- ) { disconnectedClients[i].deleteDisconnectedTimer -= deltaTime; if (disconnectedClients[i].deleteDisconnectedTimer > 0.0f) continue; if (gameStarted && disconnectedClients[i].Character!=null) { disconnectedClients[i].Character.Kill(CauseOfDeath.Damage, true); disconnectedClients[i].Character = null; } disconnectedClients.RemoveAt(i); } foreach (Client c in connectedClients) { //slowly reset spam timers c.ChatSpamTimer = Math.Max(0.0f, c.ChatSpamTimer - deltaTime); c.ChatSpamSpeed = Math.Max(0.0f, c.ChatSpamSpeed - deltaTime); } NetIncomingMessage inc = null; while ((inc = server.ReadMessage()) != null) { try { switch (inc.MessageType) { case NetIncomingMessageType.Data: ReadDataMessage(inc); break; case NetIncomingMessageType.StatusChanged: switch (inc.SenderConnection.Status) { case NetConnectionStatus.Disconnected: var connectedClient = connectedClients.Find(c => c.Connection == inc.SenderConnection); /*if (connectedClient != null && !disconnectedClients.Contains(connectedClient)) { connectedClient.deleteDisconnectedTimer = NetConfig.DeleteDisconnectedTime; disconnectedClients.Add(connectedClient); } */ DisconnectClient(inc.SenderConnection, connectedClient != null ? connectedClient.name + " has disconnected" : ""); break; } break; case NetIncomingMessageType.ConnectionApproval: if (banList.IsBanned(inc.SenderEndPoint.Address.ToString())) { inc.SenderConnection.Deny("You have been banned from the server"); } else if (ConnectedClients.Count >= MaxPlayers) { inc.SenderConnection.Deny("Server full"); } else { if ((ClientPacketHeader)inc.SenderConnection.RemoteHailMessage.ReadByte() == ClientPacketHeader.REQUEST_AUTH) { inc.SenderConnection.Approve(); ClientAuthRequest(inc.SenderConnection); } } break; } } catch (Exception e) { #if DEBUG DebugConsole.ThrowError("Failed to read incoming message", e); #endif continue; } } // if 30ms has passed if (updateTimer < DateTime.Now) { if (server.ConnectionsCount > 0) { if (sparseUpdateTimer < DateTime.Now) SparseUpdate(); foreach (Client c in ConnectedClients) { if (gameStarted && c.inGame) { ClientWriteIngame(c); } else { ClientWriteLobby(c); } } foreach (Item item in Item.ItemList) { item.NeedsPositionUpdate = false; } } updateTimer = DateTime.Now + updateInterval; } if (!registeredToMaster || refreshMasterTimer >= DateTime.Now) return; CoroutineManager.StartCoroutine(RefreshMaster()); refreshMasterTimer = DateTime.Now + refreshMasterInterval; } private void ReadDataMessage(NetIncomingMessage inc) { if (banList.IsBanned(inc.SenderEndPoint.Address.ToString())) { KickClient(inc.SenderConnection, true); return; } ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); switch (header) { case ClientPacketHeader.REQUEST_AUTH: ClientAuthRequest(inc.SenderConnection); break; case ClientPacketHeader.REQUEST_INIT: ClientInitRequest(inc); break; case ClientPacketHeader.RESPONSE_STARTGAME: var connectedClient = connectedClients.Find(c => c.Connection == inc.SenderConnection); if (connectedClient != null) { connectedClient.ReadyToStart = inc.ReadBoolean(); UpdateCharacterInfo(inc, connectedClient); //game already started -> send start message immediately if (gameStarted) { SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClient); } } break; case ClientPacketHeader.UPDATE_LOBBY: ClientReadLobby(inc); break; case ClientPacketHeader.UPDATE_INGAME: if (!gameStarted) return; ClientReadIngame(inc); break; case ClientPacketHeader.SERVER_COMMAND: ClientReadServerCommand(inc); break; case ClientPacketHeader.FILE_REQUEST: if (AllowFileTransfers) { fileSender.ReadFileRequest(inc); } break; } } private void SparseUpdate() { sparseUpdateTimer = DateTime.Now + sparseUpdateInterval; } public void CreateEntityEvent(IServerSerializable entity, object[] extraData = null) { entityEventManager.CreateEvent(entity, extraData); } private byte GetNewClientID() { byte userID = 1; while (connectedClients.Any(c => c.ID == userID)) { userID++; } return userID; } private void ClientReadLobby(NetIncomingMessage inc) { Client c = ConnectedClients.Find(x => x.Connection == inc.SenderConnection); if (c == null) { inc.SenderConnection.Disconnect("You're not a connected client."); return; } ClientNetObject objHeader; while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE) { switch (objHeader) { case ClientNetObject.SYNC_IDS: //TODO: might want to use a clever class for this c.lastRecvGeneralUpdate = NetIdUtils.Clamp(inc.ReadUInt16(), c.lastRecvGeneralUpdate, GameMain.NetLobbyScreen.LastUpdateID); c.lastRecvChatMsgID = NetIdUtils.Clamp(inc.ReadUInt16(), c.lastRecvChatMsgID, c.lastChatMsgQueueID); break; case ClientNetObject.CHAT_MESSAGE: ChatMessage.ServerRead(inc, c); break; case ClientNetObject.VOTE: Voting.ServerRead(inc, c); break; default: return; //break; } } } private void ClientReadIngame(NetIncomingMessage inc) { Client c = ConnectedClients.Find(x => x.Connection == inc.SenderConnection); if (c == null) { inc.SenderConnection.Disconnect("You're not a connected client."); return; } if (gameStarted) { if (!c.inGame) { //check if midround syncing is needed due to missed unique events entityEventManager.InitClientMidRoundSync(c); c.inGame = true; } } ClientNetObject objHeader; while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE) { switch (objHeader) { case ClientNetObject.SYNC_IDS: //TODO: might want to use a clever class for this UInt16 lastRecvChatMsgID = inc.ReadUInt16(); UInt16 lastRecvEntityEventID = inc.ReadUInt16(); //last msgs we've created/sent, the client IDs should never be higher than these UInt16 lastEntityEventID = entityEventManager.Events.Count == 0 ? (UInt16)0 : entityEventManager.Events.Last().ID; if (c.NeedsMidRoundSync) { //received all the old events -> client in sync, we can switch to normal behavior if (lastRecvEntityEventID >= c.UnreceivedEntityEventCount - 1 || c.UnreceivedEntityEventCount == 0) { c.NeedsMidRoundSync = false; lastRecvEntityEventID = (UInt16)(c.FirstNewEventID - 1); c.lastRecvEntityEventID = lastRecvEntityEventID; } else { lastEntityEventID = (UInt16)(c.UnreceivedEntityEventCount - 1); } } if (NetIdUtils.IdMoreRecent(lastRecvChatMsgID, c.lastRecvChatMsgID) && //more recent than the last ID received by the client !NetIdUtils.IdMoreRecent(lastRecvChatMsgID, c.lastChatMsgQueueID)) //NOT more recent than the latest existing ID { c.lastRecvChatMsgID = lastRecvChatMsgID; } #if DEBUG else if (lastRecvChatMsgID != c.lastRecvChatMsgID) { DebugConsole.ThrowError( "Invalid lastRecvChatMsgID " + lastRecvChatMsgID + " (previous: " + c.lastChatMsgQueueID + ", latest: "+c.lastChatMsgQueueID+")"); } #endif if (NetIdUtils.IdMoreRecent(lastRecvEntityEventID, c.lastRecvEntityEventID) && !NetIdUtils.IdMoreRecent(lastRecvEntityEventID, lastEntityEventID)) { c.lastRecvEntityEventID = lastRecvEntityEventID; } #if DEBUG else if (lastRecvEntityEventID != c.lastRecvEntityEventID) { DebugConsole.ThrowError( "Invalid lastRecvEntityEventID " + lastRecvEntityEventID + " (previous: " + c.lastRecvEntityEventID + ", latest: " + lastEntityEventID + ")"); } #endif break; case ClientNetObject.CHAT_MESSAGE: ChatMessage.ServerRead(inc, c); break; case ClientNetObject.CHARACTER_INPUT: if (c.Character != null) { c.Character.ServerRead(objHeader, inc, c); } break; case ClientNetObject.ENTITY_STATE: entityEventManager.Read(inc, c); break; case ClientNetObject.VOTE: Voting.ServerRead(inc, c); break; default: return; } } } private void ClientReadServerCommand(NetIncomingMessage inc) { Client c = ConnectedClients.Find(x => x.Connection == inc.SenderConnection); if (c == null) { inc.SenderConnection.Disconnect("You're not a connected client."); return; } ClientPermissions command = ClientPermissions.None; try { command = (ClientPermissions)inc.ReadByte(); } catch { return; } if (!c.HasPermission(command)) { Log("Client \""+c.name+"\" sent a server command \""+command+"\". Permission denied.", Color.Red); return; } switch (command) { case ClientPermissions.Kick: string kickedName = inc.ReadString(); var kickedClient = connectedClients.Find(cl => cl != c && cl.name == kickedName); if (kickedClient != null) { Log("Client \"" + c.name + "\" kicked \"" + kickedClient.name + "\".", Color.Red); KickClient(kickedClient, false, false); } break; case ClientPermissions.Ban: string bannedName = inc.ReadString(); var bannedClient = connectedClients.Find(cl => cl != c && cl.name == bannedName); if (bannedClient != null) { Log("Client \"" + c.name + "\" banned \"" + bannedClient.name + "\".", Color.Red); KickClient(bannedClient, true, false); } break; case ClientPermissions.EndRound: if (gameStarted) { Log("Client \"" + c.name + "\" ended the round.", Color.Cyan); EndGame(); } break; } inc.ReadPadBits(); } /// /// Write info that the client needs when joining the server /// private void ClientWriteInitial(Client c, NetBuffer outmsg) { outmsg.Write(c.ID); var subList = GameMain.NetLobbyScreen.GetSubList(); outmsg.Write((UInt16)subList.Count); for (int i = 0; i < subList.Count; i++) { outmsg.Write(subList[i].Name); outmsg.Write(subList[i].MD5Hash.ToString()); } outmsg.Write(GameStarted); outmsg.Write(AllowSpectating); outmsg.Write((byte)c.Permissions); } private void ClientWriteIngame(Client c) { NetOutgoingMessage outmsg = server.CreateMessage(); outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME); outmsg.Write((float)NetTime.Now); outmsg.Write((byte)ServerNetObject.SYNC_IDS); outmsg.Write(c.lastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server outmsg.Write(c.lastSentEntityEventID); c.chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, c.lastRecvChatMsgID)); int maxChatMsgsPerPacket = 50; for (int i = 0; i < c.chatMsgQueue.Count && i < maxChatMsgsPerPacket; i++) { c.chatMsgQueue[i].ServerWrite(outmsg, c); } //don't send position updates to characters who are still midround syncing //characters or items spawned mid-round don't necessarily exist at the client's end yet if (!c.NeedsMidRoundSync) { foreach (Character character in Character.CharacterList) { if (!character.Enabled) continue; if (c.Character != null && Vector2.DistanceSquared(character.WorldPosition, c.Character.WorldPosition) >= NetConfig.CharacterIgnoreDistance * NetConfig.CharacterIgnoreDistance) { continue; } outmsg.Write((byte)ServerNetObject.ENTITY_POSITION); character.ServerWrite(outmsg, c); outmsg.WritePadBits(); } foreach (Submarine sub in Submarine.Loaded) { //if docked to a sub with a smaller ID, don't send an update // (= update is only sent for the docked sub that has the smallest ID, doesn't matter if it's the main sub or a shuttle) if (sub.DockedTo.Any(s => s.ID < sub.ID)) continue; outmsg.Write((byte)ServerNetObject.ENTITY_POSITION); sub.ServerWrite(outmsg, c); outmsg.WritePadBits(); } foreach (Item item in Item.ItemList) { if (!item.NeedsPositionUpdate) continue; outmsg.Write((byte)ServerNetObject.ENTITY_POSITION); item.ServerWritePosition(outmsg, c); outmsg.WritePadBits(); } } entityEventManager.Write(c, outmsg); outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable); } private void ClientWriteLobby(Client c) { NetOutgoingMessage outmsg = server.CreateMessage(); outmsg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); outmsg.Write((byte)ServerNetObject.SYNC_IDS); if (NetIdUtils.IdMoreRecent(GameMain.NetLobbyScreen.LastUpdateID, c.lastRecvGeneralUpdate)) { outmsg.Write(true); outmsg.WritePadBits(); outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID); outmsg.Write(GameMain.NetLobbyScreen.GetServerName()); outmsg.Write(GameMain.NetLobbyScreen.ServerMessage.Text); outmsg.Write(c.lastRecvGeneralUpdate < 1); if (c.lastRecvGeneralUpdate < 1) { ClientWriteInitial(c, outmsg); } outmsg.Write((GameMain.NetLobbyScreen.SubList.SelectedData as Submarine).Name); outmsg.Write((GameMain.NetLobbyScreen.SubList.SelectedData as Submarine).MD5Hash.ToString()); outmsg.Write((GameMain.NetLobbyScreen.ShuttleList.SelectedData as Submarine).Name); outmsg.Write((GameMain.NetLobbyScreen.ShuttleList.SelectedData as Submarine).MD5Hash.ToString()); outmsg.Write(Voting.AllowSubVoting); outmsg.Write(Voting.AllowModeVoting); outmsg.WriteRangedInteger(0, 2, (int)TraitorsEnabled); outmsg.WriteRangedInteger(0, Mission.MissionTypes.Count - 1, (GameMain.NetLobbyScreen.MissionTypeIndex)); outmsg.Write((byte)GameMain.NetLobbyScreen.ModeList.SelectedIndex); outmsg.Write(GameMain.NetLobbyScreen.LevelSeed); outmsg.Write(AutoRestart); if (autoRestart) { outmsg.Write(AutoRestartTimer); } outmsg.Write((byte)connectedClients.Count); foreach (Client client in connectedClients) { outmsg.Write(client.ID); outmsg.Write(client.name); outmsg.Write(client.Character == null || !gameStarted ? (ushort)0 : client.Character.ID); } } else { outmsg.Write(false); outmsg.WritePadBits(); } outmsg.Write(c.lastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server c.chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, c.lastRecvChatMsgID)); foreach (ChatMessage cMsg in c.chatMsgQueue) { cMsg.ServerWrite(outmsg, c); } outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE); server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable); } public bool StartGameClicked(GUIButton button, object obj) { Submarine selectedSub = null; Submarine selectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle; if (Voting.AllowSubVoting) { selectedSub = Voting.HighestVoted(VoteType.Sub, connectedClients); if (selectedSub == null) selectedSub = GameMain.NetLobbyScreen.SelectedSub; } else { selectedSub = GameMain.NetLobbyScreen.SelectedSub; } if (selectedSub == null) { GameMain.NetLobbyScreen.SubList.Flash(); return false; } if (selectedShuttle == null) { GameMain.NetLobbyScreen.ShuttleList.Flash(); return false; } GameModePreset selectedMode = Voting.HighestVoted(VoteType.Mode, connectedClients); if (selectedMode == null) selectedMode = GameMain.NetLobbyScreen.SelectedMode; if (selectedMode == null) { GameMain.NetLobbyScreen.ModeList.Flash(); return false; } CoroutineManager.StartCoroutine(InitiateStartGame(selectedSub, selectedShuttle, selectedMode), "InitiateStartGame"); return true; } private IEnumerable InitiateStartGame(Submarine selectedSub, Submarine selectedShuttle, GameModePreset selectedMode) { GameMain.NetLobbyScreen.StartButton.Enabled = false; if (connectedClients.Any()) { NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.QUERY_STARTGAME); msg.Write(selectedSub.Name); msg.Write(selectedSub.MD5Hash.Hash); msg.Write(selectedShuttle.Name); msg.Write(selectedShuttle.MD5Hash.Hash); connectedClients.ForEach(c => c.ReadyToStart = false); server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0); //give the clients a few seconds to request missing sub/shuttle files before starting the round float waitForResponseTimer = 5.0f; while (connectedClients.Any(c => !c.ReadyToStart) && waitForResponseTimer > 0.0f) { waitForResponseTimer -= CoroutineManager.UnscaledDeltaTime; yield return CoroutineStatus.Running; } if (fileSender.ActiveTransfers.Count > 0) { var msgBox = new GUIMessageBox("", "Waiting for file transfers to finish before starting the round...", new string[] { "Start now" }); msgBox.Buttons[0].OnClicked += msgBox.Close; float waitForTransfersTimer = 20.0f; while (fileSender.ActiveTransfers.Count > 0 && waitForTransfersTimer > 0.0f) { waitForTransfersTimer -= CoroutineManager.UnscaledDeltaTime; //message box close, break and start the round immediately if (!GUIMessageBox.MessageBoxes.Contains(msgBox)) { break; } yield return CoroutineStatus.Running; } } } startGameCoroutine = GameMain.ShowLoading(StartGame(selectedSub, selectedShuttle, selectedMode), false); yield return CoroutineStatus.Success; } private IEnumerable StartGame(Submarine selectedSub, Submarine selectedShuttle, GameModePreset selectedMode) { initiatedStartGame = true; entityEventManager.Clear(); GameMain.NetLobbyScreen.StartButton.Enabled = false; GUIMessageBox.CloseAll(); roundStartSeed = DateTime.Now.Millisecond; Rand.SetSyncedSeed(roundStartSeed); int teamCount = 1; int hostTeam = 1; GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, Mission.MissionTypes[GameMain.NetLobbyScreen.MissionTypeIndex]); if (GameMain.GameSession.gameMode.Mission != null && GameMain.GameSession.gameMode.Mission.AssignTeamIDs(connectedClients,out hostTeam)) { teamCount = 2; } GameMain.GameSession.StartShift(GameMain.NetLobbyScreen.LevelSeed, teamCount > 1); GameServer.Log("Starting a new round...", Color.Cyan); GameServer.Log("Submarine: " + selectedSub.Name, Color.Cyan); GameServer.Log("Game mode: " + selectedMode.Name, Color.Cyan); GameServer.Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, Color.Cyan); bool missionAllowRespawn = !(GameMain.GameSession.gameMode is MissionMode) || ((MissionMode)GameMain.GameSession.gameMode).Mission.AllowRespawn; if (AllowRespawn && missionAllowRespawn) respawnManager = new RespawnManager(this, selectedShuttle); //assign jobs and spawnpoints separately for each team for (int teamID = 1; teamID <= teamCount; teamID++) { //find the clients in this team List teamClients = teamCount == 1 ? connectedClients : connectedClients.FindAll(c => c.TeamID == teamID); if (!teamClients.Any() && teamID > 1) continue; AssignJobs(teamClients, teamID == hostTeam); List characterInfos = new List(); foreach (Client client in teamClients) { client.NeedsMidRoundSync = false; client.entityEventLastSent.Clear(); client.lastSentEntityEventID = 0; client.lastRecvEntityEventID = 0; client.UnreceivedEntityEventCount = 0; if (client.characterInfo == null) { client.characterInfo = new CharacterInfo(Character.HumanConfigFile, client.name); } characterInfos.Add(client.characterInfo); client.characterInfo.Job = new Job(client.assignedJob); } //host's character if (characterInfo != null && hostTeam == teamID) { characterInfo.Job = new Job(GameMain.NetLobbyScreen.JobPreferences[0]); characterInfos.Add(characterInfo); } WayPoint[] assignedWayPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[teamID - 1]); for (int i = 0; i < teamClients.Count; i++) { Character spawnedCharacter = Character.Create(teamClients[i].characterInfo, assignedWayPoints[i].WorldPosition, true, false); spawnedCharacter.AnimController.Frozen = true; spawnedCharacter.GiveJobItems(assignedWayPoints[i]); spawnedCharacter.TeamID = (byte)teamID; teamClients[i].Character = spawnedCharacter; GameMain.GameSession.CrewManager.characters.Add(spawnedCharacter); } if (characterInfo != null && hostTeam == teamID) { myCharacter = Character.Create(characterInfo, assignedWayPoints[assignedWayPoints.Length - 1].WorldPosition, false, false); myCharacter.GiveJobItems(assignedWayPoints.Last()); myCharacter.TeamID = (byte)teamID; Character.Controlled = myCharacter; GameMain.GameSession.CrewManager.characters.Add(myCharacter); } } TraitorManager = null; if (TraitorsEnabled == YesNoMaybe.Yes || (TraitorsEnabled == YesNoMaybe.Maybe && Rand.Range(0.0f, 1.0f) < 0.5f)) { TraitorManager = new TraitorManager(this); if (TraitorManager.TraitorCharacter!=null && TraitorManager.TargetCharacter != null) { Log(TraitorManager.TraitorCharacter.Name + " is the traitor and the target is " + TraitorManager.TargetCharacter.Name, Color.Cyan); } } SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClients); //var startMessage = CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset); //server.SendMessage(startMessage, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0); yield return CoroutineStatus.Running; //UpdateCrewFrame(); //TraitorManager = null; //if (TraitorsEnabled == YesNoMaybe.Yes || // (TraitorsEnabled == YesNoMaybe.Maybe && Rand.Range(0.0f, 1.0f) < 0.5f)) //{ // TraitorManager = new TraitorManager(this); //} GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.GameScreen.Select(); AddChatMessage("Press TAB to chat. Use ''r;'' to talk through the radio.", ChatMessageType.Server); GameMain.NetLobbyScreen.StartButton.Enabled = true; gameStarted = true; initiatedStartGame = false; yield return CoroutineStatus.Success; } private void SendStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, List clients) { foreach (Client client in clients) { SendStartMessage(seed, selectedSub, selectedMode, client); } } private void SendStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, Client client) { NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.STARTGAME); msg.Write(seed); msg.Write(GameMain.NetLobbyScreen.LevelSeed); msg.Write((byte)GameMain.NetLobbyScreen.MissionTypeIndex); msg.Write(selectedSub.Name); msg.Write(selectedSub.MD5Hash.Hash); msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash); msg.Write(selectedMode.Name); bool missionAllowRespawn = !(GameMain.GameSession.gameMode is MissionMode) || ((MissionMode)GameMain.GameSession.gameMode).Mission.AllowRespawn; msg.Write(AllowRespawn && missionAllowRespawn); msg.Write(Submarine.MainSubs[1] != null); //loadSecondSub if (TraitorManager != null && TraitorManager.TraitorCharacter != null && TraitorManager.TargetCharacter != null && TraitorManager.TraitorCharacter == client.Character) { msg.Write(true); msg.Write(TraitorManager.TargetCharacter.Name); } else { msg.Write(false); } //monster spawn settings List monsterNames = monsterEnabled.Keys.ToList(); foreach (string s in monsterNames) { msg.Write(monsterEnabled[s]); } msg.WritePadBits(); server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered); } public void EndGame() { if (!gameStarted) return; string endMessage = "The round has ended." + '\n'; if (TraitorManager != null) { endMessage += TraitorManager.GetEndMessage(); } Mission mission = GameMain.GameSession.Mission; GameMain.GameSession.gameMode.End(endMessage); if (autoRestart) AutoRestartTimer = AutoRestartInterval; if (SaveServerLogs) log.Save(); Character.Controlled = null; myCharacter = null; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; entityEventManager.Clear(); foreach (Client c in connectedClients) { c.entityEventLastSent.Clear(); } #if DEBUG messageCount.Clear(); #endif respawnManager = null; gameStarted = false; if (connectedClients.Count > 0) { NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.ENDGAME); msg.Write(endMessage); msg.Write(mission != null && mission.Completed); if (server.ConnectionsCount > 0) { server.SendMessage(msg, server.Connections, NetDeliveryMethod.ReliableOrdered, 0); } foreach (Client client in connectedClients) { client.Character = null; client.inGame = false; } } CoroutineManager.StartCoroutine(EndCinematic(),"EndCinematic"); } public IEnumerable EndCinematic() { float endPreviewLength = 10.0f; var cinematic = new TransitionCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); new TransitionCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); float secondsLeft = endPreviewLength; do { secondsLeft -= CoroutineManager.UnscaledDeltaTime; yield return CoroutineStatus.Running; } while (secondsLeft > 0.0f); Submarine.Unload(); entityEventManager.Clear(); GameMain.NetLobbyScreen.Select(); yield return CoroutineStatus.Success; } public override void KickPlayer(string playerName, bool ban, bool range=false) { playerName = playerName.ToLowerInvariant(); Client client = connectedClients.Find(c => c.name.ToLowerInvariant() == playerName || (c.Character != null && c.Character.Name.ToLowerInvariant() == playerName)); KickClient(client, ban, range); } public void KickClient(NetConnection conn, bool ban = false, bool range = false) { Client client = connectedClients.Find(c => c.Connection == conn); if (client == null) { conn.Disconnect(ban ? "You have been banned from the server" : "You have been kicked from the server"); if (ban) { if (!banList.IsBanned(conn.RemoteEndPoint.Address.ToString())) { banList.BanPlayer("Unnamed", conn.RemoteEndPoint.Address.ToString()); } } } else { KickClient(client, ban, range); } } public void KickClient(Client client, bool ban = false, bool range = false) { if (client == null) return; if (ban) { DisconnectClient(client, client.name + " has been banned from the server", "You have been banned from the server"); string ip = client.Connection.RemoteEndPoint.Address.ToString(); if (range) { ip = banList.ToRange(ip); } banList.BanPlayer(client.name, ip); } else { DisconnectClient(client, client.name + " has been kicked from the server", "You have been kicked from the server"); } } public void DisconnectClient(NetConnection senderConnection, string msg = "", string targetmsg = "") { Client client = connectedClients.Find(x => x.Connection == senderConnection); if (client == null) return; DisconnectClient(client, msg, targetmsg); } public void DisconnectClient(Client client, string msg = "", string targetmsg = "") { if (client == null) return; if (gameStarted && client.Character != null) { client.Character.ClearInputs(); client.Character.Kill(CauseOfDeath.Disconnected, true); } client.Character = null; client.inGame = false; if (string.IsNullOrWhiteSpace(msg)) msg = client.name + " has left the server"; if (string.IsNullOrWhiteSpace(targetmsg)) targetmsg = "You have left the server"; Log(msg, ChatMessage.MessageColor[(int)ChatMessageType.Server]); client.Connection.Disconnect(targetmsg); GameMain.NetLobbyScreen.RemovePlayer(client.name); connectedClients.Remove(client); UpdateVoteStatus(); SendChatMessage(msg, ChatMessageType.Server); UpdateCrewFrame(); refreshMasterTimer = DateTime.Now; } private void UpdateCrewFrame() { foreach (Client c in connectedClients) { if (c.Character == null || !c.inGame) continue; } } /// /// Add the message to the chatbox and pass it to all clients who can receive it /// public void SendChatMessage(string message, ChatMessageType? type = null, Client senderClient = null) { Character senderCharacter = null; string senderName = ""; Client targetClient = null; if (type==null) { string tempStr; string command = ChatMessage.GetChatMessageCommand(message, out tempStr); switch (command.ToLowerInvariant()) { case "r": case "radio": type = ChatMessageType.Radio; break; case "d": case "dead": type = ChatMessageType.Dead; break; default: if (command != "") { if (command == name.ToLowerInvariant()) { //a private message to the host } else { targetClient = connectedClients.Find(c => command == c.name.ToLowerInvariant() || (c.Character != null && command == c.Character.Name.ToLowerInvariant())); if (targetClient == null) { if (senderClient != null) { var chatMsg = ChatMessage.Create( "", "Player \"" + command + "\" not found!", ChatMessageType.Error, null); chatMsg.NetStateID = senderClient.chatMsgQueue.Count > 0 ? (ushort)(senderClient.chatMsgQueue.Last().NetStateID + 1) : (ushort)(senderClient.lastRecvChatMsgID + 1); senderClient.chatMsgQueue.Add(chatMsg); senderClient.lastChatMsgQueueID = chatMsg.NetStateID; } else { AddChatMessage("Player \"" + command + "\" not found!", ChatMessageType.Error); } return; } } type = ChatMessageType.Private; } else { type = ChatMessageType.Default; } break; } message = tempStr; } if (gameStarted) { //msg sent by the server if (senderClient == null) { senderCharacter = myCharacter; senderName = myCharacter == null ? name : myCharacter.Name; } else //msg sent by a client { senderCharacter = senderClient.Character; senderName = senderCharacter == null ? senderClient.name : senderCharacter.Name; //sender doesn't have an alive character -> only ChatMessageType.Dead allowed if (senderCharacter == null || senderCharacter.IsDead) { type = ChatMessageType.Dead; } else if (type == ChatMessageType.Private) { //sender has an alive character, sending private messages not allowed return; } } } else { //msg sent by the server if (senderClient == null) { senderName = name; } else //msg sent by a client { //game not started -> clients can only send normal and private chatmessages if (type != ChatMessageType.Private) type = ChatMessageType.Default; senderName = senderClient.name; } } //check if the client is allowed to send the message WifiComponent senderRadio = null; switch (type) { case ChatMessageType.Radio: if (senderCharacter == null) return; //return if senderCharacter doesn't have a working radio var radio = senderCharacter.Inventory.Items.First(i => i != null && i.GetComponent() != null); if (radio == null) return; senderRadio = radio.GetComponent(); if (!senderRadio.CanTransmit()) return; break; case ChatMessageType.Dead: //character still alive -> not allowed if (senderClient != null && senderCharacter != null && !senderCharacter.IsDead) { return; } break; } if (type == ChatMessageType.Server) { senderName = null; senderCharacter = null; } //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) { string modifiedMessage = message; switch (type) { case ChatMessageType.Default: case ChatMessageType.Radio: if (senderCharacter != null && client.Character != null && !client.Character.IsDead) { modifiedMessage = ApplyChatMsgDistanceEffects(message, (ChatMessageType)type, senderCharacter, client.Character); //too far to hear the msg -> don't send if (string.IsNullOrWhiteSpace(modifiedMessage)) continue; } break; case ChatMessageType.Dead: //character still alive -> don't send if (client.Character != null && !client.Character.IsDead) continue; break; case ChatMessageType.Private: //private msg sent to someone else than this client -> don't send if (client != targetClient && client != senderClient) continue; break; } var chatMsg = ChatMessage.Create( senderName, modifiedMessage, (ChatMessageType)type, senderCharacter); chatMsg.NetStateID = client.chatMsgQueue.Count > 0 ? (ushort)(client.chatMsgQueue.Last().NetStateID + 1) : (ushort)(client.lastRecvChatMsgID + 1); client.chatMsgQueue.Add(chatMsg); client.lastChatMsgQueueID = chatMsg.NetStateID; } string myReceivedMessage = message; if (gameStarted && myCharacter != null && senderCharacter != null) { myReceivedMessage = ApplyChatMsgDistanceEffects(message, (ChatMessageType)type, senderCharacter, myCharacter); } if (!string.IsNullOrWhiteSpace(myReceivedMessage) && (targetClient == null || senderClient == null)) { AddChatMessage(myReceivedMessage, (ChatMessageType)type, senderName, senderCharacter); } } private string ApplyChatMsgDistanceEffects(string message, ChatMessageType type, Character sender, Character receiver) { if (sender == null) return ""; switch (type) { case ChatMessageType.Default: if (!receiver.IsDead) { return ChatMessage.ApplyDistanceEffect(receiver, sender, message, ChatMessage.SpeakRange); } break; case ChatMessageType.Radio: if (!receiver.IsDead) { var receiverItem = receiver.Inventory.Items.First(i => i != null && i.GetComponent() != null); //client doesn't have a radio -> don't send if (receiverItem == null) return ""; var senderItem = sender.Inventory.Items.First(i => i != null && i.GetComponent() != null); if (senderItem == null) return ""; var receiverRadio = receiverItem.GetComponent(); var senderRadio = senderItem.GetComponent(); if (!receiverRadio.CanReceive(senderRadio)) return ""; return ChatMessage.ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range); } break; } return message; } public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { base.Draw(spriteBatch); if (settingsFrame != null) { settingsFrame.Draw(spriteBatch); } else if (log.LogFrame!=null) { log.LogFrame.Draw(spriteBatch); } if (!ShowNetStats) return; GUI.Font.DrawString(spriteBatch, "Unique Events: " + entityEventManager.UniqueEvents.Count, new Vector2(10, 50), Color.White); int width = 200, height = 300; int x = GameMain.GraphicsWidth - width, y = (int)(GameMain.GraphicsHeight * 0.3f); if (clientListScrollBar == null) { clientListScrollBar = new GUIScrollBar(new Rectangle(x + width - 10, y, 10, height), "", 1.0f); } 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); GUI.SmallFont.DrawString(spriteBatch, "Connections: "+server.ConnectionsCount, new Vector2(x + 10, y + 30), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Received bytes: " + MathUtils.GetBytesReadable(server.Statistics.ReceivedBytes), new Vector2(x + 10, y + 45), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Received packets: " + server.Statistics.ReceivedPackets, new Vector2(x + 10, y + 60), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Sent bytes: " + MathUtils.GetBytesReadable(server.Statistics.SentBytes), new Vector2(x + 10, y + 75), Color.White); GUI.SmallFont.DrawString(spriteBatch, "Sent packets: " + server.Statistics.SentPackets, new Vector2(x + 10, y + 90), Color.White); int resentMessages = 0; int clientListHeight = connectedClients.Count * 40; float scrollBarHeight = (height - 110) / (float)Math.Max(clientListHeight, 110); if (clientListScrollBar.BarSize != scrollBarHeight) { clientListScrollBar.BarSize = scrollBarHeight; } int startY = y + 110; y = (startY - (int)(clientListScrollBar.BarScroll * (clientListHeight-(height - 110)))); foreach (Client c in connectedClients) { Color clientColor = c.Connection.AverageRoundtripTime > 0.3f ? Color.Red : Color.White; if (y >= startY && y < startY + height - 120) { GUI.SmallFont.DrawString(spriteBatch, c.name + " ("+c.Connection.RemoteEndPoint.Address.ToString()+")", new Vector2(x + 10, y), clientColor); GUI.SmallFont.DrawString(spriteBatch, "Ping: " + (int)(c.Connection.AverageRoundtripTime * 1000.0f) + " ms", new Vector2(x+20, y+10), clientColor); } if (y + 25 >= startY && y < startY + height - 130) GUI.SmallFont.DrawString(spriteBatch, "Resent messages: " + c.Connection.Statistics.ResentMessages, new Vector2(x + 20, y + 20), clientColor); resentMessages += (int)c.Connection.Statistics.ResentMessages; y += 40; } clientListScrollBar.Update(1.0f / 60.0f); clientListScrollBar.Draw(spriteBatch); netStats.AddValue(NetStats.NetStatType.ResentMessages, Math.Max(resentMessages, 0)); netStats.AddValue(NetStats.NetStatType.SentBytes, server.Statistics.SentBytes); netStats.AddValue(NetStats.NetStatType.ReceivedBytes, server.Statistics.ReceivedBytes); netStats.Draw(spriteBatch, new Rectangle(200,0,800,200), this); } private void FileTransferChanged(FileSender.FileTransferOut transfer) { Client recipient = connectedClients.Find(c => c.Connection == transfer.Connection); UpdateFileTransferIndicator(recipient); } public void SendCancelTransferMsg(FileSender.FileTransferOut transfer) { NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.FILE_TRANSFER); msg.Write((byte)FileTransferMessageType.Cancel); msg.Write((byte)transfer.SequenceChannel); server.SendMessage(msg, transfer.Connection, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel); } private void UpdateFileTransferIndicator(Client client) { var transfers = fileSender.ActiveTransfers.FindAll(t => t.Connection == client.Connection); var clientNameBox = GameMain.NetLobbyScreen.PlayerList.FindChild(client.name); var clientInfo = clientNameBox.FindChild("filetransfer"); if (clientInfo == null) { clientNameBox.ClearChildren(); clientInfo = new GUIFrame(new Rectangle(0, 0, 180, 0), Color.Transparent, Alignment.TopRight, null, clientNameBox); clientInfo.UserData = "filetransfer"; } else if (transfers.Count == 0) { clientInfo.Parent.RemoveChild(clientInfo); } clientInfo.ClearChildren(); var progressBar = new GUIProgressBar(new Rectangle(0, 4, 160, clientInfo.Rect.Height - 8), Color.Green, "", 0.0f, Alignment.Left, clientInfo); progressBar.IsHorizontal = true; progressBar.ProgressGetter = () => { return transfers.Sum(t => t.Progress) / transfers.Count; }; var textBlock = new GUITextBlock(new Rectangle(0, 2, 160, 0), "", "", Alignment.TopLeft, Alignment.Left | Alignment.CenterY, clientInfo, true, GUI.SmallFont); textBlock.TextGetter = () => { return MathUtils.GetBytesReadable(transfers.Sum(t => t.SentOffset)) + " / " + MathUtils.GetBytesReadable(transfers.Sum(t => t.Data.Length)); }; var cancelButton = new GUIButton(new Rectangle(-5, 0, 14, 0), "X", Alignment.Right, "", clientInfo); cancelButton.OnClicked = (GUIButton button, object userdata) => { transfers.ForEach(t => fileSender.CancelTransfer(t)); return true; }; } public void UpdateVoteStatus() { if (server.Connections.Count == 0|| connectedClients.Count == 0) return; Client.UpdateKickVotes(connectedClients); var clientsToKick = connectedClients.FindAll(c => c.KickVoteCount >= connectedClients.Count * KickVoteRequiredRatio); foreach (Client c in clientsToKick) { SendChatMessage(c.name+" has been kicked from the server.", ChatMessageType.Server, null); KickClient(c); } GameMain.NetLobbyScreen.LastUpdateID++; NetOutgoingMessage msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); msg.Write((byte)ServerNetObject.VOTE); Voting.ServerWrite(msg); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0); if (Voting.AllowEndVoting && EndVoteMax > 0 && ((float)EndVoteCount / (float)EndVoteMax) >= EndVoteRequiredRatio) { Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", Color.Cyan); EndGame(); } } public void UpdateClientPermissions(Client client) { clientPermissions.RemoveAll(cp => cp.IP == client.Connection.RemoteEndPoint.Address.ToString()); if (client.Permissions != ClientPermissions.None) { clientPermissions.Add(new SavedClientPermission( client.name, client.Connection.RemoteEndPoint.Address.ToString(), client.Permissions)); } var msg = server.CreateMessage(); msg.Write((byte)ServerPacketHeader.PERMISSIONS); msg.Write((byte)client.Permissions); server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered); SaveClientPermissions(); } public override bool SelectCrewCharacter(Character character, GUIComponent crewFrame) { if (character == null) return false; var characterFrame = crewFrame.FindChild("selectedcharacter"); if (character != myCharacter) { var banButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Ban", Alignment.BottomRight, "", characterFrame); banButton.UserData = character.Name; banButton.OnClicked += GameMain.NetLobbyScreen.BanPlayer; var rangebanButton = new GUIButton(new Rectangle(0, -25, 100, 20), "Ban range", Alignment.BottomRight, "", characterFrame); rangebanButton.UserData = character.Name; rangebanButton.OnClicked += GameMain.NetLobbyScreen.BanPlayerRange; var kickButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Kick", Alignment.BottomLeft, "", characterFrame); kickButton.UserData = character.Name; kickButton.OnClicked += GameMain.NetLobbyScreen.KickPlayer; } return true; } private void UpdateCharacterInfo(NetIncomingMessage message, Client sender) { Gender gender = Gender.Male; int headSpriteId = 0; try { gender = message.ReadBoolean() ? Gender.Male : Gender.Female; headSpriteId = message.ReadByte(); } catch (Exception e) { gender = Gender.Male; headSpriteId = 0; DebugConsole.Log("Received invalid characterinfo from \"" +sender.name+"\"! { "+e.Message+" }"); } List jobPreferences = new List(); int count = message.ReadByte(); for (int i = 0; i < Math.Min(count, 3); i++) { string jobName = message.ReadString(); JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Name == jobName); if (jobPrefab != null) jobPreferences.Add(jobPrefab); } sender.characterInfo = new CharacterInfo(Character.HumanConfigFile, sender.name, gender); sender.characterInfo.HeadSpriteId = headSpriteId; sender.jobPreferences = jobPreferences; } public void AssignJobs(List unassigned, bool assignHost) { unassigned = new List(unassigned); int[] assignedClientCount = new int[JobPrefab.List.Count]; if (assignHost) { if (characterInfo != null) { assignedClientCount[JobPrefab.List.FindIndex(jp => jp == GameMain.NetLobbyScreen.JobPreferences[0])] = 1; } else if (myCharacter != null && !myCharacter.IsDead) { assignedClientCount[JobPrefab.List.IndexOf(myCharacter.Info.Job.Prefab)] = 1; } } else if (myCharacter != null && !myCharacter.IsDead) { assignedClientCount[JobPrefab.List.IndexOf(myCharacter.Info.Job.Prefab)]++; } //count the clients who already have characters with an assigned job foreach (Client c in connectedClients) { if (unassigned.Contains(c)) continue; if (c.Character != null && !c.Character.IsDead) { assignedClientCount[JobPrefab.List.IndexOf(c.Character.Info.Job.Prefab)]++; } } //if any of the players has chosen a job that is Always Allowed, give them that job for (int i = unassigned.Count - 1; i >= 0; i--) { if (!unassigned[i].jobPreferences[0].AllowAlways) continue; unassigned[i].assignedJob = unassigned[i].jobPreferences[0]; unassigned.RemoveAt(i); } //go throught the jobs whose MinNumber>0 (i.e. at least one crew member has to have the job) bool unassignedJobsFound = true; while (unassignedJobsFound && unassigned.Count > 0) { unassignedJobsFound = false; for (int i = 0; i < JobPrefab.List.Count; i++) { if (unassigned.Count == 0) break; if (JobPrefab.List[i].MinNumber < 1 || assignedClientCount[i] >= JobPrefab.List[i].MinNumber) continue; //find the client that wants the job the most, or force it to random client if none of them want it Client assignedClient = FindClientWithJobPreference(unassigned, JobPrefab.List[i], true); assignedClient.assignedJob = JobPrefab.List[i]; assignedClientCount[i]++; unassigned.Remove(assignedClient); //the job still needs more crew members, set unassignedJobsFound to true to keep the while loop running if (assignedClientCount[i] < JobPrefab.List[i].MinNumber) unassignedJobsFound = true; } } //find a suitable job for the rest of the players for (int i = unassigned.Count - 1; i >= 0; i--) { for (int preferenceIndex = 0; preferenceIndex < 3; preferenceIndex++) { int jobIndex = JobPrefab.List.FindIndex(jp => jp == unassigned[i].jobPreferences[preferenceIndex]); //if there's enough crew members assigned to the job already, continue if (assignedClientCount[jobIndex] >= JobPrefab.List[jobIndex].MaxNumber) continue; unassigned[i].assignedJob = JobPrefab.List[jobIndex]; assignedClientCount[jobIndex]++; unassigned.RemoveAt(i); break; } } } private Client FindClientWithJobPreference(List clients, JobPrefab job, bool forceAssign = false) { int bestPreference = 0; Client preferredClient = null; foreach (Client c in clients) { int index = c.jobPreferences.IndexOf(job); if (index == -1) index = 1000; if (preferredClient == null || index < bestPreference) { bestPreference = index; preferredClient = c; } } //none of the clients wants the job, assign it to random client if (forceAssign && preferredClient == null) { preferredClient = clients[Rand.Int(clients.Count)]; } return preferredClient; } public static void Log(string line, Color? color) { if (GameMain.Server == null || !GameMain.Server.SaveServerLogs) return; GameMain.Server.log.WriteLine(line, color); } public override void Disconnect() { banList.Save(); SaveSettings(); if (registeredToMaster && restClient != null) { var request = new RestRequest("masterserver2.php", Method.GET); request.AddParameter("action", "removeserver"); request.AddParameter("serverport", Port); restClient.Execute(request); restClient = null; } if (SaveServerLogs) { Log("Shutting down server...", Color.Cyan); log.Save(); } server.Shutdown("The server has been shut down"); } } }