Removed redundant button enabled check within the FlightAppLauncher.
Removed redundant button enabled check within the FlightAppLauncher.

// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2016 CYBUTEK // Copyright (C) 2016 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
namespace KerbalEngineer.Unity namespace KerbalEngineer.Unity
{ {
using System; using System;
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
   
[RequireComponent(typeof(CanvasGroup))] [RequireComponent(typeof(CanvasGroup))]
public class CanvasGroupFader : MonoBehaviour public class CanvasGroupFader : MonoBehaviour
{ {
private CanvasGroup m_CanvasGroup; private CanvasGroup m_CanvasGroup;
private IEnumerator m_FadeCoroutine; private IEnumerator m_FadeCoroutine;
   
public bool IsFading public bool IsFading
{ {
get get
{ {
return m_FadeCoroutine != null; return m_FadeCoroutine != null;
} }
} }
   
/// <summary> /// <summary>
/// Fades the canvas group to a specified alpha using the supplied blocking state during fade with optional callback. /// Fades the canvas group to a specified alpha using the supplied blocking state during fade with optional callback.
/// </summary> /// </summary>
public void FadeTo(float alpha, float duration, Action callback = null) public void FadeTo(float alpha, float duration, Action callback = null)
{ {
if (m_CanvasGroup == null) if (m_CanvasGroup == null)
{ {
return; return;
} }
   
Fade(m_CanvasGroup.alpha, alpha, duration, callback); Fade(m_CanvasGroup.alpha, alpha, duration, callback);
} }
   
/// <summary> /// <summary>
/// Sets the alpha value of the canvas group. /// Sets the alpha value of the canvas group.
/// </summary> /// </summary>
public void SetAlpha(float alpha) public void SetAlpha(float alpha)
{ {
if (m_CanvasGroup == null) if (m_CanvasGroup == null)
{ {
return; return;
} }
   
alpha = Mathf.Clamp01(alpha); alpha = Mathf.Clamp01(alpha);
m_CanvasGroup.alpha = alpha; m_CanvasGroup.alpha = alpha;
} }
   
protected virtual void Awake() protected virtual void Awake()
{ {
// cache components // cache components
m_CanvasGroup = GetComponent<CanvasGroup>(); m_CanvasGroup = GetComponent<CanvasGroup>();
} }
   
/// <summary> /// <summary>
/// Starts a fade from one alpha value to another with callback. /// Starts a fade from one alpha value to another with callback.
/// </summary> /// </summary>
private void Fade(float from, float to, float duration, Action callback) private void Fade(float from, float to, float duration, Action callback)
{ {
if (m_FadeCoroutine != null) if (m_FadeCoroutine != null)
{ {
StopCoroutine(m_FadeCoroutine); StopCoroutine(m_FadeCoroutine);
} }
   
m_FadeCoroutine = FadeCoroutine(from, to, duration, callback); m_FadeCoroutine = FadeCoroutine(from, to, duration, callback);
StartCoroutine(m_FadeCoroutine); StartCoroutine(m_FadeCoroutine);
} }
   
/// <summary> /// <summary>
/// Coroutine that handles the fading. /// Coroutine that handles the fading.
/// </summary> /// </summary>
private IEnumerator FadeCoroutine(float from, float to, float duration, Action callback) private IEnumerator FadeCoroutine(float from, float to, float duration, Action callback)
{ {
// wait for end of frame so that only the last call to fade that frame is honoured. // wait for end of frame so that only the last call to fade that frame is honoured.
yield return new WaitForEndOfFrame(); yield return new WaitForEndOfFrame();
   
float progress = 0.0f; float progress = 0.0f;
   
while (progress <= 1.0f) while (progress <= 1.0f)
{ {
progress += Time.deltaTime / duration; progress += Time.deltaTime / duration;
SetAlpha(Mathf.Lerp(from, to, progress)); SetAlpha(Mathf.Lerp(from, to, progress));
yield return null; yield return null;
} }
   
//print(m_CanvasGroup.alpha); if (callback != null)
callback?.Invoke(); {
  callback.Invoke();
  }
   
m_FadeCoroutine = null; m_FadeCoroutine = null;
} }
} }
} }
namespace KerbalEngineer.Unity namespace KerbalEngineer.Unity
{ {
using System; using System;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.UI; using UnityEngine.UI;
   
public class Setting : MonoBehaviour public class Setting : MonoBehaviour
{ {
[SerializeField] [SerializeField]
private Text m_Label = null; private Text m_Label = null;
   
[SerializeField] [SerializeField]
private Transform m_ButtonsTransform = null; private Transform m_ButtonsTransform = null;
   
[SerializeField] [SerializeField]
private GameObject m_SettingButtonPrefab = null; private GameObject m_SettingButtonPrefab = null;
   
[SerializeField] [SerializeField]
private GameObject m_SettingTogglePrefab = null; private GameObject m_SettingTogglePrefab = null;
   
private Action m_OnUpdate; private Action m_OnUpdate;
   
public Button AddButton(string text, float width, UnityAction onClick) public Button AddButton(string text, float width, UnityAction onClick)
{ {
Button button = null; Button button = null;
   
if (m_SettingButtonPrefab != null) if (m_SettingButtonPrefab != null)
{ {
GameObject buttonObject = Instantiate(m_SettingButtonPrefab); GameObject buttonObject = Instantiate(m_SettingButtonPrefab);
if (buttonObject != null) if (buttonObject != null)
{ {
button = buttonObject.GetComponent<Button>(); button = buttonObject.GetComponent<Button>();
   
SetParentTransform(buttonObject, m_ButtonsTransform); SetParentTransform(buttonObject, m_ButtonsTransform);
SetWidth(buttonObject, width); SetWidth(buttonObject, width);
SetText(buttonObject, text); SetText(buttonObject, text);
SetButton(buttonObject, onClick); SetButton(buttonObject, onClick);
} }
} }
   
return button; return button;
} }
   
public Toggle AddToggle(string text, float width, UnityAction<bool> onValueChanged) public Toggle AddToggle(string text, float width, UnityAction<bool> onValueChanged)
{ {
Toggle toggle = null; Toggle toggle = null;
   
if (m_SettingTogglePrefab != null) if (m_SettingTogglePrefab != null)
{ {
GameObject toggleObject = Instantiate(m_SettingTogglePrefab); GameObject toggleObject = Instantiate(m_SettingTogglePrefab);
if (toggleObject != null) if (toggleObject != null)
{ {
toggle = toggleObject.GetComponent<Toggle>(); toggle = toggleObject.GetComponent<Toggle>();
   
SetParentTransform(toggleObject, m_ButtonsTransform); SetParentTransform(toggleObject, m_ButtonsTransform);
SetWidth(toggleObject, width); SetWidth(toggleObject, width);
SetText(toggleObject, text); SetText(toggleObject, text);
SetToggle(toggleObject, onValueChanged); SetToggle(toggleObject, onValueChanged);
} }
} }
   
return toggle; return toggle;
} }
   
public void AddUpdateHandler(Action onUpdate) public void AddUpdateHandler(Action onUpdate)
{ {
m_OnUpdate = onUpdate; m_OnUpdate = onUpdate;
} }
   
public void SetLabel(string text) public void SetLabel(string text)
{ {
if (m_Label != null) if (m_Label != null)
{ {
m_Label.text = text; m_Label.text = text;
} }
} }
   
protected virtual void Update() protected virtual void Update()
{ {
m_OnUpdate?.Invoke(); if (m_OnUpdate != null)
  {
  m_OnUpdate.Invoke();
  }
} }
   
private static void SetButton(GameObject buttonObject, UnityAction onClick) private static void SetButton(GameObject buttonObject, UnityAction onClick)
{ {
if (buttonObject != null) if (buttonObject != null)
{ {
Button button = buttonObject.GetComponent<Button>(); Button button = buttonObject.GetComponent<Button>();
if (button != null) if (button != null)
{ {
button.onClick.AddListener(onClick); button.onClick.AddListener(onClick);
} }
} }
} }
   
private static void SetParentTransform(GameObject childObject, Transform parentTransform) private static void SetParentTransform(GameObject childObject, Transform parentTransform)
{ {
if (childObject != null && parentTransform != null) if (childObject != null && parentTransform != null)
{ {
childObject.transform.SetParent(parentTransform, false); childObject.transform.SetParent(parentTransform, false);
} }
} }
   
private static void SetText(GameObject parentObject, string text) private static void SetText(GameObject parentObject, string text)
{ {
if (parentObject != null) if (parentObject != null)
{ {
Text textComponent = parentObject.GetComponentInChildren<Text>(); Text textComponent = parentObject.GetComponentInChildren<Text>();
if (textComponent != null) if (textComponent != null)
{ {
textComponent.text = text; textComponent.text = text;
} }
} }
} }
   
private static void SetToggle(GameObject toggleObject, UnityAction<bool> onValueChanged) private static void SetToggle(GameObject toggleObject, UnityAction<bool> onValueChanged)
{ {
if (toggleObject != null) if (toggleObject != null)
{ {
Toggle toggle = toggleObject.GetComponent<Toggle>(); Toggle toggle = toggleObject.GetComponent<Toggle>();
if (toggle != null) if (toggle != null)
{ {
toggle.onValueChanged.AddListener(onValueChanged); toggle.onValueChanged.AddListener(onValueChanged);
} }
} }
} }
   
private static void SetWidth(GameObject parentObject, float width) private static void SetWidth(GameObject parentObject, float width)
{ {
if (parentObject != null) if (parentObject != null)
{ {
LayoutElement layout = parentObject.GetComponent<LayoutElement>(); LayoutElement layout = parentObject.GetComponent<LayoutElement>();
if (layout != null) if (layout != null)
{ {
if (width > 0.0f) if (width > 0.0f)
{ {
layout.preferredWidth = width; layout.preferredWidth = width;
} }
else else
{ {
layout.flexibleWidth = 1.0f; layout.flexibleWidth = 1.0f;
} }
} }
} }
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2016 CYBUTEK // Copyright (C) 2016 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
namespace KerbalEngineer.Unity.UI namespace KerbalEngineer.Unity.UI
{ {
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
   
public class StyleApplicator : MonoBehaviour public class StyleApplicator : MonoBehaviour
{ {
public enum ElementTypes public enum ElementTypes
{ {
None, None,
Window, Window,
Box, Box,
Button, Button,
ButtonToggle, ButtonToggle,
Label Label
} }
   
[SerializeField] [SerializeField]
private ElementTypes m_ElementType = ElementTypes.None; private ElementTypes m_ElementType = ElementTypes.None;
   
/// <summary> /// <summary>
/// Gets the UI element type used by the ThemeManager for selecting how to apply the theme. /// Gets the UI element type used by the ThemeManager for selecting how to apply the theme.
/// </summary> /// </summary>
public ElementTypes ElementType public ElementTypes ElementType
{ {
get get
{ {
return m_ElementType; return m_ElementType;
} }
} }
   
/// <summary> /// <summary>
/// Sets a the applicator to apply the selected sprite to an attached image component. /// Sets a the applicator to apply the selected sprite to an attached image component.
/// </summary> /// </summary>
public void SetImage(Sprite sprite, Image.Type type) public void SetImage(Sprite sprite, Image.Type type)
{ {
Image image = GetComponent<Image>(); Image image = GetComponent<Image>();
if (image == null) if (image == null)
{ {
return; return;
} }
   
image.sprite = sprite; image.sprite = sprite;
image.type = type; image.type = type;
} }
   
/// <summary> /// <summary>
/// Sets the applicator to apply the specified values to an attached selectable component. /// Sets the applicator to apply the specified values to an attached selectable component.
/// </summary> /// </summary>
public void SetSelectable(TextStyle textStyle, Sprite normal, Sprite highlight, Sprite pressed, Sprite disabled) public void SetSelectable(TextStyle textStyle, Sprite normal, Sprite highlight, Sprite pressed, Sprite disabled)
{ {
SetText(textStyle, GetComponentInChildren<Text>()); SetText(textStyle, GetComponentInChildren<Text>());
   
Selectable selectable = GetComponent<Selectable>(); Selectable selectable = GetComponent<Selectable>();
if (selectable != null) if (selectable != null)
{ {
selectable.image.sprite = normal; selectable.image.sprite = normal;
selectable.image.type = Image.Type.Sliced; selectable.image.type = Image.Type.Sliced;
   
selectable.transition = Selectable.Transition.SpriteSwap; selectable.transition = Selectable.Transition.SpriteSwap;
   
SpriteState spriteState = selectable.spriteState; SpriteState spriteState = selectable.spriteState;
spriteState.highlightedSprite = highlight; spriteState.highlightedSprite = highlight;
spriteState.pressedSprite = pressed; spriteState.pressedSprite = pressed;
spriteState.disabledSprite = disabled; spriteState.disabledSprite = disabled;
selectable.spriteState = spriteState; selectable.spriteState = spriteState;
} }
} }
   
/// <summary> /// <summary>
/// Sets the applicator to apply a style to an attached text component. /// Sets the applicator to apply a style to an attached text component.
/// </summary> /// </summary>
public void SetText(TextStyle textStyle) public void SetText(TextStyle textStyle)
{ {
SetText(textStyle, GetComponent<Text>()); SetText(textStyle, GetComponent<Text>());
} }
   
/// <summary> /// <summary>
/// Sets the applicator to apply the specified values to an attached toggle component. /// Sets the applicator to apply the specified values to an attached toggle component.
/// </summary> /// </summary>
public void SetToggle(TextStyle textStyle, Sprite normal, Sprite highlight, Sprite pressed, Sprite disabled) public void SetToggle(TextStyle textStyle, Sprite normal, Sprite highlight, Sprite pressed, Sprite disabled)
{ {
SetSelectable(textStyle, normal, highlight, pressed, disabled); SetSelectable(textStyle, normal, highlight, pressed, disabled);
   
Image toggleImage = GetComponent<Toggle>()?.graphic as Image; Toggle toggleComponent = GetComponent<Toggle>();
if (toggleImage != null) if (toggleComponent != null)
{ {
toggleImage.sprite = pressed; Image toggleImage = toggleComponent.graphic as Image;
toggleImage.type = Image.Type.Sliced; if (toggleImage != null)
  {
  toggleImage.sprite = pressed;
  toggleImage.type = Image.Type.Sliced;
  }
} }
} }
   
/// <summary> /// <summary>
/// Sets the applicator to apply a style to the supplied text component. /// Sets the applicator to apply a style to the supplied text component.
/// </summary> /// </summary>
private static void SetText(TextStyle textStyle, Text textComponent) private static void SetText(TextStyle textStyle, Text textComponent)
{ {
if (textStyle == null || textComponent == null) if (textStyle == null || textComponent == null)
{ {
return; return;
} }
   
if (textStyle.Font != null) if (textStyle.Font != null)
{ {
textComponent.font = textStyle.Font; textComponent.font = textStyle.Font;
} }
textComponent.fontSize = textStyle.Size; textComponent.fontSize = textStyle.Size;
textComponent.fontStyle = textStyle.Style; textComponent.fontStyle = textStyle.Style;
textComponent.color = textStyle.Colour; textComponent.color = textStyle.Colour;
} }
} }
} }
namespace KerbalEngineer.Unity.UI namespace KerbalEngineer.Unity.UI
{ {
using System; using System;
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
using UnityEngine.UI; using UnityEngine.UI;
   
[RequireComponent(typeof(RectTransform), typeof(CanvasGroup))] [RequireComponent(typeof(RectTransform), typeof(CanvasGroup))]
public class Window : MonoBehaviour, IBeginDragHandler, IDragHandler public class Window : MonoBehaviour, IBeginDragHandler, IDragHandler
{ {
[SerializeField] [SerializeField]
private Text m_Title = null; private Text m_Title = null;
   
[SerializeField] [SerializeField]
private Transform m_Content = null; private Transform m_Content = null;
   
private Vector2 m_BeginMousePosition; private Vector2 m_BeginMousePosition;
private Vector3 m_BeginWindowPosition; private Vector3 m_BeginWindowPosition;
private CanvasGroup m_CanvasGroup; private CanvasGroup m_CanvasGroup;
private RectTransform m_RectTransform; private RectTransform m_RectTransform;
private IEnumerator m_ScaleFadeCoroutine; private IEnumerator m_ScaleFadeCoroutine;
   
/// <summary> /// <summary>
/// Gets the content transform. /// Gets the content transform.
/// </summary> /// </summary>
public Transform Content public Transform Content
{ {
get get
{ {
return m_Content; return m_Content;
} }
} }
   
/// <summary> /// <summary>
/// Gets the rect transform component. /// Gets the rect transform component.
/// </summary> /// </summary>
public RectTransform RectTransform public RectTransform RectTransform
{ {
get get
{ {
return m_RectTransform; return m_RectTransform;
} }
} }
   
public void OnBeginDrag(PointerEventData eventData) public void OnBeginDrag(PointerEventData eventData)
{ {
if (m_RectTransform == null) if (m_RectTransform == null)
{ {
return; return;
} }
   
// cache starting positions // cache starting positions
m_BeginMousePosition = eventData.position; m_BeginMousePosition = eventData.position;
m_BeginWindowPosition = m_RectTransform.position; m_BeginWindowPosition = m_RectTransform.position;
} }
   
public void OnDrag(PointerEventData eventData) public void OnDrag(PointerEventData eventData)
{ {
if (m_RectTransform != null) if (m_RectTransform != null)
{ {
// new position is the starting window position plus the delta of the current and starting mouse positions // new position is the starting window position plus the delta of the current and starting mouse positions
m_RectTransform.position = m_BeginWindowPosition + (Vector3)(eventData.position - m_BeginMousePosition); m_RectTransform.position = m_BeginWindowPosition + (Vector3)(eventData.position - m_BeginMousePosition);
} }
} }
   
/// <summary> /// <summary>
/// Adds a game object as a child of the window content. /// Adds a game object as a child of the window content.
/// </summary> /// </summary>
public void AddToContent(GameObject childObject) public void AddToContent(GameObject childObject)
{ {
if (m_Content != null && childObject != null) if (m_Content != null && childObject != null)
{ {
childObject.transform.SetParent(m_Content, false); childObject.transform.SetParent(m_Content, false);
} }
} }
   
/// <summary> /// <summary>
/// Closes the window. /// Closes the window.
/// </summary> /// </summary>
public void Close() public void Close()
{ {
ScaleFade(1.0f, 0.0f, () => Destroy(gameObject)); ScaleFade(1.0f, 0.0f, () => Destroy(gameObject));
} }
   
/// <summary> /// <summary>
/// Sets the window title. /// Sets the window title.
/// </summary> /// </summary>
public void SetTitle(string title) public void SetTitle(string title)
{ {
if (m_Title != null) if (m_Title != null)
{ {
m_Title.text = title; m_Title.text = title;
} }
} }
   
/// <summary> /// <summary>
/// Sets the window size. /// Sets the window size.
/// </summary> /// </summary>
public void SetWidth(float width) public void SetWidth(float width)
{ {
if (m_RectTransform != null) if (m_RectTransform != null)
{ {
Vector2 size = m_RectTransform.sizeDelta; Vector2 size = m_RectTransform.sizeDelta;
size.x = width; size.x = width;
m_RectTransform.sizeDelta = size; m_RectTransform.sizeDelta = size;
} }
} }
   
protected virtual void Awake() protected virtual void Awake()
{ {
// component caching // component caching
m_RectTransform = GetComponent<RectTransform>(); m_RectTransform = GetComponent<RectTransform>();
m_CanvasGroup = GetComponent<CanvasGroup>(); m_CanvasGroup = GetComponent<CanvasGroup>();
} }
   
protected virtual void OnEnable() protected virtual void OnEnable()
{ {
// scales and fades the window into view // scales and fades the window into view
ScaleFade(0.0f, 1.0f, null); ScaleFade(0.0f, 1.0f, null);
} }
   
/// <summary> /// <summary>
/// Scales and fades from a value to another with callback. /// Scales and fades from a value to another with callback.
/// </summary> /// </summary>
private void ScaleFade(float from, float to, Action callback) private void ScaleFade(float from, float to, Action callback)
{ {
if (m_ScaleFadeCoroutine != null) if (m_ScaleFadeCoroutine != null)
{ {
StopCoroutine(m_ScaleFadeCoroutine); StopCoroutine(m_ScaleFadeCoroutine);
} }
   
m_ScaleFadeCoroutine = ScaleFadeCoroutine(from, to, callback); m_ScaleFadeCoroutine = ScaleFadeCoroutine(from, to, callback);
StartCoroutine(m_ScaleFadeCoroutine); StartCoroutine(m_ScaleFadeCoroutine);
} }
   
/// <summary> /// <summary>
/// Coroutine to handle the scale and fading of the window. /// Coroutine to handle the scale and fading of the window.
/// </summary> /// </summary>
private IEnumerator ScaleFadeCoroutine(float from, float to, Action callback) private IEnumerator ScaleFadeCoroutine(float from, float to, Action callback)
{ {
float progress = 0.0f; float progress = 0.0f;
float value; float value;
   
while (progress <= 1.0f) while (progress <= 1.0f)
{ {
progress += (Time.deltaTime / 0.2f); progress += (Time.deltaTime / 0.2f);
value = Mathf.Lerp(from, to, progress); value = Mathf.Lerp(from, to, progress);
   
// scale // scale
transform.localScale = Vector3.one * value; transform.localScale = Vector3.one * value;
   
// fade if a canvas group is attached // fade if a canvas group is attached
if (m_CanvasGroup != null) if (m_CanvasGroup != null)
{ {
m_CanvasGroup.alpha = Mathf.Clamp01(value); m_CanvasGroup.alpha = Mathf.Clamp01(value);
} }
   
yield return null; yield return null;
} }
   
callback?.Invoke(); if (callback != null)
  {
  callback.Invoke();
  }
   
m_ScaleFadeCoroutine = null; m_ScaleFadeCoroutine = null;
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2016 CYBUTEK // Copyright (C) 2016 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
namespace KerbalEngineer.Flight namespace KerbalEngineer.Flight
{ {
using System.Collections.Generic; using System.Collections.Generic;
using KSP.UI; using KSP.UI;
using Sections; using Sections;
using Unity.Flight; using Unity.Flight;
using UnityEngine; using UnityEngine;
   
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public class FlightAppLauncher : AppLauncherButton, IFlightAppLauncher public class FlightAppLauncher : AppLauncherButton, IFlightAppLauncher
{ {
private static FlightAppLauncher s_Instance; private static FlightAppLauncher s_Instance;
private FlightMenu m_FlightMenu; private FlightMenu m_FlightMenu;
private GameObject m_MenuObject; private GameObject m_MenuObject;
private GameObject m_MenuPrefab; private GameObject m_MenuPrefab;
   
/// <summary> /// <summary>
/// Gets the current instance of the FlightAppLauncher object. /// Gets the current instance of the FlightAppLauncher object.
/// </summary> /// </summary>
public static FlightAppLauncher Instance public static FlightAppLauncher Instance
{ {
get get
{ {
return s_Instance; return s_Instance;
} }
} }
   
/// <summary> /// <summary>
/// Applies the KSP theme to a game object and its children. /// Applies the KSP theme to a game object and its children.
/// </summary> /// </summary>
public void ApplyTheme(GameObject gameObject) public void ApplyTheme(GameObject gameObject)
{ {
StyleManager.Process(gameObject); StyleManager.Process(gameObject);
} }
   
/// <summary> /// <summary>
/// Clamps the given rect transform within the screen bounds. /// Clamps the given rect transform within the screen bounds.
/// </summary> /// </summary>
public void ClampToScreen(RectTransform rectTransform) public void ClampToScreen(RectTransform rectTransform)
{ {
UIMasterController.ClampToScreen(rectTransform, Vector2.zero); UIMasterController.ClampToScreen(rectTransform, Vector2.zero);
} }
   
/// <summary> /// <summary>
/// Gets a list of custom sections. /// Gets a list of custom sections.
/// </summary> /// </summary>
IList<ISectionModule> IFlightAppLauncher.GetCustomSections() IList<ISectionModule> IFlightAppLauncher.GetCustomSections()
{ {
return new List<ISectionModule>(SectionLibrary.CustomSections.ToArray()); return new List<ISectionModule>(SectionLibrary.CustomSections.ToArray());
} }
   
/// <summary> /// <summary>
/// Gets a list of stock sections. /// Gets a list of stock sections.
/// </summary> /// </summary>
IList<ISectionModule> IFlightAppLauncher.GetStockSections() IList<ISectionModule> IFlightAppLauncher.GetStockSections()
{ {
return new List<ISectionModule>(SectionLibrary.StockSections.ToArray()); return new List<ISectionModule>(SectionLibrary.StockSections.ToArray());
} }
   
/// <summary> /// <summary>
/// Gets or sets the control bar's visibility. /// Gets or sets the control bar's visibility.
/// </summary> /// </summary>
public bool IsControlBarVisible public bool IsControlBarVisible
{ {
get get
{ {
if (DisplayStack.Instance != null) if (DisplayStack.Instance != null)
{ {
return DisplayStack.Instance.ShowControlBar; return DisplayStack.Instance.ShowControlBar;
} }
   
return false; return false;
} }
set set
{ {
if (DisplayStack.Instance != null) if (DisplayStack.Instance != null)
{ {
DisplayStack.Instance.ShowControlBar = value; DisplayStack.Instance.ShowControlBar = value;
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets or sets the display stack's visibility. /// Gets or sets the display stack's visibility.
/// </summary> /// </summary>
public bool IsDisplayStackVisible public bool IsDisplayStackVisible
{ {
get get
{ {
if (DisplayStack.Instance != null) if (DisplayStack.Instance != null)
{ {
return DisplayStack.Instance.Hidden == false; return DisplayStack.Instance.Hidden == false;
} }
   
return false; return false;
} }
set set
{ {
if (DisplayStack.Instance != null) if (DisplayStack.Instance != null)
{ {
DisplayStack.Instance.Hidden = !value; DisplayStack.Instance.Hidden = !value;
} }
} }
} }
   
/// <summary> /// <summary>
/// Creates and initialises a new custom section. /// Creates and initialises a new custom section.
/// </summary> /// </summary>
public ISectionModule NewCustomSection() public ISectionModule NewCustomSection()
{ {
SectionModule section = new SectionModule SectionModule section = new SectionModule
{ {
Name = "Custom " + (SectionLibrary.CustomSections.Count + 1), Name = "Custom " + (SectionLibrary.CustomSections.Count + 1),
Abbreviation = "CUST " + (SectionLibrary.CustomSections.Count + 1), Abbreviation = "CUST " + (SectionLibrary.CustomSections.Count + 1),
IsVisible = true, IsVisible = true,
IsCustom = true, IsCustom = true,
IsEditorVisible = true IsEditorVisible = true
}; };
   
SectionLibrary.CustomSections.Add(section); SectionLibrary.CustomSections.Add(section);
   
return section; return section;
} }
   
protected override void Awake() protected override void Awake()
{ {
base.Awake(); base.Awake();
   
// set singleton instance // set singleton instance
s_Instance = this; s_Instance = this;
   
// cache menu prefab // cache menu prefab
if (m_MenuPrefab == null && AssetBundleLoader.Prefabs != null) if (m_MenuPrefab == null && AssetBundleLoader.Prefabs != null)
{ {
m_MenuPrefab = AssetBundleLoader.Prefabs.LoadAsset<GameObject>("FlightMenu"); m_MenuPrefab = AssetBundleLoader.Prefabs.LoadAsset<GameObject>("FlightMenu");
} }
} }
   
protected override void OnFalse() protected override void OnFalse()
{ {
Close(); Close();
} }
   
protected override void OnHover() protected override void OnHover()
{ {
Open(); Open();
} }
   
protected override void OnHoverOut() protected override void OnHoverOut()
{ {
if (IsOn == false) if (IsOn == false)
{ {
Close(); Close();
} }
} }
   
protected override void OnTrue() protected override void OnTrue()
{ {
Open(); Open();
} }
   
protected virtual void Update() protected virtual void Update()
{ {
if (Button == null) if (FlightEngineerCore.IsDisplayable)
return;  
   
if (FlightEngineerCore.IsDisplayable && Button.IsEnabled == false)  
{ {
Enable(); Enable();
} }
else if (FlightEngineerCore.IsDisplayable == false && Button.IsEnabled) else if (FlightEngineerCore.IsDisplayable == false)
{ {
Disable(); Disable();
} }
} }
   
/// <summary> /// <summary>
/// Closes the menu. /// Closes the menu.
/// </summary> /// </summary>
private void Close() private void Close()
{ {
if (m_FlightMenu != null) if (m_FlightMenu != null)
{ {
m_FlightMenu.Close(); m_FlightMenu.Close();
} }
else if (m_MenuObject != null) else if (m_MenuObject != null)
{ {
Destroy(m_MenuObject); Destroy(m_MenuObject);
} }
} }
   
/// <summary> /// <summary>
/// Opens the menu. /// Opens the menu.
/// </summary> /// </summary>
private void Open() private void Open()
{ {
// fade menu in if already open // fade menu in if already open
if (m_FlightMenu != null) if (m_FlightMenu != null)
{ {
m_FlightMenu.FadeIn(); m_FlightMenu.FadeIn();
return; return;
} }
   
if (m_MenuPrefab == null || m_MenuObject != null) if (m_MenuPrefab == null || m_MenuObject != null)
{ {
return; return;
} }
   
// create object // create object
m_MenuObject = Instantiate(m_MenuPrefab, GetAnchor(), Quaternion.identity) as GameObject; m_MenuObject = Instantiate(m_MenuPrefab, GetAnchor(), Quaternion.identity) as GameObject;
if (m_MenuObject == null) if (m_MenuObject == null)
{ {
return; return;
} }
   
StyleManager.Process(m_MenuObject); StyleManager.Process(m_MenuObject);
   
// set object as a child of the main canvas // set object as a child of the main canvas
m_MenuObject.transform.SetParent(MainCanvasUtil.MainCanvas.transform); m_MenuObject.transform.SetParent(MainCanvasUtil.MainCanvas.transform);
   
// set menu's reference to this object for cross-communication // set menu's reference to this object for cross-communication
m_FlightMenu = m_MenuObject.GetComponent<FlightMenu>(); m_FlightMenu = m_MenuObject.GetComponent<FlightMenu>();
if (m_FlightMenu != null) if (m_FlightMenu != null)
{ {
m_FlightMenu.SetFlightAppLauncher(this); m_FlightMenu.SetFlightAppLauncher(this);
} }
} }
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
#region Using Directives #region Using Directives
   
#endregion #endregion
   
namespace KerbalEngineer.Flight namespace KerbalEngineer.Flight
{ {
#region Using Directives #region Using Directives
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Extensions; using Extensions;
using Readouts; using Readouts;
using Sections; using Sections;
using Settings; using Settings;
using UnityEngine; using UnityEngine;
using VesselSimulator; using VesselSimulator;
   
#endregion #endregion
   
/// <summary> /// <summary>
/// Core management system for the Flight Engineer. /// Core management system for the Flight Engineer.
/// </summary> /// </summary>
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public sealed class FlightEngineerCore : MonoBehaviour public sealed class FlightEngineerCore : MonoBehaviour
{ {
#region Instance #region Instance
   
/// <summary> /// <summary>
/// Gets the current instance of FlightEngineerCore. /// Gets the current instance of FlightEngineerCore.
/// </summary> /// </summary>
public static FlightEngineerCore Instance { get; private set; } public static FlightEngineerCore Instance { get; private set; }
   
#endregion #endregion
   
#region Fields #region Fields
   
private static bool isCareerMode = true; private static bool isCareerMode = true;
private static bool isKerbalLimited = true; private static bool isKerbalLimited = true;
private static bool isTrackingStationLimited = true; private static bool isTrackingStationLimited = true;
   
#endregion #endregion
   
#region Constructors #region Constructors
   
static FlightEngineerCore() static FlightEngineerCore()
{ {
try try
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Get("isCareerMode", ref isCareerMode); handler.Get("isCareerMode", ref isCareerMode);
handler.Get("isKerbalLimited", ref isKerbalLimited); handler.Get("isKerbalLimited", ref isKerbalLimited);
handler.Get("isTrackingStationLimited", ref isTrackingStationLimited); handler.Get("isTrackingStationLimited", ref isTrackingStationLimited);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
#endregion #endregion
   
#region Properties #region Properties
   
/// <summary> /// <summary>
/// Gets and sets whether to the Flight Engineer should be run using career limitations. /// Gets and sets whether to the Flight Engineer should be run using career limitations.
/// </summary> /// </summary>
public static bool IsCareerMode public static bool IsCareerMode
{ {
get { return isCareerMode; } get { return isCareerMode; }
set set
{ {
try try
{ {
if (isCareerMode != value) if (isCareerMode != value)
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Set("isCareerMode", value); handler.Set("isCareerMode", value);
handler.Save("FlightEngineerCore.xml"); handler.Save("FlightEngineerCore.xml");
} }
isCareerMode = value; isCareerMode = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets whether the FlightEngineer should be displayed. /// Gets whether the FlightEngineer should be displayed.
/// </summary> /// </summary>
public static bool IsDisplayable public static bool IsDisplayable
{ {
get get
{ {
if (MainCanvasUtil.MainCanvas.enabled == false) if (MainCanvasUtil.MainCanvas.enabled == false)
{ {
return false; return false;
} }
   
if (isCareerMode) if (isCareerMode)
{ {
if (isKerbalLimited && FlightGlobals.ActiveVessel.GetVesselCrew().Exists(c => c.experienceTrait.TypeName == "Engineer")) if (isKerbalLimited && FlightGlobals.ActiveVessel.GetVesselCrew().Exists(c => c.experienceTrait.TypeName == "Engineer"))
{ {
return true; return true;
} }
if (isTrackingStationLimited && ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation) == 1.0f) if (isTrackingStationLimited && ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.TrackingStation) == 1.0f)
{ {
return true; return true;
} }
return FlightGlobals.ActiveVessel.parts.Any(p => p.HasModule<FlightEngineerModule>()); return FlightGlobals.ActiveVessel.parts.Any(p => p.HasModule<FlightEngineerModule>());
} }
   
return true; return true;
} }
} }
   
/// <summary> /// <summary>
/// Gets and sets whether to the Flight Engineer should be kerbal limited. /// Gets and sets whether to the Flight Engineer should be kerbal limited.
/// </summary> /// </summary>
public static bool IsKerbalLimited public static bool IsKerbalLimited
{ {
get { return isKerbalLimited; } get { return isKerbalLimited; }
set set
{ {
try try
{ {
if (isKerbalLimited != value) if (isKerbalLimited != value)
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Set("isKerbalLimited", value); handler.Set("isKerbalLimited", value);
handler.Save("FlightEngineerCore.xml"); handler.Save("FlightEngineerCore.xml");
} }
isKerbalLimited = value; isKerbalLimited = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets and sets whether to the Flight Engineer should be tracking station limited. /// Gets and sets whether to the Flight Engineer should be tracking station limited.
/// </summary> /// </summary>
public static bool IsTrackingStationLimited public static bool IsTrackingStationLimited
{ {
get { return isTrackingStationLimited; } get { return isTrackingStationLimited; }
set set
{ {
try try
{ {
if (isTrackingStationLimited != value) if (isTrackingStationLimited != value)
{ {
var handler = SettingHandler.Load("FlightEngineerCore.xml"); var handler = SettingHandler.Load("FlightEngineerCore.xml");
handler.Set("isTrackingStationLimited", value); handler.Set("isTrackingStationLimited", value);
handler.Save("FlightEngineerCore.xml"); handler.Save("FlightEngineerCore.xml");
} }
isTrackingStationLimited = value; isTrackingStationLimited = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
} }
   
/// <summary> /// <summary>
/// Gets the editor windows for sections with open editors. /// Gets the editor windows for sections with open editors.
/// </summary> /// </summary>
public List<SectionEditor> SectionEditors { get; private set; } public List<SectionEditor> SectionEditors { get; private set; }
   
/// <summary> /// <summary>
/// Gets the section windows for floating sections. /// Gets the section windows for floating sections.
/// </summary> /// </summary>
public List<SectionWindow> SectionWindows { get; private set; } public List<SectionWindow> SectionWindows { get; private set; }
   
/// <summary> /// <summary>
/// Gets the list of currently running updatable modules. /// Gets the list of currently running updatable modules.
/// </summary> /// </summary>
public List<IUpdatable> UpdatableModules { get; private set; } public List<IUpdatable> UpdatableModules { get; private set; }
   
#endregion #endregion
   
#region Methods #region Methods
   
/// <summary> /// <summary>
/// Creates a section editor, adds it to the FlightEngineerCore and returns a reference to it. /// Creates a section editor, adds it to the FlightEngineerCore and returns a reference to it.
/// </summary> /// </summary>
public SectionEditor AddSectionEditor(SectionModule section) public SectionEditor AddSectionEditor(SectionModule section)
{ {
try try
{ {
var editor = this.gameObject.AddComponent<SectionEditor>(); var editor = this.gameObject.AddComponent<SectionEditor>();
editor.ParentSection = section; editor.ParentSection = section;
editor.Position = new Rect(section.EditorPositionX, section.EditorPositionY, SectionEditor.Width, SectionEditor.Height); editor.Position = new Rect(section.EditorPositionX, section.EditorPositionY, SectionEditor.Width, SectionEditor.Height);
this.SectionEditors.Add(editor); this.SectionEditors.Add(editor);
return editor; return editor;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
return null; return null;
} }
} }
   
/// <summary> /// <summary>
/// Creates a section window, adds it to the FlightEngineerCore and returns a reference to it. /// Creates a section window, adds it to the FlightEngineerCore and returns a reference to it.
/// </summary> /// </summary>
public SectionWindow AddSectionWindow(SectionModule section) public SectionWindow AddSectionWindow(SectionModule section)
{ {
try try
{ {
var window = this.gameObject.AddComponent<SectionWindow>(); var window = this.gameObject.AddComponent<SectionWindow>();
window.ParentSection = section; window.ParentSection = section;
window.WindowPosition = new Rect(section.FloatingPositionX, section.FloatingPositionY, 0, 0); window.WindowPosition = new Rect(section.FloatingPositionX, section.FloatingPositionY, 0, 0);
this.SectionWindows.Add(window); this.SectionWindows.Add(window);
return window; return window;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
return null; return null;
} }
} }
   
/// <summary> /// <summary>
/// Adds an updatable object to be automatically updated every frame and will ignore duplicate objects. /// Adds an updatable object to be automatically updated every frame and will ignore duplicate objects.
/// </summary> /// </summary>
public void AddUpdatable(IUpdatable updatable) public void AddUpdatable(IUpdatable updatable)
{ {
try try
{ {
if (!this.UpdatableModules.Contains(updatable)) if (!this.UpdatableModules.Contains(updatable))
{ {
this.UpdatableModules.Add(updatable); this.UpdatableModules.Add(updatable);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Create base Flight Engineer child objects. /// Create base Flight Engineer child objects.
/// </summary> /// </summary>
private void Awake() private void Awake()
{ {
try try
{ {
Instance = this; Instance = this;
   
this.SectionWindows = new List<SectionWindow>(); this.SectionWindows = new List<SectionWindow>();
this.SectionEditors = new List<SectionEditor>(); this.SectionEditors = new List<SectionEditor>();
this.UpdatableModules = new List<IUpdatable>(); this.UpdatableModules = new List<IUpdatable>();
   
SimManager.UpdateModSettings(); SimManager.UpdateModSettings();
   
Logger.Log("FlightEngineerCore->Awake"); Logger.Log("FlightEngineerCore->Awake");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Fixed update all required Flight Engineer objects. /// Fixed update all required Flight Engineer objects.
/// </summary> /// </summary>
private void FixedUpdate() private void FixedUpdate()
{ {
if (FlightGlobals.ActiveVessel == null) if (FlightGlobals.ActiveVessel == null)
  {
return; return;
  }
   
try try
{ {
SectionLibrary.FixedUpdate(); SectionLibrary.FixedUpdate();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Force the destruction of child objects on core destruction. /// Force the destruction of child objects on core destruction.
/// </summary> /// </summary>
private void OnDestroy() private void OnDestroy()
{ {
try try
{ {
SectionLibrary.Save(); SectionLibrary.Save();
   
foreach (var window in this.SectionWindows) foreach (var window in this.SectionWindows)
{ {
print("[FlightEngineer]: Destroying Floating Window for " + window.ParentSection.Name); print("[FlightEngineer]: Destroying Floating Window for " + window.ParentSection.Name);
Destroy(window); Destroy(window);
} }
   
foreach (var editor in this.SectionEditors) foreach (var editor in this.SectionEditors)
{ {
print("[FlightEngineer]: Destroying Editor Window for " + editor.ParentSection.Name); print("[FlightEngineer]: Destroying Editor Window for " + editor.ParentSection.Name);
Destroy(editor); Destroy(editor);
} }
   
Logger.Log("FlightEngineerCore->OnDestroy"); Logger.Log("FlightEngineerCore->OnDestroy");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Initialises the object's state on creation. /// Initialises the object's state on creation.
/// </summary> /// </summary>
private void Start() private void Start()
{ {
try try
{ {
SectionLibrary.Load(); SectionLibrary.Load();
ReadoutLibrary.Reset(); ReadoutLibrary.Reset();
Logger.Log("FlightEngineerCore->Start"); Logger.Log("FlightEngineerCore->Start");
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Update all required Flight Engineer objects. /// Update all required Flight Engineer objects.
/// </summary> /// </summary>
private void Update() private void Update()
{ {
if (FlightGlobals.ActiveVessel == null) if (FlightGlobals.ActiveVessel == null)
  {
return; return;
  }
   
try try
{ {
SectionLibrary.Update(); SectionLibrary.Update();
this.UpdateModules(); this.UpdateModules();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
/// <summary> /// <summary>
/// Update all updatable modules. /// Update all updatable modules.
/// </summary> /// </summary>
private void UpdateModules() private void UpdateModules()
{ {
try try
{ {
foreach (var updatable in this.UpdatableModules) foreach (var updatable in this.UpdatableModules)
{ {
if (updatable is IUpdateRequest) if (updatable is IUpdateRequest)
{ {
var request = updatable as IUpdateRequest; var request = updatable as IUpdateRequest;
if (request.UpdateRequested) if (request.UpdateRequested)
{ {
updatable.Update(); updatable.Update();
request.UpdateRequested = false; request.UpdateRequested = false;
} }
} }
else else
{ {
updatable.Update(); updatable.Update();
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Exception(ex); Logger.Exception(ex);
} }
} }
   
#endregion #endregion
} }
} }
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using CompoundParts; using CompoundParts;
using Extensions; using Extensions;
using UnityEngine; using UnityEngine;
   
public class PartSim public class PartSim
{ {
private static readonly Pool<PartSim> pool = new Pool<PartSim>(Create, Reset); private static readonly Pool<PartSim> pool = new Pool<PartSim>(Create, Reset);
   
private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>(); private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
   
public double realMass; public double realMass;
public double baseMass; public double baseMass;
public double baseMassForCoM; public double baseMassForCoM;
public Vector3d centerOfMass; public Vector3d centerOfMass;
public double baseCost; public double baseCost;
public int decoupledInStage; public int decoupledInStage;
public bool fuelCrossFeed; public bool fuelCrossFeed;
public List<PartSim> fuelTargets = new List<PartSim>(); public List<PartSim> fuelTargets = new List<PartSim>();
public List<PartSim> surfaceMountFuelTargets = new List<PartSim>(); public List<PartSim> surfaceMountFuelTargets = new List<PartSim>();
public bool hasModuleEngines; public bool hasModuleEngines;
public bool hasMultiModeEngine; public bool hasMultiModeEngine;
   
public bool hasVessel; public bool hasVessel;
public String initialVesselName; public String initialVesselName;
public int inverseStage; public int inverseStage;
public bool isDecoupler; public bool isDecoupler;
public bool isEngine; public bool isEngine;
public bool isFuelLine; public bool isFuelLine;
public bool isFuelTank; public bool isFuelTank;
public bool isLanded; public bool isLanded;
public bool isNoPhysics; public bool isNoPhysics;
public bool isSepratron; public bool isSepratron;
//public bool isFairing; //public bool isFairing;
public float postStageMassAdjust; public float postStageMassAdjust;
public int stageIndex; public int stageIndex;
public String name; public String name;
public String noCrossFeedNodeKey; public String noCrossFeedNodeKey;
public PartSim parent; public PartSim parent;
public AttachModes parentAttach; public AttachModes parentAttach;
public Part part; // This is only set while the data structures are being initialised public Part part; // This is only set while the data structures are being initialised
public int partId = 0; public int partId = 0;
public ResourceContainer resourceDrains = new ResourceContainer(); public ResourceContainer resourceDrains = new ResourceContainer();
public ResourceContainer resourceFlowStates = new ResourceContainer(); public ResourceContainer resourceFlowStates = new ResourceContainer();
public ResourceContainer resources = new ResourceContainer(); public ResourceContainer resources = new ResourceContainer();
public double startMass = 0d; public double startMass = 0d;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
   
private static PartSim Create() private static PartSim Create()
{ {
return new PartSim(); return new PartSim();
} }
   
private static void Reset(PartSim partSim) private static void Reset(PartSim partSim)
{ {
for (int i = 0; i < partSim.attachNodes.Count; i++) for (int i = 0; i < partSim.attachNodes.Count; i++)
{ {
partSim.attachNodes[i].Release(); partSim.attachNodes[i].Release();
} }
partSim.attachNodes.Clear(); partSim.attachNodes.Clear();
partSim.fuelTargets.Clear(); partSim.fuelTargets.Clear();
partSim.surfaceMountFuelTargets.Clear(); partSim.surfaceMountFuelTargets.Clear();
partSim.resourceDrains.Reset(); partSim.resourceDrains.Reset();
partSim.resourceFlowStates.Reset(); partSim.resourceFlowStates.Reset();
partSim.resources.Reset(); partSim.resources.Reset();
partSim.parent = null; partSim.parent = null;
partSim.baseCost = 0d; partSim.baseCost = 0d;
partSim.baseMass = 0d; partSim.baseMass = 0d;
partSim.baseMassForCoM = 0d; partSim.baseMassForCoM = 0d;
partSim.startMass = 0d; partSim.startMass = 0d;
} }
   
public void Release() public void Release()
{ {
pool.Release(this); pool.Release(this);
} }
   
public static PartSim New(Part p, int id, double atmosphere, LogMsg log) public static PartSim New(Part p, int id, double atmosphere, LogMsg log)
{ {
PartSim partSim = pool.Borrow(); PartSim partSim = pool.Borrow();
   
partSim.part = p; partSim.part = p;
partSim.centerOfMass = p.transform.TransformPoint(p.CoMOffset); partSim.centerOfMass = p.transform.TransformPoint(p.CoMOffset);
partSim.partId = id; partSim.partId = id;
partSim.name = p.partInfo.name; partSim.name = p.partInfo.name;
   
if (log != null) log.buf.AppendLine("Create PartSim for " + partSim.name); if (log != null) log.buf.AppendLine("Create PartSim for " + partSim.name);
   
partSim.parent = null; partSim.parent = null;
partSim.parentAttach = p.attachMode; partSim.parentAttach = p.attachMode;
partSim.fuelCrossFeed = p.fuelCrossFeed; partSim.fuelCrossFeed = p.fuelCrossFeed;
partSim.noCrossFeedNodeKey = p.NoCrossFeedNodeKey; partSim.noCrossFeedNodeKey = p.NoCrossFeedNodeKey;
partSim.decoupledInStage = partSim.DecoupledInStage(p); partSim.decoupledInStage = partSim.DecoupledInStage(p);
partSim.isFuelLine = p.HasModule<CModuleFuelLine>(); partSim.isFuelLine = p.HasModule<CModuleFuelLine>();
partSim.isFuelTank = p is FuelTank; partSim.isFuelTank = p is FuelTank;
partSim.isSepratron = partSim.IsSepratron(); partSim.isSepratron = partSim.IsSepratron();
partSim.inverseStage = p.inverseStage; partSim.inverseStage = p.inverseStage;
//MonoBehaviour.print("inverseStage = " + inverseStage); //MonoBehaviour.print("inverseStage = " + inverseStage);
   
partSim.baseCost = p.GetCostDry(); partSim.baseCost = p.GetCostDry();
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Parent part = " + (p.parent == null ? "null" : p.parent.partInfo.name)); log.buf.AppendLine("Parent part = " + (p.parent == null ? "null" : p.parent.partInfo.name));
log.buf.AppendLine("physicalSignificance = " + p.physicalSignificance); log.buf.AppendLine("physicalSignificance = " + p.physicalSignificance);
log.buf.AppendLine("PhysicsSignificance = " + p.PhysicsSignificance); log.buf.AppendLine("PhysicsSignificance = " + p.PhysicsSignificance);
} }
   
// Work out if the part should have no physical significance // Work out if the part should have no physical significance
// The root part is never "no physics" // The root part is never "no physics"
partSim.isNoPhysics = p.physicalSignificance == Part.PhysicalSignificance.NONE || partSim.isNoPhysics = p.physicalSignificance == Part.PhysicalSignificance.NONE ||
p.PhysicsSignificance == 1; p.PhysicsSignificance == 1;
   
if (p.HasModule<LaunchClamp>()) if (p.HasModule<LaunchClamp>())
{ {
partSim.realMass = 0d; partSim.realMass = 0d;
if (log != null) log.buf.AppendLine("Ignoring mass of launch clamp"); if (log != null) log.buf.AppendLine("Ignoring mass of launch clamp");
} }
else else
{ {
partSim.realMass = p.mass; partSim.realMass = p.mass;
if (log != null) log.buf.AppendLine("Using part.mass of " + p.mass); if (log != null) log.buf.AppendLine("Using part.mass of " + p.mass);
} }
   
partSim.postStageMassAdjust = 0f; partSim.postStageMassAdjust = 0f;
if (log != null) log.buf.AppendLine("Calculating postStageMassAdjust, prefabMass = " + p.prefabMass); if (log != null) log.buf.AppendLine("Calculating postStageMassAdjust, prefabMass = " + p.prefabMass);
int count = p.Modules.Count; int count = p.Modules.Count;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
if (log != null) log.buf.AppendLine("Module: " + p.Modules[i].moduleName); if (log != null) log.buf.AppendLine("Module: " + p.Modules[i].moduleName);
IPartMassModifier partMassModifier = p.Modules[i] as IPartMassModifier; IPartMassModifier partMassModifier = p.Modules[i] as IPartMassModifier;
if (partMassModifier != null) if (partMassModifier != null)
{ {
if (log != null) log.buf.AppendLine("ChangeWhen = " + partMassModifier.GetModuleMassChangeWhen()); if (log != null) log.buf.AppendLine("ChangeWhen = " + partMassModifier.GetModuleMassChangeWhen());
if (partMassModifier.GetModuleMassChangeWhen() == ModifierChangeWhen.STAGED) if (partMassModifier.GetModuleMassChangeWhen() == ModifierChangeWhen.STAGED)
{ {
float preStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.UNSTAGED); float preStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.UNSTAGED);
float postStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.STAGED); float postStage = partMassModifier.GetModuleMass(p.prefabMass, ModifierStagingSituation.STAGED);
if (log != null) log.buf.AppendLine("preStage = " + preStage + " postStage = " + postStage); if (log != null) log.buf.AppendLine("preStage = " + preStage + " postStage = " + postStage);
partSim.postStageMassAdjust += (postStage - preStage); partSim.postStageMassAdjust += (postStage - preStage);
} }
} }
} }
if (log != null) log.buf.AppendLine("postStageMassAdjust = " + partSim.postStageMassAdjust); if (log != null) log.buf.AppendLine("postStageMassAdjust = " + partSim.postStageMassAdjust);
   
for (int i = 0; i < p.Resources.Count; i++) for (int i = 0; i < p.Resources.Count; i++)
{ {
PartResource resource = p.Resources[i]; PartResource resource = p.Resources[i];
   
// Make sure it isn't NaN as this messes up the part mass and hence most of the values // Make sure it isn't NaN as this messes up the part mass and hence most of the values
// This can happen if a resource capacity is 0 and tweakable // This can happen if a resource capacity is 0 and tweakable
if (!Double.IsNaN(resource.amount)) if (!Double.IsNaN(resource.amount))
{ {
if (log != null) if (log != null)
log.buf.AppendLine(resource.resourceName + " = " + resource.amount); log.buf.AppendLine(resource.resourceName + " = " + resource.amount);
   
partSim.resources.Add(resource.info.id, resource.amount); partSim.resources.Add(resource.info.id, resource.amount);
partSim.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); partSim.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
} }
else else
{ {
if (log != null) log.buf.AppendLine(resource.resourceName + " is NaN. Skipping."); if (log != null) log.buf.AppendLine(resource.resourceName + " is NaN. Skipping.");
} }
} }
   
partSim.hasVessel = (p.vessel != null); partSim.hasVessel = (p.vessel != null);
partSim.isLanded = partSim.hasVessel && p.vessel.Landed; partSim.isLanded = partSim.hasVessel && p.vessel.Landed;
if (partSim.hasVessel) if (partSim.hasVessel)
{ {
partSim.vesselName = p.vessel.vesselName; partSim.vesselName = p.vessel.vesselName;
partSim.vesselType = p.vesselType; partSim.vesselType = p.vesselType;
} }
partSim.initialVesselName = p.initialVesselName; partSim.initialVesselName = p.initialVesselName;
   
partSim.hasMultiModeEngine = p.HasModule<MultiModeEngine>(); partSim.hasMultiModeEngine = p.HasModule<MultiModeEngine>();
partSim.hasModuleEngines = p.HasModule<ModuleEngines>(); partSim.hasModuleEngines = p.HasModule<ModuleEngines>();
   
partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEngines; partSim.isEngine = partSim.hasMultiModeEngine || partSim.hasModuleEngines;
   
if (log != null) log.buf.AppendLine("Created " + partSim.name + ". Decoupled in stage " + partSim.decoupledInStage); if (log != null) log.buf.AppendLine("Created " + partSim.name + ". Decoupled in stage " + partSim.decoupledInStage);
   
return partSim; return partSim;
} }
   
public ResourceContainer ResourceDrains public ResourceContainer ResourceDrains
{ {
get get
{ {
return resourceDrains; return resourceDrains;
} }
} }
   
public ResourceContainer Resources public ResourceContainer Resources
{ {
get get
{ {
return resources; return resources;
} }
} }
   
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log) public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("CreateEngineSims for " + this.name); log.buf.AppendLine("CreateEngineSims for " + this.name);
for (int i = 0; i < this.part.Modules.Count; i++) for (int i = 0; i < this.part.Modules.Count; i++)
{ {
PartModule partMod = this.part.Modules[i]; PartModule partMod = this.part.Modules[i];
log.buf.AppendLine("Module: " + partMod.moduleName); log.buf.AppendLine("Module: " + partMod.moduleName);
} }
} }
   
if (hasMultiModeEngine) if (hasMultiModeEngine)
{ {
// A multi-mode engine has multiple ModuleEngines but only one is active at any point // A multi-mode engine has multiple ModuleEngines but only one is active at any point
// The mode of the engine is the engineID of the ModuleEngines that is active // The mode of the engine is the engineID of the ModuleEngines that is active
string mode = part.GetModule<MultiModeEngine>().mode; string mode = part.GetModule<MultiModeEngine>().mode;
   
List<ModuleEngines> engines = part.GetModules<ModuleEngines>(); List<ModuleEngines> engines = part.GetModules<ModuleEngines>();
for (int i = 0; i < engines.Count; ++i) for (int i = 0; i < engines.Count; ++i)
{ {
ModuleEngines engine = engines[i]; ModuleEngines engine = engines[i];
if (engine.engineID == mode) if (engine.engineID == mode)
{ {
if (log != null) log.buf.AppendLine("Module: " + engine.moduleName); if (log != null) log.buf.AppendLine("Module: " + engine.moduleName);
   
EngineSim engineSim = EngineSim.New( EngineSim engineSim = EngineSim.New(
this, this,
engine, engine,
atmosphere, atmosphere,
(float)mach, (float)mach,
vectoredThrust, vectoredThrust,
fullThrust, fullThrust,
log); log);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
else if (hasModuleEngines) else if (hasModuleEngines)
{ {
List<ModuleEngines> engines = part.GetModules<ModuleEngines>(); List<ModuleEngines> engines = part.GetModules<ModuleEngines>();
for (int i = 0; i < engines.Count; ++i) for (int i = 0; i < engines.Count; ++i)
{ {
ModuleEngines engine = engines[i]; ModuleEngines engine = engines[i];
if (log != null) log.buf.AppendLine("Module: " + engine.moduleName); if (log != null) log.buf.AppendLine("Module: " + engine.moduleName);
   
EngineSim engineSim = EngineSim.New( EngineSim engineSim = EngineSim.New(
this, this,
engine, engine,
atmosphere, atmosphere,
(float)mach, (float)mach,
vectoredThrust, vectoredThrust,
fullThrust, fullThrust,
log); log);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
   
if (log != null) if (log != null)
{ {
log.Flush(); log.Flush();
} }
} }
   
public int DecouplerCount() public int DecouplerCount()
{ {
int count = 0; int count = 0;
PartSim partSim = this; PartSim partSim = this;
while (partSim != null) while (partSim != null)
{ {
if (partSim.isDecoupler) if (partSim.isDecoupler)
{ {
count++; count++;
} }
   
partSim = partSim.parent; partSim = partSim.parent;
} }
return count; return count;
} }
   
public void DrainResources(double time) public void DrainResources(double time)
{ {
//MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")"); //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
for (int i = 0; i < resourceDrains.Types.Count; ++i) for (int i = 0; i < resourceDrains.Types.Count; ++i)
{ {
int type = resourceDrains.Types[i]; int type = resourceDrains.Types[i];
   
//MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type)); //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
resources.Add(type, -time * resourceDrains[type]); resources.Add(type, -time * resourceDrains[type]);
//MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]); //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
} }
} }
   
public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix) public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
{ {
if (parent != null) if (parent != null)
{ {
prefix = parent.DumpPartAndParentsToBuffer(buffer, prefix) + " "; prefix = parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
} }
   
DumpPartToBuffer(buffer, prefix); DumpPartToBuffer(buffer, prefix);
   
return prefix; return prefix;
} }
   
public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null) public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)
{ {
buffer.Append(prefix); buffer.Append(prefix);
buffer.Append(name); buffer.Append(name);
buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage); buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage);
   
//buffer.AppendFormat(", vesselName = '{0}'", vesselName); //buffer.AppendFormat(", vesselName = '{0}'", vesselName);
//buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(vesselType)); //buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(vesselType));
//buffer.AppendFormat(", initialVesselName = '{0}'", initialVesselName); //buffer.AppendFormat(", initialVesselName = '{0}'", initialVesselName);
   
buffer.AppendFormat(", isNoPhys = {0}", isNoPhysics); buffer.AppendFormat(", isNoPhys = {0}", isNoPhysics);
buffer.AppendFormat(", baseMass = {0}", baseMass); buffer.AppendFormat(", baseMass = {0}", baseMass);
buffer.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM); buffer.AppendFormat(", baseMassForCoM = {0}", baseMassForCoM);
   
buffer.AppendFormat(", fuelCF = {0}", fuelCrossFeed); buffer.AppendFormat(", fuelCF = {0}", fuelCrossFeed);
buffer.AppendFormat(", noCFNKey = '{0}'", noCrossFeedNodeKey); buffer.AppendFormat(", noCFNKey = '{0}'", noCrossFeedNodeKey);
   
buffer.AppendFormat(", isSep = {0}", isSepratron); buffer.AppendFormat(", isSep = {0}", isSepratron);
   
for (int i = 0; i < resources.Types.Count; i++) for (int i = 0; i < resources.Types.Count; i++)
{ {
int type = resources.Types[i]; int type = resources.Types[i];
buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]); buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]);
} }
   
if (attachNodes.Count > 0) if (attachNodes.Count > 0)
{ {
buffer.Append(", attached = <"); buffer.Append(", attached = <");
attachNodes[0].DumpToBuffer(buffer); attachNodes[0].DumpToBuffer(buffer);
for (int i = 1; i < attachNodes.Count; i++) for (int i = 1; i < attachNodes.Count; i++)
{ {
buffer.Append(", "); buffer.Append(", ");
attachNodes[i].DumpToBuffer(buffer); attachNodes[i].DumpToBuffer(buffer);
} }
buffer.Append(">"); buffer.Append(">");
} }
   
// Add more info here // Add more info here
   
buffer.Append("]\n"); buffer.Append("]\n");
   
if (allParts != null) if (allParts != null)
{ {
String newPrefix = prefix + " "; String newPrefix = prefix + " ";
for (int i = 0; i < allParts.Count; i++) for (int i = 0; i < allParts.Count; i++)
{ {
PartSim partSim = allParts[i]; PartSim partSim = allParts[i];
if (partSim.parent == this) if (partSim.parent == this)
partSim.DumpPartToBuffer(buffer, newPrefix, allParts); partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
} }
} }
} }
   
public bool EmptyOf(HashSet<int> types) public bool EmptyOf(HashSet<int> types)
{ {
foreach (int type in types) foreach (int type in types)
{ {
if (resources.HasType(type) && resourceFlowStates[type] != 0 && resources[type] > SimManager.RESOURCE_PART_EMPTY_THRESH) if (resources.HasType(type) && resourceFlowStates[type] != 0 && resources[type] > SimManager.RESOURCE_PART_EMPTY_THRESH)
return false; return false;
} }
   
return true; return true;
} }
   
public double GetMass(int currentStage, bool forCoM = false) public double GetMass(int currentStage, bool forCoM = false)
{ {
if (decoupledInStage >= currentStage) if (decoupledInStage >= currentStage)
return 0d; return 0d;
   
double mass = forCoM ? baseMassForCoM : baseMass; double mass = forCoM ? baseMassForCoM : baseMass;
   
for (int i = 0; i < resources.Types.Count; ++i) for (int i = 0; i < resources.Types.Count; ++i)
{ {
mass += resources.GetResourceMass(resources.Types[i]); mass += resources.GetResourceMass(resources.Types[i]);
} }
   
if (postStageMassAdjust != 0.0 && currentStage <= inverseStage) if (postStageMassAdjust != 0.0 && currentStage <= inverseStage)
{ {
mass += postStageMassAdjust; mass += postStageMassAdjust;
} }
   
return mass; return mass;
} }
   
public double GetCost(int currentStage) public double GetCost(int currentStage)
{ {
if (decoupledInStage >= currentStage) if (decoupledInStage >= currentStage)
return 0d; return 0d;
   
double cost = baseCost; double cost = baseCost;
   
for (int i = 0; i < resources.Types.Count; ++i) for (int i = 0; i < resources.Types.Count; ++i)
{ {
cost += resources.GetResourceCost(resources.Types[i]); cost += resources.GetResourceCost(resources.Types[i]);
} }
   
return cost; return cost;
} }
   
public void ReleasePart() public void ReleasePart()
{ {
this.part = null; this.part = null;
} }
   
// All functions below this point must not rely on the part member (it may be null) // All functions below this point must not rely on the part member (it may be null)
// //
   
public void GetSourceSet(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent) public void GetSourceSet(int type, bool includeSurfaceMountedParts, List<PartSim> allParts, HashSet<PartSim> visited, HashSet<PartSim> allSources, LogMsg log, String indent)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId); log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId);
indent += " "; indent += " ";
} }
   
// Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is. // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns as is.
if (visited.Contains(this)) if (visited.Contains(this))
{ {
if (log != null) log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")");
return; return;
} }
   
if (log != null) log.buf.AppendLine(indent + "Adding this to visited"); if (log != null) log.buf.AppendLine(indent + "Adding this to visited");
   
visited.Add(this); visited.Add(this);
   
// Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed.
// Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.
//MonoBehaviour.print("for each fuel line"); //MonoBehaviour.print("for each fuel line");
   
int lastCount = allSources.Count; int lastCount = allSources.Count;
   
for (int i = 0; i < this.fuelTargets.Count; i++) for (int i = 0; i < this.fuelTargets.Count; i++)
{ {
PartSim partSim = this.fuelTargets[i]; PartSim partSim = this.fuelTargets[i];
if (partSim != null) if (partSim != null)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); partSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent);
} }
} }
} }
   
// check surface mounted fuel targets // check surface mounted fuel targets
if (includeSurfaceMountedParts) if (includeSurfaceMountedParts)
{ {
for (int i = 0; i < surfaceMountFuelTargets.Count; i++) for (int i = 0; i < surfaceMountFuelTargets.Count; i++)
{ {
PartSim partSim = this.surfaceMountFuelTargets[i]; PartSim partSim = this.surfaceMountFuelTargets[i];
if (partSim != null) if (partSim != null)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSim.GetSourceSet(type, true, allParts, visited, allSources, log, indent); partSim.GetSourceSet(type, true, allParts, visited, allSources, log, indent);
} }
} }
} }
} }
   
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " fuel target sources (" + this.name + ":" + this.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " fuel target sources (" + this.name + ":" + this.partId + ")");
return; return;
} }
   
   
// Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed
   
// Rule 4: Part performs scan on each of its axially mounted neighbors. // Rule 4: Part performs scan on each of its axially mounted neighbors.
// Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, // Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side,
// skip the points on the side where multiple points are. [Experiment] // skip the points on the side where multiple points are. [Experiment]
// Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. // Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list.
// The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment] // The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]
if (fuelCrossFeed) if (fuelCrossFeed)
{ {
lastCount = allSources.Count; lastCount = allSources.Count;
//MonoBehaviour.print("for each attach node"); //MonoBehaviour.print("for each attach node");
for (int i = 0; i < this.attachNodes.Count; i++) for (int i = 0; i < this.attachNodes.Count; i++)
{ {
AttachNodeSim attachSim = this.attachNodes[i]; AttachNodeSim attachSim = this.attachNodes[i];
if (attachSim.attachedPartSim != null) if (attachSim.attachedPartSim != null)
{ {
if (attachSim.nodeType == AttachNode.NodeType.Stack) if (attachSim.nodeType == AttachNode.NodeType.Stack)
{ {
if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false) if ((string.IsNullOrEmpty(noCrossFeedNodeKey) == false && attachSim.id.Contains(noCrossFeedNodeKey)) == false)
{ {
if (visited.Contains(attachSim.attachedPartSim)) if (visited.Contains(attachSim.attachedPartSim))
{ {
if (log != null) log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
   
attachSim.attachedPartSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); attachSim.attachedPartSim.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent);
} }
} }
} }
} }
} }
   
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " attached sources (" + this.name + ":" + this.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " attached sources (" + this.name + ":" + this.partId + ")");
return; return;
} }
} }
   
// Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel
// type was not disabled [Experiment]) and it contains fuel, it returns itself. // type was not disabled [Experiment]) and it contains fuel, it returns itself.
// Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel
// type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment] // type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]
if (resources.HasType(type) && resourceFlowStates[type] > 0.0) if (resources.HasType(type) && resourceFlowStates[type] > 0.0)
{ {
if (resources[type] > SimManager.RESOURCE_MIN) if (resources[type] > SimManager.RESOURCE_MIN)
{ {
allSources.Add(this); allSources.Add(this);
   
if (log != null) log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")");
} }
   
return; return;
} }
else else
{ {
if (log != null) log.buf.AppendLine(indent + "Not fuel tank or disabled. HasType = " + resources.HasType(type) + " FlowState = " + resourceFlowStates[type]); if (log != null) log.buf.AppendLine(indent + "Not fuel tank or disabled. HasType = " + resources.HasType(type) + " FlowState = " + resourceFlowStates[type]);
} }
   
// Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its
// parent and returns whatever the parent scan returned. [Experiment] [Experiment] // parent and returns whatever the parent scan returned. [Experiment] [Experiment]
if (parent != null && parentAttach == AttachModes.SRF_ATTACH) if (parent != null && parentAttach == AttachModes.SRF_ATTACH)
{ {
if (fuelCrossFeed) if (fuelCrossFeed)
{ {
if (visited.Contains(parent)) if (visited.Contains(parent))
{ {
if (log != null) log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
} }
else else
{ {
lastCount = allSources.Count; lastCount = allSources.Count;
this.parent.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent); this.parent.GetSourceSet(type, includeSurfaceMountedParts, allParts, visited, allSources, log, indent);
if (allSources.Count > lastCount) if (allSources.Count > lastCount)
{ {
if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " parent sources (" + this.name + ":" + this.partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning " + (allSources.Count - lastCount) + " parent sources (" + this.name + ":" + this.partId + ")");
return; return;
} }
} }
} }
} }
   
// Rule 8: If all preceding rules failed, part returns empty list. // Rule 8: If all preceding rules failed, part returns empty list.
if (log != null) log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")"); if (log != null) log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
   
return; return;
} }
   
public double GetStartMass() public double GetStartMass()
{ {
return startMass; return startMass;
} }
   
public void RemoveAttachedParts(HashSet<PartSim> partSims) public void RemoveAttachedParts(HashSet<PartSim> partSims)
{ {
// Loop through the attached parts // Loop through the attached parts
for (int i = 0; i < this.attachNodes.Count; i++) for (int i = 0; i < this.attachNodes.Count; i++)
{ {
AttachNodeSim attachSim = this.attachNodes[i]; AttachNodeSim attachSim = this.attachNodes[i];
// If the part is in the set then "remove" it by clearing the PartSim reference // If the part is in the set then "remove" it by clearing the PartSim reference
if (partSims.Contains(attachSim.attachedPartSim)) if (partSims.Contains(attachSim.attachedPartSim))
{ {
attachSim.attachedPartSim = null; attachSim.attachedPartSim = null;
} }
} }
   
// Loop through the fuel targets (fuel line sources) // Loop through the fuel targets (fuel line sources)
for (int i = 0; i < this.fuelTargets.Count; i++) for (int i = 0; i < this.fuelTargets.Count; i++)
{ {
PartSim fuelTargetSim = this.fuelTargets[i]; PartSim fuelTargetSim = this.fuelTargets[i];
// If the part is in the set then "remove" it by clearing the PartSim reference // If the part is in the set then "remove" it by clearing the PartSim reference
if (fuelTargetSim != null && partSims.Contains(fuelTargetSim)) if (fuelTargetSim != null && partSims.Contains(fuelTargetSim))
{ {
this.fuelTargets[i] = null; this.fuelTargets[i] = null;
} }
} }
   
// Loop through the surface attached fuel targets (surface attached parts for new flow modes) // Loop through the surface attached fuel targets (surface attached parts for new flow modes)
for (int i = 0; i < this.surfaceMountFuelTargets.Count; i++) for (int i = 0; i < this.surfaceMountFuelTargets.Count; i++)
{ {
PartSim fuelTargetSim = this.surfaceMountFuelTargets[i]; PartSim fuelTargetSim = this.surfaceMountFuelTargets[i];
// If the part is in the set then "remove" it by clearing the PartSim reference // If the part is in the set then "remove" it by clearing the PartSim reference
if (fuelTargetSim != null && partSims.Contains(fuelTargetSim)) if (fuelTargetSim != null && partSims.Contains(fuelTargetSim))
{ {
this.surfaceMountFuelTargets[i] = null; this.surfaceMountFuelTargets[i] = null;
} }
} }
} }
   
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (log != null) log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + ""); if (log != null) log.buf.AppendLine("SetupAttachNodes for " + name + ":" + partId + "");
   
attachNodes.Clear(); attachNodes.Clear();
   
for (int i = 0; i < part.attachNodes.Count; ++i) for (int i = 0; i < part.attachNodes.Count; ++i)
{ {
AttachNode attachNode = part.attachNodes[i]; AttachNode attachNode = part.attachNodes[i];
   
if (log != null) log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); if (log != null) log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
   
if (attachNode.attachedPart != null && attachNode.id != "Strut") if (attachNode.attachedPart != null && attachNode.id != "Strut")
{ {
PartSim attachedSim; PartSim attachedSim;
if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
{ {
if (log != null) log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + ""); if (log != null) log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
   
attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType)); attachNodes.Add(AttachNodeSim.New(attachedSim, attachNode.id, attachNode.nodeType));
} }
else else
{ {
if (log != null) log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")"); if (log != null) log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
} }
} }
} }
   
for (int i = 0; i < part.fuelLookupTargets.Count; ++i) for (int i = 0; i < part.fuelLookupTargets.Count; ++i)
{ {
Part p = part.fuelLookupTargets[i]; Part p = part.fuelLookupTargets[i];
   
if (p != null) if (p != null)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue(p, out targetSim)) if (partSimLookup.TryGetValue(p, out targetSim))
{ {
if (log != null) log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId); if (log != null) log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
   
fuelTargets.Add(targetSim); fuelTargets.Add(targetSim);
} }
else else
{ {
if (log != null) log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")"); if (log != null) log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
} }
} }
} }
} }
   
public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (part.parent != null) if (part.parent != null)
{ {
parent = null; parent = null;
if (partSimLookup.TryGetValue(part.parent, out parent)) if (partSimLookup.TryGetValue(part.parent, out parent))
{ {
if (log != null) log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId); if (log != null) log.buf.AppendLine("Parent part is " + parent.name + ":" + parent.partId);
if (part.attachMode == AttachModes.SRF_ATTACH && part.attachRules.srfAttach && part.fuelCrossFeed && part.parent.fuelCrossFeed) if (part.attachMode == AttachModes.SRF_ATTACH && part.attachRules.srfAttach && part.fuelCrossFeed && part.parent.fuelCrossFeed)
{ {
if (log != null) log.buf.AppendLine("Added " + name + " to " + parent.name + " surface mounted fuel targets."); if (log != null) log.buf.AppendLine("Added " + name + " to " + parent.name + " surface mounted fuel targets.");
parent.surfaceMountFuelTargets.Add(this); parent.surfaceMountFuelTargets.Add(this);
} }
} }
else else
{ {
if (log != null) log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")"); if (log != null) log.buf.AppendLine("No PartSim for parent part (" + part.parent.partInfo.name + ")");
} }
} }
} }
   
public double TimeToDrainResource() public double TimeToDrainResource()
{ {
//MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")"); //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
double time = double.MaxValue; double time = double.MaxValue;
   
for (int i = 0; i < resourceDrains.Types.Count; ++i) for (int i = 0; i < resourceDrains.Types.Count; ++i)
{ {
int type = resourceDrains.Types[i]; int type = resourceDrains.Types[i];
   
if (resourceDrains[type] > 0) if (resourceDrains[type] > 0)
{ {
time = Math.Min(time, resources[type] / resourceDrains[type]); time = Math.Min(time, resources[type] / resourceDrains[type]);
//MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time); //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time);
} }
} }
   
//if (time < double.MaxValue) //if (time < double.MaxValue)
// MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time); // MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);
return time; return time;
} }
   
private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log) private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log)
{ {
if (thrustTransforms == null) if (thrustTransforms == null)
{ {
return Vector3.forward; return Vector3.forward;
} }
   
Vector3 thrustvec = Vector3.zero; Vector3 thrustvec = Vector3.zero;
for (int i = 0; i < thrustTransforms.Count; ++i) for (int i = 0; i < thrustTransforms.Count; ++i)
{ {
Transform trans = thrustTransforms[i]; Transform trans = thrustTransforms[i];
   
if (log != null) log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude); if (log != null) log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
   
thrustvec -= trans.forward; thrustvec -= trans.forward;
} }
   
if (log != null) log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); if (log != null) log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); if (log != null) log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
   
return thrustvec; return thrustvec;
} }
   
private int DecoupledInStage(Part thePart, int stage = -1) private int DecoupledInStage(Part thePart, int stage = -1)
{ {
if (IsDecoupler(thePart) && thePart.inverseStage > stage) if (IsDecoupler(thePart) && thePart.inverseStage > stage)
stage = thePart.inverseStage; stage = thePart.inverseStage;
   
if (thePart.parent != null) if (thePart.parent != null)
stage = DecoupledInStage(thePart.parent, stage); stage = DecoupledInStage(thePart.parent, stage);
   
return stage; return stage;
} }
   
private bool IsActiveDecoupler(Part thePart) private bool IsActiveDecoupler(Part thePart)
{ {
return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) || return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||
thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled); thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);
} }
   
private bool IsDecoupler(Part thePart) private bool IsDecoupler(Part thePart)
{ {
return thePart.GetProtoModuleDecoupler()?.IsStageEnabled ?? false; PartExtensions.ProtoModuleDecoupler protoDecoupler = thePart.GetProtoModuleDecoupler();
  return protoDecoupler != null && protoDecoupler.IsStageEnabled;
} }
   
private bool IsSepratron() private bool IsSepratron()
{ {
if (!part.ActivatesEvenIfDisconnected) if (!part.ActivatesEvenIfDisconnected)
{ {
return false; return false;
} }
   
if (part is SolidRocket) if (part is SolidRocket)
{ {
return true; return true;
} }
   
IEnumerable<ModuleEngines> modList = part.Modules.OfType<ModuleEngines>(); IEnumerable<ModuleEngines> modList = part.Modules.OfType<ModuleEngines>();
if (modList.Count() == 0) if (modList.Count() == 0)
{ {
return false; return false;
} }
   
if (modList.First().throttleLocked) if (modList.First().throttleLocked)
{ {
return true; return true;
} }
   
return false; return false;
} }
} }
} }