public override void OnLoad(ConfigNode node) { try { FlightRecorder.LoadRecordings(node); MissionController.LoadMissions(node); GUI.Reset(); } catch (Exception e) { Debug.LogError("[KSTS] OnLoad(): " + e.ToString()); } }
public static void LoadRecordings(ConfigNode node) { FlightRecorder.flightRecordings.Clear(); ConfigNode flightRecorderNode = node.GetNode("FlightRecorder"); if (flightRecorderNode == null) { return; } foreach (ConfigNode flightRecordingNode in flightRecorderNode.GetNodes()) { FlightRecorder.flightRecordings.Add(flightRecordingNode.name, FlightRecording.CreateFromConfigNode(flightRecordingNode)); } FlightRecorder.CollectGarbage(); // Might not work as expected in KSP 1.2, so we added this also to the timer-function. }
public void Timer() { try { // Don't update while not in game: if (HighLogic.LoadedScene == GameScenes.MAINMENU || HighLogic.LoadedScene == GameScenes.CREDITS || HighLogic.LoadedScene == GameScenes.SETTINGS) { return; } // Call all background-jobs: FlightRecorder.Timer(); MissionController.Timer(); } catch (Exception e) { Debug.LogError("[KSTS] Timer(): " + e.ToString()); } }
public override void OnLoad(ConfigNode node) { Log.Warning("KSTS: OnLoad"); try { FlightRecorder.LoadRecordings(node); MissionController.LoadMissions(node); if (node.HasValue("useKACifAvailable")) { MissionController.useKACifAvailable = bool.Parse(node.GetValue("useKACifAvailable")); } if (node.HasValue("useStockAlarmClock")) { MissionController.useStockAlarmClock = bool.Parse(node.GetValue("useStockAlarmClock")); } GUI.Reset(); } catch (Exception e) { Debug.LogError("OnLoad(): " + e.ToString()); } }
// Is called every second and keeps track of used parts during a flight-recording: public static void Timer() { try { // Maybe remove old, invalid running recordings: FlightRecorder.CollectGarbage(); // Check if we are on an vessel which is recording a flight: Vessel vessel = FlightGlobals.ActiveVessel; if (!vessel) { return; } FlightRecording recording = GetFlightRecording(vessel); if (recording == null) { return; } if (vessel.id.ToString() != FlightRecorder.timerVesselId) { // The vessel has changed, reset all variables from the last timer-tick: FlightRecorder.timerVesselId = vessel.id.ToString(); FlightRecorder.timerPartResources.Clear(); } // Check all parts, if something has changed which makes the part unusable for payload-deployments: foreach (Part part in vessel.parts) { if (recording.usedPartIds.Contains(part.flightID.ToString())) { continue; // Already blocked } bool blockThis = false; string partId = part.flightID.ToString(); // Check for running engines: foreach (ModuleEngines engineModule in part.FindModulesImplementing <ModuleEngines>()) { if (engineModule.GetCurrentThrust() > 0) { blockThis = true; } } foreach (ModuleEnginesFX engineModule in part.FindModulesImplementing <ModuleEnginesFX>()) { if (engineModule.GetCurrentThrust() > 0) { blockThis = true; } } // Check for resource-consumption: foreach (PartResource resource in part.Resources) { PartResourceDefinition resourceDefinition = null; string resourceId = resource.resourceName.ToString(); if (!KSTS.resourceDictionary.TryGetValue(resourceId, out resourceDefinition)) { continue; } if (resourceDefinition.density <= 0) { continue; // We only care about resources with mass, skipping electricity and such. } if (!FlightRecorder.timerPartResources.ContainsKey(partId)) { FlightRecorder.timerPartResources.Add(partId, new Dictionary <string, double>()); } double lastAmount; if (!FlightRecorder.timerPartResources[partId].TryGetValue(resourceId, out lastAmount)) { FlightRecorder.timerPartResources[partId].Add(resourceId, resource.amount); } else { if (lastAmount != resource.amount) { blockThis = true; // The amount has changed relative to the last timer-tick. } } } if (blockThis) { Debug.Log("[KSTS] marking part " + part.name.ToString() + " (" + part.flightID.ToString() + ") as used"); recording.usedPartIds.Add(part.flightID.ToString()); } } } catch (Exception e) { Debug.LogError("[KSTS] FlightRecoorder.Timer(): " + e.ToString()); } }
public override void OnSave(ConfigNode node) { try { /* * When transporting an available kerbal to a ship or recovering one from orbit, we manipulate the (unloaded) vessel's crew * as well as the roster-status of the kerbal. This is apparently not expected by KSP's core functionality, because there * seems to be a secret list of roster-status which is enforced when the game is safed: * * [WRN 15:16:14.678] [ProtoCrewMember Warning]: Crewmember Sierina Kerman found inside a part but status is set as missing. Vessel must have failed to save earlier. Restoring assigned status. * [WRN 15:17:42.913] [ProtoCrewMember Warning]: Crewmember Sierina Kerman found assigned but no vessels reference him. Sierina Kerman set as missing. * * Afterwards these kerbals would be lost for the player, which is why we have to use the workaround below to revert these * changes and make sure each kerbal has the correct status. This effectively disables the "missing" status as these kerbals * will always respawn, but I haven't seen a valid use-case for this thus far, so it is probably fine. */ if (HighLogic.CurrentGame.CrewRoster.Count > 0 && FlightGlobals.Vessels.Count > 0) { // Build a list of all Kerbals which are assigned to vessels: List <string> vesselCrewNames = new List <string>(); foreach (Vessel vessel in FlightGlobals.Vessels) { foreach (ProtoCrewMember crewMember in TargetVessel.GetCrew(vessel)) { vesselCrewNames.Add(crewMember.name); } } // Build a list of all kerbals which we could have manipulated: List <ProtoCrewMember> kerbals = new List <ProtoCrewMember>(); foreach (ProtoCrewMember kerbal in HighLogic.CurrentGame.CrewRoster.Kerbals(ProtoCrewMember.KerbalType.Crew)) { kerbals.Add(kerbal); } foreach (ProtoCrewMember kerbal in HighLogic.CurrentGame.CrewRoster.Kerbals(ProtoCrewMember.KerbalType.Tourist)) { kerbals.Add(kerbal); } // Check those kerbals against our vessel-list and maybe restore their correct status: foreach (ProtoCrewMember kerbal in kerbals) { if (kerbal.rosterStatus == ProtoCrewMember.RosterStatus.Dead) { continue; } if (vesselCrewNames.Contains(kerbal.name) && kerbal.rosterStatus != ProtoCrewMember.RosterStatus.Assigned) { Debug.Log("[KSTS] setting kerbal " + kerbal.name + " from " + kerbal.rosterStatus.ToString() + " to Assigned (see code for more info)"); kerbal.rosterStatus = ProtoCrewMember.RosterStatus.Assigned; } else if (!vesselCrewNames.Contains(kerbal.name) && kerbal.rosterStatus != ProtoCrewMember.RosterStatus.Available) { Debug.Log("[KSTS] setting kerbal " + kerbal.name + " from " + kerbal.rosterStatus.ToString() + " to Available (see code for more info)"); kerbal.rosterStatus = ProtoCrewMember.RosterStatus.Available; } } } FlightRecorder.SaveRecordings(node); MissionController.SaveMissions(node); } catch (Exception e) { Debug.LogError("[KSTS] OnSave(): " + e.ToString()); } }
// Is called when this Addon is first loaded to initializes all values (eg registration of event-handlers and creation // of original-stats library). public void Awake() { try { FlightRecorder.Initialize(); MissionController.Initialize(); // Build dictionary of all parts for easier access: if (KSTS.partDictionary == null) { KSTS.partDictionary = new Dictionary <string, AvailablePart>(); foreach (AvailablePart part in PartLoader.LoadedPartsList) { if (KSTS.partDictionary.ContainsKey(part.name.ToString())) { Debug.LogError("[KSTS] duplicate part-name '" + part.name.ToString() + "'"); continue; } KSTS.partDictionary.Add(part.name.ToString(), part); } } // Build a dictionay of all resources for easier access: if (KSTS.resourceDictionary == null) { KSTS.resourceDictionary = new Dictionary <string, PartResourceDefinition>(); foreach (PartResourceDefinition resourceDefinition in PartResourceLibrary.Instance.resourceDefinitions) { KSTS.resourceDictionary.Add(resourceDefinition.name.ToString(), resourceDefinition); } } // Invoke the timer-function every second to run background-code: if (!IsInvoking("Timer")) { InvokeRepeating("Timer", 1, 1); } parentDictionary = new Dictionary <string, string>(); if (StageRecoveryAPI.StageRecoveryAvailable) { StageRecoveryAPI.AddRecoverySuccessEvent((vessel, array, str) => { if (StageRecoveryAPI.StageRecoveryEnabled) { StageRecovered?.Invoke(this, new StageRecoveredEventArgs { Vessel = vessel, FundsRecovered = array[1] }); } }); GameEvents.onStageSeparation.Add(new EventData <EventReport> .OnEvent(this.onStageSeparation)); GameEvents.onVesselWasModified.Add(new EventData <Vessel> .OnEvent(this.onVesselModified)); } // Execute the following code only once: if (KSTS.initialized) { return; } DontDestroyOnLoad(this); KSTS.initialized = true; } catch (Exception e) { Debug.LogError("[KSTS] Awake(): " + e.ToString()); } }
public static void Display() { if (!initialized) { Initialize(); } Vessel vessel = FlightGlobals.ActiveVessel; FlightRecording recording = null; if (vessel) { recording = FlightRecorder.GetFlightRecording(vessel); } if (!vessel || recording == null) { Reset(); // Show list of recorded profiles: missionProfileSelector.DisplayList(); if (missionProfileSelector.selectedProfile != null) { if (missionProfileSelector.selectedProfile != lastSelectedProfile) { // The selecte profile was switched: lastSelectedProfile = missionProfileSelector.selectedProfile; newMissionProfileName = missionProfileSelector.selectedProfile.profileName; } GUILayout.BeginHorizontal(); GUILayout.Label("<size=14><b>Profile name:</b></size>", new GUIStyle(GUI.labelStyle) { stretchWidth = false }); newMissionProfileName = GUILayout.TextField(newMissionProfileName, new GUIStyle(GUI.textFieldStyle) { stretchWidth = true }); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button("Save Profile", GUI.buttonStyle)) { MissionController.ChangeMissionProfileName(missionProfileSelector.selectedProfile.profileName, newMissionProfileName); missionProfileSelector = new GUIMissionProfileSelector(); // Deselect & Reset } if (GUILayout.Button("Delete Profile", GUI.buttonStyle)) { MissionController.DeleteMissionProfile(missionProfileSelector.selectedProfile.profileName); missionProfileSelector = new GUIMissionProfileSelector(); // Deselect & Reset } GUILayout.EndHorizontal(); } } else { // During the recording, allow the player to change the name of the new flight-profile: GUILayout.BeginHorizontal(); GUILayout.Label("<size=14><b>Profile name:</b></size>", new GUIStyle(GUI.labelStyle) { stretchWidth = false }); if (recording.status != FlightRecordingStatus.PRELAUNCH) { recording.profileName = GUILayout.TextField(recording.profileName, new GUIStyle(GUI.textFieldStyle) { stretchWidth = true }); } else { GUILayout.Label(recording.profileName, new GUIStyle(GUI.labelStyle) { stretchWidth = true }); } GUILayout.EndHorizontal(); // Display all Information about the current recording: GUILayout.BeginScrollView(new Vector2(0, 0), new GUIStyle(GUI.scrollStyle) { stretchHeight = true }); List <KeyValuePair <string, string> > displayAttributes = recording.GetDisplayAttributes(); foreach (KeyValuePair <string, string> displayAttribute in displayAttributes) { GUILayout.BeginHorizontal(); GUILayout.Label("<b>" + displayAttribute.Key + "</b>"); GUILayout.Label(displayAttribute.Value + " ", new GUIStyle(GUI.labelStyle) { alignment = TextAnchor.MiddleRight }); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); // Display payload selector: if (recording.status == FlightRecordingStatus.ASCENDING || recording.status == FlightRecordingStatus.PRELAUNCH) { GUILayout.BeginHorizontal(); GUILayout.Label("<size=14><b>Mission Type:</b></size>"); string[] missionTypeStrings = new string[] { "Deploy", "Transport" }; selectedMissionTypeTab = GUILayout.Toolbar(selectedMissionTypeTab, missionTypeStrings); GUILayout.EndHorizontal(); scrollPos = GUILayout.BeginScrollView(scrollPos, GUI.scrollStyle, GUILayout.Height(210), GUILayout.MaxHeight(210)); if (selectedMissionTypeTab == 0) { // Show all deployable payloads: if (!recording.CanPerformMission(MissionProfileType.DEPLOY)) { GUILayout.Label("Deployment missions can only be performed if the vessel has detachable parts which haven't been used during the flight (no resource consumption, inactive, uncrewed, etc)."); } else { // Show all detachable subassemblies: bool selectionChanged = false; foreach (PayloadAssembly payloadAssembly in recording.GetPayloadAssemblies()) { GUILayout.BeginHorizontal(); if (GUILayout.Toggle(selectedPayloadAssemblyIds.Contains(payloadAssembly.id), "<b>" + payloadAssembly.name + "</b>")) { if (!selectedPayloadAssemblyIds.Contains(payloadAssembly.id)) { selectedPayloadAssemblyIds.Add(payloadAssembly.id); selectionChanged = true; } } else { if (selectedPayloadAssemblyIds.Contains(payloadAssembly.id)) { selectedPayloadAssemblyIds.Remove(payloadAssembly.id); selectionChanged = true; } } GUILayout.Label(payloadAssembly.partCount.ToString() + " part" + (payloadAssembly.partCount != 1 ? "s" : "") + ", " + payloadAssembly.mass.ToString("#,##0.00 t") + " ", new GUIStyle(GUI.labelStyle) { alignment = TextAnchor.MiddleRight }); GUILayout.EndHorizontal(); } // Highlight all selected assemblies (to make sure these don't cancel each other out, we first turn all off and then switch the selected ones on): if (selectionChanged) { foreach (PayloadAssembly payloadAssembly in recording.GetPayloadAssemblies()) { payloadAssembly.Highlight(false); } foreach (PayloadAssembly payloadAssembly in recording.GetPayloadAssemblies()) { if (selectedPayloadAssemblyIds.Contains(payloadAssembly.id)) { payloadAssembly.Highlight(true); } } } } } else { if (!recording.CanPerformMission(MissionProfileType.TRANSPORT)) { GUILayout.Label("Transport missions can only be performed with vessels which have at least one docking port as well as RCS thrusters."); } else { // Show all payload-resources: double totalPayloadMass = 0; foreach (PayloadResource payloadResource in recording.GetPayloadResources()) { double selectedAmount = 0; selectedPayloadDeploymentResources.TryGetValue(payloadResource.name, out selectedAmount); GUILayout.BeginHorizontal(); GUILayout.Label("<b>" + payloadResource.name + "</b>"); GUILayout.Label(((selectedAmount / payloadResource.amount) * 100).ToString("0.00") + "% (" + selectedAmount.ToString("#,##0.00") + " / " + payloadResource.amount.ToString("#,##0.00") + "): " + (selectedAmount * payloadResource.mass).ToString("#,##0.00 t") + " ", new GUIStyle(GUI.labelStyle) { alignment = TextAnchor.MiddleRight }); GUILayout.EndHorizontal(); selectedAmount = GUILayout.HorizontalSlider((float)selectedAmount, 0, (float)payloadResource.amount); if (selectedAmount < 0) { selectedAmount = 0; } if (selectedAmount > payloadResource.amount) { selectedAmount = payloadResource.amount; } if (payloadResource.amount - selectedAmount < 0.01) { selectedAmount = payloadResource.amount; } totalPayloadMass += selectedAmount * payloadResource.mass; if (selectedPayloadDeploymentResources.ContainsKey(payloadResource.name)) { selectedPayloadDeploymentResources[payloadResource.name] = selectedAmount; } else { selectedPayloadDeploymentResources.Add(payloadResource.name, selectedAmount); } } GUILayout.BeginHorizontal(); GUILayout.Label("<b>Total Payload</b>"); GUILayout.Label(totalPayloadMass.ToString("#,##0.00 t "), new GUIStyle(GUI.labelStyle) { alignment = TextAnchor.MiddleRight }); GUILayout.EndHorizontal(); } } GUILayout.EndScrollView(); } // Bottom pane with action-buttons: GUILayout.BeginHorizontal(); if (recording.status == FlightRecordingStatus.PRELAUNCH && GUILayout.Button("Record", GUI.buttonStyle)) { // Start Recording: FlightRecorder.StartRecording(vessel); } if (recording.CanDeploy() && GUILayout.Button("Release Payload", GUI.buttonStyle)) { if (selectedMissionTypeTab == 0) { List <PayloadAssembly> payloadAssemblies = recording.GetPayloadAssemblies(); List <PayloadAssembly> selectedPayloadAssemblies = new List <PayloadAssembly>(); foreach (PayloadAssembly payloadAssembly in recording.GetPayloadAssemblies()) { if (selectedPayloadAssemblyIds.Contains(payloadAssembly.id)) { selectedPayloadAssemblies.Add(payloadAssembly); } } if (selectedPayloadAssemblies.Count > 0) { recording.DeployPayloadAssembly(selectedPayloadAssemblies); } } else { recording.DeployPayloadResources(selectedPayloadDeploymentResources); } } if (recording.CanFinish() && GUILayout.Button("Stop & Save", GUI.buttonStyle)) { // Stop recording and create a mission-profile: FlightRecorder.SaveRecording(vessel); } if (recording.status != FlightRecordingStatus.PRELAUNCH && GUILayout.Button("Abort", GUI.buttonStyle)) { // Cancel runnig recording: FlightRecorder.CancelRecording(vessel); } GUILayout.EndHorizontal(); } }
// Is called when this Addon is first loaded to initializes all values (eg registration of event-handlers and creation // of original-stats library). public void Awake() { try { FlightRecorder.Initialize(); MissionController.Initialize(); // Build dictionary of all parts for easier access: if (KSTS.partDictionary == null) { KSTS.partDictionary = new Dictionary <string, AvailablePart>(); foreach (var part in PartLoader.LoadedPartsList) { if (KSTS.partDictionary.ContainsKey(part.name.ToString())) { Debug.LogError("duplicate part-name '" + part.name.ToString() + "'"); continue; } KSTS.partDictionary.Add(part.name.ToString(), part); } } // Build a dictionay of all resources for easier access: if (KSTS.resourceDictionary == null) { KSTS.resourceDictionary = new Dictionary <string, PartResourceDefinition>(); foreach (var resourceDefinition in PartResourceLibrary.Instance.resourceDefinitions) { KSTS.resourceDictionary.Add(resourceDefinition.name.ToString(), resourceDefinition); } } // Invoke the timer-function every second to run background-code: if (!IsInvoking("Timer")) { InvokeRepeating("Timer", 1, 1); } // In case the Stage Recovery Mod is installed, add lists and handlers to track the separation and recovery of stages: if (StageRecoveryAPI.StageRecoveryAvailable && KSTS.stageParentDictionary == null) { Log.Warning("detected stage recovery mod"); stageParentDictionary = new Dictionary <string, string>(); StageRecoveryAPI.AddRecoverySuccessEvent((vessel, array, str) => { if (StageRecoveryAPI.StageRecoveryEnabled) { FlightRecorder.OnStageRecovered(vessel.id.ToString(), array[1]); } }); GameEvents.onStageSeparation.Add(new EventData <EventReport> .OnEvent(this.onStageSeparation)); GameEvents.onVesselWasModified.Add(new EventData <Vessel> .OnEvent(this.onVesselModified)); } // Execute the following code only once: if (KSTS.initialized) { return; } DontDestroyOnLoad(this); KSTS.initialized = true; } catch (Exception e) { Debug.LogError("Awake(): " + e.ToString()); } }