From 44d161f5c0bee7da1e113e5e677b53e0354f50bb Mon Sep 17 00:00:00 2001 From: annabunches Date: Thu, 6 Feb 2025 19:02:13 -0500 Subject: [PATCH] Initial SE scripts. --- airlock.cs | 258 ++++++++++++++++++++++++++++++++++++++++++ docking.cs | 88 ++++++++++++++ maintenance_panels.cs | 130 +++++++++++++++++++++ 3 files changed, 476 insertions(+) create mode 100644 airlock.cs create mode 100644 docking.cs create mode 100644 maintenance_panels.cs diff --git a/airlock.cs b/airlock.cs new file mode 100644 index 0000000..82dc200 --- /dev/null +++ b/airlock.cs @@ -0,0 +1,258 @@ +enum AirlockState { + Off, + DoorsClosing, + Cycling, + DoorOpening, + Cooldown, +} + +enum AirlockLightState { + Off, + Cycling, + Cooldown, + Error, +} + +string airlockName; +IEnumerator state; +float targetOxygenLevel = 0.0F; + +// Blocks we need to track +IMyDoor innerDoor; +IMyDoor outerDoor; +List lights; +IMyAirVent airVent; +IMyAirVent airSensor; // TODO: we don't use this yet +IMyGasTank oxygenTank; + +public Program() +{ + airlockName = Me.CustomName.Split(' ')[0]; + state = null; + + // Find the correct objects + InitDoors(); + InitVents(); + InitLights(); + InitOxygen(); +} + +private void InitDoors() { + List onlyDoors = new List(); + GridTerminalSystem.GetBlocksOfType(onlyDoors); + foreach (IMyDoor door in onlyDoors) { + if (door.CustomName.StartsWith(airlockName)) { + if (door.CustomName.Contains("Inner")) { + innerDoor = door; + } + else if (door.CustomName.Contains("Outer")) { + outerDoor = door; + } + } + + if (innerDoor != null && outerDoor != null) { + break; + } + } +} + +private void InitVents() { + List onlyFans = new List(); + GridTerminalSystem.GetBlocksOfType(onlyFans); + foreach (IMyAirVent vent in onlyFans) { + if (vent.CustomName.StartsWith(airlockName)) { + if (vent.CustomName.Contains("Main")) { + airVent = vent; + } + else if (vent.CustomName.Contains("Reference")) { + airSensor = vent; + } + } + + // A global reference vent will be used if we don't have one specific to our airlock. + // A specific vent found later will overwrite this assignment. + if(vent.CustomName.StartsWith("Airlock Reference") && airSensor == null) { + airSensor = vent; + } + } +} + +private void InitLights() { + lights = new List(); + List allLights = new List(); + GridTerminalSystem.GetBlocksOfType(allLights); + foreach (IMyLightingBlock light in allLights) { + if (light.CustomName.StartsWith(airlockName)) { + lights.Add(light); + } + } +} + +private void InitOxygen() { + List allTanks = new List(); + GridTerminalSystem.GetBlocksOfType(allTanks); + foreach (IMyGasTank tank in allTanks) { + if (tank.CustomName.StartsWith(airlockName)) { + oxygenTank = tank; + break; + } + } +} + +public void Main(string argument, UpdateType updateSource) +{ + if (state == null) { + state = CycleAirlock(); + Runtime.UpdateFrequency = UpdateFrequency.Update1; + return; + } + + if (!state.MoveNext()) { + state.Dispose(); + state = null; + Runtime.UpdateFrequency = UpdateFrequency.None; + } +} + +private IEnumerator CycleAirlock() { + SetLights(AirlockLightState.Cycling); + + CloseDoors(); + while(!DoorsClosed()) { yield return true; } + LockDoors(); + + if (!airVent.CanPressurize) { + Error("Airlock is not airtight."); + yield return false; + } + + PressurizeDepressurize(); + while(!PressureStabilized()) { yield return true; } + airVent.Enabled = false; + + OpenDoor(); + while(!DoorOpened()) { yield return true; } + LockDoors(); + + // Balance oxygen storage + SetLights(AirlockLightState.Cooldown); + if (BalanceOxygen()) { + while(!OxygenBalanced()) { yield return true; } + } + airVent.Enabled = false; + + // Cooldown period + Runtime.UpdateFrequency = UpdateFrequency.Update100; + yield return true; + Runtime.UpdateFrequency = UpdateFrequency.Update1; + + SetLights(AirlockLightState.Off); +} + +private void CloseDoors() { + Echo("DEBUG: Closing Doors"); + + // close the doors + innerDoor.Enabled = true; + outerDoor.Enabled = true; + innerDoor.CloseDoor(); + outerDoor.CloseDoor(); +} + +private bool DoorsClosed() { + return innerDoor.Status == DoorStatus.Closed && outerDoor.Status == DoorStatus.Closed; +} + +private void PressurizeDepressurize() { + Echo("DEBUG: Cycling"); + + // toggle the current state + airVent.Depressurize = !airVent.Depressurize; + airVent.Enabled = true; + + // When depressurizing, check the external pressure and only depressurize to that value. + // TODO: test this for floating point errors + if (airVent.Depressurize && airSensor != null) { + targetOxygenLevel = airSensor.GetOxygenLevel(); + Echo($"Set depressurization target to {targetOxygenLevel}"); + } +} + +// TODO: this should check for depressurizing to the *reference* amount +private bool PressureStabilized() { + return ((airVent.Depressurize && airVent.GetOxygenLevel() <= targetOxygenLevel) || + (!airVent.Depressurize && airVent.Status == VentStatus.Pressurized)); +} + +// Open the appropriate door based on pressure state. +private void OpenDoor() { + Echo("DEBUG: Opening Door"); + + if (airVent.Status == VentStatus.Pressurized) { + innerDoor.Enabled = true; + innerDoor.OpenDoor(); + } else { + outerDoor.Enabled = true; + outerDoor.OpenDoor(); + } +} + +private bool DoorOpened() { + return innerDoor.Status == DoorStatus.Open || outerDoor.Status == DoorStatus.Open; +} + +// Returns false if we are in a state where we can't or don't need to balance +private bool BalanceOxygen() { + if (innerDoor.Status == DoorStatus.Closed || outerDoor.Status == DoorStatus.Open || OxygenBalanced()) { return false; } + + Echo("DEBUG: Balancing Oxygen Tank"); + + // Configure the vent to suck in Oxygen. + airVent.Depressurize = true; + airVent.Enabled = true; + return true; +} + +private bool OxygenBalanced() { + return oxygenTank.FilledRatio > 0.25; +} + +// TODO: blinkenlights are unsatisfying right now... +private void SetLights(AirlockLightState lightState) { + float blinkLength = 1.0F; + float blinkInterval = 0.0F; + Color color = Color.Red; + + switch(lightState) { + case AirlockLightState.Off: + color = Color.Green; + break; + case AirlockLightState.Cycling: + blinkInterval = 1.0F; + blinkLength = 0.75F; + break; + case AirlockLightState.Cooldown: + color = Color.Yellow; + break; + case AirlockLightState.Error: + // the defaults already set this correctly + break; + } + + foreach (IMyLightingBlock light in lights) { + light.Enabled = true; + light.BlinkIntervalSeconds = blinkInterval; + light.BlinkLength = blinkLength; + light.Color = color; + } +} + +private void LockDoors() { + innerDoor.Enabled = false; + outerDoor.Enabled = false; +} + +private void Error(string error) { + Echo(error); + SetLights(AirlockLightState.Error); +} \ No newline at end of file diff --git a/docking.cs b/docking.cs new file mode 100644 index 0000000..c145467 --- /dev/null +++ b/docking.cs @@ -0,0 +1,88 @@ +// Docking controller script; handles docking and undocking. +// Specifically, when you activate the script it will: +// * Connect any Connectors whose names start with "Docking Port" that are ready to connect. +// * Turn off all engines. +// * Set all O2 and Hydrogen tanks to Stockpile. +// * Set all batteries to Recharge. +// +// When you activate the script with "-undock" it will reverse all of these actions. +// +// TODO: None of the below switches work yet. +// You can selectively disable some functionality with switches. +// * "-no-refuel" to disable refueling +// * "-no-recharge" to disable battery recharge +// (batteries will be left in whatever state they are already in) + +MyCommandLine cli; + +List dockingPorts; +List thrusters; +List batteries; +List tanks; + +public void Program() { + cli = new MyCommandLine(); + + dockingPorts = new List(); + List allConnectors = new List(); + foreach(IMyShipConnector connector in allConnectors) { + if (connector.CustomName.StartsWith("Docking Port")) { + dockingPorts.Add(connector); + } + } + + thrusters = new List(); + GridTerminalSystem.GetBlocksOfType(thrusters); + + batteries = new List(); + GridTerminalSystem.GetBlocksOfType(batteries); + + tanks = new List(); + GridTerminalSystem.GetBlocksOfType(tanks); +} + +public void Main(string argument, UpdateType updateSource) { + cli.TryParse(argument); + if (cli.Switch("undock")) Undock(); + else Dock(); +} + +private void Dock() { + foreach (IMyShipConnector dockingPort in dockingPorts) { + if (dockingPort.Status == MyShipConnectorStatus.Connectable) { + dockingPort.Connect(); + } + } + + foreach (IMyThrust thruster in thrusters) { + thruster.Enabled = false; + } + + foreach (IMyBatteryBlock battery in batteries) { + battery.ChargeMode = ChargeMode.Recharge; + } + + foreach (IMyGasTank tank in tanks) { + tank.Stockpile = true; + } +} + +private void Undock() { + foreach (IMyBatteryBlock battery in batteries) { + battery.ChargeMode = ChargeMode.Recharge; + } + + foreach (IMyGasTank tank in tanks) { + tank.Stockpile = true; + } + + foreach (IMyThrust thruster in thrusters) { + thruster.Enabled = true; + } + + foreach (IMyShipConnector dockingPort in dockingPorts) { + if (dockingPort.Status == MyShipConnectorStatus.Connected) { + dockingPort.Disconnect(); + } + } +} \ No newline at end of file diff --git a/maintenance_panels.cs b/maintenance_panels.cs new file mode 100644 index 0000000..19b2f13 --- /dev/null +++ b/maintenance_panels.cs @@ -0,0 +1,130 @@ +// Script for opening maintence panels on a ship. +// +// Currently this simply opens and closes *all* of the panels attached to hinges +// whose names start with "Maintenance". Default behavior is to open. +// Pass the argument -close to close the panels. +// +// By default "open" is assumed to be 90 degrees and "closed" is assumed to be 0 degrees. +// Each hinge can have its behavior configured via custom data. A sample config +// (with the default values): +// +// OpenAngle=90 +// ClosedAngle=0 +// Velocity=5 + + +MyCommandLine cli; +IEnumerator state; +List panels; + +public class Panel { + public Panel(IMyMotorStator hinge) { + Hinge = hinge; + ParseConfig(); + } + + public void OpenPanel() { + Hinge.RotorLock = false; + TargetAngle = OpenAngle; + Hinge.RotateToAngle(MyRotationDirection.AUTO, TargetAngle, Velocity); + } + + public void ClosePanel() { + Hinge.RotorLock = false; + TargetAngle = ClosedAngle; + Hinge.RotateToAngle(MyRotationDirection.AUTO, TargetAngle, Velocity); + } + + // Call this function every tick after OpenPanel or ClosePanel. + // It will return true when the panel has finished moving. + public bool Monitor() { + if (Math.Abs(Hinge.Angle - TargetAngle) < 0.01) { + Hinge.RotorLock = true; + } + return Hinge.RotorLock; + } + + private IMyMotorStator Hinge { get; set; } + private float TargetAngle { get; set; } + private float OpenAngle { get; set; } = 90F; + private float ClosedAngle { get; set; } = 0F; + private float Velocity { get; set; } = 5F; + + private void ParseConfig() { + string[] lines = Hinge.CustomData.Split('\n'); + foreach (string line in lines) { + string[] tokens = line.Split('='); + if (tokens.Length != 2) continue; + switch(tokens[0]) { + case "OpenAngle": + OpenAngle = float.Parse(tokens[1]); + break; + case "ClosedAngle": + ClosedAngle = float.Parse(tokens[1]); + break; + case "Velocity": + Velocity = float.Parse(tokens[1]); + break; + } + } + } +} + +public Program() { + cli = new MyCommandLine(); + state = null; + + panels = new List(); + List allHinges = new List(); + foreach(IMyMotorStator hinge in allHinges) { + if (hinge.CustomName.StartsWith("Maintenance")) { + panels.Add(new Panel(hinge)); + } + } + + Echo($"Found {panels.Length} panels."); +} + +public void Main(string argument, UpdateType updateSource) { + if (state == null) { + cli.TryParse(argument); + if (cli.Switch("close")) state = ClosePanels(); + else state = OpenPanels(); + Runtime.UpdateFrequency = UpdateFrequency.Update1; + return; + } + + if (!state.MoveNext()) { + state.Dispose(); + state = null; + Runtime.UpdateFrequency = UpdateFrequency.None; + } +} + +private IEnumerator OpenPanels() { + Echo("Opening panels."); + foreach (Panel panel in panels) { + panel.OpenPanel(); + } + return MonitorPanels(); +} + +private IEnumerator ClosePanels() { + Echo("Closing panels."); + foreach (Panel panel in panels) { + panel.ClosePanel(); + } + return MonitorPanels(); +} + +private IEnumerator MonitorPanels() { + while (true) { + Echo("Monitoring panels."); + bool done = true; // assume we've finished, then falsify it below + foreach (Panel panel in panels) { + if (!panel.Monitor()) done = false; + } + if (done) yield break; + yield return true; + } +}