public static void DisconnectClient(ClientStructure client, string reason = "") { if (!string.IsNullOrEmpty(reason)) { LunaLog.Debug($"{client.PlayerName} sent Connection end message, reason: {reason}"); } //Remove Clients from list if (ServerContext.Clients.ContainsKey(client.Endpoint)) { ServerContext.Clients.TryRemove(client.Endpoint, out client); LunaLog.Debug($"Online Players: {ServerContext.PlayerCount}, connected: {ServerContext.Clients.Count}"); } 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); if (!ServerContext.Clients.Any(c => c.Value.Subspace == client.Subspace)) { WarpSystem.RemoveSubspace(client.Subspace); VesselRelaySystem.RemoveSubspace(client.Subspace); } } try { client.Connection?.Disconnect(reason); } catch (Exception e) { LunaLog.Debug($"Error closing client Connection: {e.Message}"); } ServerContext.LastPlayerActivity = ServerContext.ServerClock.ElapsedMilliseconds; } }
public static void DisconnectClient(ClientStructure client, string reason = "") { if (!string.IsNullOrEmpty(reason)) { LunaLog.Debug($"{client.PlayerName} sent Connection end message, reason: {reason}"); } //Remove Clients from list if (ServerContext.Clients.ContainsKey(client.Endpoint)) { ServerContext.Clients.TryRemove(client.Endpoint, out client); LunaLog.Debug($"Online Players: {ServerContext.PlayerCount}, connected: {ServerContext.Clients.Count}"); } 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}"); } } //As this is the last client that is connected to the server, run a safety backup once they disconnect if (ServerContext.Clients.Count == 0) { BackupSystem.RunBackup(); } }
/// <summary> /// This method should be called in a thread. /// It runs over the old messages and sends them once the subspace time matches the message send time. /// </summary> public static async void RelayOldVesselMessages() { while (ServerContext.ServerRunning) { var messagesByGroup = DbCollection.FindAll().GroupBy(m => m.SubspaceId); foreach (var subspace in messagesByGroup) { var subspaceTime = WarpSystem.GetCurrentSubspaceTime(subspace.Key); var msgToSend = subspace.Where(m => subspaceTime >= m.Msg.SentTime).ToList(); msgToSend.ForEach(m => { MessageQueuer.SendMessageToSubspace <VesselSrvMsg>(m.Msg, m.SubspaceId); DbCollection.Delete(m.Id); }); } DataBase.Shrink(); await Task.Delay(IntervalSettings.SettingsStore.SendReceiveThreadTickMs); } }
/// <summary> /// This method relays a message to the other clients in the same subspace. /// In case there are other players in OLDER subspaces it stores it in their queue for further processing /// </summary> public static void HandleVesselMessage(ClientStructure client, dynamic msg) { if (client.Subspace == -1) { return; } var vesselId = (Guid)msg.VesselId; var gameTime = (double)msg.GameTime; MessageQueuer.RelayMessageToSubspace <VesselSrvMsg>(client, msg); if (GeneralSettings.SettingsStore.ShowVesselsInThePast) { //Here we send this update to all the players in the FUTURE foreach (var subspace in WarpSystem.GetFutureSubspaces(client.Subspace)) { MessageQueuer.RelayMessageToSubspace <VesselSrvMsg>(client, msg, subspace); } } //In case the client is running in the future here we adjust the real sent time of the message msg.SentTime += WarpSystem.GetSubspaceTimeDifference(client.Subspace); foreach (var subspace in WarpSystem.GetPastSubspaces(client.Subspace)) { var vesselsAndQueues = OldSubspaceVesselMessages.GetOrAdd(subspace, new ConcurrentDictionary <Guid, ConcurrentQueue <VesselRelayItem> >()); var vesselQueue = vesselsAndQueues.GetOrAdd(vesselId, new ConcurrentQueue <VesselRelayItem>()); //This is the case when a user reverted (the message received has a game time LOWER than the last message received). //To handle this, we remove all the messages that we received UNTIL this revert. if (vesselQueue.LastOrDefault()?.GameTime > gameTime) { while (vesselQueue.TryPeek(out var peekValue) && peekValue.GameTime > gameTime) { vesselQueue.TryDequeue(out _); } } vesselQueue.Enqueue(new VesselRelayItem(subspace, vesselId, gameTime, msg)); } }
/// <summary> /// This method should be called in a thread. /// It runs over the old messages and sends them once the subspace time matches the message send time. /// </summary> public static async void RelayOldVesselMessages() { while (ServerContext.ServerRunning) { foreach (var subspaceVessels in OldSubspaceVesselMessages.Where(m => !m.Value.IsEmpty)) { var subspaceTime = WarpSystem.GetCurrentSubspaceTime(subspaceVessels.Key); foreach (var queue in subspaceVessels.Value) { while (queue.Value.TryPeek(out var relayItem) && subspaceTime >= relayItem.Msg.SentTime) { queue.Value.TryDequeue(out relayItem); MessageQueuer.SendMessageToSubspace <VesselSrvMsg>(relayItem?.Msg, subspaceVessels.Key); } } } await Task.Delay(GeneralSettings.SettingsStore.SendReceiveThreadTickMs); } }
public void HandleMessage(IServerMessageBase msg) { if (!(msg.Data is VesselDockMsgData msgData)) { return; } LunaLog.Log("Docking message received!"); if (msgData.WeakVesselId == CurrentDockEvent.WeakVesselId && msgData.DominantVesselId == CurrentDockEvent.DominantVesselId && (LunaNetworkTime.UtcNow - CurrentDockEvent.DockingTime) < TimeSpan.FromSeconds(5)) { LunaLog.Log("Docking message received was detected so ignore it"); return; } var dominantProto = VesselSerializer.DeserializeVessel(msgData.FinalVesselData, msgData.NumBytes); VesselLoader.LoadVessel(dominantProto); WarpSystem.WarpIfSubspaceIsMoreAdvanced(msgData.SubspaceId); if (FlightGlobals.ActiveVessel && FlightGlobals.ActiveVessel.id == msgData.WeakVesselId) { LunaLog.Log($"Docking NOT detected. We DON'T OWN the dominant vessel. Switching to {msgData.DominantVesselId}"); if (dominantProto.vesselRef != null) { dominantProto.vesselRef.Load(); dominantProto.vesselRef.GoOffRails(); FlightGlobals.ForceSetActiveVessel(dominantProto.vesselRef); } } else if (FlightGlobals.ActiveVessel && FlightGlobals.ActiveVessel.id == msgData.DominantVesselId) { LunaLog.Log("Docking NOT detected. We OWN the dominant vessel"); } VesselRemoveSystem.Singleton.KillVessel(msgData.WeakVesselId, "Killing weak (active) vessel during a docking that was not detected"); CurrentDockEvent.Reset(); }
/// <summary> /// This method relays a message to the other clients in the same subspace. /// In case there are other players in OLDER subspaces it stores it in their queue for further processing /// </summary> public static void HandleVesselMessage(ClientStructure client, dynamic msg) { if (client.Subspace == -1) { return; } var vesselId = (Guid)msg.VesselId; var gameTime = (double)msg.GameTime; MessageQueuer.RelayMessageToSubspace <VesselSrvMsg>(client, msg); if (GeneralSettings.SettingsStore.ShowVesselsInThePast) { foreach (var subspace in WarpSystem.GetFutureSubspaces(client.Subspace)) { MessageQueuer.RelayMessageToSubspace <VesselSrvMsg>(client, msg, subspace); } } //The client is running in the future so here we adjust the real sent time of the message msg.SentTime += WarpSystem.GetSubspaceTimeDifference(client.Subspace); foreach (var subspace in WarpSystem.GetPastSubspaces(client.Subspace)) { //This is the case when a user reverted (the message received has a game time LOWER than the last message received). //To handle this, we remove all the messages that we received UNTIL this revert. if (DbCollection.Exists(x => x.VesselId == vesselId && x.GameTime > gameTime)) { DbCollection.Delete(x => x.VesselId == vesselId && x.GameTime > gameTime); DataBase.Shrink(); } DbCollection.Insert(new VesselRelayDbItem(subspace, vesselId, gameTime, msg)); } DbCollection.EnsureIndex(x => x.Id); DbCollection.EnsureIndex(x => x.SubspaceId); DbCollection.EnsureIndex(x => x.VesselId); DbCollection.EnsureIndex(x => x.GameTime); }
public static void Main() { try { Console.Title = $"LMP {LmpVersioning.CurrentVersion}"; Console.OutputEncoding = Encoding.Unicode; ServerContext.StartTime = LunaNetworkTime.UtcNow.Ticks; LunaLog.Info("Remember! Quit the server by using 'Control + C' so a backup is properly made before closing!"); if (Common.PlatformIsWindows()) { ExitSignal.Exit += (sender, args) => Exit(); } else { //Register the ctrl+c event and exit signal if we are on linux Console.CancelKeyPress += (sender, args) => Exit(); } //We disable quick edit as otherwise when you select some text for copy/paste then you can't write to the console and server freezes //This just happens on windows.... if (Common.PlatformIsWindows()) { ConsoleUtil.DisableConsoleQuickEdit(); } //We cannot run more than 6 instances ofd servers + clients as otherwise the sync time will fail (30 seconds / 5 seconds = 6) but we use 3 for safety if (GetRunningInstances() > 3) { throw new HandledException("Cannot run more than 3 servers at a time!"); } //Start the server clock ServerContext.ServerClock.Start(); ServerContext.ServerStarting = true; //Set day for log change ServerContext.Day = LunaNetworkTime.Now.Day; LunaLog.Normal($"Luna Server version: {LmpVersioning.CurrentVersion} ({Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)})"); Universe.CheckUniverse(); LoadSettingsAndGroups(); VesselStoreSystem.LoadExistingVessels(); var scenariosCreated = ScenarioSystem.GenerateDefaultScenarios(); ScenarioStoreSystem.LoadExistingScenarios(scenariosCreated); LmpPluginHandler.LoadPlugins(); WarpSystem.Reset(); LunaLog.Normal($"Starting '{GeneralSettings.SettingsStore.ServerName}' on Port {ConnectionSettings.SettingsStore.Port}... "); LidgrenServer.SetupLidgrenServer(); LmpPortMapper.OpenLmpPort().Wait(); LmpPortMapper.OpenWebPort().Wait(); ServerContext.ServerRunning = true; WebServer.StartWebServer(); //Do not add the command handler thread to the TaskContainer as it's a blocking task LongRunTaskFactory.StartNew(CommandHandler.ThreadMain, CancellationTokenSrc.Token); TaskContainer.Add(LongRunTaskFactory.StartNew(WebServer.RefreshWebServerInformation, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LmpPortMapper.RefreshUpnpPort, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LogThread.RunLogThread, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(ClientMainThread.ThreadMain, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(() => BackupSystem.PerformBackups(CancellationTokenSrc.Token), CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LidgrenServer.StartReceivingMessages, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LidgrenMasterServer.RegisterWithMasterServer, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(VersionChecker.RefreshLatestVersion, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(VersionChecker.DisplayNewVersionMsg, CancellationTokenSrc.Token)); while (ServerContext.ServerStarting) { Thread.Sleep(500); } LunaLog.Normal("All systems up and running. Поехали!"); LmpPluginHandler.FireOnServerStart(); QuitEvent.WaitOne(); LmpPluginHandler.FireOnServerStop(); LunaLog.Normal("So long and thanks for all the fish!"); } catch (Exception e) { LunaLog.Fatal(e is HandledException ? e.Message : $"Error in main server thread, Exception: {e}"); Console.ReadLine(); //Avoid closing automatically } }
public static void Main() { try { Console.Title = $"LMPServer {LmpVersioning.CurrentVersion}"; #if DEBUG Console.Title += " DEBUG"; #endif Console.OutputEncoding = Encoding.Unicode; ServerContext.StartTime = LunaTime.UtcNow.Ticks; if (!Common.PlatformIsWindows()) { LunaLog.Warning("Remember! Quit the server by using Control+C so the vessels are saved to the hard drive!"); } if (Common.PlatformIsWindows()) { ExitSignal.Exit += (sender, args) => Exit(); } else { //Register the ctrl+c event and exit signal if we are on linux Console.CancelKeyPress += (sender, args) => Exit(); } //We disable quick edit as otherwise when you select some text for copy/paste then you can't write to the console and server freezes //This just happens on windows.... if (Common.PlatformIsWindows()) { ConsoleUtil.DisableConsoleQuickEdit(); } //We cannot run more than 6 instances ofd servers + clients as otherwise the sync time will fail (30 seconds / 5 seconds = 6) but we use 3 for safety if (GetRunningInstances() > 3) { throw new HandledException("Cannot run more than 3 servers at a time!"); } //Start the server clock ServerContext.ServerClock.Start(); //Set the last player activity time to server start ServerContext.LastPlayerActivity = ServerContext.ServerClock.ElapsedMilliseconds; ServerContext.ServerStarting = true; //Set day for log change ServerContext.Day = LunaTime.Now.Day; LunaLog.Normal($"Starting Luna Server version: {LmpVersioning.CurrentVersion}"); Universe.CheckUniverse(); LoadSettingsAndGroups(); VesselStoreSystem.LoadExistingVessels(); ScenarioSystem.GenerateDefaultScenarios(); ScenarioStoreSystem.LoadExistingScenarios(); LmpPluginHandler.LoadPlugins(); WarpSystem.Reset(); LunaLog.Normal($"Starting {GeneralSettings.SettingsStore.WarpMode} server on Port {GeneralSettings.SettingsStore.Port}... "); LunaLog.Normal($"Server name: '{GeneralSettings.SettingsStore.ServerName}'..."); LunaLog.Normal($"Server location: '{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}'..."); ServerContext.ServerRunning = true; LidgrenServer.SetupLidgrenServer(); //Do not add the command handler thread to the TaskContainer as it's a blocking task LongRunTaskFactory.StartNew(() => new CommandHandler().ThreadMain(), CancellationTokenSrc.Token); TaskContainer.Add(LongRunTaskFactory.StartNew(LogThread.RunLogThread, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(() => new ClientMainThread().ThreadMain(), CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(() => BackupSystem.PerformBackups(CancellationTokenSrc.Token), CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LidgrenServer.StartReceiveingMessages, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LidgrenMasterServer.RefreshMasterServersList, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(LidgrenMasterServer.RegisterWithMasterServer, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(VesselRelaySystem.RelayOldVesselMessages, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(VersionChecker.RefreshLatestVersion, CancellationTokenSrc.Token)); TaskContainer.Add(LongRunTaskFactory.StartNew(VersionChecker.DisplayNewVersionMsg, CancellationTokenSrc.Token)); while (ServerContext.ServerStarting) { Thread.Sleep(500); } LunaLog.Normal("All systems up and running. Поехали!"); LmpPluginHandler.FireOnServerStart(); QuitEvent.WaitOne(); LmpPluginHandler.FireOnServerStop(); LunaLog.Normal("So long and thanks for all the fish!"); } catch (Exception e) { if (e is HandledException) { LunaLog.Fatal(e.Message); } else { LunaLog.Fatal($"Error in main server thread, Exception: {e}"); } Console.ReadLine(); //Avoid closing automatically } }
public static void Main() { try { Console.Title = $"LMPServer {LmpVersioning.CurrentVersion}"; #if DEBUG Console.Title += " DEBUG"; #endif Console.OutputEncoding = Encoding.Unicode; ServerContext.StartTime = LunaTime.UtcNow.Ticks; //We disable quick edit as otherwise when you select some text for copy/paste then you can't write to the console and server freezes //This just happens on windows.... if (Common.PlatformIsWindows()) { ConsoleUtil.DisableConsoleQuickEdit(); } //We cannot run more than 6 instances ofd servers + clients as otherwise the sync time will fail (30 seconds / 5 seconds = 6) but we use 3 for safety if (GetRunningInstances() > 3) { throw new HandledException("Cannot run more than 3 servers at a time!"); } //Start the server clock ServerContext.ServerClock.Start(); //Set the last player activity time to server start ServerContext.LastPlayerActivity = ServerContext.ServerClock.ElapsedMilliseconds; //Register the ctrl+c event Console.CancelKeyPress += CatchExit; ServerContext.ServerStarting = true; //Set day for log change ServerContext.Day = LunaTime.Now.Day; LunaLog.Normal($"Starting Luna Server version: {LmpVersioning.CurrentVersion}"); Universe.CheckUniverse(); LoadSettingsAndGroups(); LmpPluginHandler.LoadPlugins(); WarpSystem.Reset(); ChatSystem.Reset(); LunaLog.Normal($"Starting {GeneralSettings.SettingsStore.WarpMode} server on Port {GeneralSettings.SettingsStore.Port}... "); ServerContext.ServerRunning = true; ServerContext.LidgrenServer.SetupLidgrenServer(); Task.Run(() => new CommandHandler().ThreadMain()); Task.Run(() => new ClientMainThread().ThreadMain()); Task.Run(() => ServerContext.LidgrenServer.StartReceiveingMessages()); Task.Run(() => ServerContext.LidgrenServer.RefreshMasterServersList()); Task.Run(() => ServerContext.LidgrenServer.RegisterWithMasterServer()); Task.Run(() => LogThread.RunLogThread()); Task.Run(() => VesselRelaySystem.RelayOldVesselMessages()); Task.Run(() => VesselUpdateRelaySystem.RelayToFarPlayers()); Task.Run(() => VesselUpdateRelaySystem.RelayToMediumDistancePlayers()); Task.Run(() => VesselUpdateRelaySystem.RelayToClosePlayers()); Task.Run(() => VersionChecker.CheckForNewVersions()); while (ServerContext.ServerStarting) { Thread.Sleep(500); } LunaLog.Normal("All systems up and running. Поехали!"); LmpPluginHandler.FireOnServerStart(); QuitEvent.WaitOne(); WarpSystem.SaveSubspacesToFile(); LmpPluginHandler.FireOnServerStop(); LunaLog.Normal("Goodbye and thanks for all the fish!"); } catch (Exception e) { if (e is HandledException) { LunaLog.Fatal(e.Message); } else { LunaLog.Fatal($"Error in main server thread, Exception: {e}"); } Console.ReadLine(); //Avoid closing automatically } }
public void HandleMessage(IServerMessageBase msg) { if (!(msg.Data is VesselDockMsgData msgData)) { return; } LunaLog.Log("Docking message received!"); //Add the new vessel data to the store VesselsProtoStore.HandleVesselProtoData(msgData.FinalVesselData, msgData.NumBytes, msgData.DominantVesselId, false); if (FlightGlobals.ActiveVessel?.id == msgData.WeakVesselId) { LunaLog.Log($"Docking NOT detected. We DON'T OWN the dominant vessel. Switching to {msgData.DominantVesselId}"); VesselRemoveSystem.Singleton.AddToKillList(FlightGlobals.ActiveVessel.id, "Killing weak (active) vessel during a docking that was not detected"); VesselSwitcherSystem.Singleton.SwitchToVessel(msgData.DominantVesselId); WarpSystem.WarpIfSubspaceIsMoreAdvanced(msgData.SubspaceId); } if (FlightGlobals.ActiveVessel?.id == msgData.DominantVesselId && !VesselCommon.IsSpectating) { var newProto = VesselSerializer.DeserializeVessel(msgData.FinalVesselData, msgData.NumBytes); /* This is a strange case were we didn't detect the docking on time and the weak vessel send the new definition... * Usually it happens when a vessel in a future subspace docks with a vessel in the past and the vessel in the past is the dominant vessel * The reason why is bad is because the ModuleDockingNode sent by the WEAK vessel will tell us that we are * NOT the dominant (because we received the vesselproto from the WEAK vessel) so we won't be able to undock properly... * This will be fixed if we go to the space center and reload again the vessel... */ LunaLog.Log("Docking NOT detected. We OWN the dominant vessel"); //This is the case when the user that docked with us has invalid parts if (VesselCommon.ProtoVesselHasInvalidParts(newProto)) { return; } if (FlightGlobals.FindVessel(msgData.WeakVesselId) != null) { LunaLog.Log($"Weak vessel {msgData.WeakVesselId} still exists in our game. Removing it now"); VesselRemoveSystem.Singleton.AddToKillList(msgData.WeakVesselId, "Killing weak vessel during a docking that was not detected"); VesselRemoveSystem.Singleton.KillVessel(msgData.WeakVesselId, "Weak vessel in a docking"); } /* We own the dominant vessel and dind't detected the docking event so we need to reload our OWN vessel * so if we send our own protovessel later, we send the updated definition */ LunaLog.Log($"Creating the missing parts in our own vessel. Current: {FlightGlobals.ActiveVessel.parts.Count} Expected: {newProto.protoPartSnapshots.Count}"); //ProtoToVesselRefresh.CreateMissingPartsInCurrentProtoVessel(FlightGlobals.ActiveVessel, newProto); VesselLoader.ReloadVessel(newProto); LunaLog.Log("Force sending the new proto vessel"); VesselProtoSystem.Singleton.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel, true, false); WarpSystem.WarpIfSubspaceIsMoreAdvanced(msgData.SubspaceId); return; } //Some other 2 players docked so just remove the weak vessel. VesselRemoveSystem.Singleton.AddToKillList(msgData.WeakVesselId, "Killing weak vessel during a docking of 2 far players"); }