/// <summary> /// This event is called after a game is loaded. We use it to detect if the player has done a revert /// </summary> public void OnGameStatePostLoad(ConfigNode data) { if (FlightGlobals.ActiveVessel != null && !VesselCommon.IsSpectating) { LunaLog.Log("[LMP]: Detected a revert!"); var vesselIdsToRemove = FlightGlobals.Vessels .Where(v => v.rootPart?.missionID == FlightGlobals.ActiveVessel.rootPart.missionID && v.id != FlightGlobals.ActiveVessel.id) .Select(v => v.id).Distinct(); //We detected a revert, now pick all the vessel parts (debris) that came from our main active //vessel and remove them both from our game and server foreach (var vesselIdToRemove in vesselIdsToRemove) { System.MessageSender.SendVesselRemove(vesselIdToRemove); System.AddToKillList(vesselIdToRemove); } //Store it here so the delayed routine can access it! var activeVesselId = FlightGlobals.ActiveVessel.id; //Now tell the server to remove our old vessel CoroutineUtil.StartDelayedRoutine("SendProperVesselRemoveMsg", () => { //We delay the send vessel remove to wait until the proper scene is loaded. //In case we revert to editor we must fully delete that vessel as when flying again we will get a new ID. //Otherwise we say to not keep it in the vessels remove list as perhaps we are reverting to flight and then our vessel id will stay the same. //If we set the keepvesselinremovelist to true then the server will ignore every change we do to our vessel! System.MessageSender.SendVesselRemove(activeVesselId, HighLogic.LoadedSceneIsEditor); if (HighLogic.LoadedSceneIsEditor) { System.AddToKillList(activeVesselId); } }, 3); } }
/// <summary> /// Checks and sends if we took a screenshot /// </summary> public void CheckScreenshots() { if (GameSettings.TAKE_SCREENSHOT.GetKeyDown()) { if (TimeUtil.IsInInterval(ref _lastTakenScreenshot, SettingsSystem.ServerSettings.MinScreenshotIntervalMs)) { var path = CommonUtil.CombinePaths(MainSystem.KspPath, "Screenshots"); CoroutineUtil.StartDelayedRoutine(nameof(CheckScreenshots), () => { var photo = new DirectoryInfo(path).GetFiles().OrderByDescending(f => f.LastWriteTime).FirstOrDefault(); if (photo != null) { var imageData = ScaleScreenshot(File.ReadAllBytes(photo.FullName), 800, 600); TaskFactory.StartNew(() => { MessageSender.SendScreenshot(imageData); }); LunaScreenMsg.PostScreenMessage(LocalizationContainer.ScreenText.ScreenshotTaken, 10f, ScreenMessageStyle.UPPER_CENTER); } }, 0.3f); } else { var msg = LocalizationContainer.ScreenText.ScreenshotInterval.Replace("$1", TimeSpan.FromMilliseconds(SettingsSystem.ServerSettings.MinScreenshotIntervalMs).TotalSeconds .ToString(CultureInfo.InvariantCulture)); LunaScreenMsg.PostScreenMessage(msg, 20f, ScreenMessageStyle.UPPER_CENTER); } } }
/// <summary> /// Called when a vessel is initiated. /// </summary> public void VesselInitialized(Vessel vessel, bool fromShipAssembly) { if (vessel == null) { return; } //The vessel is being created by the loader if (VesselLoader.CurrentlyLoadingVesselId == vessel.id || fromShipAssembly) { return; } //This happens when the vessel you're spectating crashes if (VesselCommon.IsSpectating) { VesselRemoveSystem.Singleton.KillVessel(vessel.id, true, "Tried to create a new vessel while spectating"); return; } //It's a debris vessel that we made it if (!LockSystem.LockQuery.UnloadedUpdateLockExists(vessel.id)) { //We delay it a bit because we must wait until the vessel is named correctly and so on. CoroutineUtil.StartDelayedRoutine("VesselInitialized", () => System.MessageSender.SendVesselMessage(vessel), 0.5f); LockSystem.Singleton.AcquireUnloadedUpdateLock(vessel.id, true); } }
/// <summary> /// Sends our vessel just when we start the flight /// </summary> public void FlightReady() { if (!VesselCommon.IsSpectating && FlightGlobals.ActiveVessel != null) { if (!System.CheckVessel(FlightGlobals.ActiveVessel)) { VesselRemoveSystem.Singleton.AddToKillList(FlightGlobals.ActiveVessel.id, "Vessel check not passed"); VesselRemoveSystem.Singleton.KillVessel(FlightGlobals.ActiveVessel.id, "Vessel check not passed"); return; } CoroutineUtil.StartDelayedRoutine(nameof(FlightReady), () => { if (VesselCommon.IsSpectating || FlightGlobals.ActiveVessel == null || FlightGlobals.ActiveVessel.id == Guid.Empty) { return; } System.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel, true, false); }, 5f); //Only show safety bubble text if safety bubble is active and player is spawning a new vessel if (SettingsSystem.ServerSettings.SafetyBubbleDistance > 0 && FlightGlobals.ActiveVessel.vesselSpawning) { LunaScreenMsg.PostScreenMessage(LocalizationContainer.ScreenText.SafetyBubble, 10f, ScreenMessageStyle.UPPER_CENTER); } } }
/// <summary> /// Triggered when the vessel parts change. /// CAUTION!: When staging this method can be called a lot of times! /// Also, this method is called when docking/undocking and when reloading a vessel /// </summary> public void VesselPartCountChanged(Vessel vessel) { if (vessel == null) { return; } //The vessel is being created by the loader if (VesselLoader.CurrentlyLoadingVesselId == vessel.id) { return; } //When a vessel docks it's part count changes but we must not relay it as it's handled by the vessel dock system if (CurrentDockEvent.DominantVesselId == vessel.id || CurrentUndockEvent.UndockingVesselId == vessel.id) { return; } //This event is called when the vessel is being created and we don't want to send protos of vessels we don't own or while our vessel is not 100% loaded (FlightReady) if (vessel.vesselSpawning) { return; } //Vessel is scheduled to be killed so ignore this if (VesselRemoveSystem.Singleton.VesselWillBeKilled(vessel.id)) { return; } //We are spectating and the vessel has been modified so trigger a reload if (VesselCommon.IsSpectating && FlightGlobals.ActiveVessel && FlightGlobals.ActiveVessel.id == vessel.id && vessel.protoVessel.protoPartSnapshots.Count != FlightGlobals.ActiveVessel.Parts.Count) { VesselLoader.LoadVessel(vessel.protoVessel); return; } if (!LockSystem.LockQuery.UpdateLockExists(vessel.id)) { LockSystem.Singleton.AcquireUpdateLock(vessel.id, true); LockSystem.Singleton.AcquireUnloadedUpdateLock(vessel.id, true); VesselProtoSystem.Singleton.MessageSender.SendVesselMessage(vessel); } if (LockSystem.LockQuery.UpdateLockBelongsToPlayer(vessel.id, SettingsSystem.CurrentSettings.PlayerName)) { //This method can be called a lot of times during staging (for every part that decouples) //For this reason we wait 0.5 seconds so we send all the changes at once. if (QueuedVessels.Contains(vessel.id)) { return; } QueuedVessels.Add(vessel.id); CoroutineUtil.StartDelayedRoutine("QueueVesselMessageAsPartsChanged", () => QueueNewVesselChange(vessel), 0.5f); VesselProtoSystem.Singleton.MessageSender.SendVesselMessage(vessel); } }
/// <summary> /// Kills a vessel. /// </summary> public void DelayedKillVessel(Guid vesselId, bool addToKilledList, string reason, int delayInMs) { CoroutineUtil.StartDelayedRoutine("DelayedKillVessel", () => { LunaLog.Log($"Delayed attempt to kill vessel {vesselId}"); KillVessel(vesselId, addToKilledList, reason); }, (float)TimeSpan.FromMilliseconds(delayInMs).TotalSeconds); }
/// <summary> /// This coroutine removes the vessels when switching to the KSC. We delay the removal of the vessels so /// in case we recover a vessel while in flight we correctly recover the crew, funds etc /// </summary> private static void DelayedClearVessels() { CoroutineUtil.StartDelayedRoutine(nameof(DelayedClearVessels), () => { FlightGlobals.Vessels.Clear(); HighLogic.CurrentGame?.flightState?.protoVessels?.Clear(); }, 3); }
/// <summary> /// Release the given kerbal lock /// </summary> public void ReleaseKerbalLock(string kerbalName, float delayInSec) { if (delayInSec > 0) { CoroutineUtil.StartDelayedRoutine("ReleaseKerbalLock", () => ReleaseLock(new LockDefinition(LockType.Kerbal, SettingsSystem.CurrentSettings.PlayerName, kerbalName)), delayInSec); } else { ReleaseLock(new LockDefinition(LockType.Kerbal, SettingsSystem.CurrentSettings.PlayerName, kerbalName)); } }
/// <summary> /// Release all the locks (unloaded update, update, control and kerbals) of a vessel /// </summary> public void ReleaseAllVesselLocks(IEnumerable <string> crewNames, Guid vesselId, float delayInSec = 0) { if (delayInSec > 0) { CoroutineUtil.StartDelayedRoutine("ReleaseAllVesselLocks", () => ReleaseAllVesselLocksImpl(crewNames, vesselId), delayInSec); } else { ReleaseAllVesselLocksImpl(crewNames, vesselId); } }
/// <summary> /// Sends a delayed vessel definition to the server. /// Call this method if you expect to do a lot of modifications to a vessel and you want to send it only once /// </summary> public void DelayedSendVesselMessage(Guid vesselId, float delayInSec, bool forceReload = false) { if (QueuedVesselsToSend.Contains(vesselId)) { return; } QueuedVesselsToSend.Add(vesselId); CoroutineUtil.StartDelayedRoutine("QueueVesselMessageAsPartsChanged", () => { QueuedVesselsToSend.Remove(vesselId); LunaLog.Log($"[LMP]: Sending delayed proto vessel {vesselId}"); MessageSender.SendVesselMessage(FlightGlobals.FindVessel(vesselId)); }, delayInSec); }
/// <summary> /// Sends our vessel just when we start the flight /// </summary> public void FlightReady() { if (!VesselCommon.IsSpectating && FlightGlobals.ActiveVessel != null) { CoroutineUtil.StartDelayedRoutine(nameof(FlightReady), () => { if (VesselCommon.IsSpectating || FlightGlobals.ActiveVessel == null || FlightGlobals.ActiveVessel.id == Guid.Empty) { return; } System.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel, true); }, 5f); ScreenMessages.PostScreenMessage(LocalizationContainer.ScreenText.SafetyBubble, 10f, ScreenMessageStyle.UPPER_CENTER); } }
/// <summary> /// Sends our vessel just when we start the flight /// </summary> public void FlightReady() { if (!VesselCommon.IsSpectating && FlightGlobals.ActiveVessel != null) { CoroutineUtil.StartDelayedRoutine(nameof(FlightReady), () => { if (VesselCommon.IsSpectating || FlightGlobals.ActiveVessel == null || FlightGlobals.ActiveVessel.id == Guid.Empty) { return; } System.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel, true); }, 5f); ScreenMessages.PostScreenMessage("Remember!! While you're inside the safety bubble you won't see vessels that are close to you!!", 10f, ScreenMessageStyle.UPPER_CENTER); } }
public void LevelLoaded(GameScenes data) { if (data == GameScenes.SPACECENTER) { System.ClearVesselMarkers?.Invoke(KSCVesselMarkers.fetch, null); //Delay it to have time to recover the vessel, crew and funds CoroutineUtil.StartDelayedRoutine("ClearVesselsInKsc", () => { HighLogic.CurrentGame?.flightState?.protoVessels?.Clear(); if (KSCVesselMarkers.fetch != null) { System.ClearVesselMarkers?.Invoke(KSCVesselMarkers.fetch, null); } }, 3); } }
/// <summary> /// Sends our vessel just when we start the flight /// </summary> public void FlightReady() { if (!VesselCommon.IsSpectating && FlightGlobals.ActiveVessel != null) { CoroutineUtil.StartDelayedRoutine(nameof(FlightReady), () => { if (VesselCommon.IsSpectating || FlightGlobals.ActiveVessel == null || FlightGlobals.ActiveVessel.id == Guid.Empty) { return; } System.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel, true); //Add our own vessel to the dictionary aswell VesselsProtoStore.AddVesselToDictionary(FlightGlobals.ActiveVessel); }, 5f); ScreenMessages.PostScreenMessage("Remember!! While you're inside the safety bubble you won't see other players!!", 10f, ScreenMessageStyle.UPPER_CENTER); } }
public void FlightReady() { //Only show safety bubble text if safety bubble is active and player is spawning a new vessel if (VesselCommon.IsSpectating || FlightGlobals.ActiveVessel == null || !FlightGlobals.ActiveVessel.vesselSpawning || SettingsSystem.ServerSettings.SafetyBubbleDistance <= 0) { return; } if (System.IsInSafetyBubble(FlightGlobals.ActiveVessel) && FlightGlobals.ActiveVessel.situation == Vessel.Situations.PRELAUNCH) { System.DrawSafetyBubble(); } if (FlightGlobals.ActiveVessel.vesselSpawning) { LunaScreenMsg.PostScreenMessage(LocalizationContainer.ScreenText.SafetyBubble, 10f, ScreenMessageStyle.UPPER_CENTER); CoroutineUtil.StartDelayedRoutine(nameof(SafetyBubbleEvents), () => LunaScreenMsg.PostScreenMessage(LocalizationContainer.ScreenText.CheckParts, 15f, ScreenMessageStyle.UPPER_CENTER, Color.red), 25f); } }
public void OnDockingComplete(GameEvents.FromToAction <Part, Part> data) { LunaLog.Log(_ownDominantVessel ? $"[LMP]: Docking finished! We own the dominant vessel {CurrentDockEvent.DominantVesselId}" : $"[LMP]: Docking finished! We DON'T own the dominant vessel {CurrentDockEvent.DominantVesselId}"); JumpIfVesselOwnerIsInFuture(CurrentDockEvent.DominantVesselId); if (_ownDominantVessel) { System.MessageSender.SendDockInformation(CurrentDockEvent.WeakVesselId, FlightGlobals.ActiveVessel, WarpSystem.Singleton.CurrentSubspace); VesselProtoSystem.Singleton.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel); } else { CoroutineUtil.StartDelayedRoutine("OnDockingComplete", () => System.MessageSender.SendDockInformation(CurrentDockEvent.WeakVesselId, FlightGlobals.ActiveVessel, WarpSystem.Singleton.CurrentSubspace), 3); } VesselRemoveSystem.Singleton.MessageSender.SendVesselRemove(CurrentDockEvent.WeakVesselId, false); }
public void EVAConstructionModePartDetached(Vessel vessel, Part part) { if (VesselCommon.IsSpectating) { return; } System.MessageSender.SendVesselMessage(vessel); _detachedPart = part; CoroutineUtil.StartDelayedRoutine("SendDetachedPartAsVessel", () => { var newVessel = FlightGlobals.VesselsLoaded.FirstOrDefault(v => v.Parts.Contains(_detachedPart)); if (newVessel != null) { LockSystem.Singleton.AcquireUnloadedUpdateLock(newVessel.id, true, true); LockSystem.Singleton.AcquireUpdateLock(newVessel.id, true, true); System.MessageSender.SendVesselMessage(newVessel); } }, 0.50f); }
/// <summary> /// Sends our vessel just when we start the flight /// </summary> public void FlightReady() { if (!VesselCommon.IsSpectating && FlightGlobals.ActiveVessel != null) { if (!System.CheckVessel(FlightGlobals.ActiveVessel)) { VesselRemoveSystem.Singleton.AddToKillList(FlightGlobals.ActiveVessel.id); VesselRemoveSystem.Singleton.KillVessel(FlightGlobals.ActiveVessel.id); return; } CoroutineUtil.StartDelayedRoutine(nameof(FlightReady), () => { if (VesselCommon.IsSpectating || FlightGlobals.ActiveVessel == null || FlightGlobals.ActiveVessel.id == Guid.Empty) { return; } System.MessageSender.SendVesselMessage(FlightGlobals.ActiveVessel, true); }, 5f); LunaScreenMsg.PostScreenMessage(LocalizationContainer.ScreenText.SafetyBubble, 10f, ScreenMessageStyle.UPPER_CENTER); } }
/// <summary> /// Loads the vessel proto into the current game /// </summary> private static bool LoadVesselIntoGame(ProtoVessel vesselProto, bool forceReload) { if (HighLogic.CurrentGame?.flightState == null) { return(false); } var reloadingOwnVessel = FlightGlobals.ActiveVessel && vesselProto.vesselID == FlightGlobals.ActiveVessel.id; //In case the vessel exists, silently remove them from unity and recreate it again var existingVessel = FlightGlobals.FindVessel(vesselProto.vesselID); if (existingVessel != null) { if (existingVessel.Parts.Count == vesselProto.protoPartSnapshots.Count && !forceReload) { return(true); } LunaLog.Log($"[LMP]: Reloading vessel {vesselProto.vesselID}"); if (reloadingOwnVessel) { existingVessel.RemoveAllCrew(); } FlightGlobals.RemoveVessel(existingVessel); foreach (var part in existingVessel.parts) { Object.Destroy(part.gameObject); } Object.Destroy(existingVessel.gameObject); } else { LunaLog.Log($"[LMP]: Loading vessel {vesselProto.vesselID}"); } vesselProto.Load(HighLogic.CurrentGame.flightState); if (vesselProto.vesselRef == null) { LunaLog.Log($"[LMP]: Protovessel {vesselProto.vesselID} failed to create a vessel!"); return(false); } VesselPositionSystem.Singleton.ForceUpdateVesselPosition(vesselProto.vesselRef.id); vesselProto.vesselRef.protoVessel = vesselProto; if (vesselProto.vesselRef.isEVA) { var evaModule = vesselProto.vesselRef.FindPartModuleImplementing <KerbalEVA>(); if (evaModule != null && evaModule.fsm != null && !evaModule.fsm.Started) { evaModule.fsm?.StartFSM("Idle (Grounded)"); } vesselProto.vesselRef.GoOnRails(); } if (vesselProto.vesselRef.situation > Vessel.Situations.PRELAUNCH) { vesselProto.vesselRef.orbitDriver.updateFromParameters(); } if (double.IsNaN(vesselProto.vesselRef.orbitDriver.pos.x)) { LunaLog.Log($"[LMP]: Protovessel {vesselProto.vesselID} has an invalid orbit"); return(false); } if (reloadingOwnVessel) { vesselProto.vesselRef.Load(); vesselProto.vesselRef.RebuildCrewList(); //Do not do the setting of the active vessel manually, too many systems are dependant of the events triggered by KSP FlightGlobals.ForceSetActiveVessel(vesselProto.vesselRef); vesselProto.vesselRef.SpawnCrew(); foreach (var crew in vesselProto.vesselRef.GetVesselCrew()) { if (crew.KerbalRef) { crew.KerbalRef.state = Kerbal.States.ALIVE; } } CoroutineUtil.StartDelayedRoutine("ReloadOwnVessel", () => { if (KerbalPortraitGallery.Instance.ActiveCrew.Count == 0) { FlightGlobals.ActiveVessel.SpawnCrew(); foreach (var kerbal in KerbalPortraitGallery.Instance.ActiveCrew) { kerbal.state = Kerbal.States.ALIVE; } KerbalPortraitGallery.Instance.StartRefresh(FlightGlobals.ActiveVessel); } }, 0.5f); } return(true); }