<commit_before>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using UnityEngine;


namespace KERBALISM
{
	// TODO : SolarPanelFixer features that require testing :
	// - fully untested : time efficiency curve (must try with a stock panel defined curve, and a SolarPanelFixer defined curve)
	// - background update : must check that the output is consistent with what we get when loaded (should be but checking can't hurt)
	//   (note : only test it with equatorial circular orbits, other orbits will give inconsistent output due to sunlight evaluation algorithm limitations)
	// - reliability support should work but I did only a very quick test

	// TODO : SolarPanelFixer missing features :
	// - SSTU automation / better reliability support

	// This module is used to disable stock and other plugins solar panel EC output and provide specific support
	// EC must be produced using the resource cache, that give us correct behaviour independent from timewarp speed and vessel EC capacity.
	// To be able to support a custom module, we need to be able to do the following :
	// - (imperative) prevent the module from using the stock API calls to generate EC 
	// - (imperative) get the nominal rate at 1 AU
	// - (imperative) get the "suncatcher" transforms or vectors
	// - (imperative) get the "pivot" transforms or vectors if it's a tracking panel
	// - (imperative) get the "deployed" state if its a deployable panel.
	// - (imperative) get the "broken" state if the target module implement it
	// - (optional)   set the "deployed" state if its a deployable panel (both for unloaded and loaded vessels, with handling of the animation)
	// - (optional)   get the time effiency curve if its supported / defined
	// Notes :
	// - We don't support temperature efficiency curve
	// - We don't have any support for the animations, the target module must be able to keep handling them despite our hacks.
	// - Depending on how "hackable" the target module is, we use different approaches :
	//   either we disable the monobehavior and call the methods manually, or if possible we let it run and we just get/set what we need
	public class SolarPanelFixer : PartModule
	{
		#region Declarations
		/// <summary>Main PAW info label</summary>
		[KSPField(guiActive = true, guiActiveEditor = false, guiName = "Solar panel")]
		public string panelStatus = string.Empty;

		[KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Solar panel output")]
		[UI_Toggle(enabledText = "<color=#00ff00>simulated</color>", disabledText = "<color=#ffff00>ignored</color>")]
		public bool editorEnabled = true;

		/// <summary>nominal rate at 1 UA (Kerbin distance from the sun)</summary>
		[KSPField(isPersistant = true)]
		public double nominalRate = 10.0; // doing this on the purpose of not breaking existing saves

		/// <summary>aggregate efficiency factor for angle exposure losses and occlusion from parts</summary>
		[KSPField(isPersistant = true)]
		public double persistentFactor = 1.0; // doing this on the purpose of not breaking existing saves

		/// <summary>current state of the module</summary>
		[KSPField(isPersistant = true)]
		public PanelState state;

		/// <summary>tracked star/sun body index</summary>
		[KSPField(isPersistant = true)]
		public int trackedSunIndex = 0;

		/// <summary>has the player manually selected the star to be tracked ?</summary>
		[KSPField(isPersistant = true)]
		private bool manualTracking = false;

		/// <summary>
		/// Time based output degradation curve. Keys in hours, values in [0;1] range.
		/// Copied from the target solar panel module if supported and present.
		/// If defined in the SolarPanelFixer config, the target module curve will be overriden.
		/// </summary>
		[KSPField]
		public FloatCurve timeEfficCurve;

		/// <summary>UT of part creation in flight, used to evaluate the timeEfficCurve</summary>
		[KSPField(isPersistant = true)]
		public double launchUT = -1.0;

		/// <summary>internal object for handling the various hacks depending on the target solar panel module</summary>
		public SupportedPanel SolarPanel { get; private set; }

		/// <summary>current state of the module</summary>
		public bool isInitialized = false;

		/// <summary>for tracking analytic mode changes and ui updating</summary>
		private bool analyticSunlight;

		// The following fields are local to FixedUpdate() but are shared for status string updates in Update()
		// Their value can be inconsistent, don't rely on them for anything else
		private double currentOutput;
		private double exposureFactor;
		private double wearFactor;
		private ExposureState exposureState;
		private string mainOccludingPart;

		public enum PanelState
		{
			Unknown = 0,
			Retracted,
			Extending,
			Extended,
			ExtendedFixed,
			Retracting,
			Static,
			Broken,
			Failure
		}

		public enum ExposureState
		{
			Disabled,
			Exposed,
			InShadow,
			OccludedTerrain,
			OccludedPart,
			BadOrientation
		}
		#endregion

		#region KSP/Unity methods + background update

		[KSPEvent(active = true, guiActive = true, guiName = "Select tracked star")]
		public void ManualTracking()
		{
			// Assemble the buttons
			DialogGUIBase[] options = new DialogGUIBase[Sim.suns.Count + 1];
			options[0] = new DialogGUIButton("Automatic", () => { manualTracking = false; }, true);
			for (int i = 0; i < Sim.suns.Count; i++)
			{
				CelestialBody body = Sim.suns[i].body;
				options[i + 1] = new DialogGUIButton(body.bodyDisplayName.Replace("^N", ""), () =>
				{
					manualTracking = true;
					trackedSunIndex = body.flightGlobalsIndex;
					SolarPanel.SetTrackedBody(body);
				}, true);
			}

			PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new MultiOptionDialog(
				"SelectTrackingBody",
				"Select the star you want to track with this solar panel.",
				"Select tracked star",
				UISkinManager.GetSkin("MainMenuSkin"),
				options), false, UISkinManager.GetSkin("MainMenuSkin"));
		}

