A Kerbal Space Program mod that adds balloon parts to create aerostats
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
12 KiB

9 years ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Aerostats
{
public class ModuleBalloonCase : PartModule
9 years ago
{
/// <summary>
/// Maximum rate at which gas can be injected in the balloon to fill it, in m^3/s at stp
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
public float MaxGasFillRate = 100;
/// <summary>
/// Maximum rate at which gas can be removed from the balloon to deflate it, in m^3/s at stp
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
public float MaxGasVentRate = 500;
/// <summary>
/// Weight, in kilograms, of 1m^3 of gas at 100kPa and 0°C
/// Helium is 179g/m^3
/// Hydrogen is 90g/m^3
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
public float GasDensity = 0.179f;
/// <summary>
/// Quantity of gas that the system is trying to get inside the balloon, in m^3 at 100kPa and 0°C. If the balloon contains more gas, some will be vented, otherwise, gas will be injected.
/// If the balloon can not store the target amount of gas (because the maximum volume has been reached), the system won't try to inject more gas, to avoid venting through the security valve of the balloon.
/// </summary>
[KSPField(isPersistant = true, guiActive = true)]
9 years ago
[UI_FloatRange(minValue = 0, maxValue = 20000.0f, stepIncrement = 100.0f)]
public float LiftingGasTargetQuantity = 100.0f;
9 years ago
/// <summary>
9 years ago
/// Minimum amount of gas that will be used to inflate the balloon at the beginning. The balloon can not be deflated below this amount.
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
9 years ago
public float MinimumFillQuantity = 10.0f;
/// <summary>
/// Length of spring that can not be extended (no force applied under this length)
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
public float SpringRestLength = 10.0f;
/// <summary>
/// Force applied by the spring between the balloon and the vessel, proportional to spring extension, in newton per meter
/// </summary>
[KSPField(isPersistant = false, guiActive = false)]
public float SpringHardness = 5000.0f;
[KSPField(isPersistant = false, guiActive = true)]
public string Status;
private bool Deployed;
9 years ago
private bool Destroyed;
private Vector3 EstimatedNextFramePosition;
9 years ago
/// <summary>
/// Gas quantity currently inside the balloon
/// </summary>
[KSPField(guiName = "Gas quantity", isPersistant = true, guiActive = true)]
9 years ago
public float LiftingGasQuantity;
[KSPField(guiName = "Volume", isPersistant = false, guiActive = true)]
public string VolumeStatus;
private ModuleBalloon Balloon;
private LineRenderer Spring;
public static void RemoveAttachJointBetween(Part part1, Part part2)
{
if (part1.attachJoint && ((part1.attachJoint.Host == part1 && part1.attachJoint.Target == part2) || (part1.attachJoint.Host == part2 && part1.attachJoint.Target == part1)))
{
part1.attachJoint.DestroyJoint();
}
if (part2.attachJoint && ((part2.attachJoint.Host == part2 && part2.attachJoint.Target == part1) || (part2.attachJoint.Host == part1 && part2.attachJoint.Target == part2)))
{
part2.attachJoint.DestroyJoint();
}
}
[KSPEvent(guiActive = true, active = true, externalToEVAOnly = false, guiActiveUnfocused = true, guiName = "Deploy balloon", unfocusedRange = 5)]
public void Deploy()
{
if (Destroyed || Deployed)
return;
9 years ago
Deployed = true;
9 years ago
UnityEngine.Debug.Log("Aerostats: staged");
Util.PostDebugScreenMessage("staged");
/*var balloonObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
balloonObject.SetActive(false);
balloonObject.transform.position = part.Rigidbody.position + part.Rigidbody.transform.up;
var balloonPart = balloonObject.AddComponent<Part>();
balloonPart.children = new List<Part>();
balloonPart.partTransform = balloonObject.transform;
balloonPart.name = "heliumBalloon";
new GameObject("model").transform.parent = balloonPart.partTransform;
balloonPart.SetHierarchyRoot(balloonPart);
balloonPart.vessel = vessel;
balloonPart.setParent(part);*/
AvailablePart avPart = PartLoader.Instance.parts.Single(p => p.name == "heliumBalloon");
var balloonPart = (Part)UnityEngine.Object.Instantiate(avPart.partPrefab);
var balloonObject = balloonPart.gameObject;
balloonObject.transform.position = part.Rigidbody.position + part.Rigidbody.transform.up * 3.0f;
//balloonObject.transform.rotation = part.Rigidbody.rotation;
balloonPart.SetHierarchyRoot(balloonPart);
balloonPart.vessel = vessel;
balloonPart.setParent(part);
Balloon = balloonObject.GetComponent<ModuleBalloon>();
Balloon.part = balloonPart;
float initialGasQuantity = Mathf.Min(0.1f, part.RequestResource("Helium", MinimumFillQuantity));
balloonObject.SetActive(true);
GameEvents.onVesselWasModified.Fire(vessel);
Balloon.Initialize(this, part.rigidbody.velocity, GasDensity, initialGasQuantity);
LiftingGasQuantity = Balloon.LiftingGasQuantity;
Spring = part.gameObject.AddComponent<LineRenderer>();
Spring.useWorldSpace = true;
Spring.material = new Material(Shader.Find("VertexLit"));
Spring.SetColors(Color.black, Color.black);
Spring.SetWidth(0.2f, 0.2f);
Spring.SetVertexCount(2);
part.OnJustAboutToBeDestroyed += OnPartDestroyed;
EstimatedNextFramePosition = part.Rigidbody.position;
}
[KSPEvent(guiActive = true, active = true, externalToEVAOnly = false, guiActiveUnfocused = true, guiName = "Cut rope", unfocusedRange = 5)]
public void Cut()
{
if (Destroyed || !Deployed)
return;
if (Balloon != null)
{
Balloon.OnDetached();
Balloon.part.decouple(); // after that, the balloon becomes an independent vessel
Balloon.part.vessel.vesselName = vessel.vesselName + " balloon";
Balloon = null;
}
Destroy(Spring);
Spring = null;
part.OnJustAboutToBeDestroyed -= OnPartDestroyed;
Destroyed = true;
}
public override void OnStart(PartModule.StartState state)
{
if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) { return; }
Util.PostDebugScreenMessage("Aerostat loaded");
part.stagingIcon = "PARACHUTES";
}
private void OnPartDestroyed()
{
Cut();
9 years ago
}
private void CheckKrakensbane()
{
// detect Krakensbane teleportation, and fix up the balloon position (otherwise it results in instant ship disintegration due to extreme forces on the spring)
if ((part.Rigidbody.position - EstimatedNextFramePosition).magnitude > 1000.0f)
{
Util.PostDebugScreenMessage("Krakensbane teleportation detected! (dist=" + (part.Rigidbody.position - EstimatedNextFramePosition).magnitude + ")");
var offset = part.rigidbody.position - EstimatedNextFramePosition;
Balloon.rigidbody.position += offset;
Balloon.transform.position = Balloon.rigidbody.position;
}
EstimatedNextFramePosition = part.rigidbody.position + part.rigidbody.velocity * Time.fixedDeltaTime;
}
9 years ago
private void FixedUpdate()
{
if (Destroyed)
{
Status = "separated";
VolumeStatus = "-";
return;
}
if (GameSettings.LAUNCH_STAGES.GetKeyDown() && vessel.isActiveVessel && (part.inverseStage == Staging.CurrentStage - 1 || Staging.CurrentStage == 0))
9 years ago
{
Deploy();
9 years ago
}
if (Deployed)
9 years ago
{
// makes sure the balloon can move freely relatively to the vessel (apparently, KSP creates a joint when attaching a part)
RemoveAttachJointBetween(Balloon.part, part);
CheckKrakensbane();
float currentMaxQuantity = Balloon.GetCurrentMaxQuantity();
LiftingGasTargetQuantity = Math.Max(MinimumFillQuantity, LiftingGasTargetQuantity);
if(LiftingGasTargetQuantity > Balloon.LiftingGasQuantity)
{
// inflating balloon
float stepFinalQuantity = Math.Min(Balloon.LiftingGasQuantity + MaxGasFillRate * Time.fixedDeltaTime, Math.Min(LiftingGasTargetQuantity, currentMaxQuantity));
float step = Math.Max(stepFinalQuantity - Balloon.LiftingGasQuantity, 0.0f);
float stepResource = part.RequestResource("Helium", step);
LiftingGasQuantity = Balloon.InjectGas(stepResource);
if(step > 0.0f)
{
Status = stepResource > 0.0f ? "inflating" : "out-of-gas";
}
else
{
Status = "nominal";
}
}
else
{
// deflating balloon
Status = LiftingGasTargetQuantity == Balloon.LiftingGasQuantity ? "nominal" : "deflating";
float quantityAfterVenting = Math.Max(Balloon.LiftingGasQuantity - MaxGasVentRate * Time.fixedDeltaTime, LiftingGasTargetQuantity);
LiftingGasQuantity = Balloon.InjectGas(quantityAfterVenting - Balloon.LiftingGasQuantity);
}
if(Balloon.IsVenting)
9 years ago
{
Status = "full (venting)";
}
VolumeStatus = Mathf.Round(Balloon.CurrentVolume) + " / " + Mathf.Round(Balloon.MaxVolume);
// spring between balloon and base
float restLength = SpringRestLength + Balloon.Radius;
var springVec = Balloon.rigidbody.position - part.rigidbody.position;
float springLength = springVec.magnitude;
float springForceMag = 0;
var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Balloon.Radius;
if(springLength > restLength)
{
float tensingLength = springLength - restLength;
9 years ago
springForceMag = tensingLength * SpringHardness;
var springForce = springVec * (springForceMag / springLength * 0.001f);
part.rigidbody.AddForce(springForce, ForceMode.Force);
Balloon.rigidbody.AddForceAtPosition(-springForce, balloonAttachPoint, ForceMode.Force);
}
Util.PostDebugSingleScreenMessage("spring force", "Spring force = " + springForceMag + "N");
}
else
{
Status = "packed";
VolumeStatus = "-";
}
}
private void LateUpdate()
{
if (Destroyed)
return;
if (Deployed)
{
CheckKrakensbane();
var balloonAttachPoint = Balloon.rigidbody.position - Balloon.transform.up.normalized * Balloon.Radius;
Spring.SetPosition(0, part.transform.position);
Spring.SetPosition(1, balloonAttachPoint);
}
}
private void Update()
{
if (Destroyed)
return;
if (Deployed)
{
CheckKrakensbane();
}
}
9 years ago
}
}