private void ListenerOnPeerDisconnectedEvent(NetPeer peer, DisconnectInfo disconnectInfo) { if (!ConnectedPlayers.TryGetValue(peer.Id, out Player player)) { return; } _logger.Info($"Player {player.Username} lost connection! Reason: {disconnectInfo.Reason}"); switch (disconnectInfo.Reason) { case DisconnectReason.RemoteConnectionClose: ChatLogPanel.PrintGameMessage($"Player {player.Username} disconnected!"); break; case DisconnectReason.Timeout: ChatLogPanel.PrintGameMessage($"Player {player.Username} timed out!"); break; default: ChatLogPanel.PrintGameMessage($"Player {player.Username} lost connection!"); break; } HandlePlayerDisconnect(player); }
public static void RemoveUnused(A arr, N id, string type) { N[] unusedItems = (N[])arr.GetType().GetField("m_unusedItems", AccessTools.all).GetValue(arr); FieldInfo unusedCountField = arr.GetType().GetField("m_unusedCount", AccessTools.all); uint unusedCount = (uint)unusedCountField.GetValue(arr); uint expectedPos = Convert.ToUInt32(id) - 1; // Special case, when array was modified (search for the id) if (!EqualityComparer <N> .Default.Equals(unusedItems[expectedPos], id)) { bool found = false; for (uint num = 0; num <= unusedCount; num++) { if (EqualityComparer <N> .Default.Equals(unusedItems[num], id)) { expectedPos = num; found = true; break; } } if (!found) { // The arrays are no longer in sync LogManager.GetCurrentClassLogger().Error($"{type}: Received id already in use. Please restart the multiplayer session!"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "ID collision. Please restart the multiplayer session."); return; } } unusedItems[expectedPos] = unusedItems[--unusedCount]; unusedCountField.SetValue(arr, unusedCount); }
private static void RemoveUnused <N>(object arr, N id, string type) where N : IConvertible { N[] unusedItems = (N[])arr.GetType().GetField("m_unusedItems", ReflectionHelper.AllAccessFlags).GetValue(arr); FieldInfo unusedCountField = arr.GetType().GetField("m_unusedCount", ReflectionHelper.AllAccessFlags); uint unusedCount = (uint)unusedCountField.GetValue(arr); uint expectedPos = Convert.ToUInt32(id) - 1; // Special case, when array was modified (search for the id) if (expectedPos >= unusedCount || !EqualityComparer <N> .Default.Equals(unusedItems[expectedPos], id)) { bool found = false; for (uint num = 0; num < unusedCount; num++) { if (EqualityComparer <N> .Default.Equals(unusedItems[num], id)) { expectedPos = num; found = true; break; } } if (!found) { // The arrays are no longer in sync Log.Error($"{type}: Received id {id} already in use. Please restart the multiplayer session!"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "ID collision. Please restart the multiplayer session."); return; } } unusedItems[expectedPos] = unusedItems[--unusedCount]; unusedCountField.SetValue(arr, unusedCount); }
public void HandlePlayerConnect(Player player) { _logger.Info($"Player {player.Username} has connected!"); ChatLogPanel.PrintGameMessage($"Player {player.Username} has connected!"); MultiplayerManager.Instance.PlayerList.Add(player.Username); Command.HandleClientConnect(player); }
protected override void Handle(ClientConnectCommand command) { LogManager.GetCurrentClassLogger().Info($"Player {command.Username} has connected!"); ChatLogPanel.PrintGameMessage($"Player {command.Username} has connected!"); MultiplayerManager.Instance.PlayerList.Add(command.Username); }
/// <summary> /// When we get a message from a client, we handle the message here /// and perform any necessary tasks. /// </summary> private void ListenerOnNetworkReceiveEvent(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { try { // Parse this message bool relayOnServer = CommandReceiver.Parse(reader, peer); if (relayOnServer) { // Copy relevant message part (exclude protocol headers) byte[] data = new byte[reader.UserDataSize]; Array.Copy(reader.RawData, reader.UserDataOffset, data, 0, reader.UserDataSize); // Send this message to all other clients var peers = _netServer.ConnectedPeerList; foreach (var client in peers) { // Don't send the message back to the client that sent it. if (client.Id == peer.Id) { continue; } // Send the message so the other client can stay in sync client.Send(data, DeliveryMethod.ReliableOrdered); } } } catch (Exception ex) { ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "Error while parsing command. See log."); _logger.Error(ex, $"Encountered an error while reading command from {peer.EndPoint.Address}:{peer.EndPoint.Port}:"); } }
public override void Handle(ClientDisconnectCommand command) { LogManager.GetCurrentClassLogger().Info($"Player {command.Username} has disconnected!"); ChatLogPanel.PrintGameMessage($"Player {command.Username} has disconnected!"); MultiplayerManager.Instance.PlayerList.Remove(command.Username); TransactionHandler.ClearTransactions(command.ClientId); }
protected override void Handle(ClientDisconnectCommand command) { Log.Info($"Player {command.Username} has disconnected!"); ChatLogPanel.PrintGameMessage($"Player {command.Username} has disconnected!"); MultiplayerManager.Instance.PlayerList.Remove(command.Username); TransactionHandler.ClearTransactions(command.ClientId); ToolSimulator.RemoveSender(command.ClientId); }
/// <summary> /// Starts the server with the specified config options /// </summary> /// <param name="serverConfig">Server config information</param> /// <returns>If the server has started.</returns> public bool StartServer(ServerConfig serverConfig) { // If the server is already running, we will stop and start it again if (Status == ServerStatus.Running) { StopServer(); } // Set the config Config = serverConfig; // Let the user know that we are trying to start the server _logger.Info($"Attempting to start server on port {Config.Port}..."); // Attempt to start the server _netServer.DiscoveryEnabled = true; var result = _netServer.Start(Config.Port); // If the server has not started, tell the user and return false. if (!result) { _logger.Error("The server failed to start."); StopServer(); // Make sure the server is fully stopped return(false); } try { // This async stuff is nasty, but we have to target .net 3.5 (unless cities skylines upgrades to something higher). var nat = new NatDiscoverer(); var cts = new CancellationTokenSource(); cts.CancelAfter(5000); nat.DiscoverDeviceAsync(PortMapper.Upnp, cts).ContinueWith(task => task.Result.CreatePortMapAsync(new Mapping(Protocol.Udp, Config.Port, Config.Port, "Cities Skylines Multiplayer (UDP)"))).Wait(); } catch (Exception e) { _logger.Error($"Failed to automatically open port. Manual Port Forwarding is required: {e.Message}"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "Failed to automatically open port. Manual port forwarding is required."); } // Update the status Status = ServerStatus.Running; // Initialize host player _hostPlayer = new Player(Config.Username); MultiplayerManager.Instance.PlayerList.Add(_hostPlayer.Username); // Update the console to let the user know the server is running _logger.Info("The server has started."); ChatLogPanel.PrintGameMessage("The server has started."); return(true); }
public override void Handle(ParkCreateCommand command) { DistrictHandler.IgnoreAll = true; DistrictManager.instance.CreatePark(out byte park, command.ParkType, command.ParkLevel); if (park != command.ParkId) { LogManager.GetCurrentClassLogger().Log(LogLevel.Error, $"Park array no longer in sync! Generated {park} instead of {command.ParkId}"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "Park array no longer in sync! Please restart the multiplayer session!"); } DistrictManager.instance.m_parks.m_buffer[park].m_randomSeed = command.Seed; DistrictHandler.IgnoreAll = false; }
protected override void Handle(DistrictCreateCommand command) { IgnoreHelper.StartIgnore(); DistrictManager.instance.CreateDistrict(out byte district); if (district != command.DistrictId) { LogManager.GetCurrentClassLogger().Log(LogLevel.Error, $"District array no longer in sync! Generated {district} instead of {command.DistrictId}"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "District array no longer in sync! Please restart the multiplayer session!"); } DistrictManager.instance.m_districts.m_buffer[district].m_randomSeed = command.Seed; IgnoreHelper.EndIgnore(); }
protected override void Handle(ParkCreateCommand command) { IgnoreHelper.StartIgnore(); DistrictManager.instance.CreatePark(out byte park, command.ParkType, command.ParkLevel); if (park != command.ParkId) { Log.Error($"Park array no longer in sync! Generated {park} instead of {command.ParkId}"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, "Park array no longer in sync! Please restart the multiplayer session!"); } DistrictManager.instance.m_parks.m_buffer[park].m_randomSeed = command.Seed; IgnoreHelper.EndIgnore(); }
/// <summary> /// Stops the client or server, depending on the current role /// </summary> public void StopEverything() { switch (CurrentRole) { case MultiplayerRole.Client: CurrentClient.Disconnect(); ChatLogPanel.PrintGameMessage("Disconnected from server."); break; case MultiplayerRole.Server: CurrentServer.StopServer(); ChatLogPanel.PrintGameMessage("Server stopped."); break; } CurrentRole = MultiplayerRole.None; }
private void ListenerOnPeerDisconnectedEvent(NetPeer peer, DisconnectInfo disconnectInfo) { if (Status == ClientStatus.Connecting) { ConnectionMessage = $"Failed to connect!"; } // Log the error message _logger.Info($"Disconnected from server. Message: {disconnectInfo.Reason}, Code: {disconnectInfo.SocketErrorCode}"); // Log the reason to the console if we are not in 'connecting' state if (Status != ClientStatus.Connecting) { switch (disconnectInfo.Reason) { case DisconnectReason.Timeout: ChatLogPanel.PrintGameMessage("Disconnected: Timed out!"); break; case DisconnectReason.DisconnectPeerCalled: ChatLogPanel.PrintGameMessage("Disconnected!"); break; case DisconnectReason.RemoteConnectionClose: ChatLogPanel.PrintGameMessage("Disconnected: Server closed!"); break; default: ChatLogPanel.PrintGameMessage($"Disconnected: Connection lost ({disconnectInfo.Reason})!"); break; } } // If we are connected, disconnect if (Status == ClientStatus.Connected) { MultiplayerManager.Instance.StopEverything(); } // In the case of ClientStatus.Connecting, this also ends the wait loop Status = ClientStatus.Disconnected; }
public override void HandleOnClient(ConnectionResultCommand command) { // We only want this message while connecting if (MultiplayerManager.Instance.CurrentClient.Status != ClientStatus.Connecting) { return; } // If we are allowed to connect if (command.Success) { // Log and set that we are connected. _logger.Info("Successfully connected to server."); ChatLogPanel.PrintGameMessage("Successfully connected to server."); MultiplayerManager.Instance.CurrentClient.Status = ClientStatus.Connected; } else { _logger.Info($"Could not connect: {command.Reason}"); MultiplayerManager.Instance.CurrentClient.ConnectionMessage = command.Reason; MultiplayerManager.Instance.CurrentClient.Disconnect(); } }
/// <summary> /// Called when the speed or pause state have changed. /// This normally happens when the player clicks on one of the two buttons in the bottom left. /// This will only trigger any action if the current state is either Playing or Paused. /// </summary> /// <param name="pause">If the game should be paused.</param> /// <param name="speed">The newly selected speed.</param> private static void PlayPauseSpeedChanged(bool pause, int speed) { if (_state == SpeedPauseState.Paused && !pause) { RequestPlay(speed); Log.Debug($"[SpeedPauseHelper] State {SpeedPauseState.Playing} requested locally."); } else if (_state == SpeedPauseState.Playing && pause) { RequestPause(); Log.Debug($"[SpeedPauseHelper] State {SpeedPauseState.Paused} requested locally."); } else if (_state == SpeedPauseState.Playing && speed != _speed) { RequestSpeedChange(speed); Log.Debug("[SpeedPauseHelper] Speed change requested locally."); } else { ChatLogPanel.PrintGameMessage("Please wait until all players have reached the same play/pause state and speed!"); Log.Info($"[SpeedPauseHelper] (Pause: {pause}, Speed: {speed}) requested, but state was {_state}"); } }
public override void Handle(ConnectionResultCommand command) { // We only want this message while connecting if (MultiplayerManager.Instance.CurrentClient.Status != ClientStatus.Connecting) { return; } // If we are allowed to connect if (command.Success) { // Log and set that we are connected. _logger.Info("Successfully connected to server."); ChatLogPanel.PrintGameMessage("Successfully connected to server."); MultiplayerManager.Instance.CurrentClient.Status = ClientStatus.Connected; MultiplayerManager.Instance.CurrentClient.ClientId = command.ClientId; } else { _logger.Info($"Could not connect: {command.Reason}"); MultiplayerManager.Instance.CurrentClient.ConnectionMessage = command.Reason; MultiplayerManager.Instance.CurrentClient.Disconnect(); if (command.DLCBitMask != SteamHelper.DLC_BitMask.None) { DLCHelper.DLCComparison compare = DLCHelper.Compare(command.DLCBitMask, DLCHelper.GetOwnedDLCs()); if (compare.ClientMissing != SteamHelper.DLC_BitMask.None) { ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, $"You are missing the following DLCs: {compare.ClientMissing}"); } if (compare.ServerMissing != SteamHelper.DLC_BitMask.None) { ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, $"The server doesn't have the following DLCs: {compare.ServerMissing}"); } ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Normal, "DLCs can be disabled via checkbox in Steam"); } } }
/// <summary> /// Attempt to connect to a server /// </summary> /// <param name="clientConfig">Client config params</param> /// <returns>True if the client is connected to the server, false if not</returns> public bool Connect(ClientConfig clientConfig) { // Let the user know that we are trying to connect to a server _logger.Info($"Attempting to connect to server at {clientConfig.HostAddress}:{clientConfig.Port}..."); // if we are currently trying to connect, cancel // and try again. if (Status == ClientStatus.Connecting) { _logger.Info("Current status is 'connecting', attempting to disconnect first."); Disconnect(); } // The client is already connected so we need to // disconnect. if (Status == ClientStatus.Connected) { _logger.Info("Current status is 'connected', attempting to disconnect first."); Disconnect(); } // Set the configuration Config = clientConfig; // Start the client, if client setup fails, return out and // tell the user var result = _netClient.Start(); if (!result) { _logger.Error("The client failed to start."); ConnectionMessage = "The client failed to start."; Disconnect(); // make sure we are fully disconnected return(false); } _logger.Info("Set status to 'connecting'..."); // Try connect to server, update the status to say that // we are trying to connect. try { _netClient.Connect(Config.HostAddress, Config.Port, "CSM"); } catch (Exception ex) { ConnectionMessage = "Failed to connect."; _logger.Error(ex, $"Failed to connect to {Config.HostAddress}:{Config.Port}"); ChatLogPanel.PrintGameMessage(ChatLogPanel.MessageType.Error, $"Failed to connect: {ex.Message}"); Disconnect(); return(false); } // Start processing networking Status = ClientStatus.Connecting; ClientId = 0; // We need to wait in a loop for 30 seconds (waiting 500ms each time) // while we wait for a successful connection (Status = Connected) or a // failed connection (Status = Disconnected). var waitWatch = new Stopwatch(); waitWatch.Start(); // Try connect for 30 seconds while (waitWatch.Elapsed < TimeSpan.FromSeconds(30)) { // If we connect, exit the loop and return true if (Status == ClientStatus.Connected) { _logger.Info("Client has connected."); return(true); } // The client cannot connect for some reason, the ConnectionMessage // variable will contain why. if (Status == ClientStatus.Disconnected) { _logger.Warn("Client disconnected while in connecting loop."); Disconnect(); // make sure we are fully disconnected return(false); } // Wait 500ms Thread.Sleep(500); } // We have timed out ConnectionMessage = "Could not connect to server, timed out."; _logger.Warn("Connection timeout!"); // Did not connect Disconnect(); // make sure we are fully disconnected return(false); }