diff --git a/glib/maneuver_atmosphere.to2 b/glib/maneuver_atmosphere.to2 index 4399432..968bbdf 100644 --- a/glib/maneuver_atmosphere.to2 +++ b/glib/maneuver_atmosphere.to2 @@ -2,7 +2,7 @@ // Atmospheric Maneuvers // -use { Vessel, VesselSituation, AutopilotMode, DeltaVSituation, ParachuteSafeStates } from ksp::vessel +use { Vessel, VesselSituation, AutopilotMode, DeltaVSituation, ParachuteSafeStates, FlightCtrlState } from ksp::vessel use { GeoCoordinates, find_body } from ksp::orbit use { sleep, current_time, wait_until, yield } from ksp::game use { warp_to } from ksp::game::warp @@ -16,7 +16,7 @@ use { format } from core::str use { warning, panic, pretty_time, pretty_trend, pretty_distance } from glib::display use { Node, ErrorNode, circularize_at_ap, circularize_at_pe, change_pe_at_ap, change_ap_at_pe } from glib::maneuver_vacuum use { Mission } from glib::mission -use { Universe, Locations, lon_sunny_side, lat_nice_spot, check_and_finish_ssb } from glib::utility +use { Universe, Locations, lon_sunny_side, lat_nice_spot, check_and_finish_ssb, has_ssb } from glib::utility // Burn a maneuver node that is part of an atmospheric ascent // This means: @@ -119,6 +119,233 @@ pub fn burnNextNodeAtm(mission: Mission, ap_target: float) -> Unit = { node.remove() } +// +// Launch the vessel from a planet with an atmosphere into orbit using a gravity turn +// +pub fn gravity_turn(mission: Mission, target_apoapsis: float, + turn_start: float, turn_pitch: float, heading: float) -> Unit = { + + const vessel = mission.vessel + const con = mission.console.value + const startDV = mission.getTotalDV() + + // If we are landed or pre-launch, launch + if (vessel.situation == VesselSituation.Landed || vessel.situation == VesselSituation.PreLaunch) {4 + con.log("Vessel is on the ground. Launch.") + vessel.set_throttle(1) + sleep(0.2) + vessel.autopilot.mode = AutopilotMode.StabilityAssist + vessel.staging.next() + sleep(0.3) + + // find launch clamps and check if they are still latched + for (part in vessel.parts) + { + if (part.is_launch_clamp) + { + con.log("Found a launch clamp") + if (part.launch_clamp.defined) + { + con.log("Releasing launch clamps!") + let lac = part.launch_clamp.value + if (!lac.is_released) + lac.release() + } + } + } + } + + // calculate gravity turn altitudes + const atm_depth = vessel.main_body.atmosphere_depth + con.log(format("Target Apoapsis {0:N0}km, heading {1:N0} deg.", (target_apoapsis/1000.0, heading))) + con.h3 = format("Gravity turn will start at {0:N1} m/s", turn_start) + + let spd = 0.0 + let alt = vessel.altitude_terrain + let t_apo = 0.0 + // Phase one: burn until turn start speed in m/s + while(vessel.surface_velocity.magnitude < turn_start-5) { + spd = vessel.vertical_surface_speed + con.h4 = format(" speed: {0,6:N1}m/s, alt: {0,6:N1}", (spd, vessel.altitude_terrain)) + con.update() + t_apo = vessel.orbit.next_apoapsis_time().value - current_time() + sleep(0.1) + } + vessel.override_input_roll(0) + + // + // start pitching maneuver + // + con.h3 = format("Pitching over to {0:N1}°...",(turn_pitch)) + con.update() + ksp::game::warp::cancel() + vessel.autopilot.mode = AutopilotMode.Autopilot + + // pitch over + const target_pitch = turn_pitch + for (pitch in 0..(target_pitch*5).to_int-1) { + sleep(0.15) + con.update() + vessel.autopilot.target_orientation = vessel.heading_direction(heading, 90.0-pitch/5, 0).vector + } + sleep(2) + vessel.autopilot.target_orientation = vessel.heading_direction(heading, 90-target_pitch, 0).vector + sleep(4) + + spd = vessel.surface_velocity.magnitude + t_apo = vessel.orbit.next_apoapsis_time().value - current_time() + + // + // start gravity turn + // + con.h3 = "Gravity turn..." + ksp::game::warp::cancel() + vessel.autopilot.mode = AutopilotMode.Prograde + + let th = 100.0 + let targetApTime = 50.0 + let accApt = 0.0 + let lastApt = t_apo + let hasSsb = has_ssb(mission) + + // Phase two: burn until trajectory reaches apoapsis + while(vessel.orbit.apoapsis.value < target_apoapsis) { + spd = vessel.surface_velocity.magnitude + t_apo = vessel.orbit.next_apoapsis_time().value - current_time() + + accApt = t_apo - lastApt + lastApt = t_apo + + // TODO: should probably use a PID controller for this + let apTimeDiff = t_apo - targetApTime + let throttleDiff = abs(apTimeDiff/accApt)/1.7 + if (abs(accApt) > 0.05) + { + if (throttleDiff > 5) + throttleDiff = 5.0 + } + else + { + if (throttleDiff > 1) + throttleDiff = 1.0 + } + + // make sure we don't over- or undershoot! + if (t_apo >= targetApTime && accApt > 0) + th -= throttleDiff + if (t_apo <= targetApTime && accApt < 0) + th += throttleDiff + + if (th >= 100) + th = 100 + else + con.h3 = "Throttling down to keep apoapsis..." + + if (th <= 30) + { + th = 30 + if (hasSsb) + { + targetApTime = lastApt + } + } + + let dv = vessel.delta_v.stage(vessel.staging.current) + let burnTime = dv.value.burn_time + + // cancel warp before staging + if (burnTime < 5.0 && !has_ssb(mission) && ksp::game::warp::current_rate() > 1) + { + ksp::game::warp::cancel() + } + // increase throttle before staging + if (burnTime < 3.0 && !has_ssb(mission)) + { + th *= 2 + } + + // set throttle according to calculations + vessel.set_throttle(th/100.0) + + // Check if we need to stage. + // Sometimes stage command is not executed, + // also check if there is any ignited engine and repeat if not + if( mission.flameout_any() || !mission.ignited_any()) { + + if (mission.flameout()) // all flamed out? + th = 50 + + vessel.set_throttle(.05) + con.log("Flameout, advancing to new stage.") + mission.stage() + vessel.set_throttle(th/100.0) + } + + con.h4 = format(" apoapsis in: {0,6:N1}s", (t_apo)) + con.h5 = format(" stage burn remaining:{0,6:N1}s", (burnTime)) + + con.update_slow() + sleep(0.1/ksp::game::warp::current_rate()) + } + + ksp::game::warp::cancel() + vessel.set_throttle(0) + vessel.autopilot.mode = AutopilotMode.Prograde + sleep(0.1) + + // If a solid state booster is still burning, wait for it to finish and stage + check_and_finish_ssb(mission) + + t_apo = vessel.orbit.next_apoapsis_time().value + con.h5 = format("Apoapsis {0:N0}km in {1:N0} seconds.", (vessel.orbit.apoapsis.value/1000, t_apo)) + + // + // start gravity turn + // + con.h3 = "Coasting..." + con.update() + sleep(1) + // use time warp out of atmosphere during coasting + warp_to(t_apo) + + while(vessel.altitude_sealevel < vessel.main_body.atmosphere_depth) { + // boost if needed + if (vessel.orbit.apoapsis.value < target_apoapsis) + { + for(i in 1..7 ) + { + vessel.set_throttle(i/100.0) + sleep(0.04) + } + for(i in 5..1 ) + { + vessel.set_throttle(i/100.0) + sleep(0.04) + } + vessel.set_throttle(0) + } + con.update() + sleep(0.5) + } + sleep(0.5) + ksp::game::warp::cancel() + sleep(0.5) + + // Circularize at apoapsis + let n1 = circularize_at_ap(vessel) + mission.addManeuver(n1) + // align with maneuver + vessel.autopilot.mode = AutopilotMode.Maneuver + sleep(1) // give it some time + + // execute + mission.burnNextNode() + + const endDV = mission.getTotalDV() + con.log("circularization burn complete.") + con.log(format("Gravity turn launch complete. DV used {0:N0}m/s", (startDV - endDV))) +} + // Launch the vessel from a planet with an atmosphere into orbit // - It doesn't trust maneuver nodes until it is in space // - Less likely to overshoot apoapsis diff --git a/glib/maneuver_vacuum.to2 b/glib/maneuver_vacuum.to2 index f5574d3..43d9e2a 100644 --- a/glib/maneuver_vacuum.to2 +++ b/glib/maneuver_vacuum.to2 @@ -54,7 +54,7 @@ impl Node { // Plan: We need to step through every remaining stage, and calculate burn time for the stage let stage = vessel.staging.count - let dv_remaining = self.delta_v.magnitude + let dv_remaining = 0.0 //mission.getTotalDV() self.burn_time = 0.0 self.burn_stages = 0 self.burn_info = "stages: " @@ -137,7 +137,7 @@ pub sync fn ErrorNode() -> Node = { pub sync fn t_hohmann(orbit : Orbit, alt1: float, alt2: float) -> float = { let body_radius = 1.0 // XXX let body_mu = orbit.reference_body.grav_parameter - return PI * sqrt( (alt1+alt2+2*body_radius)^3/(8*body_mu) ) + return PI * sqrt( (alt1+alt2+2*body_radius)**3/(8*body_mu) ) } // Velocity at a specific altitude for current orbit. diff --git a/glib/mission.to2 b/glib/mission.to2 index 7462759..d1ca902 100644 --- a/glib/mission.to2 +++ b/glib/mission.to2 @@ -62,6 +62,20 @@ impl Mission { return true } + // Check if any engines have flamed out. + sync fn flameout_any(self) -> bool = { + for(engine in self.vessel.engines) + if(engine.has_ignited && engine.is_flameout) return true + return false + } + + // Check if any engines are ignited. + sync fn ignited_any(self) -> bool = { + for(engine in self.vessel.engines) + if(engine.has_ignited) return true + return false + } + // Check if we have landed or splashed down sync fn landed(self) -> bool = { if (self.vessel.situation != VesselSituation.Landed && self.vessel.situation != VesselSituation.Splashed) return false @@ -97,9 +111,11 @@ impl Mission { } else { eng_name = "" } + if (deltav <= 0.1) + continue let s = format(" {0} dv: {1,5:N0} m/s mass: {2,4:N1} t T/W: {3,4:N1} {4}", - (stage, deltav, mass, thrust/mass, eng_name)) + (vessel.staging.count-stage, deltav, mass, thrust/mass/10.0, eng_name)) con.log(s) } @@ -275,5 +291,22 @@ impl Mission { sleep(self.stage_delay_post) self.new_stage(true) } + + fn getTotalDV(self) -> float = { + let dv = 0.0 + + for(stage in 1..self.vessel.staging.current) + { + const maybe_stage_info = self.vessel.delta_v.stage(stage) + if(!maybe_stage_info.defined) continue + + const stage_info = maybe_stage_info.value + const dv_stage = stage_info.get_deltav(DeltaVSituation.Vaccum) + + dv += dv_stage + } + + return dv + } } diff --git a/glib/transfer.to2 b/glib/transfer.to2 index 88c8528..476540e 100644 --- a/glib/transfer.to2 +++ b/glib/transfer.to2 @@ -56,13 +56,13 @@ impl Transfer { // Calculate correct phase angle for a Hohmann Transfer sync fn hohmannPhaseAngle(self, alt_start: float, alt_end: float, o: Orbit) -> float = { - return 180*(1-(1/(2*sqrt(2))*sqrt((1+(alt_start+o.reference_body.radius)/(alt_end+o.reference_body.radius))^3))) + return 180*(1-(1/(2*sqrt(2))*sqrt((1+(alt_start+o.reference_body.radius)/(alt_end+o.reference_body.radius))**3))) } // Time needed for Hohmann transfer from alt1->alt2 sync fn hohmannTime(self, orbit: Orbit, new_ap: float, time: float) -> float = { - return PI*( (orbit.radius(time)+new_ap+2*orbit.reference_body.radius)^3/(8*orbit.reference_body.grav_parameter) )^0.5 + return PI*( (orbit.radius(time)+new_ap+2*orbit.reference_body.radius)**3/(8*orbit.reference_body.grav_parameter) )**0.5 } // Current True Anomaly in deg. Will work even with circular/eccentric orbits diff --git a/glib/utility.to2 b/glib/utility.to2 index b1da9d5..6e3327d 100644 --- a/glib/utility.to2 +++ b/glib/utility.to2 @@ -221,4 +221,17 @@ pub fn check_and_finish_ssb(mission: Mission) -> Unit = { mission.stage() return } -} \ No newline at end of file +} + +pub fn has_ssb(mission: Mission) -> bool = { + + const vessel = mission.vessel + + for(engine in vessel.engines) + if(engine.has_ignited && engine.current_engine_mode.engine_type == ksp::vessel::EngineType.SolidBooster) + { + return true + } + + return false +} diff --git a/mission/gravity_turn.to2 b/mission/gravity_turn.to2 new file mode 100644 index 0000000..5d1cf1a --- /dev/null +++ b/mission/gravity_turn.to2 @@ -0,0 +1,76 @@ +use { Vessel } from ksp::vessel +use { Mission } from glib::mission +use { gravity_turn } from glib::maneuver_atmosphere +use { CONSOLE } from ksp::console +use { format } from core::str + +use { open_centered_window, Align } from ksp::ui +use { wait_until } from ksp::game + +const startCell : Cell = Cell(80) +const pitchCell : Cell = Cell(10) +const headingCell : Cell = Cell(90) +const apCell : Cell = Cell(85) +const doLaunch : Cell = Cell(false) + +// estimates parameters of current vessel +fn suggestParameters(vessel: Vessel) -> Unit = +{ + let sdv = vessel.delta_v.stage(vessel.staging.current) + let twr = sdv.value.get_TWR(ksp::vessel::DeltaVSituation.Altitude ) + CONSOLE.print_line(format("twr: {0:N1}",(twr))) + startCell.value = (75 + (1.5-twr) * 15).to_int + pitchCell.value = ((11 - (1.5-twr) * 15)*10).to_int / 10.0 // round to 1/10th + if (pitchCell.value < 7) + pitchCell.value = 7 + if (pitchCell.value > 20) + pitchCell.value = 20 +} + +// +// UI to input parameters +// +fn getParameters(vessel: Vessel) -> Unit = { + + const window = open_centered_window("Gravity Turn V0.1", 400, 400) + + let startLabel = window.add_label("Start vel [m/s]:", Align.Start) + const startInput = window.add_int_input(Align.Start).bind(startCell) + window.add_label("Initial pitch [°]:") + const pitchInput = window.add_float_input(Align.Start).bind(pitchCell) + window.add_label("Heading [°]:") + const headingInput = window.add_float_input(Align.Start).bind(headingCell) + window.add_label("Apoapsis [km]:") + const apInput = window.add_int_input(Align.Start).bind(apCell) + + window.add_button("Launch").on_click(fn () -> { + doLaunch.value = true + window.close() + }) + + const done: Cell = Cell(false) + + window.add_button("Cancel").on_click(fn () -> + { + doLaunch.value = false + window.close() + }) + + wait_until(fn() -> window.is_closed) +} + +pub fn main_flight(vessel: Vessel, target_apoapsis: int = 80000) -> Unit = { + CONSOLE.clear() + suggestParameters(vessel) + getParameters(vessel) + + if (doLaunch.value) + { + // Standard Setup + let mission = Mission("Launch to Kerbin Orbit with gravity turn",vessel) + let con = mission.start() + gravity_turn(mission, apCell.value*1000.0, startCell.value, pitchCell.value, headingCell.value) + + mission.end() + } +} \ No newline at end of file diff --git a/test/info.to2 b/test/info.to2 index 8bc2c99..dccfe88 100644 --- a/test/info.to2 +++ b/test/info.to2 @@ -6,7 +6,6 @@ use { format } from core::str // Print is much useful info about our current state as possible pub fn main_flight(vessel: Vessel) -> Unit = { - // Standard Setup let mission = glib::mission::Mission("Vessel Info",vessel) let con = mission.start() diff --git a/test/test_ui.to2 b/test/test_ui.to2 new file mode 100644 index 0000000..de2a9d6 --- /dev/null +++ b/test/test_ui.to2 @@ -0,0 +1,42 @@ +use { Vessel } from ksp::vessel +use { CONSOLE } from ksp::console +use { open_centered_window, Align } from ksp::ui +use { wait_until } from ksp::game + +pub fn main_flight(vessel: Vessel) -> Result = { + const stringCell : Cell = Cell("") + const intCell : Cell = Cell(0) + const floatCell : Cell = Cell(0) + const window = open_centered_window("UI: Test", 400, 400) + + const l1 = window.add_label("Label1") + window.add_label("Label3").bind(stringCell) + + const v = window.add_horizontal() + + v.add_label("L2.1", Align.Stretch, 1.0).bind(intCell, "{0}") + v.add_label("L2.2", Align.Stretch, 1.0).bind(floatCell, "{0:F2} Deg") + + const sInput = window.add_string_input().bind(stringCell) + const iInput = window.add_int_input().bind(intCell) + const fInput = window.add_float_input().bind(floatCell) + + window.add_toggle("Test toggle") + + window.add_horizontal_slider(-10, 20).bind(floatCell) + + window.add_button("Button1").on_click(fn () -> { + l1.font_size = 100 + + CONSOLE.print_line("Button1") + CONSOLE.print_line("S: " + sInput.value) + CONSOLE.print_line("I: " + iInput.value.to_string()) + CONSOLE.print_line("F: " + fInput.value.to_string()) + }) + + const done: Cell = Cell(false) + + window.add_button("Close").on_click(fn () -> window.close()) + + wait_until(fn() -> window.is_closed) +} \ No newline at end of file