		public override void OnLoad(ConfigNode node)
		{
			if (SolarPanel == null && !GetSolarPanelModule())
				return;

			if (Lib.IsEditor()) return;

			// apply states changes we have done trough automation
			if ((state == PanelState.Retracted || state == PanelState.Extended || state == PanelState.ExtendedFixed) && state != SolarPanel.GetState())
				SolarPanel.SetDeployedStateOnLoad(state);

			// apply reliability broken state and ensure we are correctly initialized (in case we are repaired mid-flight)
			// note : this rely on the fact that the reliability module is disabling the SolarPanelFixer monobehavior from OnStart, after OnLoad has been called
			if (!isEnabled)
			{
				ReliabilityEvent(true);
				OnStart(StartState.None);
			}
		}

		public override void OnStart(StartState startState)
		{
			// don't break tutorial scenarios
			// TODO : does this actually work ?
			if (Lib.DisableScenario(this)) return;

			if (SolarPanel == null && !GetSolarPanelModule())
			{
				isInitialized = true;
				return;
			}

			// disable everything if the target module data/logic acquisition has failed
			if (!SolarPanel.OnStart(isInitialized, ref nominalRate))
				enabled = isEnabled = moduleIsEnabled = false;

			isInitialized = true;

			// not sure why (I guess because of the KSPField attribute), but timeEfficCurve is instanciated with 0 keys by something instead of being null
			if (timeEfficCurve == null || timeEfficCurve.Curve.keys.Length == 0)
			{
				timeEfficCurve = SolarPanel.GetTimeCurve();
				if (Lib.IsFlight() && launchUT < 0.0)
					launchUT = Planetarium.GetUniversalTime();
			}

			// setup star selection GUI
			Events["ManualTracking"].active = Sim.suns.Count > 1 && SolarPanel.IsTracking;
			Events["ManualTracking"].guiActive = state == PanelState.Extended || state == PanelState.ExtendedFixed || state == PanelState.Static;

			// setup target module animation for custom star tracking
			SolarPanel.SetTrackedBody(FlightGlobals.Bodies[trackedSunIndex]);
		}

		public override void OnSave(ConfigNode node)
		{
			// vessel can be null in OnSave (ex : on vessel creation)
			if (!Lib.IsFlight()
				|| vessel == null
				|| !isInitialized
				|| SolarPanel == null
				|| !Lib.Landed(vessel))
				return;

			// get vessel data
			VesselData vd = vessel.KerbalismData();

			// do nothing if vessel is invalid
			if (!vd.IsValid) return;

			// calculate average exposure over a full day when landed, will be used for panel background processing
			node.SetValue("persistentFactor", GetAnalyticalCosineFactorLanded(vd));
		}

