/// <summary> /// This method relay the update to players closer than 25km /// </summary> public static async void RelayToClosePlayers() { while (ServerContext.ServerRunning) { try { if (IncomingUpdates.TryDequeue(out var vesselUpdate) && VesselsDictionary.ContainsKey(vesselUpdate.Key)) { VesselsDictionary.TryUpdate(vesselUpdate.Key, vesselUpdate.Value, VesselsDictionary[vesselUpdate.Key]); IncomingMediumUpdates.Enqueue(vesselUpdate); var closeClients = VesselsDictionary.Where(v => !Equals(v.Key, vesselUpdate.Key) && v.Value != null && v.Value.BodyIndex == vesselUpdate.Value.BodyIndex && CalculateDistance(v.Value, vesselUpdate.Value) <= GeneralSettings.SettingsStore.CloseDistanceInMeters) .Select(v => v.Key); foreach (var closeClient in closeClients.Where(c => ServerContext.Clients.ContainsKey(c.Endpoint))) { MessageQueuer.RelayMessage <VesselSrvMsg>(closeClient, vesselUpdate.Value); } } } catch (Exception e) { LunaLog.Error($"Error in RelayToClosePlayers method! Details: {e}"); } await Task.Delay(GeneralSettings.SettingsStore.CloseDistanceUpdateIntervalMs); } }
public void SendMessageToClient(ClientStructure client, IServerMessageBase message) { if (message.MessageType == ServerMessageType.SyncTime) { SyncTimeSystem.RewriteMessage(client, message); } message.Data.SentTime = DateTime.UtcNow.Ticks; var messageBytes = message.Serialize(GeneralSettings.SettingsStore.CompressionEnabled); if (messageBytes == null) { LunaLog.Error("Error serializing message!"); return; } client.LastSendTime = ServerContext.ServerClock.ElapsedMilliseconds; client.BytesSent += messageBytes.Length; var outmsg = Server.CreateMessage(messageBytes.Length); outmsg.Write(messageBytes); Server.SendMessage(outmsg, client.Connection, message.NetDeliveryMethod, message.Channel); Server.FlushSendQueue(); //Manually force to send the msg }
/// <summary> /// Starts the web server /// </summary> public static void StartWebServer() { if (WebsiteSettings.SettingsStore.EnableWebsite) { try { if (!LunaNetUtils.IsTcpPortInUse(WebsiteSettings.SettingsStore.Port)) { Server.Use(new TcpListenerAdapter(new TcpListener(IPAddress.Any, WebsiteSettings.SettingsStore.Port))); Server.Use(new ExceptionHandler()); Server.Use(new CompressionHandler(DeflateCompressor.Default, GZipCompressor.Default)); Server.Use(new FileHandler()); Server.Use(new HttpRouter().With(string.Empty, new RestHandler <ServerInformation>(new ServerInformationRestController(), JsonResponseProvider.Default))); Server.Start(); } else { LunaLog.Error("Could not start web server. Port is already in use."); LunaLog.Info("You can change the web server settings inside 'Config/WebsiteSettings.xml'"); } } catch (Exception e) { LunaLog.Error($"Could not start web server. Details: {e}"); } } }
private static bool ValidateParameters(string[] parameters) { if (parameters == null) { LunaLog.Error($"Syntax error. No parameters found. {Usage}"); return(false); } if (parameters.Length != 3) { LunaLog.Error($"Syntax error. Wrong number of parameters. {Usage}"); return(false); } if (string.IsNullOrEmpty(parameters[0])) { LunaLog.Error($"Syntax error. First parameter not found. {Usage}"); return(false); } if (string.IsNullOrEmpty(parameters[1])) { LunaLog.Error($"Syntax error. Second parameter not found. {Usage}"); return(false); } if (string.IsNullOrEmpty(parameters[2])) { LunaLog.Error($"Syntax error. Third parameter not found. {Usage}"); return(false); } return(true); }
//Fire OnMessageReceived public static void FireOnMessageReceived(ClientStructure client, IClientMessageBase message) { var handledByAny = false; lock (ListLock) { foreach (var plugin in LoadedPlugins) { try { plugin.OnMessageReceived(client, message); //prevent plugins from unhandling other plugin's Handled requests if (message.Handled) { handledByAny = true; } } catch (Exception e) { var type = plugin.GetType(); LunaLog.Error($"Error thrown in OnMessageReceived event for {type.FullName} ({type.Assembly.FullName}), Exception: {e}"); } message.Handled = handledByAny; } } }
/// <summary> /// This method relay the update to players in the same planet but farther than 25km /// </summary> public static void RelayToMediumDistancePlayers() { while (ServerContext.ServerRunning) { try { KeyValuePair <ClientStructure, VesselPositionMsgData> vesselUpdate; if (IncomingMediumUpdates.TryDequeue(out vesselUpdate) && VesselsDictionary.ContainsKey(vesselUpdate.Key)) { IncomingFarUpdates.Enqueue(vesselUpdate); var mediumDistanceClients = VesselsDictionary.Where( v => !Equals(v.Key, vesselUpdate.Key) && v.Value != null && v.Value.BodyName == vesselUpdate.Value.BodyName && CalculateDistance(v.Value, vesselUpdate.Value) > GeneralSettings.SettingsStore.CloseDistanceInMeters) .Select(v => v.Key); foreach ( var mediumDistanceClient in mediumDistanceClients.Where(c => ServerContext.Clients.ContainsKey(c.Endpoint))) { MessageQueuer.RelayMessage <VesselSrvMsg>(mediumDistanceClient, vesselUpdate.Value); } } } catch (Exception e) { LunaLog.Error($"Error in RelayToMediumDistancePlayers method! Details: {e}"); } Thread.Sleep(GeneralSettings.SettingsStore.MediumDistanceUpdateIntervalMs); } }
public override bool Execute(string commandArgs) { CommandSystemHelperMethods.SplitCommandParamArray(commandArgs, out var parameters); if (!ValidateParameters(parameters)) { return(false); } var settingsFile = parameters[0]; var settingName = parameters[1]; var newValue = parameters[2]; var settingsPath = GetSettingsFilePath(settingsFile); if (string.IsNullOrEmpty(settingsPath)) { LunaLog.Error($"Syntax error. Could not find a settings file called '{settingsFile}'"); return(false); } try { ChangeServerSettings(settingName, newValue, settingsPath); } catch (Exception e) { LunaLog.Error($"Error while changing settings: {e}"); } return(true); }
private void Save() { //Lists are not thread safe so lock it lock (CommandLock) { try { if (FileHandler.FileExists(FullPath)) { FileHandler.SetAttributes(FullPath, FileAttributes.Normal); var newItems = Items.Except(FileHandler.ReadFileLines(FullPath) .Select(l => l.Trim()) .Where(l => !string.IsNullOrEmpty(l))) .ToList(); newItems.ForEach(u => FileHandler.AppendToFile(FullPath, u + Environment.NewLine)); } else { Items.ForEach(u => FileHandler.AppendToFile(FullPath, u + Environment.NewLine)); } } catch (Exception e) { LunaLog.Error("Error saving!, Exception: " + e); } } }
/// <summary> /// This method relay the vessel update to players in other planets /// </summary> public static void RelayToFarPlayers() { while (ServerContext.ServerRunning) { try { if (IncomingFarUpdates.TryDequeue(out var vesselUpdate) && VesselsDictionary.ContainsKey(vesselUpdate.Key)) { var farClients = VesselsDictionary.Where(v => !Equals(v.Key, vesselUpdate.Key) && v.Value != null && v.Value.BodyName != vesselUpdate.Value.BodyName) .Select(v => v.Key); foreach (var farClient in farClients.Where(c => ServerContext.Clients.ContainsKey(c.Endpoint))) { MessageQueuer.RelayMessage <VesselSrvMsg>(farClient, vesselUpdate.Value); } } } catch (Exception e) { LunaLog.Error($"Error in RelayToFarPlayers method! Details: {e}"); } Thread.Sleep(GeneralSettings.SettingsStore.FarDistanceUpdateIntervalMs); } }
public static void HandleServerInput(string input) { var commandPart = input; var argumentPart = ""; if (commandPart.Contains(" ")) { if (commandPart.Length > commandPart.IndexOf(' ') + 1) { argumentPart = commandPart.Substring(commandPart.IndexOf(' ') + 1); } commandPart = commandPart.Substring(0, commandPart.IndexOf(' ')); } if (commandPart.Length > 0) { if (Commands.ContainsKey(commandPart)) { try { Commands[commandPart].Func(argumentPart); } catch (Exception e) { LunaLog.Error($"Error handling server command {commandPart}, Exception {e}"); } } else { LunaLog.Normal($"Unknown server command: {commandPart}"); } } }
public override void Execute(string commandArgs) { string playerName; string reason; CommandSystemHelperMethods.SplitCommand(commandArgs, out playerName, out reason); reason = string.IsNullOrEmpty(reason) ? "No reason specified" : reason; if (playerName != "") { var player = ClientRetriever.GetClientByName(playerName); if (player != null) { LunaLog.Normal($"Kicking {playerName} from the server"); MessageQueuer.SendConnectionEnd(player, $"Kicked from the server: {reason}"); } else { LunaLog.Normal($"Player: {playerName} not found"); } } else { LunaLog.Error("Syntax error. Usage: /kick playername [reason]"); } }
private static KeyValuePair <int, double> GetLatestSubspaceLineFromFile() { var subspaceLines = FileHandler.ReadFileLines(SubspaceFile) .Select(l => l.Trim()).Where(l => !string.IsNullOrEmpty(l) && !l.StartsWith("#")) .Select(s => new KeyValuePair <int, double>(int.Parse(s.Split(':')[0]), double.Parse(s.Split(':')[1]))) .ToArray(); if (subspaceLines.Length == 0) { LunaLog.Error("Incorrect Subspace.txt file!"); return(new KeyValuePair <int, double>(0, 0)); } //TODO: Retrocompatibility - Remove next 2 lines 2/3 months after 1/july/2018 if (subspaceLines.Length > 1) { return(subspaceLines.OrderByDescending(s => s.Value).First()); } //TODO: Uncomment this 2/3 months after 1/july/2018 //if (SubspaceFile.Length > 1) //{ // LunaLog.Error("Incorrect Subspace.txt file!"); // return subspaceLines.OrderByDescending(s => s.Value).First(); //} return(subspaceLines.First()); }
//Checks the given parameter private static bool CheckParameter(string[] parameters) { if (parameters == null || parameters.Length != 1) { LunaLog.Error($"Syntax error. Use valid number as parameter!"); return(false); } return(true); }
public static async void Start() { var config = new NetPeerConfiguration("masterserver") { Port = Port, SuppressUnreliableUnorderedAcks = true, PingInterval = 500, ConnectionTimeout = ServerMsTimeout }; config.EnableMessageType(NetIncomingMessageType.UnconnectedData); var peer = new NetServer(config); peer.Start(); CheckMasterServerListed(); LunaLog.Info($"Master server {LmpVersioning.CurrentVersion} started! Поехали!"); RemoveExpiredServers(); while (RunServer) { NetIncomingMessage msg; while ((msg = peer.ReadMessage()) != null) { switch (msg.MessageType) { case NetIncomingMessageType.DebugMessage: LunaLog.NetworkDebug(msg.ReadString()); break; case NetIncomingMessageType.VerboseDebugMessage: LunaLog.NetworkVerboseDebug(msg.ReadString()); break; case NetIncomingMessageType.WarningMessage: LunaLog.Warning(msg.ReadString()); break; case NetIncomingMessageType.ErrorMessage: LunaLog.Error(msg.ReadString()); break; case NetIncomingMessageType.UnconnectedData: var message = GetMessage(msg); if (message != null && !message.VersionMismatch) { HandleMessage(message, msg, peer); } break; } } await Task.Delay(ServerMsTick); } peer.Shutdown("So long and thanks for all the fish!"); }
private static bool ParsePortNumber(Arguments commandLineArguments, string parameter, out ushort portNum) { if (ushort.TryParse(commandLineArguments[parameter].Trim(), out portNum)) { return(true); } LunaLog.Error($"Invalid port specified: {commandLineArguments[parameter].Trim()}"); return(false); }
private static IClientMessageBase DeserializeMessage(NetIncomingMessage msg) { try { return(ServerContext.ClientMessageFactory.Deserialize(msg, LunaNetworkTime.UtcNow.Ticks) as IClientMessageBase); } catch (Exception e) { LunaLog.Error($"Error deserializing message! {e}"); return(null); } }
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { //This will find and return the assembly requested if it is already loaded foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(assembly => assembly.FullName == args.Name)) { LunaLog.Debug($"Resolved plugin assembly reference: {args.Name} (referenced by {args.RequestingAssembly.FullName})"); return(assembly); } LunaLog.Error($"Could not resolve assembly {args.Name} referenced by {args.RequestingAssembly.FullName}"); return(null); }
public static void LoadModFile() { try { ModControl = LunaXmlSerializer.ReadXmlFromPath <ModControlStructure>(ServerContext.ModFilePath); } catch (Exception) { LunaLog.Error("Cannot read LMPModControl file. Will load the default one. Please regenerate it"); ModControl = LunaXmlSerializer.ReadXmlFromString <ModControlStructure>(LunaCommon.Properties.Resources.LMPModControl); } }
/// <summary> /// Starts the web server /// </summary> public static void StartWebServer() { if (WebsiteSettings.SettingsStore.EnableWebsite) { try { if (!LunaNetUtils.IsTcpPortInUse(WebsiteSettings.SettingsStore.Port)) { IPAddress listenAddress; if (!string.IsNullOrEmpty(WebsiteSettings.SettingsStore.ListenAddress)) { // Custom ListenAddress for web server, parse it if (!IPAddress.TryParse(WebsiteSettings.SettingsStore.ListenAddress, out listenAddress)) { // Parsing failed, fall back to IPAddress.Any LunaLog.Warning( "Could not parse ListenAddress for web server, falling back to 0.0.0.0"); } } else { // ListenAddress unset for web server, try the one of the game server IPAddress.TryParse(ConnectionSettings.SettingsStore.ListenAddress, out listenAddress); } listenAddress ??= IPAddress.Any; var listener = new TcpListener(listenAddress, WebsiteSettings.SettingsStore.Port); // Listen on dual-stack for the unspecified address in IPv6 format ([::]). if (listenAddress.Equals(IPAddress.IPv6Any)) { listener.Server.DualMode = true; } Server.Use(new TcpListenerAdapter(listener)); Server.Use(new ExceptionHandler()); Server.Use(new CompressionHandler(DeflateCompressor.Default, GZipCompressor.Default)); Server.Use(new FileHandler()); Server.Use(new HttpRouter().With(string.Empty, new RestHandler <ServerInformation>(new ServerInformationRestController(), JsonResponseProvider.Default))); Server.Start(); } else { LunaLog.Error("Could not start web server. Port is already in use."); LunaLog.Info("You can change the web server settings inside 'Config/WebsiteSettings.xml'"); } } catch (Exception e) { LunaLog.Error($"Could not start web server. Details: {e}"); } } }
private static void CheckMasterServerListed() { var ownEndpoint = new IPEndPoint(LunaNetUtils.GetOwnExternalIpAddress(), Port); if (!MasterServerRetriever.MasterServers.Contains(ownEndpoint)) { LunaLog.Error($"You're not in the master-servers URL ({RepoConstants.MasterServersListShortUrl}) Clients/Servers won't see you"); } else { LunaLog.Normal("Own ip correctly listed in master - servers URL"); } }
private static DateTime GetStoredStartTimeFromFile() { var startTimeLine = FileHandler.ReadFileLines(StartTimeFile) .Select(l => l.Trim()).Where(l => !string.IsNullOrEmpty(l) && !l.StartsWith("#")).SingleOrDefault(); if (startTimeLine == null || !DateTime.TryParse(startTimeLine, out var startTime)) { LunaLog.Error("Incorrect StartTime.txt file!"); return(LunaNetworkTime.UtcNow); } return(startTime); }
/// <summary> /// Thread safe method to append text /// </summary> /// <param name="path">Path to the file</param> /// <param name="text">Text to insert</param> public static void AppendToFile(string path, string text) { lock (GetLockSemaphore(path)) { try { File.AppendAllText(path, text); } catch (Exception e) { LunaLog.Error($"Error writing to file: {path}, Exception: {e}"); } } }
private static ILmpPlugin ActivatePluginType(Type loadedType) { try { //"as ILmpPlugin" will cast or return null if the Type is not a ILmpPlugin var pluginInstance = Activator.CreateInstance(loadedType) as ILmpPlugin; return(pluginInstance); } catch (Exception e) { LunaLog.Error($"Cannot activate plugin {loadedType.Name}, Exception: {e}"); return(null); } }
/// <summary> /// Starts the web server /// </summary> public static void StopWebServer() { if (WebsiteSettings.SettingsStore.EnableWebsite) { try { Server.Dispose(); } catch (Exception e) { LunaLog.Error($"Could not stop web server." + Environment.NewLine + $"Details: {e}"); } } }
public void ReceiveCallback(ClientStructure client, NetIncomingMessage msg) { if (client == null || msg.LengthBytes <= 1) { return; } if (client.ConnectionStatus == ConnectionStatus.Connected) { client.LastReceiveTime = ServerContext.ServerClock.ElapsedMilliseconds; } var message = DeserializeMessage(msg); if (message == null) { return; } LmpPluginHandler.FireOnMessageReceived(client, message); //A plugin has handled this message and requested suppression of the default behavior if (message.Handled) { return; } if (message.VersionMismatch) { MessageQueuer.SendConnectionEnd(client, $"Version mismatch: Your version ({message.Data.MajorVersion}.{message.Data.MinorVersion}.{message.Data.BuildVersion}) " + $"does not match the server version: {LmpVersioning.CurrentVersion}."); return; } //Clients can only send HANDSHAKE until they are Authenticated. if (!client.Authenticated && message.MessageType != ClientMessageType.Handshake) { MessageQueuer.SendConnectionEnd(client, $"You must authenticate before sending a {message.MessageType} message"); return; } //Handle the message try { HandlerDictionary[message.MessageType].HandleMessage(client, message); } catch (Exception e) { LunaLog.Error($"Error handling a message from {client.PlayerName}! {e}"); } }
public static void DisconnectClient(ClientStructure client, string reason = "") { if (!string.IsNullOrEmpty(reason)) { LunaLog.Debug($"{client.PlayerName} sent Connection end message, reason: {reason}"); } if (client.ConnectionStatus != ConnectionStatus.Disconnected) { client.ConnectionStatus = ConnectionStatus.Disconnected; LmpPluginHandler.FireOnClientDisconnect(client); if (client.Authenticated) { var msgData = ServerContext.ServerMessageFactory.CreateNewMessageData <PlayerConnectionLeaveMsgData>(); msgData.PlayerName = client.PlayerName; MessageQueuer.RelayMessage <PlayerConnectionSrvMsg>(client, msgData); LockSystem.ReleasePlayerLocks(client); WarpSystem.RemoveSubspace(client.Subspace); } try { client.Connection?.Disconnect(reason); } catch (Exception e) { LunaLog.Error($"Error closing client Connection: {e.Message}"); } } //Remove Clients from list if (ServerContext.Clients.TryRemove(client.Endpoint, out ClientStructure removed)) { LunaLog.Debug($"Online Players: {ServerContext.PlayerCount}, connected: {ServerContext.Clients.Count}"); } else { LunaLog.Error($"Error removing client: {client.PlayerName} from list"); } //As this is the last client that is connected to the server, run a safety backup once he disconnects if (ServerContext.Clients.Count == 0) { BackupSystem.RunBackup(); GcSystem.PerformGCNow(); } }
public void ThreadMain() { try { WarpSystem.Reset(); ChatSystem.Reset(); while (ServerContext.ServerRunning) { //Check timers NukeCommand.CheckTimer(); DekesslerCommand.CheckTimer(); LmpPluginHandler.FireOnUpdate(); //Run plugin update Thread.Sleep(GeneralSettings.SettingsStore.MainTimeTick); } } catch (Exception e) { LunaLog.Error($"Fatal error thrown, exception: {e}"); new ShutDownCommand().Execute("Crashed!"); } try { var disconnectTime = DateTime.UtcNow.Ticks; var sendingMessages = true; while (sendingMessages) { if (DateTime.UtcNow.Ticks - disconnectTime > TimeSpan.FromSeconds(5).Ticks) { LunaLog.Debug($"Shutting down with {ServerContext.PlayerCount} Players, " + $"{ServerContext.Clients.Count} connected Clients"); break; } sendingMessages = ClientRetriever.GetAuthenticatedClients().Any(c => c.SendMessageQueue.Count > 0); Thread.Sleep(GeneralSettings.SettingsStore.MainTimeTick); } ServerContext.LidgrenServer.ShutdownLidgrenServer(); } catch (Exception e) { LunaLog.Fatal($"Fatal error thrown during shutdown, exception: {e}"); throw; } }
//Executes the ClearVesselsCommand public override bool Execute(string commandArgs) { CommandSystemHelperMethods.SplitCommandParamArray(commandArgs, out var parameters); var msgUsage = $"{Environment.NewLine}{Environment.NewLine}" + $"Usage:{Environment.NewLine}" + "/clearvessels vesselType vesselSituation vesselSplashed vesselName"; var msgDescription = $"{Environment.NewLine}{Environment.NewLine}" + $"Description:{Environment.NewLine}" + $"You can use * on a search param to accept all possible values.{Environment.NewLine}" + $"Using * for all params like in the following example{Environment.NewLine}" + $"will clear every vessel from universe.{Environment.NewLine}" + $"/clearvessels * * * * *{Environment.NewLine}" + $"vesselType can be ship, plane, debris, spaceobject etc.{Environment.NewLine}" + $"vesselSituation can be orbiting, flying, landed.{Environment.NewLine}" + $"vesselSplashed can be true or false.{Environment.NewLine}" + $"vesselName is the given vessel name.{Environment.NewLine}" + $"Example: /clearvessels plane orbiting * aeris{Environment.NewLine}" + $"Clears all vessels that are planes and have{Environment.NewLine}" + "name containing the word aeris."; try { if (parameters.Length == 4) { var vesselType = string.IsNullOrEmpty(parameters[0]) ? "" : parameters[0]; var vesselSituation = string.IsNullOrEmpty(parameters[1]) ? "" : parameters[1]; var vesselSplashed = string.IsNullOrEmpty(parameters[2]) ? "" : parameters[2]; var vesselName = string.IsNullOrEmpty(parameters[3]) ? "" : parameters[3]; RunRemove(vesselType, vesselSituation, vesselSplashed, vesselName); } else { LunaLog.Error($"{Environment.NewLine}Syntax error. Wrong number of parameters.{msgUsage}{msgDescription}"); return(false); } } catch (Exception) { LunaLog.Error($"{Environment.NewLine}Syntax error.{msgUsage}{msgDescription}"); return(false); } return(true); }
public void ReceiveCallback(ClientStructure client, NetIncomingMessage msg) { if (client == null || msg.LengthBytes <= 1) { return; } if (client.ConnectionStatus == ConnectionStatus.Connected) { client.LastReceiveTime = ServerContext.ServerClock.ElapsedMilliseconds; } var messageBytes = msg.ReadBytes(msg.LengthBytes); var message = ServerContext.ClientMessageFactory.Deserialize(messageBytes, DateTime.UtcNow.Ticks) as IClientMessageBase; if (message == null) { LunaLog.Error("Error deserializing message!"); return; } LmpPluginHandler.FireOnMessageReceived(client, message); //A plugin has handled this message and requested suppression of the default behavior if (message.Handled) { return; } if (message.VersionMismatch) { MessageQueuer.SendConnectionEnd(client, $"Version mismatch. Your version doesn't match the server's version: {VersionInfo.VersionNumber}. Update your plugin."); return; } //Clients can only send HANDSHAKE until they are Authenticated. if (!client.Authenticated && message.MessageType != ClientMessageType.Handshake) { MessageQueuer.SendConnectionEnd(client, $"You must authenticate before sending a {message.MessageType} message"); return; } LunaLog.Debug(message.MessageType.ToString()); //Handle the message HandlerDictionary[message.MessageType].HandleMessage(client, message.Data); }
public static async void ThreadMain() { try { while (ServerContext.ServerRunning) { //Check timers NukeCommand.CheckTimer(); DekesslerCommand.CheckTimer(); LmpPluginHandler.FireOnUpdate(); //Run plugin update await Task.Delay(IntervalSettings.SettingsStore.MainTimeTick); } } catch (Exception e) { LunaLog.Error($"Fatal error thrown, exception: {e}"); ServerContext.Shutdown("Fatal error server side"); } try { var disconnectTime = LunaNetworkTime.UtcNow.Ticks; var sendingMessages = true; while (sendingMessages) { if (LunaNetworkTime.UtcNow.Ticks - disconnectTime > TimeSpan.FromSeconds(5).Ticks) { LunaLog.Debug($"Shutting down with {ServerContext.PlayerCount} Players, " + $"{ServerContext.Clients.Count} connected Clients"); break; } sendingMessages = ClientRetriever.GetAuthenticatedClients().Any(c => c.SendMessageQueue.Count > 0); await Task.Delay(IntervalSettings.SettingsStore.MainTimeTick); } LidgrenServer.ShutdownLidgrenServer(); } catch (Exception e) { LunaLog.Fatal($"Fatal error thrown during shutdown, exception: {e}"); throw; } }