		public void Update()
		{
			// sanity check
			if (SolarPanel == null) return;

			// call Update specfic handling, if any
			SolarPanel.OnUpdate();

			// Do nothing else in the editor
			if (Lib.IsEditor()) return;

			// Update tracked body selection button (Kopernicus multi-star support)
			if (Events["ManualTracking"].active && (state == PanelState.Extended || state == PanelState.ExtendedFixed || state == PanelState.Static))
			{
				Events["ManualTracking"].guiActive = true;
				Events["ManualTracking"].guiName = Lib.BuildString("Tracked star ", manualTracking ? ": " : "[Auto] : ", FlightGlobals.Bodies[trackedSunIndex].bodyDisplayName.Replace("^N", ""));
			}<commit_msg>Fix for issue #492 : Exception when recovering craft (SolarPanelFixer)<commit_after>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using UnityEngine;


namespace KERBALISM
{
	// TODO : SolarPanelFixer features that require testing :
	// - fully untested : time efficiency curve (must try with a stock panel defined curve, and a SolarPanelFixer defined curve)
	// - background update : must check that the output is consistent with what we get when loaded (should be but checking can't hurt)
	//   (note : only test it with equatorial circular orbits, other orbits will give inconsistent output due to sunlight evaluation algorithm limitations)
	// - reliability support should work but I did only a very quick test

	// TODO : SolarPanelFixer missing features :
	// - SSTU automation / better reliability support

	// This module is used to disable stock and other plugins solar panel EC output and provide specific support
	// EC must be produced using the resource cache, that give us correct behaviour independent from timewarp speed and vessel EC capacity.
	// To be able to support a custom module, we need to be able to do the following :
	// - (imperative) prevent the module from using the stock API calls to generate EC 
	// - (imperative) get the nominal rate at 1 AU
	// - (imperative) get the "suncatcher" transforms or vectors
	// - (imperative) get the "pivot" transforms or vectors if it's a tracking panel
	// - (imperative) get the "deployed" state if its a deployable panel.
	// - (imperative) get the "broken" state if the target module implement it
	// - (optional)   set the "deployed" state if its a deployable panel (both for unloaded and loaded vessels, with handling of the animation)
	// - (optional)   get the time effiency curve if its supported / defined
	// Notes :
	// - We don't support temperature efficiency curve
	// - We don't have any support for the animations, the target module must be able to keep handling them despite our hacks.
	// - Depending on how "hackable" the target module is, we use different approaches :
	//   either we disable the monobehavior and call the methods manually, or if possible we let it run and we just get/set what we need
	public class SolarPanelFixer : PartModule
	{
		#region Declarations
		/// <summary>Main PAW info label</summary>
		[KSPField(guiActive = true, guiActiveEditor = false, guiName = "Solar panel")]
		public string panelStatus = string.Empty;

		[KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Solar panel output")]
		[UI_Toggle(enabledText = "<color=#00ff00>simulated</color>", disabledText = "<color=#ffff00>ignored</color>")]
		public bool editorEnabled = true;

		/// <summary>nominal rate at 1 UA (Kerbin distance from the sun)</summary>
		[KSPField(isPersistant = true)]
		public double nominalRate = 10.0; // doing this on the purpose of not breaking existing saves

		/// <summary>aggregate efficiency factor for angle exposure losses and occlusion from parts</summary>
		[KSPField(isPersistant = true)]
		public double persistentFactor = 1.0; // doing this on the purpose of not breaking existing saves

		/// <summary>current state of the module</summary>
		[KSPField(isPersistant = true)]
		public PanelState state;

		/// <summary>tracked star/sun body index</summary>
		[KSPField(isPersistant = true)]
		public int trackedSunIndex = 0;

		/// <summary>has the player manually selected the star to be tracked ?</summary>
		[KSPField(isPersistant = true)]
		private bool manualTracking = false;

		/// <summary>
		/// Time based output degradation curve. Keys in hours, values in [0;1] range.
		/// Copied from the target solar panel module if supported and present.
		/// If defined in the SolarPanelFixer config, the target module curve will be overriden.
		/// </summary>
		[KSPField]
		public FloatCurve timeEfficCurve;

		/// <summary>UT of part creation in flight, used to evaluate the timeEfficCurve</summary>
		[KSPField(isPersistant = true)]
		public double launchUT = -1.0;

		/// <summary>internal object for handling the various hacks depending on the target solar panel module</summary>
		public SupportedPanel SolarPanel { get; private set; }

		/// <summary>current state of the module</summary>
		public bool isInitialized = false;

		/// <summary>for tracking analytic mode changes and ui updating</summary>
		private bool analyticSunlight;

		// The following fields are local to FixedUpdate() but are shared for status string updates in Update()
		// Their value can be inconsistent, don't rely on them for anything else
		private double currentOutput;
		private double exposureFactor;
		private double wearFactor;
		private ExposureState exposureState;
		private string mainOccludingPart;

		public enum PanelState
		{
			Unknown = 0,
			Retracted,
			Extending,
			Extended,
			ExtendedFixed,
			Retracting,
			Static,
			Broken,
			Failure
		}

		public enum ExposureState
		{
			Disabled,
			Exposed,
			InShadow,
			OccludedTerrain,
			OccludedPart,
			BadOrientation
		}
		#endregion

		#region KSP/Unity methods + background update

		[KSPEvent(active = true, guiActive = true, guiName = "Select tracked star")]
		public void ManualTracking()
		{
			// Assemble the buttons
			DialogGUIBase[] options = new DialogGUIBase[Sim.suns.Count + 1];
			options[0] = new DialogGUIButton("Automatic", () => { manualTracking = false; }, true);
			for (int i = 0; i < Sim.suns.Count; i++)
			{
				CelestialBody body = Sim.suns[i].body;
				options[i + 1] = new DialogGUIButton(body.bodyDisplayName.Replace("^N", ""), () =>
				{
					manualTracking = true;
					trackedSunIndex = body.flightGlobalsIndex;
					SolarPanel.SetTrackedBody(body);
				}, true);
			}

			PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new MultiOptionDialog(
				"SelectTrackingBody",
				"Select the star you want to track with this solar panel.",
				"Select tracked star",
				UISkinManager.GetSkin("MainMenuSkin"),
				options), false, UISkinManager.GetSkin("MainMenuSkin"));
		}

		public override void OnLoad(ConfigNode node)
		{
			if (SolarPanel == null && !GetSolarPanelModule())
				return;

			if (Lib.IsEditor()) return;

			// apply states changes we have done trough automation
			if ((state == PanelState.Retracted || state == PanelState.Extended || state == PanelState.ExtendedFixed) && state != SolarPanel.GetState())
				SolarPanel.SetDeployedStateOnLoad(state);

			// apply reliability broken state and ensure we are correctly initialized (in case we are repaired mid-flight)
			// note : this rely on the fact that the reliability module is disabling the SolarPanelFixer monobehavior from OnStart, after OnLoad has been called
			if (!isEnabled)
			{
				ReliabilityEvent(true);
				OnStart(StartState.None);
			}
		}

		public override void OnStart(StartState startState)
		{
			// don't break tutorial scenarios
			// TODO : does this actually work ?
			if (Lib.DisableScenario(this)) return;

			if (SolarPanel == null && !GetSolarPanelModule())
			{
				isInitialized = true;
				return;
			}

			// disable everything if the target module data/logic acquisition has failed
			if (!SolarPanel.OnStart(isInitialized, ref nominalRate))
				enabled = isEnabled = moduleIsEnabled = false;

			isInitialized = true;

			// not sure why (I guess because of the KSPField attribute), but timeEfficCurve is instanciated with 0 keys by something instead of being null
			if (timeEfficCurve == null || timeEfficCurve.Curve.keys.Length == 0)
			{
				timeEfficCurve = SolarPanel.GetTimeCurve();
				if (Lib.IsFlight() && launchUT < 0.0)
					launchUT = Planetarium.GetUniversalTime();
			}

			// setup star selection GUI
			Events["ManualTracking"].active = Sim.suns.Count > 1 && SolarPanel.IsTracking;
			Events["ManualTracking"].guiActive = state == PanelState.Extended || state == PanelState.ExtendedFixed || state == PanelState.Static;

			// setup target module animation for custom star tracking
			SolarPanel.SetTrackedBody(FlightGlobals.Bodies[trackedSunIndex]);
		}

		public override void OnSave(ConfigNode node)
		{
			// vessel can be null in OnSave (ex : on vessel creation)
			if (!Lib.IsFlight()
				|| vessel == null
				|| !isInitialized
				|| SolarPanel == null
				|| !Lib.Landed(vessel)
				|| exposureState == ExposureState.Disabled) // don't to broken panels ! (issue #492)
				return;

			// get vessel data
			VesselData vd = vessel.KerbalismData();

			// do nothing if vessel is invalid
			if (!vd.IsValid) return;

			// calculate average exposure over a full day when landed, will be used for panel background processing
			node.SetValue("persistentFactor", GetAnalyticalCosineFactorLanded(vd));
		}

		public void Update()
		{
			// sanity check
			if (SolarPanel == null) return;

			// call Update specfic handling, if any
			SolarPanel.OnUpdate();

			// Do nothing else in the editor
			if (Lib.IsEditor()) return;

			// Update tracked body selection button (Kopernicus multi-star support)
			if (Events["ManualTracking"].active && (state == PanelState.Extended || state == PanelState.ExtendedFixed || state == PanelState.Static))
			{
				Events["ManualTracking"].guiActive = true;
				Events["ManualTracking"].guiName = Lib.BuildString("Tracked star ", manualTracking ? ": " : "[Auto] : ", FlightGlobals.Bodies[trackedSunIndex].bodyDisplayName.Replace("^N", ""));
			}
{'fraction_non_alphanumeric': 0.09708118453931629, 'fraction_numerical': 0.004359615077888245, 'mean_word_length': 3.736842105263158, 'pattern_counts': {'":': 2, '<': 59, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3386979', 'n_tokens_mistral': 6087, 'n_tokens_neox': 5261, 'n_words': 1952}