· 5 years ago · Sep 24, 2020, 06:04 AM
1{"slots":{"0":{"name":"core","type":{"events":[],"methods":[]}},"1":{"name":"atmofueltank_1","type":{"events":[],"methods":[]}},"2":{"name":"spacefueltank_1","type":{"events":[],"methods":[]}},"3":{"name":"spacefueltank_2","type":{"events":[],"methods":[]}},"4":{"name":"spacefueltank_3","type":{"events":[],"methods":[]}},"5":{"name":"spacefueltank_4","type":{"events":[],"methods":[]}},"6":{"name":"spacefueltank_5","type":{"events":[],"methods":[]}},"7":{"name":"slot8","type":{"events":[],"methods":[]}},"8":{"name":"slot9","type":{"events":[],"methods":[]}},"9":{"name":"slot10","type":{"events":[],"methods":[]}},"10":{"name":"weapon_1","type":{"events":[],"methods":[]}},"11":{"name":"weapon_2","type":{"events":[],"methods":[]}},"12":{"name":"slot13","type":{"events":[],"methods":[]}},"13":{"name":"slot14","type":{"events":[],"methods":[]}},"14":{"name":"slot15","type":{"events":[],"methods":[]}},"15":{"name":"slot16","type":{"events":[],"methods":[]}},"16":{"name":"slot17","type":{"events":[],"methods":[]}},"17":{"name":"slot18","type":{"events":[],"methods":[]}},"18":{"name":"slot19","type":{"events":[],"methods":[]}},"19":{"name":"slot20","type":{"events":[],"methods":[]}},"20":{"name":"radar_1","type":{"events":[],"methods":[]}},"-1":{"name":"unit","type":{"events":[],"methods":[]}},"-2":{"name":"system","type":{"events":[],"methods":[]}},"-3":{"name":"library","type":{"events":[],"methods":[]}}},"handlers":[{"code":"-- NO USER CHANGES\nyawInput2 = 0\nrollInput2 = 0\npitchInput2 = 0\nLastApsDiff = -1\nlocal velocity = vec3(core.getWorldVelocity())\nlocal velMag = vec3(velocity):len()\nlocal sys = galaxyReference[0]\nplanet = sys:closestBody(core.getConstructWorldPos())\nkepPlanet = Kep(planet)\norbit = kepPlanet:orbitalParameters(core.getConstructWorldPos(), velocity)\n\nlocal deltaX = system.getMouseDeltaX()\nlocal deltaY = system.getMouseDeltaY()\n\nbrakeInput = 0\nif BrakeIsOn then\n brakeInput = 1\nend\n\nif AutopilotTargetName ~= \"None\" then\n\n ShowInterplanetaryPanel()\n \n system.updateData(interplanetaryHeaderText, '{\"label\": \"Target\", \"value\": \"' .. AutopilotTargetName .. '\", \"unit\":\"\"}')\n travelTime = GetAutopilotTravelTime() -- This also sets AutopilotDistance so we don't have to calc it again\n distance = AutopilotDistance\n if not TurnBurn then \n brakeDistance, brakeTime = GetAutopilotBrakeDistanceAndTime(velMag)\n maxBrakeDistance, maxBrakeTime = GetAutopilotBrakeDistanceAndTime(MaxGameVelocity)\n else\n brakeDistance, brakeTime = GetAutopilotTBBrakeDistanceAndTime(velMag)\n maxBrakeDistance, maxBrakeTime = GetAutopilotTBBrakeDistanceAndTime(MaxGameVelocity)\n end\n \n system.updateData(widgetDistanceText, '{\"label\": \"Distance\", \"value\": \"' .. getDistanceDisplayString(distance) .. '\", \"unit\":\"\"}')\n system.updateData(widgetTravelTimeText, '{\"label\": \"Travel Time\", \"value\": \"' .. FormatTimeString(travelTime) .. '\", \"unit\":\"\"}')\n system.updateData(widgetCurBrakeDistanceText, '{\"label\": \"Cur Brake Distance\", \"value\": \"' .. getDistanceDisplayString(brakeDistance) .. '\", \"unit\":\"\"}')\n system.updateData(widgetCurBrakeTimeText, '{\"label\": \"Cur Brake Time\", \"value\": \"' .. FormatTimeString(brakeTime) .. '\", \"unit\":\"\"}')\n system.updateData(widgetMaxBrakeDistanceText, '{\"label\": \"Max Brake Distance\", \"value\": \"' .. getDistanceDisplayString(maxBrakeDistance) .. '\", \"unit\":\"\"}')\n system.updateData(widgetMaxBrakeTimeText, '{\"label\": \"Max Brake Time\", \"value\": \"' .. FormatTimeString(maxBrakeTime) .. '\", \"unit\":\"\"}')\nelse\n HideInterplanetaryPanel()\nend\nupdateHud() -- sets up Content for us\ncontent = content .. [[<svg width=\"100%\" height=\"100%\" style=\"position:absolute;top:0;left:0\" viewBox=\"0 0 2560 1440\">]]\nDrawDeadZone()\nif system.isViewLocked() == 0 then\n CheckButtons()\n simulatedX = 0\n simulatedY = 0 -- Reset after they do view things, and don't keep sending inputs while unlocked view\n -- Except of course autopilot, which is later.\nelse\n simulatedX = simulatedX + deltaX\n simulatedY = simulatedY + deltaY\n distance = math.sqrt(simulatedX*simulatedX + simulatedY*simulatedY)\n if not HoldingCtrl then -- Draw deadzone circle if it's navigating\n if userControlScheme == \"Virtual Joystick\" then -- Virtual Joystick\n -- Do navigation things\n \n if simulatedX > 0 and simulatedX > DeadZone then\n yawInput2 = yawInput2 - (simulatedX - DeadZone) * MouseXSensitivity\n elseif simulatedX < 0 and simulatedX < (DeadZone * -1) then\n yawInput2 = yawInput2 - (simulatedX + DeadZone) * MouseXSensitivity\n else\n yawInput2 = 0\n end\n \n if simulatedY > 0 and simulatedY > DeadZone then\n pitchInput2 = pitchInput2 - (simulatedY - DeadZone) * MouseYSensitivity\n elseif simulatedY < 0 and simulatedY < (DeadZone * -1) then\n pitchInput2 = pitchInput2 - (simulatedY + DeadZone) * MouseYSensitivity\n else\n pitchInput2 = 0\n end\n elseif userControlScheme == \"Mouse\" then -- Mouse Direct\n simulatedX = 0\n simulatedY = 0\n pitchInput2 = pitchInput2 - deltaY * mousePitchFactor\n yawInput2 = yawInput2 - deltaX * mouseYawFactor\n else -- Keyboard mode\n simulatedX = 0\n simulatedY = 0\n -- Don't touch anything, they have it with kb only. \n end\n\n \n \n -- Right so. We can't detect a mouse click. That's stupid. \n -- We have two options. 1. Use mouse wheel movement as a click, or 2. If you're hovered over a button and let go of Ctrl, it's a click\n -- I think 2 is a much smoother solution. Even if we later want to have them input some coords\n -- We'd have to hook 0-9 in their events, and they'd have to point at the target, so it wouldn't be while this screen is open\n \n -- What that means is, if we get here, check our hovers. If one of them is active, trigger the thing and deactivate the hover\n CheckButtons()\n \n \n if distance > DeadZone then -- Draw a line to the cursor from the screen center\n -- Note that because SVG lines fucking suck, we have to do a translate and they can't use calc in their params\n DrawCursorLine()\n end\n else\n -- Ctrl is being held, draw buttons.\n -- Brake toggle, face prograde, face retrograde (for now)\n -- We've got some vars setup in Start for them to make this easier to work with\n \n \n SetButtonContains()\n \n \n DrawButtons()\n \n \n end\n \n -- Cursor always on top, draw it last\n \n content = content .. \"<circle stroke='white' cx='calc(50% + \" .. simulatedX .. \"px)' cy='calc(50% + \" .. simulatedY .. \"px)' r='5'/>\"\n\nend\ncontent = content .. [[</svg></body>]]\nsystem.setScreen(content)\n\nif AutoBrake and AutopilotTargetPlanetName ~= \"None\" and (vec3(core.getConstructWorldPos())-vec3(AutopilotTargetPlanet.center)):len() <= maxBrakeDistance then\n brakeInput = 1\n if planet.name == AutopilotTargetPlanet.name and orbit.apoapsis ~= nil and orbit.eccentricity < 1 then\n -- We're increasing eccentricity by braking, time to stop\n brakeInput = 0\n AutoBrake = false\n end\nend\nif ProgradeIsOn then \n if velMag > MinAutopilotSpeed then -- Help with div by 0 errors and careening into terrain at low speed\n AlignToWorldVector(vec3(velocity))\n end\nend\nif RetrogradeIsOn then \n if velMag > MinAutopilotSpeed then -- Help with div by 0 errors and careening into terrain at low speed\n AlignToWorldVector(-(vec3(velocity)))\n end\nend\nif Autopilot and unit.getAtmosphereDensity() == 0 then\n -- Planetary autopilot engaged, we are out of atmo, and it has a target\n -- Do it. \n -- And tbh we should calc the brakeDistance live too, and of course it's also in meters\n local brakeDistance, brakeTime = GetAutopilotBrakeDistanceAndTime(velMag)\n --system.print(brakeDistance)\n brakeDistance = brakeDistance \n brakeTime = brakeTime -- * 1.05 -- Padding?\n -- Maybe instead of pointing at our vector, we point at our vector + how far off our velocity vector is\n -- This is gonna be hard to get the negatives right.\n -- If we're still in orbit, don't do anything, that velocity will suck\n local targetCoords = AutopilotTargetCoords\n if orbit.apoapsis == nil and velMag > 300 and AutopilotAccelerating then\n -- Get the angle between forward and velocity\n -- Get the magnitude for each of yaw and pitch\n -- Consider a right triangle, with side a being distance to our target\n -- get side b, where have the angle. Do this once for each of yaw and pitch\n -- The result of each of those would then be multiplied by something to make them vectors...\n \n \n -- Okay another idea.\n -- Normalize forward and velocity, then get the ratio of normvelocity:velocity\n -- And scale forward back up by that amount. Then take forward-velocity, the \n \n \n -- No no.\n -- Okay so, first, when we realign, we store shipright and shipup, just for this\n -- Get the difference between ship forward and normalized worldvel\n -- Get the components in each of the stored shipright and shipup directions\n -- Get the ratio of velocity to normalized velocity and scale up that component (Hey this is just velmag btw)\n -- Add that component * shipright or shipup\n local velVectorOffset = (vec3(AutopilotTargetCoords) - vec3(core.getConstructWorldPos())):normalize() - vec3(velocity):normalize()\n local pitchComponent = getMagnitudeInDirection(velVectorOffset, AutopilotShipUp)\n local yawComponent = getMagnitudeInDirection(velVectorOffset, AutopilotShipRight)\n local leftAmount = -yawComponent * AutopilotDistance * velMag/10\n local downAmount = -pitchComponent * AutopilotDistance * velMag/10\n targetCoords = AutopilotTargetCoords + (-leftAmount * vec3(AutopilotShipRight)) + (-downAmount * vec3(AutopilotShipUp))\n end\n -- If we're here, sadly, we really need to calc the distance every update (or tick)\n AutopilotDistance = (vec3(targetCoords) - vec3(core.getConstructWorldPos())):len()\n local aligned = true -- It shouldn't be used if the following condition isn't met, but just in case\n \n local projectedAltitude = (AutopilotTargetPlanet.center - (vec3(core.getConstructWorldPos()) + (vec3(velocity):normalize() * AutopilotDistance))):len()\n system.updateData(widgetTrajectoryAltitudeText, '{\"label\": \"Projected Altitude\", \"value\": \"' .. getDistanceDisplayString(projectedAltitude) .. '\", \"unit\":\"\"}')\n\n if not AutopilotCruising and not AutopilotBraking then\n aligned = AlignToWorldVector((targetCoords-vec3(core.getConstructWorldPos())):normalize())\n elseif TurnBurn then\n aligned = AlignToWorldVector(-vec3(velocity):normalize())\n end\n if AutopilotAccelerating then\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, 100)\n if vec3(core.getVelocity()):len() >= MaxGameVelocity then -- This is 29999 kph\n AutopilotAccelerating = false\n AutopilotStatus = \"Cruising\"\n AutopilotCruising = true\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, 0)\n end\n -- Check if accel needs to stop for braking\n if AutopilotDistance <= brakeDistance then\n AutopilotAccelerating = false\n AutopilotStatus = \"Braking\"\n AutopilotBraking = true\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, 0)\n end\n elseif AutopilotBraking then\n BrakeIsOn = true\n brakeInput = 1\n if TurnBurn then\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, 100)\n end\n -- Check if an orbit has been established and cut brakes and disable autopilot if so\n \n -- We'll try <0.9 instead of <1 so that we don't end up in a barely-orbit where touching the controls will make it an escape orbit\n -- Though we could probably keep going until it starts getting more eccentric, so we'd maybe have a circular orbit\n \n if orbit.periapsis ~= nil and orbit.eccentricity < 1 then\n AutopilotStatus = \"Circularizing Orbit\"\n -- Keep going until the apoapsis and periapsis start getting further apart\n -- Rather than: orbit.periapsis ~= nil and orbit.periapsis.altitude < ((vec3(planet.center) - vec3(core.getConstructWorldPos())):len() - planet.radius)-1000\n --local apsDiff = math.abs(orbit.apoapsis.altitude - orbit.periapsis.altitude)\n --if LastApsDiff ~= -1 and apsDiff > LastApsDiff then \n if orbit.eccentricity > LastEccentricity or (orbit.apoapsis.altitude < AutopilotTargetOrbit and orbit.periapsis.altitude < AutopilotTargetOrbit) then\n --LastApsDiff = -1\n BrakeIsOn = false\n AutopilotBraking = false\n Autopilot = false\n AutopilotStatus = \"Aligning\" -- Disable autopilot and reset\n system.print(\"Autopilot completed, orbit established\")\n brakeInput = 0\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, 0)\n end\n LastApsDiff = apsDiff\n end\n elseif AutopilotCruising then\n if AutopilotDistance <= brakeDistance then\n AutopilotAccelerating = false\n AutopilotStatus = \"Braking\"\n AutopilotBraking = true\n end\n else\n -- It's engaged but hasn't started accelerating yet.\n if aligned then\n -- Re-align to 200km from our aligned right \n if not AutopilotRealigned then -- Removed radius from this because it makes our readouts look inaccurate?\n AutopilotTargetCoords = vec3(AutopilotTargetPlanet.center) + ((AutopilotTargetOrbit) * vec3(core.getConstructWorldOrientationRight()))\n AutopilotRealigned = true\n AutopilotShipUp = core.getConstructWorldOrientationUp()\n AutopilotShipRight = core.getConstructWorldOrientationRight()\n elseif aligned then\n AutopilotAccelerating = true\n AutopilotStatus = \"Accelerating\"\n -- Set throttle to max\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, 100) -- 100?\n end\n end\n -- If it's not aligned yet, don't try to burn yet.\n end\nend\nLastEccentricity = orbit.eccentricity\n","filter":{"args":[{"value":"apTick"}],"signature":"tick(apTick)","slotKey":"-1"},"key":"0"},{"code":"_autoconf.hideCategoryPanels()\nif antigrav ~= nil then antigrav.hide() end\nif warpdrive ~= nil then warpdrive.hide() end\nif gyro ~= nil then gyro.hide() end\ncore.hide()\nNav.control.switchOffHeadlights()\n-- Open door and extend ramp if available\nif door ~= nil then door.activate() end\nif forcefield ~= nil then forcefield.activate() end\n","filter":{"args":[],"signature":"stop()","slotKey":"-1"},"key":"1"},{"code":"-- category panel display helpers\n_autoconf = {}\n_autoconf.panels = {}\n_autoconf.panels_size = 0\n_autoconf.displayCategoryPanel = function(elements, size, title, type, widgetPerData)\n widgetPerData = widgetPerData or false -- default to one widget for all data\n if size > 0 then\n local panel = system.createWidgetPanel(title)\n local widget\n if not widgetPerData then\n widget = system.createWidget(panel, type)\n end\n for i = 1, size do\n if widgetPerData then\n widget = system.createWidget(panel, type)\n end\n system.addDataToWidget(elements[i].getDataId(), widget)\n end\n _autoconf.panels_size = _autoconf.panels_size + 1\n _autoconf.panels[_autoconf.panels_size] = panel\n end\nend\n_autoconf.hideCategoryPanels = function()\n for i=1,_autoconf.panels_size do\n system.destroyWidgetPanel(_autoconf.panels[i])\n end\nend\n-- Proxy array to access auto-plugged slots programmatically\n\nrocketfueltank = {}\nrocketfueltank_size = 0\n\natmofueltank = {}\natmofueltank[1] = atmofueltank_1\natmofueltank_size = 1\n\nspacefueltank = {}\nspacefueltank[1] = spacefueltank_1\nspacefueltank[2] = spacefueltank_2\nspacefueltank[3] = spacefueltank_3\nspacefueltank[4] = spacefueltank_4\nspacefueltank[5] = spacefueltank_5\nspacefueltank_size = 5\n\nweapon = {}\nweapon[1] = weapon_1\nweapon[2] = weapon_2\nweapon_size = 2\n\nradar = {}\nradar[1] = radar_1\nradar_size = 1\n-- End of auto-generated code\nNav = Navigator.new(system, core, unit)\nNav.axisCommandManager:setupCustomTargetSpeedRanges(axisCommandId.longitudinal, {1000, 5000, 10000, 20000, 30000})\n-- Close door and retract ramp if available\nif door ~= nil then door.deactivate() end\nif forcefield ~= nil then forcefield.deactivate() end\nhasGear = false\n\n-- element widgets\n-- For now we have to alternate between PVP and non-PVP widgets to have them on the same side.\n_autoconf.displayCategoryPanel(weapon, weapon_size, \"Weapons\", \"weapon\", true)\n-- If radar is installed but no weapon, don't show periscope\nif weapon_size > 0 then\n _autoconf.displayCategoryPanel(radar, radar_size, \"Periscope\", \"periscope\")\nend\n--core.show()\nplaceRadar = true\nif atmofueltank_size > 0 then\n _autoconf.displayCategoryPanel(atmofueltank, atmofueltank_size, \"Atmo Fuel\", \"fuel_container\")\n fuelPanelID = _autoconf.panels[_autoconf.panels_size]\n if placeRadar then\n _autoconf.displayCategoryPanel(radar, radar_size, \"Radar\", \"radar\")\n placeRadar = false\n end\nend\nif spacefueltank_size > 0 then\n _autoconf.displayCategoryPanel(spacefueltank, spacefueltank_size, \"Space Fuel\", \"fuel_container\")\n spacefuelPanelID = _autoconf.panels[_autoconf.panels_size]\n if placeRadar then\n _autoconf.displayCategoryPanel(radar, radar_size, \"Radar\", \"radar\")\n placeRadar = false\n end\nend\n_autoconf.displayCategoryPanel(rocketfueltank, rocketfueltank_size, \"Rocket Fuel\", \"fuel_container\")\nif placeRadar then -- We either have only rockets or no fuel tanks at all, uncommon for usual vessels\n _autoconf.displayCategoryPanel(radar, radar_size, \"Radar\", \"radar\")\n placeRadar = false\nend\nif antigrav ~= nil then antigrav.show() end\nif warpdrive ~= nil then warpdrive.show() end\n--if gyro ~= nil then gyro.show() end\n\n-- freeze the player in he is remote controlling the construct\nif Nav.control.isRemoteControlled() == 1 then\n system.freeze(1)\nend\n\n-- landing gear\n-- make sure every gears are synchonized with the first\n\nelements = core.getElementIdList() \nfor k in pairs(elements) do\n if (core.getElementTypeById(elements[k]) == \"landing gear\") then \n hasGear = true \n end\nend\nif hasGear then\n gearExtended = (Nav.control.isAnyLandingGearExtended() == 1) -- make sure it's a lua boolean\n if gearExtended then\n Nav.control.extendLandingGears()\n else\n Nav.control.retractLandingGears()\n end\n if gearExtended then\n Nav.axisCommandManager:setTargetGroundAltitude(0)\n else\n Nav.axisCommandManager:setTargetGroundAltitude(500) -- lol I wish\n end\nelse\n if unit.getAtmosphereDensity() == 0 then\n gearExtended = false\n Nav.axisCommandManager:setTargetGroundALtitude(500)\n else\n gearExtended = true -- Show warning message and set behavior\n Nav.axisCommandManager:setTargetGroundAltitude(0)\n end\nend\n \nunit.setTimer(\"apTick\", 1/60)\nUnitHidden = false\n--unit.hide()\n","filter":{"args":[],"signature":"start()","slotKey":"-1"},"key":"2"},{"code":"DecrementAutopilotTargetIndex()","filter":{"args":[{"value":"option2"}],"signature":"actionStart(option2)","slotKey":"-2"},"key":"3"},{"code":"IncrementAutopilotTargetIndex()","filter":{"args":[{"value":"option1"}],"signature":"actionStart(option1)","slotKey":"-2"},"key":"4"},{"code":"ToggleAutoBrake()","filter":{"args":[{"value":"option3"}],"signature":"actionStart(option3)","slotKey":"-2"},"key":"5"},{"code":"ToggleFuelPanels()","filter":{"args":[{"value":"option6"}],"signature":"actionStart(option6)","slotKey":"-2"},"key":"6"},{"code":"ToggleAutoPilot()","filter":{"args":[{"value":"option4"}],"signature":"actionStart(option4)","slotKey":"-2"},"key":"7"},{"code":"if UnitHidden then\n unit.show()\n UnitHidden = false\nelse\n unit.hide()\n UnitHidden = true\nend\n","filter":{"args":[{"value":"option7"}],"signature":"actionStart(option7)","slotKey":"-2"},"key":"8"},{"code":"if system.isViewLocked() == 1 then\n HoldingCtrl = true\n PrevViewLock = system.isViewLocked()\n system.lockView(1)\nend\n","filter":{"args":[{"value":"lshift"}],"signature":"actionStart(lshift)","slotKey":"-2"},"key":"9"},{"code":"if system.isViewLocked() == 1 then\n HoldingCtrl = false\n simulatedX = 0\n simulatedY = 0 -- Reset for steering purposes\n system.lockView(PrevViewLock)\nend\n","filter":{"args":[{"value":"lshift"}],"signature":"actionStop(lshift)","slotKey":"-2"},"key":"10"},{"code":"BrakeToggle()","filter":{"args":[{"value":"brake"}],"signature":"actionStart(brake)","slotKey":"-2"},"key":"11"},{"code":"if system.isViewLocked() == 1 then\n system.lockView(0)\nelse\n system.lockView(1)\nend\n","filter":{"args":[{"value":"lalt"}],"signature":"actionStart(lalt)","slotKey":"-2"},"key":"12"},{"code":"--Nav:toggleBoosters()\n-- Dodgin's Don't Die Rocket Govenor - Cruise Control Edition\nisboosting = not isboosting\nif(isboosting) then unit.setEngineThrust('rocket_engine',1)\nelse unit.setEngineThrust('rocket_engine',0)\nend\n","filter":{"args":[{"value":"booster"}],"signature":"actionStart(booster)","slotKey":"-2"},"key":"13"},{"code":"if not HoldingCtrl then \n Nav.axisCommandManager:updateCommandFromActionStart(axisCommandId.longitudinal, 5.0)\nelse\n IncrementAutopilotTargetIndex()\nend\n","filter":{"args":[{"value":"speedup"}],"signature":"actionStart(speedup)","slotKey":"-2"},"key":"14"},{"code":"Nav.axisCommandManager:resetCommand(axisCommandId.longitudinal)","filter":{"args":[{"value":"stopengines"}],"signature":"actionStart(stopengines)","slotKey":"-2"},"key":"15"},{"code":"if not HoldingCtrl then \n Nav.axisCommandManager:updateCommandFromActionLoop(axisCommandId.longitudinal, 1.0)\nend\n","filter":{"args":[{"value":"speedup"}],"signature":"actionLoop(speedup)","slotKey":"-2"},"key":"16"},{"code":"if not HoldingCtrl then \n Nav.axisCommandManager:updateCommandFromActionLoop(axisCommandId.longitudinal, -1.0)\nend\n","filter":{"args":[{"value":"speeddown"}],"signature":"actionLoop(speeddown)","slotKey":"-2"},"key":"17"},{"code":"if not HoldingCtrl then \n Nav.axisCommandManager:updateCommandFromActionStart(axisCommandId.longitudinal, -5.0)\nelse\n DecrementAutopilotTargetIndex()\nend\n","filter":{"args":[{"value":"speeddown"}],"signature":"actionStart(speeddown)","slotKey":"-2"},"key":"18"},{"code":"if warpdrive ~= nil then warpdrive.activateWarp() end","filter":{"args":[{"value":"warp"}],"signature":"actionStart(warp)","slotKey":"-2"},"key":"19"},{"code":"if antigrav ~= nil then antigrav.toggle() end","filter":{"args":[{"value":"antigravity"}],"signature":"actionStart(antigravity)","slotKey":"-2"},"key":"20"},{"code":"yawInput = yawInput + 1","filter":{"args":[{"value":"yawleft"}],"signature":"actionStart(yawleft)","slotKey":"-2"},"key":"21"},{"code":"yawInput = yawInput - 1","filter":{"args":[{"value":"yawleft"}],"signature":"actionStop(yawleft)","slotKey":"-2"},"key":"22"},{"code":"Nav.axisCommandManager:updateCommandFromActionStart(axisCommandId.lateral, -1.0)","filter":{"args":[{"value":"strafeleft"}],"signature":"actionStart(strafeleft)","slotKey":"-2"},"key":"23"},{"code":"Nav.axisCommandManager:updateCommandFromActionStop(axisCommandId.lateral, 1.0)","filter":{"args":[{"value":"strafeleft"}],"signature":"actionStop(strafeleft)","slotKey":"-2"},"key":"24"},{"code":"Nav.axisCommandManager:deactivateGroundEngineAltitudeStabilization()\nNav.axisCommandManager:updateCommandFromActionStart(axisCommandId.vertical, 1.0)\n","filter":{"args":[{"value":"up"}],"signature":"actionStart(up)","slotKey":"-2"},"key":"25"},{"code":"Nav.axisCommandManager:updateCommandFromActionStop(axisCommandId.vertical, -1.0)\nNav.axisCommandManager:activateGroundEngineAltitudeStabilization(currentGroundAltitudeStabilization)\n","filter":{"args":[{"value":"up"}],"signature":"actionStop(up)","slotKey":"-2"},"key":"26"},{"code":"Nav.axisCommandManager:updateCommandFromActionStop(axisCommandId.vertical, 1.0)\nNav.axisCommandManager:activateGroundEngineAltitudeStabilization(currentGroundAltitudeStabilization)\n","filter":{"args":[{"value":"down"}],"signature":"actionStop(down)","slotKey":"-2"},"key":"27"},{"code":"Nav.axisCommandManager:deactivateGroundEngineAltitudeStabilization()\nNav.axisCommandManager:updateCommandFromActionStart(axisCommandId.vertical, -1.0)\n","filter":{"args":[{"value":"down"}],"signature":"actionStart(down)","slotKey":"-2"},"key":"28"},{"code":"Nav.axisCommandManager:updateTargetGroundAltitudeFromActionLoop(1.0)","filter":{"args":[{"value":"groundaltitudeup"}],"signature":"actionLoop(groundaltitudeup)","slotKey":"-2"},"key":"29"},{"code":"Nav.axisCommandManager:updateTargetGroundAltitudeFromActionStart(1.0)","filter":{"args":[{"value":"groundaltitudeup"}],"signature":"actionStart(groundaltitudeup)","slotKey":"-2"},"key":"30"},{"code":"Nav.axisCommandManager:updateTargetGroundAltitudeFromActionStart(-1.0)","filter":{"args":[{"value":"groundaltitudedown"}],"signature":"actionStart(groundaltitudedown)","slotKey":"-2"},"key":"31"},{"code":"Nav.axisCommandManager:updateTargetGroundAltitudeFromActionLoop(-1.0)","filter":{"args":[{"value":"groundaltitudedown"}],"signature":"actionLoop(groundaltitudedown)","slotKey":"-2"},"key":"32"},{"code":"yawInput = yawInput - 1","filter":{"args":[{"value":"yawright"}],"signature":"actionStart(yawright)","slotKey":"-2"},"key":"33"},{"code":"yawInput = yawInput + 1","filter":{"args":[{"value":"yawright"}],"signature":"actionStop(yawright)","slotKey":"-2"},"key":"34"},{"code":"if Nav.control.isAnyHeadlightSwitchedOn() == 1 then\n Nav.control.switchOffHeadlights()\nelse\n Nav.control.switchOnHeadlights()\nend\n","filter":{"args":[{"value":"light"}],"signature":"actionStart(light)","slotKey":"-2"},"key":"35"},{"code":"gearExtended = not gearExtended\nif gearExtended then\n Nav.control.extendLandingGears()\n Nav.axisCommandManager:setTargetGroundAltitude(0)\nelse\n Nav.control.retractLandingGears()\n Nav.axisCommandManager:setTargetGroundAltitude(500) -- What's max?\nend\n","filter":{"args":[{"value":"gear"}],"signature":"actionStart(gear)","slotKey":"-2"},"key":"36"},{"code":"-- constants: use 'myvar = defaultValue --export: description' to expose the variable in context menu\n\npitchSpeedFactor = 0.8 --export: For keyboard control\nyawSpeedFactor = 1 --export: For keyboard control\nrollSpeedFactor = 1.5 --export: This factor will increase/decrease the player input along the roll axis<br>(higher value may be unstable)<br>Valid values: Superior or equal to 0.01\n\nlocal brakeSpeedFactor = 3 --export: When braking, this factor will increase the brake force by brakeSpeedFactor * velocity<br>Valid values: Superior or equal to 0.01\nlocal brakeFlatFactor = 1 --export: When braking, this factor will increase the brake force by a flat brakeFlatFactor * velocity direction><br>(higher value may be unstable)<br>Valid values: Superior or equal to 0.01\n\nlocal autoRoll = true --export: [Only in atmosphere]<br>When the pilot stops rolling, flight model will try to get back to horizontal (no roll)\nlocal autoRollFactor = 2 --export: [Only in atmosphere]<br>When autoRoll is engaged, this factor will increase to strength of the roll back to 0<br>Valid values: Superior or equal to 0.01\n\nlocal turnAssist = true --export: [Only in atmosphere]<br>When the pilot is rolling, the flight model will try to add yaw and pitch to make the construct turn better<br>The flight model will start by adding more yaw the more horizontal the construct is and more pitch the more vertical it is\nlocal turnAssistFactor = 2 --export: [Only in atmosphere]<br>This factor will increase/decrease the turnAssist effect<br>(higher value may be unstable)<br>Valid values: Superior or equal to 0.01\n\nlocal torqueFactor = 2 -- Force factor applied to reach rotationSpeed<br>(higher value may be unstable)<br>Valid values: Superior or equal to 0.01\n\n-- validate params\npitchSpeedFactor = math.max(pitchSpeedFactor, 0.01)\nyawSpeedFactor = math.max(yawSpeedFactor, 0.01)\nrollSpeedFactor = math.max(rollSpeedFactor, 0.01)\ntorqueFactor = math.max(torqueFactor, 0.01)\nbrakeSpeedFactor = math.max(brakeSpeedFactor, 0.01)\nbrakeFlatFactor = math.max(brakeFlatFactor, 0.01)\nautoRollFactor = math.max(autoRollFactor, 0.01)\nturnAssistFactor = math.max(turnAssistFactor, 0.01)\n\n-- final inputs\nlocal finalPitchInput = pitchInput + pitchInput2 + system.getControlDeviceForwardInput()\nlocal finalRollInput = rollInput + rollInput2 + system.getControlDeviceYawInput()\nlocal finalYawInput = (yawInput + yawInput2) - system.getControlDeviceLeftRightInput()\nlocal finalBrakeInput = brakeInput\n\n-- Axis\nlocal worldVertical = vec3(core.getWorldVertical()) -- along gravity\nlocal constructUp = vec3(core.getConstructWorldOrientationUp())\nlocal constructForward = vec3(core.getConstructWorldOrientationForward())\nlocal constructRight = vec3(core.getConstructWorldOrientationRight())\nlocal constructVelocity = vec3(core.getWorldVelocity())\nlocal constructVelocityDir = vec3(core.getWorldVelocity()):normalize()\nlocal currentRollDeg = getRoll(worldVertical, constructForward, constructRight)\nlocal currentRollDegAbs = math.abs(currentRollDeg)\nlocal currentRollDegSign = utils.sign(currentRollDeg)\n\n-- Rotation\nlocal constructAngularVelocity = vec3(core.getWorldAngularVelocity())\nlocal targetAngularVelocity = finalPitchInput * pitchSpeedFactor * constructRight\n + finalRollInput * rollSpeedFactor * constructForward\n + finalYawInput * yawSpeedFactor * constructUp\n\n-- In atmosphere?\nif worldVertical:len() > 0.01 and unit.getAtmosphereDensity() > 0.0 then\n local autoRollRollThreshold = 1.0\n -- autoRoll on AND currentRollDeg is big enough AND player is not rolling\n if autoRoll == true and currentRollDegAbs > autoRollRollThreshold and finalRollInput == 0 then\n local targetRollDeg = utils.clamp(0,currentRollDegAbs-30, currentRollDegAbs+30); -- we go back to 0 within a certain limit\n if (rollPID == nil) then\n rollPID = pid.new(autoRollFactor * 0.01, 0, autoRollFactor * 0.1) -- magic number tweaked to have a default factor in the 1-10 range\n end\n rollPID:inject(targetRollDeg - currentRollDeg)\n local autoRollInput = rollPID:get()\n\n targetAngularVelocity = targetAngularVelocity + autoRollInput * constructForward\n end\n local turnAssistRollThreshold = 20.0\n -- turnAssist AND currentRollDeg is big enough AND player is not pitching or yawing\n if turnAssist == true and currentRollDegAbs > turnAssistRollThreshold and finalPitchInput == 0 and finalYawInput == 0 then\n local rollToPitchFactor = turnAssistFactor * 0.1 -- magic number tweaked to have a default factor in the 1-10 range\n local rollToYawFactor = turnAssistFactor * 0.025 -- magic number tweaked to have a default factor in the 1-10 range\n\n -- rescale (turnAssistRollThreshold -> 180) to (0 -> 180)\n local rescaleRollDegAbs = ((currentRollDegAbs - turnAssistRollThreshold) / (180 - turnAssistRollThreshold)) * 180\n local rollVerticalRatio = 0\n if rescaleRollDegAbs < 90 then\n rollVerticalRatio = rescaleRollDegAbs / 90\n elseif rescaleRollDegAbs < 180 then\n rollVerticalRatio = (180 - rescaleRollDegAbs) / 90\n end\n\n rollVerticalRatio = rollVerticalRatio * rollVerticalRatio\n\n local turnAssistYawInput = - currentRollDegSign * rollToYawFactor * (1.0 - rollVerticalRatio)\n local turnAssistPitchInput = rollToPitchFactor * rollVerticalRatio\n\n targetAngularVelocity = targetAngularVelocity\n + turnAssistPitchInput * constructRight\n + turnAssistYawInput * constructUp\n end\nend\n\n-- Engine commands\nlocal keepCollinearity = 1 -- for easier reading\nlocal dontKeepCollinearity = 0 -- for easier reading\nlocal tolerancePercentToSkipOtherPriorities = 1 -- if we are within this tolerance (in%), we don't go to the next priorities\n\n-- Rotation\nif(stateHoldLevel=='on') then targetAngularVelocity = targetAngularVelocity + worldVertical:cross(constructUp) end\nlocal angularAcceleration = torqueFactor * (targetAngularVelocity - constructAngularVelocity)\nlocal airAcceleration = vec3(core.getWorldAirFrictionAngularAcceleration())\nangularAcceleration = angularAcceleration - airAcceleration -- Try to compensate air friction\nNav:setEngineTorqueCommand('torque', angularAcceleration, keepCollinearity, 'airfoil', '', '', tolerancePercentToSkipOtherPriorities)\n\n-- Brakes\nlocal brakeAcceleration = -finalBrakeInput * (brakeSpeedFactor * constructVelocity + brakeFlatFactor * constructVelocityDir)\nNav:setEngineForceCommand('brake', brakeAcceleration)\n\n-- AutoNavigation regroups all the axis command by 'TargetSpeed'\nlocal autoNavigationEngineTags = ''\nlocal autoNavigationAcceleration = vec3()\nlocal autoNavigationUseBrake = false\n\n-- Longitudinal Translation\nlocal longitudinalEngineTags = 'thrust analog longitudinal'\nlocal longitudinalCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.longitudinal)\nif (longitudinalCommandType == axisCommandType.byThrottle) then\n local longitudinalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(longitudinalEngineTags,axisCommandId.longitudinal)\n Nav:setEngineForceCommand(longitudinalEngineTags, longitudinalAcceleration, keepCollinearity)\nelseif (longitudinalCommandType == axisCommandType.byTargetSpeed) then\n local longitudinalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.longitudinal)\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. longitudinalEngineTags\n autoNavigationAcceleration = autoNavigationAcceleration + longitudinalAcceleration\n if (Nav.axisCommandManager:getTargetSpeed(axisCommandId.longitudinal) == 0 or -- we want to stop\n Nav.axisCommandManager:getCurrentToTargetDeltaSpeed(axisCommandId.longitudinal) < - Nav.axisCommandManager:getTargetSpeedCurrentStep(axisCommandId.longitudinal) * 0.5) -- if the longitudinal velocity would need some braking\n then\n autoNavigationUseBrake = true\n end\n\nend\n\n-- Lateral Translation\nlocal lateralStrafeEngineTags = 'thrust analog lateral'\nlocal lateralCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.lateral)\nif (lateralCommandType == axisCommandType.byThrottle) then\n local lateralStrafeAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(lateralStrafeEngineTags,axisCommandId.lateral)\n Nav:setEngineForceCommand(lateralStrafeEngineTags, lateralStrafeAcceleration, keepCollinearity)\nelseif (lateralCommandType == axisCommandType.byTargetSpeed) then\n local lateralAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.lateral)\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. lateralStrafeEngineTags\n autoNavigationAcceleration = autoNavigationAcceleration + lateralAcceleration\nend\n\n-- Vertical Translation\nlocal verticalStrafeEngineTags = 'thrust analog vertical'\nlocal verticalCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.vertical)\nif (verticalCommandType == axisCommandType.byThrottle) then\n local verticalStrafeAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(verticalStrafeEngineTags,axisCommandId.vertical)\n Nav:setEngineForceCommand(verticalStrafeEngineTags, verticalStrafeAcceleration, keepCollinearity, 'airfoil', 'ground', '', tolerancePercentToSkipOtherPriorities)\nelseif (verticalCommandType == axisCommandType.byTargetSpeed) then\n local verticalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.vertical)\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. verticalStrafeEngineTags\n autoNavigationAcceleration = autoNavigationAcceleration + verticalAcceleration\nend\n\n-- Auto Navigation (Cruise Control)\nif (autoNavigationAcceleration:len() > constants.epsilon) then\n if (brakeInput ~= 0 or autoNavigationUseBrake or math.abs(constructVelocityDir:dot(constructForward)) < 0.95) -- if the velocity is not properly aligned with the forward\n then\n autoNavigationEngineTags = autoNavigationEngineTags .. ', brake'\n end\n Nav:setEngineForceCommand(autoNavigationEngineTags, autoNavigationAcceleration, dontKeepCollinearity, '', '', '', tolerancePercentToSkipOtherPriorities)\nend\n\n-- Rockets\nNav:setBoosterCommand('rocket_engine')\n-- Dodgin's Don't Die Rocket Govenor - Cruise Control Edition\nspeed = vec3(core.getVelocity()):len() \ncc_speed = Nav.axisCommandManager:getTargetSpeed(axisCommandId.longitudinal) \nif(speed * 3.6 > cc_speed) then \n unit.setEngineThrust('rocket_engine',0) \nelse if(isboosting) then \n unit.setEngineThrust('rocket_engine',1) end \nend\n","filter":{"args":[],"signature":"flush()","slotKey":"-2"},"key":"37"},{"code":"-- Written by Dimencia. Linked sources where appropriate, most have been modified. HUD by Archeageo\n\n-- USER DEFINABLE GLOBAL AND LOCAL VARIABLES\nAutopilotTargetOrbit = 150000 --export: How far you want the orbit to be from the planet in m. 200,000 = 1SU\nDeadZone = 50 --export: Number of pixels of deadzone at the center of the screen\nMouseYSensitivity = 0.003 --export: For virtual joystick only\nMouseXSensitivity = 0.003 --export: For virtual joystick only\nMinAutopilotSpeed = 55 --export: Minimum speed for autopilot to maneuver in m/s. Keep above 25m/s to prevent nosedives when boosters kick in\nuserControlScheme = \"Keyboard\" --export: Set to \"Virtual Joystick\", \"Mouse\", or \"Keyboard\"\n\nmousePitchFactor = 0.2 --export: Mouse control only\nmouseYawFactor = 0.2 --export: Mouse control only\n\nlocal PrimaryR = 255 --export: Primary HUD color\nlocal PrimaryG = 0 --export: Primary HUD color\nlocal PrimaryB = 0 --export: Primary HUD color\nlocal AutopilotStrength = 1 --export: How strongly autopilot tries to point at a target\nlocal DampingMultiplier = 70 --export: How strongly it dampens when nearing the correct orientation\nlocal alignmentTolerance = 0.001 --export: How closely it must align to a planet before accelerating to it\nlocal circleRad = 99 --export: The size of the roll/pitch circle, set to 0 to remove.\n\nlocal ResolutionWidth = 2560\nlocal ResolutionHeight = 1440\n\nlocal ButtonBrakeWidth = 240 --export: Size and positioning for brake button\nlocal ButtonBrakeHeight = 50 --export: Size and positioning for brake button\nlocal ButtonBrakeX = ResolutionWidth/2 - ButtonBrakeWidth/2 --export: Size and positioning for brake button\nlocal ButtonBrakeY = ResolutionHeight/2 - ButtonBrakeHeight + 400 --export: Size and positioning for brake button\n \nlocal ButtonProgradeWidth = 260 --export: Size and positioning for prograde button\nlocal ButtonProgradeHeight = 50 --export: Size and positioning for prograde button \nlocal ButtonProgradeX = ResolutionWidth/2 - ButtonProgradeWidth/2 - ButtonBrakeWidth - 50 --export: Size and positioning for prograde button\nlocal ButtonProgradeY = ResolutionHeight/2 - ButtonProgradeHeight + 380 --export: Size and positioning for prograde button\n \nlocal ButtonRetrogradeWidth = 260 --export: Size and positioning for retrograde button\nlocal ButtonRetrogradeHeight = 50 --export: Size and positioning for retrograde button \nlocal ButtonRetrogradeX = ResolutionWidth/2 - ButtonRetrogradeWidth/2 + ButtonBrakeWidth + 50 --export: Size and positioning for retrograde button\nlocal ButtonRetrogradeY = ResolutionHeight/2 - ButtonRetrogradeHeight + 380 --export: Size and positioning for retrograde button\n\nlocal ButtonAutopilotWidth = 600 --export: Size and positioning for autopilot button\nlocal ButtonAutopilotHeight = 60 --export: Size and positioning for autopilot button\nlocal ButtonAutopilotX = ResolutionWidth/2 - ButtonAutopilotWidth/2\nlocal ButtonAutopilotY = ResolutionHeight/2 - ButtonAutopilotHeight/2 - 400\n\nwarmup = 16 --export: How long it takes your engines to warmup. Basic Space Engines, from XS to XL: 0.25,1,4,16,32\n\n-- GLOBAL VARIABLES SECTION, IF NOT USED OUTSIDE THIS START, MAKE IT LOCAL\nhasGear = false\npitchInput = 0\nrollInput = 0\nyawInput = 0\nbrakeInput = 0\npitchInput2 = 0\nrollInput2 = 0\nyawInput2 = 0\nBrakeIsOn = false\nRetrogradeIsOn = false \nProgradeIsOn = false \nAutoBrake = false\nAutopilot = false\nTurnBurn = false\nAutopilotAccelerating = false\nAutopilotBraking = false\nAutopilotCruising = false \nAutopilotRealigned = false\nAutopilotEndSpeed = 0\nAutopilotStatus = \"Aligning\"\nsimulatedX = 0\nsimulatedY = 0\nHoldingCtrl = false\nPrevViewLock = 1\nPreviousYawAmount = 0\nPreviousPitchAmount = 0\n\nisBoosting = false -- Dodgin's Don't Die Rocket Govenor - Cruise Control Edition\n\ndistance = 0\nbrakeDistance, brakeTime = 0\nmaxBrakeDistance, maxBrakeTime = 0\n\n\n-- LOCAL VARIABLES, USERS DO NOT CHANGE\nBrakeButtonHovered = false \nRetrogradeButtonHovered = false \nProgradeButtonHovered = false \nAutopilotButtonHovered = false\n\nif userControlScheme ~= \"Keyboard\" then\n system.lockView(1)\nelse\n system.lockView(0)\nend\nif unit.getAtmosphereDensity() > 0 then\n BrakeIsOn = true\nend \n\nfunction ShowInterplanetaryPanel()\n-- Interplanetary helper\n if panelInterplanetary == nil then\n panelInterplanetary = system.createWidgetPanel(\"Interplanetary Helper\")\n interplanetaryHeader = system.createWidget(panelInterplanetary, \"value\")\n interplanetaryHeaderText = system.createData('{\"label\": \"Target Planet\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(interplanetaryHeaderText, interplanetaryHeader)\n widgetDistance = system.createWidget(panelInterplanetary, \"value\")\n widgetDistanceText = system.createData('{\"label\": \"Distance\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetDistanceText, widgetDistance)\n widgetTravelTime = system.createWidget(panelInterplanetary, \"value\")\n widgetTravelTimeText = system.createData('{\"label\": \"Travel Time\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetTravelTimeText, widgetTravelTime)\n widgetCurBrakeDistance = system.createWidget(panelInterplanetary, \"value\")\n widgetCurBrakeDistanceText = system.createData('{\"label\": \"Cur Brake Distance\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetCurBrakeDistanceText, widgetCurBrakeDistance)\n widgetCurBrakeTime = system.createWidget(panelInterplanetary, \"value\")\n widgetCurBrakeTimeText = system.createData('{\"label\": \"Cur Brake Time\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetCurBrakeTimeText, widgetCurBrakeTime)\n widgetMaxBrakeDistance = system.createWidget(panelInterplanetary, \"value\")\n widgetMaxBrakeDistanceText = system.createData('{\"label\": \"Max Brake Distance\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetMaxBrakeDistanceText, widgetMaxBrakeDistance)\n widgetMaxBrakeTime = system.createWidget(panelInterplanetary, \"value\")\n widgetMaxBrakeTimeText = system.createData('{\"label\": \"Max Brake Time\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetMaxBrakeTimeText, widgetMaxBrakeTime)\n widgetTrajectoryAltitude = system.createWidget(panelInterplanetary, \"value\")\n widgetTrajectoryAltitudeText = system.createData('{\"label\": \"Projected Altitude\", \"value\": \"N/A\", \"unit\":\"\"}')\n system.addDataToWidget(widgetTrajectoryAltitudeText, widgetTrajectoryAltitude)\n end\nend\nfunction ToggleFuelPanels()\n if fuelPanelID ~= nil then\n system.destroyWidgetPanel(fuelPanelID)\n fuelPanelID = nil\n elseif atmofueltank_size > 0 then\n _autoconf.displayCategoryPanel(atmofueltank, atmofueltank_size, \"Atmo Fuel\", \"fuel_container\")\n fuelPanelID = _autoconf.panels[_autoconf.panels_size]\n end\n if spacefuelPanelID ~= nil then\n system.destroyWidgetPanel(spacefuelPanelID)\n spacefuelPanelID = nil\n elseif spacefueltank_size > 0 then\n _autoconf.displayCategoryPanel(spacefueltank, spacefueltank_size, \"Space Fuel\", \"fuel_container\")\n spacefuelPanelID = _autoconf.panels[_autoconf.panels_size]\n end\nend\nfunction Contains(mousex, mousey, x, y, width, height) \n if mousex > x and mousex < (x + width) and mousey > y and mousey < (y + height) then\n return true\n else\n return false\n end\nend\nfunction ProgradeToggle()\n -- Toggle Progrades\n ProgradeIsOn = not ProgradeIsOn\n RetrogradeIsOn = false -- Don't let both be on\n Autopilot = false\n ProgradeButtonHovered = false\n local Progradestring = \"Off\"\n if ProgradeIsOn then\n Progradestring = \"On\"\n end\nend\n\nfunction RetrogradeToggle()\n -- Toggle Retrogrades\n RetrogradeIsOn = not RetrogradeIsOn\n ProgradeIsOn = false -- Don't let both be on\n Autopilot = false\n RetrogradeButtonHovered = false\n local Retrogradestring = \"Off\"\n if RetrogradeIsOn then\n Retrogradestring = \"On\"\n end\nend\n\nfunction BrakeToggle()\n -- Toggle brakes\n BrakeIsOn = not BrakeIsOn\n BrakeButtonHovered = false\n local Brakestring = \"Off\"\n if BrakeIsOn then\n Brakestring = \"On\"\n end\nend\n\nfunction AutopilotToggle()\n -- Toggle Autopilot, as long as the target isn't None\n if AutopilotTargetName ~= \"None\" and not Autopilot then\n Autopilot = true\n RetrogradeIsOn = false\n ProgradeIsOn = false\n AutopilotButtonHovered = false\n AutopilotRealigned = false\n else\n Autopilot = false\n AutopilotButtonHovered = false\n AutopilotRealigned = false\n end\nend\n\nfunction CheckButtons()\n if BrakeButtonHovered then\n BrakeToggle()\n end\n if ProgradeButtonHovered then\n ProgradeToggle()\n end\n if RetrogradeButtonHovered then\n RetrogradeToggle()\n end\n if AutopilotButtonHovered then\n AutopilotToggle()\n end\nend\nfunction DrawDeadZone()\n if system.isViewLocked() == 0 then\n content = content .. \"<circle cx='50%' cy='50%' r='\".. DeadZone .. \"' stroke=rgb(\" .. math.floor(PrimaryR*0.3) .. \",\" .. math.floor(PrimaryG*0.3) .. \",\" .. math.floor(PrimaryB*0.3) .. \") stroke-width='2' fill='none' />\"\n else\n content = content .. \"<circle cx='50%' cy='50%' r='\".. DeadZone .. \"' stroke=rgb(\" .. math.floor(PrimaryR*0.8) .. \",\" .. math.floor(PrimaryG*0.8) .. \",\" .. math.floor(PrimaryB*0.8) .. \") stroke-width='2' fill='none' />\"\n end\nend\nfunction DrawCursorLine()\n local strokeColor = math.floor(utils.clamp((distance/(ResolutionWidth/4))*255,0,255))\n content = content .. \"<line x1='0' y1='0' x2='\" .. simulatedX .. \"px' y2='\" .. simulatedY .. \"px' style='stroke:rgb(\" .. PrimaryR + strokeColor .. \",\" .. PrimaryG-strokeColor .. \",\" .. PrimaryB-strokeColor .. \");stroke-width:2;transform:translate(50%, 50%)' />\"\nend\nfunction SetButtonContains()\n BrakeButtonHovered = Contains(simulatedX + ResolutionWidth/2, simulatedY + ResolutionHeight/2, ButtonBrakeX, ButtonBrakeY, ButtonBrakeWidth, ButtonBrakeHeight)\n ProgradeButtonHovered = Contains(simulatedX + ResolutionWidth/2, simulatedY + ResolutionHeight/2, ButtonProgradeX, ButtonProgradeY, ButtonProgradeWidth, ButtonProgradeHeight)\n RetrogradeButtonHovered = Contains(simulatedX + ResolutionWidth/2, simulatedY + ResolutionHeight/2, ButtonRetrogradeX, ButtonRetrogradeY, ButtonRetrogradeWidth, ButtonRetrogradeHeight)\n AutopilotButtonHovered = Contains(simulatedX + ResolutionWidth/2, simulatedY + ResolutionHeight/2, ButtonAutopilotX, ButtonAutopilotY, ButtonAutopilotWidth, ButtonAutopilotHeight)\nend\n\nfunction DrawButtons()\n --local defaultColor = \"rgb(\" .. math.floor(PrimaryR*0.25+0.5) .. \",\" .. math.floor(PrimaryG*0.25+0.5) .. \",\" .. math.floor(PrimaryB*0.25+0.5) .. \")'\"\n local defaultColor = \"rgb(\" .. 0 .. \",\" .. 18 .. \",\" .. 133 .. \")'\"\n -- Brake button\n content = content .. \"<rect rx='5' ry='5' x='\" .. ButtonBrakeX .. \"' y='\" .. ButtonBrakeY .. \"' width='\" .. ButtonBrakeWidth .. \"' height='\" .. ButtonBrakeHeight .. \"' fill='\" \n if BrakeIsOn then \n content = content .. \"#CC0000'\" -- Red if it's on\n else\n content = content .. defaultColor\n end\n if BrakeButtonHovered then \n content = content .. \" style='stroke:white; stroke-width:2;stroke-opacity:0.5;fill-opacity:1'\"\n else\n content = content .. \" style='stroke:black; stroke-width:1;stroke-opacity:0.5;fill-opacity:0.5'\"\n end \n \n content = content .. \"></rect>\"\n \n content = content .. \"<text x='\" .. ButtonBrakeX + ButtonBrakeWidth/2 .. \"' y='\" .. ButtonBrakeY + (ButtonBrakeHeight/2) + 5 .. \"' font-size='24' fill='\"\n if BrakeIsOn then\n content = content .. \"black\"\n else\n content = content .. \"white\"\n end\n \n content = content .. \"' text-anchor='middle' font-family='Montserrat'>\"\n \n if BrakeIsOn then \n content = content .. \"Disengage Brake</text>\"\n else\n content = content .. \"Engage Brake</text>\"\n end\n \n \n -- Prograde button\n content = content .. \"<rect rx='5' ry='5' x='\" .. ButtonProgradeX .. \"' y='\" .. ButtonProgradeY .. \"' width='\" .. ButtonProgradeWidth .. \"' height='\" .. ButtonProgradeHeight .. \"' fill='\" \n if ProgradeIsOn then \n content = content .. \"#FFEECC'\" -- Orange if it's on\n else\n content = content .. defaultColor\n end\n if ProgradeButtonHovered then \n content = content .. \" style='stroke:white; stroke-width:2;stroke-opacity:0.5;fill-opacity:1'\"\n else\n content = content .. \" style='stroke:black; stroke-width:1;stroke-opacity:0.5;fill-opacity:0.5'\"\n end \n content = content .. \"></rect>\"\n \n content = content .. \"<text x='\" .. ButtonProgradeX + ButtonProgradeWidth/2 .. \"' y='\" .. ButtonProgradeY + (ButtonProgradeHeight/2) + 5 .. \"' font-size='24' fill='\"\n if ProgradeIsOn then\n content = content .. \"black\"\n else\n content = content .. \"white\"\n end\n \n content = content .. \"' text-anchor='middle' font-family='Montserrat'>\"\n \n if ProgradeIsOn then \n content = content .. \"Disable Prograde</text>\"\n else\n content = content .. \"Align Prograde</text>\"\n end\n \n \n -- Retrograde button\n content = content .. \"<rect rx='5' ry='5' x='\" .. ButtonRetrogradeX .. \"' y='\" .. ButtonRetrogradeY .. \"' width='\" .. ButtonRetrogradeWidth .. \"' height='\" .. ButtonRetrogradeHeight .. \"' fill='\" \n if RetrogradeIsOn then \n content = content .. \"#42006b'\" -- Purple if it's on\n else\n content = content .. defaultColor\n end\n if RetrogradeButtonHovered then \n content = content .. \" style='stroke:white; stroke-width:2;stroke-opacity:0.5;fill-opacity:1'\"\n else\n content = content .. \" style='stroke:black; stroke-width:1;stroke-opacity:0.5;fill-opacity:0.5'\"\n end \n content = content .. \"></rect>\"\n \n content = content .. \"<text x='\" .. ButtonRetrogradeX + ButtonRetrogradeWidth/2 .. \"' y='\" .. ButtonRetrogradeY + (ButtonRetrogradeHeight/2) + 5 .. \"' font-size='24' fill='\"\n if RetrogradeIsOn then\n content = content .. \"black\"\n else\n content = content .. \"white\"\n end\n \n \n content = content .. \"' text-anchor='middle' font-family='Montserrat'>\"\n \n if RetrogradeIsOn then \n content = content .. \"Disable Retrograde</text>\"\n else\n content = content .. \"Align Retrograde</text>\"\n end\n \n \n -- Autopilot button\n content = content .. \"<rect rx='5' ry='5' x='\" .. ButtonAutopilotX .. \"' y='\" .. ButtonAutopilotY .. \"' width='\" .. ButtonAutopilotWidth .. \"' height='\" .. ButtonAutopilotHeight .. \"' fill='\" \n if Autopilot then \n content = content .. \"red'\" -- Red if it's on\n else\n content = content .. defaultColor\n end\n if AutopilotButtonHovered then \n content = content .. \" style='stroke:white; stroke-width:2;stroke-opacity:0.5;fill-opacity:1'\"\n else\n content = content .. \" style='stroke:black; stroke-width:1;stroke-opacity:0.5;fill-opacity:0.5'\"\n end \n content = content .. \"></rect>\"\n \n content = content .. \"<text x='\" .. ButtonAutopilotX + ButtonAutopilotWidth/2 .. \"' y='\" .. ButtonAutopilotY + (ButtonAutopilotHeight/2) + 5 .. \"' font-size='22' fill='\"\n if Autopilot then\n content = content .. \"black\"\n else\n content = content .. \"white\"\n end\n \n \n content = content .. \"' text-anchor='middle' font-family='Montserrat'>\"\n \n if Autopilot then \n content = content .. \"Disable Autopilot</text>\"\n else\n content = content .. \"Engage Autopilot: \" .. AutopilotTargetName .. \"</text>\"\n end\nend\nfunction HideInterplanetaryPanel()\n system.destroyWidgetPanel(panelInterplanetary)\n panelInterplanetary = nil\nend\nfunction ToggleAutoBrake()\n if AutopilotTargetPlanetName ~= \"None\" and brakeInput == 0 and not AutoBrake then\n AutoBrake = true\n Autopilot = false\n else\n AutoBrake = false\n end\nend\nfunction ToggleAutoPilot()\n if AutopilotTargetPlanetName ~= \"None\" and brakeInput == 0 and unit.getAtmosphereDensity() == 0 and not Autopilot then\n Autopilot = true\n AutoBrake = false\n else\n Autopilot = false\n end\nend\nfunction ToggleTurnBurn()\n TurnBurn = not TurnBurn\nend\nsystem.showScreen(1)\nfunction getRelativePitch(velocity) \n velocity = vec3(velocity)\n local pitch = -math.deg(math.atan(velocity.y, velocity.z)) + 180\n -- This is 0-360 where 0 is straight up\n pitch = pitch - 90\n -- So now 0 is straight, but we can now get angles up to 420\n if pitch < 0 then\n pitch = 360 + pitch \n end \n -- Now, if it's greater than 180, say 190, make it go to like -170\n if pitch > 180 then\n pitch = -180 + (pitch-180) \n end\n -- And it's backwards. \n return -pitch\nend\nfunction getRelativeYaw(velocity) \n velocity = vec3(velocity)\n return math.deg(math.atan(velocity.y, velocity.x)) - 90\nend\nfunction AlignToWorldVector(vector)\n -- Sets inputs to attempt to point at the autopilot target\n -- Meant to be called from Update or Tick repeatedly\n vector = vec3(vector):normalize()\n local targetVec = (vec3(core.getConstructWorldOrientationForward()) - vector)\n local yawAmount = -getMagnitudeInDirection(targetVec, core.getConstructWorldOrientationRight()) * AutopilotStrength\n local pitchAmount = -getMagnitudeInDirection(targetVec, core.getConstructWorldOrientationUp()) * AutopilotStrength\n\n yawInput2 = yawInput2 - (yawAmount + (yawAmount - PreviousYawAmount) * DampingMultiplier)\n pitchInput2 = pitchInput2 + (pitchAmount + (pitchAmount - PreviousPitchAmount) * DampingMultiplier)\n \n PreviousYawAmount = yawAmount\n PreviousPitchAmount = pitchAmount\n --system.print(math.abs(vector:dot(vec3(core.getConstructWorldOrientationForward()))))\n -- Return true or false depending on whether or not we're aligned\n if math.abs(yawAmount) < alignmentTolerance and math.abs(pitchAmount) < alignmentTolerance then\n return true\n end\n return false\nend\n-- HUD - https://github.com/Rezoix/DU-hud with major modifications by Archeageo\nfunction updateHud()\n local altitude = core.getAltitude()\n local velocity = core.getVelocity()\n local speed = vec3(velocity):len()\n local worldV = vec3(core.getWorldVertical())\n local constrF = vec3(core.getConstructWorldOrientationForward())\n local constrR = vec3(core.getConstructWorldOrientationRight())\n local constrV = vec3(core.getConstructWorldOrientationUp())\n local pitch = getPitch(worldV, constrF, constrR)--180 - getRoll(worldV, constrR, constrF)\n local roll = getRoll(worldV, constrF, constrR) --getRoll(worldV, constrF, constrR)\n local originalRoll = roll\n local originalPitch = math.floor(pitch)\n local bottomText = \"ROLL\"\n local grav = core.getWorldGravity()\n local gravity = math.floor(vec3(grav):len()*100)/100\n local atmos = unit.getAtmosphereDensity()\n local throt = math.floor(unit.getThrottle())\n local spd = speed*3.6\n local flightValue = unit.getAxisCommandValue(0)\n local flightType = Nav.axisCommandManager:getAxisCommandType(0)\n local flightStyle = \"TRAVEL\"\n local rgb = [[rgb(]] .. PrimaryR .. \",\" .. PrimaryG .. \",\" .. PrimaryB .. [[)]]\n local rgbdim = [[rgb(]] .. math.floor(PrimaryR *0.9 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.9 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.9 + 0.5) .. [[)]]\n local rgbdimmer = [[rgb(]] .. math.floor(PrimaryR *0.8 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.8 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.8 + 0.5) .. [[)]]\n local rgbO = rgb\n local rgbdimO = rgbdim\n local rgbdimmerO = rgbdimmer\n if system.isViewLocked() == 0 and userControlScheme ~= \"Keyboard\" then\n rgb = [[rgb(]] .. math.floor(PrimaryR *0.5 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.5 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.5 + 0.5) .. [[)]]\n rgbdim = [[rgb(]] .. math.floor(PrimaryR *0.4 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.4 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.4 + 0.5) .. [[)]]\n rgbdimmer = [[rgb(]] .. math.floor(PrimaryR *0.3 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.3 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.3 + 0.5) .. [[)]]\n end\n if (flightType == 1) then\n flightStyle = \"CRUISE\"\n\n end\n if Autopilot then\n flightStyle = \"AUTOPILOT\"\n end\n\n if (atmos == 0) then\n if (speed > 5) then\n pitch = getRelativePitch(velocity)\n roll = getRelativeYaw(velocity)\n else\n pitch = 0\n roll = 0\n end\n bottomText = \"YAW\"\n end\n\n content = [[\n <head>\n <style>\n body {margin: 0}\n svg {display:block; position:absolute; top:0; left:0} \n .majorLine {stroke:]] .. rgbO .. [[;opacity:0.7;stroke-width:3;fill-opacity:0;}\n .minorLine {stroke:]] .. rgb .. [[;opacity:0.7;stroke-width:3;fill-opacity:0;}\n .text {fill:]] .. rgbdimmer .. [[;font-family:Montserrat;font-weight:bold}\n #space {}\n #ecu {}\n #atmos {}\n\n </style>\n </head>\n <body>\n <svg height=\"100%\" width=\"100%\" viewBox=\"0 0 1920 1080\">\n <defs>\n <filter id=\"shadow\">\n <feDropShadow dx=\"0.2\" dy=\"0.4\" stdDeviation=\"0.2\"/>\n </filter>\n </defs>\n <g class=\"majorLine\">\n <path d=\"M 700 0 L 740 35 Q 960 55 1180 35 L 1220 0\"/>\n </g>\n <g class=\"minorLine\">\n <path d=\"M 792 550 L 785 550 L 785 650 L 792 650\"/>\n </g>\n <g>\n <polygon points=\"1138,540 1120,535 1120,545\" style=\"fill:]] ..rgb.. [[;opacity:0.7\"/>\n </g>\n <g class=\"text\">\n <g font-size=10>\n <text x=\"960\" y=\"375\" text-anchor=\"middle\" style=\"fill:]] .. rgbO .. [[\">SPEED</text>\n <text x=\"960\" y=\"390\" text-anchor=\"middle\" style=\"fill:]] .. rgbO .. [[;font-size:14;\">]]..math.floor(spd)..[[ km/h</text>\n <text x=\"1180\" y=\"710\" text-anchor=\"end\">GRAVITY</text>\n <text x=\"1180\" y=\"720\" text-anchor=\"end\">]]..gravity..[[ m/s2</text>\n </g>\n <g font-size=15>\n <text x=\"960\" y=\"33\" text-anchor=\"middle\" style=\"fill:]] .. rgbO .. [[\">]]..flightStyle..[[</text>\n </g>\n </g>]]\n if (flightStyle == \"TRAVEL\" or flightStyle == \"AUTOPILOT\") then\n content = content..[[\n <g class=\"text\">\n <g font-size=10>\n <text x=\"790\" y=\"660\" text-anchor=\"start\" style=\"fill:]] .. rgbO .. [[\">THROT</text>\n <text x=\"790\" y=\"670\" text-anchor=\"start\" style=\"fill:]] .. rgbO .. [[\">]]..throt..[[%</text>\n </g>\n </g>]]\n else\n content = content..[[\n <g class=\"text\">\n <g font-size=10>\n <text x=\"790\" y=\"660\" text-anchor=\"start\" style=\"fill:]] .. rgbO .. [[\">CRUISE</text>\n <text x=\"790\" y=\"670\" text-anchor=\"start\" style=\"fill:]] .. rgbO .. [[\">]]..flightValue..[[ km/h</text>\n </g>\n </g>]]\n end\n speedC=math.floor(spd)\n rollC = math.floor(roll)\n pitchC = math.floor(pitch) \n for i = pitchC-25,pitchC+25 do\n if (i%10==0) then\n num = i\n if (num > 180) then\n num = -180 + (num-180)\n elseif (num < -180) then\n num = 180 + (num+180)\n end\n content = content..[[<g transform=\"translate(0 ]]..(-i*5 + pitch*5 + 5)..[[)\">\n <text x=\"1180\" y=\"540\" style=\"fill:]] ..rgbdim.. [[;text-anchor:start;font-size:12;font-family:Montserrat;font-weight:bold\">]]..num..[[</text></g>]]\n end\n if (i%10==0) then\n len = 30\n elseif (i%5==0) then\n len = 20\n else\n len = 7\n end\n content = content..[[\n <g transform=\"translate(0 ]]..(-i*5 + pitch*5)..[[)\">\n <line x1=\"]]..(1140+len)..[[\" y1=\"540\" x2=\"1140\" y2=\"540\"style=\"stroke:]] ..rgbdim.. [[;opacity:0.3;stroke-width:2\"/></g>]]\n end\n content = content..[[\n <g class=\"text\">\n <g font-size=10>\n <text x=\"1180\" y=\"380\" text-anchor=\"end\" style=\"fill:]] .. rgbO .. [[\">PITCH</text>\n <text x=\"1180\" y=\"390\" text-anchor=\"end\" style=\"fill:]] .. rgbO .. [[\">]]..pitchC..[[ deg</text>\n </g>\n </g>\n ]]\n\n \n --** CIRCLE ALTIMETER - Base Code from Discord @Rainsome = Youtube CaptainKilmar** \n if circleRad > 0 and unit.getClosestPlanetInfluence() > 0 then\n if originalPitch > 90 and atmos == 0 then\n originalPitch = 90-(originalPitch-90)\n elseif originalPitch < -90 and atmos == 0 then\n originalPitch = -90 - (originalPitch+90)\n end\n --system.print(originalPitch)\n content = content..[[<circle r=\"]]..circleRad..[[\" cx=\"960\" cy=\"540\" opacity=\"0.1\" fill=\"]] ..\"#0083cb\".. [[\" stroke=\"black\" stroke-width=\"2\"/><clipPath id=\"cut\"><circle r=\"]]..(circleRad-1)..[[\" cx=\"960\" cy=\"540\"/></clipPath>\n <rect x=\"]]..(960-circleRad)..[[\" y=\"]] ..(540 + circleRad*(originalPitch/90)).. [[\" height=\"]]..(circleRad*2)..[[\" width=\"]]..(circleRad*2)..[[\" opacity=\"0.3\" fill=\"]] ..\"#6b5835\".. [[\" clip-path=\"url(#cut)\" transform=\"rotate(]] ..(-1*originalRoll).. [[ 960 540)\"/>]]\n end\n content = content..[[\n <g class=\"text\">\n <g font-size=10>\n <text x=\"960\" y=\"688\" text-anchor=\"middle\" style=\"fill:]] .. rgbO .. [[\">]]..bottomText..[[</text>\n <text x=\"960\" y=\"698\" text-anchor=\"middle\" style=\"fill:]] .. rgbO .. [[\">]]..math.floor(roll)..[[ deg</text>]]\n content = content..[[<g>\n <polygon points=\"960,725 955,707 965,707\" style=\"fill:]] ..rgb.. [[;opacity:0.7\"/>\n </g>]]\n for i = rollC-35,rollC+35 do\n if (i%10==0) then\n local sign = i/math.abs(i)\n if i == 0 then\n sign = 0\n end\n num = math.abs(i)\n if (num > 180) then\n num = 180 + (180-num) \n end\n content = content..[[<g transform=\"rotate(]]..(i - roll)..[[,960,460)\">\n <text x=\"960\" y=\"760\" style=\"fill:]] ..rgbdim.. [[;text-anchor:middle;font-size:12;font-family:Montserrat;opacity:0.3;font-weight:bold\">]]..math.floor(sign*num+0.5)..[[</text></g>]]\n end\n len = 5\n if (i%10==0) then\n len = 15\n elseif (i%5==0) then\n len = 10\n end\n content = content..[[<g transform=\"rotate(]]..(i - roll)..[[,960,460)\">\n <line x1=\"960\" y1=\"730\" x2=\"960\" y2=\"]]..(730+len)..[[\" style=\"stroke:]] ..rgbdim ..[[;opacity:0.3;stroke-width:2\"/></g>]]\n end\n\n if altitude > 0 then\n content = content..[[\n <g>\n <polygon points=\"782,540 800,535 800,545\" style=\"fill:]] ..rgb.. [[;opacity:0.7\"/>\n </g>\n <g class=\"text\">\n <g font-size=10>\n <text x=\"770\" y=\"380\" text-anchor=\"end\">ALTITUDE</text>\n <text x=\"770\" y=\"390\" text-anchor=\"end\">]]..math.floor(altitude)..[[ m</text>\n <text x=\"770\" y=\"710\" text-anchor=\"end\">ATMOSPHERE</text>\n <text x=\"770\" y=\"720\" text-anchor=\"end\">]]..(math.floor((atmos)*100)/100)..[[ m</text>\n </g>\n </g>]]\n altC = math.floor(altitude)\n for i = altC-250,altC+250 do\n if (i%10==0) then\n if (i%100==0) then\n num = i\n if (num < 0) then\n num = 0\n end\n \n \tcontent = content..[[<g transform=\"translate(0 ]]..(-i*.5 + altitude*.5)..[[)\">\n \t<text x=\"745\" y=\"540\" style=\"fill:]] ..rgbdim.. [[;text-anchor:end;font-size:12;font-family:Montserrat;font-weight:bold\">]]..num..[[</text></g>]]\n \tend\n \tlen = 5\n \tif (i%100==0) then\n \tlen = 30\n \telseif (i%50==0) then\n \tlen = 15\n \tend\n \tcontent = content..[[\n \t<g transform=\"translate(0 ]]..(-i*.5 + altitude*.5)..[[)\">\n \t<line x1=\"]]..(780-len)..[[\" y1=\"540\" x2=\"780\" y2=\"540\"style=\"stroke:]]..rgbdim..[[;opacity:0.3;stroke-width:2\"/></g>]]\n end\n end \n end\n content = content..[[<g transform=\"translate(0 ]]..(1-throt)..[[)\">\n <polygon points=\"798,650 810,647 810,653\" style=\"fill:]] ..rgbdim.. [[;opacity:0.7\"/>\n </g>]]\n\n -- After the HUD, set RGB values back to undimmed even if view is unlocked\n rgb = [[rgb(]] .. PrimaryR .. \",\" .. PrimaryG .. \",\" .. PrimaryB .. [[)]]\n rgbdim = [[rgb(]] .. math.floor(PrimaryR *0.9 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.9 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.9 + 0.5) .. [[)]]\n rgbdimmer = [[rgb(]] .. math.floor(PrimaryR *0.8 + 0.5) .. \",\" .. math.floor(PrimaryG * 0.8 + 0.5) .. \",\" .. math.floor(PrimaryB * 0.8 + 0.5) .. [[)]]\n \n if unit.isMouseControlActivated() == 1 then\n content = content .. \"<text x='960' y='550' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Warning: Invalid Control Scheme Detected</text>\"\n content = content .. \"<text x='960' y='600' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Keyboard Scheme must be selected</text>\"\n content = content .. \"<text x='960' y='650' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Set your preferred scheme in Lua Parameters instead</text>\"\n end\n if brakeInput == 1 then\n content = content .. \"<text x='960' y='850' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Brake Engaged</text>\"\n end\n if gearExtended then\n if hasGear then \n content = content .. \"<text x='960' y='900' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Gear Extended</text>\"\n else\n content = content .. \"<text x='960' y='900' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Landing Mode - Press G to Takeoff</text>\"\n end\n content = content .. \"<text x='960' y='930' font-size='20' fill='red' text-anchor='middle' font-family='Montserrat'>Hover Height: \" .. getDistanceDisplayString(Nav:getTargetGroundAltitude()) .. \"</text>\"\n end\n if AutoBrake and AutopilotTargetPlanetName ~= \"None\" then\n if brakeInput == 0 then\n content = content .. \"<text x='960' y='225' font-size='20' fill='orange' text-anchor='middle' font-family='Montserrat'>Auto-Braking when within \" .. getDistanceDisplayString(maxBrakeDistance) .. \" of \" .. AutopilotTargetPlanet.name .. \"</text>\"\n else\n content = content .. \"<text x='960' y='225' font-size='20' fill='orange' text-anchor='middle' font-family='Montserrat'>Auto-Braking until eccentricity:\" .. round(orbit.eccentricity,2) .. \" begins to increase</text>\"\n end\n elseif Autopilot and AutopilotTargetPlanetName ~= \"None\" then\n content = content .. \"<text x='960' y='225' font-size='20' fill='orange' text-anchor='middle' font-family='Montserrat'>Autopilot Engaged - \" .. AutopilotStatus .. \"</text>\"\n end\n if TurnBurn then\n content = content .. \"<text x='960' y='150' font-size='20' fill='darkred' text-anchor='middle' font-family='Montserrat'>Turn & Burn Braking</text>\"\n end\n if orbit ~= nil and orbit.eccentricity < 1 and orbit.eccentricity > 0 and unit.getAtmosphereDensity() < 0.25 and planet ~= nil and orbit.period ~= nil then\n -- If orbits are up, let's try drawing a mockup\n -- We don't really care about scale.\n -- First let's define an area to draw it\n local orbitMapX = 75\n local orbitMapY = 0\n local orbitMapSize = 250 -- Always square\n \n -- Draw a darkened box around it to keep it visible\n content = content .. '<rect width=\"' .. orbitMapSize+orbitMapX*2 .. '\" height=\"' .. orbitMapSize+orbitMapY .. '\" rx=\"10\" ry=\"10\" x=\"' .. 0 .. '\" y=\"' .. 0 .. '\" style=\"fill:rgb(0,0,100);stroke-width:4;stroke:white;fill-opacity:0.3;\" />'\n \n -- Then, draw a 'planet' in the center\n -- Now draw an ellipse, with both foci at the planet because we're assuming 0 mass ship\n -- Where rx is apoapsis - periapsis (scaled), and ry is... related to eccentricity\n -- Such that an eccentricity of 0 gives ry = rx, and eccentricity of 1+ gives ry = 0 (don't draw it)\n -- So ry would be (1-eccentricity)*rx\n -- And actually. To scale rx would to be to make it always equal to width.\n local rx = orbitMapSize/4\n \n -- And, the center of the thing should be shifted so that the apo is far and periapsis is near\n -- We have arbitrarily chosen the AP to be to the right\n -- So shift it right by (AP-PE)... scaled...\n -- To figure out the scale, compare (AP + PE + radius*2) to rx\n local scale = (orbit.apoapsis.altitude + orbit.periapsis.altitude + planet.radius*2)/(rx*2)\n --local ry = (1-orbit.eccentricity)*rx\n local ry = (planet.radius + orbit.periapsis.altitude + (orbit.apoapsis.altitude - orbit.periapsis.altitude)/2)/scale * (1-orbit.eccentricity)\n -- Then if we divide our xOffset by this it should be good\n --local xOffset = ((orbit.apoapsis.altitude - orbit.periapsis.altitude)/scale)\n local xOffset = rx - orbit.periapsis.altitude/scale - planet.radius/scale\n \n local ellipseColor = rgbdim\n if orbit.periapsis.altitude <= 0 then\n ellipseColor = 'red'\n end\n content = content .. '<ellipse cx=\"' .. orbitMapX + orbitMapSize/2 + xOffset .. '\" cy=\"' .. orbitMapY + orbitMapSize/2 .. '\" rx=\"' .. rx .. '\" ry=\"' .. ry .. '\" style=\"fill:none;stroke:' .. ellipseColor .. ';stroke-width:2\" />'\n content = content .. '<circle cx=\"' .. orbitMapX + orbitMapSize/2 .. '\" cy=\"' .. orbitMapY + orbitMapSize/2 .. '\" r=\"' .. planet.radius/scale .. '\" stroke=\"white\" stroke-width=\"3\" fill=\"blue\" />' -- I think the stroke-width is extending past the radius so -3? IDK though. Things just get weird on scales like these.\n -- Mark the apoapsis and periapsis, AP is on the right\n \n local orbitInfoYOffset = 15\n local x = orbitMapX + orbitMapSize + orbitMapX/2\n local y = orbitMapY + orbitMapSize/2 + 5\n \n -- Draw guide lines on the ellipse somehow, from the edge of where the title should be\n content = content .. [[<line x1=\"]].. x - 35 ..[[\" y1=\"]] .. y-5 .. [[\" x2=\"]] .. orbitMapX + orbitMapSize/2 + rx + xOffset .. [[\" y2=\"]] .. y-5 .. [[\"style=\"stroke:]] .. rgbdim .. [[;opacity:0.3;stroke-width:3\"/>]]\n \n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='14' fill=\" .. rgb .. \" text-anchor='middle' font-family='Montserrat'>Apoapsis</text>\"\n y = y + orbitInfoYOffset\n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='12' fill=\" .. rgbdimmer .. \" text-anchor='middle' font-family='Montserrat'>\" .. getDistanceDisplayString(orbit.apoapsis.altitude) .. \"</text>\"\n y = y + orbitInfoYOffset\n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='12' fill=\" .. rgbdimmer .. \" text-anchor='middle' font-family='Montserrat'>\" .. FormatTimeString(orbit.timeToApoapsis) .. \"</text>\"\n y = y + orbitInfoYOffset\n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='12' fill=\" .. rgbdimmer .. \" text-anchor='middle' font-family='Montserrat'>\" .. getSpeedDisplayString(orbit.apoapsis.speed) .. \"</text>\"\n \n y = orbitMapY + orbitMapSize/2 + 5\n x = orbitMapX - orbitMapX/2\n \n content = content .. [[<line x1=\"]].. x + 35 ..[[\" y1=\"]] .. y-5 .. [[\" x2=\"]] .. orbitMapX + orbitMapSize/2 - rx + xOffset .. [[\" y2=\"]] .. y-5 .. [[\"style=\"stroke:]] .. rgbdim .. [[;opacity:0.3;stroke-width:3\"/>]]\n \n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='14' fill=\" .. rgb .. \" text-anchor='middle' font-family='Montserrat'>Periapsis</text>\"\n \n y = y + orbitInfoYOffset\n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='12' fill=\" .. rgbdimmer .. \" text-anchor='middle' font-family='Montserrat'>\" .. getDistanceDisplayString(orbit.periapsis.altitude) .. \"</text>\"\n y = y + orbitInfoYOffset\n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='12' fill=\" .. rgbdimmer .. \" text-anchor='middle' font-family='Montserrat'>\" .. FormatTimeString(orbit.timeToPeriapsis) .. \"</text>\"\n y = y + orbitInfoYOffset\n content = content .. \"<text x='\" .. x .. \"' y='\" .. y .. \"' font-size='12' fill=\" .. rgbdimmer .. \" text-anchor='middle' font-family='Montserrat'>\" .. getSpeedDisplayString(orbit.periapsis.speed) .. \"</text>\"\n \n -- Add a label for the planet\n content = content .. \"<text x='\" .. orbitMapX + orbitMapSize/2 .. \"' y='\" .. 20 .. \"' font-size='18' fill=\" .. rgb .. \" text-anchor='middle' font-family='Montserrat'>\" .. planet.name .. \"</text>\"\n \n \n \n -- And ... the hard part. Figure out how to mark the ship.\n -- We could use the scale we calced and get the ship's distance from the planet center\n -- And that would tell us a part of it.\n -- If we take TimeToPeriapsis/Period, it should give us a value from 0 to 1 where 0.5 means we're at the apoapsis, 0 and 1 both mean basically at periapsis\n -- So from 1 to 0.5 is the bottom half of the ellipse\n -- And from 0.5 to 0 is the top half\n -- And then from 0.5 to 0, and 1 to 0.5, is the right half...\n -- Or, if ratio is more than 0.25 or less than 0.75\n \n local apsisRatio = (orbit.timeToApoapsis/orbit.period) * 2 * math.pi\n -- So now that we have directions, we need to calc a distance from the center to the orbit\n \n -- Or am I stupid.\n -- I have a number from 0 to 1 representing an angle. If I multiply by 2pi that's radians right?\n \n -- Alright alright.\n -- x = xr * cos(t)\n -- y = yr * sin(t)\n -- Where t ranges from 0 to 2pi radians\n local shipX = rx * math.cos(apsisRatio)\n local shipY = ry * math.sin(apsisRatio)\n \n content = content .. '<circle cx=\"' .. orbitMapX + orbitMapSize/2 + shipX + xOffset .. '\" cy=\"' .. orbitMapY + orbitMapSize/2 + shipY .. '\" r=\"5\" stroke=\"white\" stroke-width=\"3\" fill=\"white\" />'\n \n \n -- Once we have all that, we should probably rotate the entire thing so that the ship is always at the bottom so you can see AP and PE move?\n \n end\n content = content..[[</svg>]]\n --system.setScreen(content)\n\nend\nfunction getPitch(gravityDirection, forward, right)\n local horizontalForward = gravityDirection:cross(right):normalize_inplace() -- Cross forward?\n local pitch = math.acos(utils.clamp(horizontalForward:dot(-forward), -1, 1)) * constants.rad2deg -- acos?\n if horizontalForward:cross(-forward):dot(right) < 0 then pitch = -pitch end -- Cross right dot forward?\n return pitch\nend\n-- Planet Info - https://gitlab.com/JayleBreak/dualuniverse/-/tree/master/DUflightfiles/autoconf/custom with minor modifications\nfunction Atlas()\n return {\n[0] = {\n [1]={\n GM=6930729684,\n bodyId=1,\n center={x=17465536.000,y=22665536.000,z=-34464.000},\n name='Madis',\n planetarySystemId=0,\n radius=44300\n },\n [2]={\n GM=157470826617,\n bodyId=2,\n center={x=-8.000,y=-8.000,z=-126303.000},\n name='Alioth',\n planetarySystemId=0,\n radius=126068\n },\n [3]={\n GM=11776905000,\n bodyId=3,\n center={x=29165536.000,y=10865536.000,z=65536.000},\n name='Thades',\n planetarySystemId=0,\n radius=49000\n },\n [4]={\n GM=14893847582,\n bodyId=4,\n center={x=-13234464.000,y=55765536.000,z=465536.000},\n name='Talemai',\n planetarySystemId=0,\n radius=57450\n },\n [5]={\n GM=16951680000,\n bodyId=5,\n center={x=-43534464.000,y=22565536.000,z=-48934464.000},\n name='Feli',\n planetarySystemId=0,\n radius=60000\n },\n [6]={\n GM=10502547741,\n bodyId=6,\n center={x=52765536.000,y=27165538.000,z=52065535.000},\n name='Sicari',\n planetarySystemId=0,\n radius=51100\n },\n [7]={\n GM=13033380591,\n bodyId=7,\n center={x=58665538.000,y=29665535.000,z=58165535.000},\n name='Sinnen',\n planetarySystemId=0,\n radius=54950\n },\n [8]={\n GM=18477723600,\n bodyId=8,\n center={x=80865538.000,y=54665536.000,z=-934463.940},\n name='Teoma',\n planetarySystemId=0,\n radius=62000\n },\n [9]={\n GM=18606274330,\n bodyId=9,\n center={x=-94134462.000,y=12765534.000,z=-3634464.000},\n name='Jago',\n planetarySystemId=0,\n radius=61590\n },\n [10]={\n GM=78480000,\n bodyId=10,\n center={x=17448118.224,y=22966846.286,z=143078.820},\n name='Madis Moon 1',\n planetarySystemId=0,\n radius=10000\n },\n [11]={\n GM=237402000,\n bodyId=11,\n center={x=17194626.000,y=22243633.880,z=-214962.810},\n name='Madis Moon 2',\n planetarySystemId=0,\n radius=11000\n },\n [12]={\n GM=265046609,\n bodyId=12,\n center={x=17520614.000,y=22184730.000,z=-309989.990},\n name='Madis Moon 3',\n planetarySystemId=0,\n radius=15005\n },\n [21]={\n GM=2118960000,\n bodyId=21,\n center={x=457933.000,y=-1509011.000,z=115524.000},\n name='Alioth Moon 1',\n planetarySystemId=0,\n radius=30000\n },\n [22]={\n GM=2165833514,\n bodyId=22,\n center={x=-1692694.000,y=729681.000,z=-411464.000},\n name='Alioth Moon 4',\n planetarySystemId=0,\n radius=30330\n },\n [26]={\n GM=68234043600,\n bodyId=26,\n center={x=-1404835.000,y=562655.000,z=-285074.000},\n name='Sanctuary',\n planetarySystemId=0,\n radius=83400\n },\n [30]={\n GM=211564034,\n bodyId=30,\n center={x=29214402.000,y=10907080.695,z=433858.200},\n name='Thades Moon 1',\n planetarySystemId=0,\n radius=14002\n },\n [31]={\n GM=264870000,\n bodyId=31,\n center={x=29404193.000,y=10432768.000,z=19554.131},\n name='Thades Moon 2',\n planetarySystemId=0,\n radius=15000\n },\n [40]={\n GM=141264000,\n bodyId=40,\n center={x=-13503090.000,y=55594325.000,z=769838.640},\n name='Talemai Moon 2',\n planetarySystemId=0,\n radius=12000\n },\n [41]={\n GM=106830900,\n bodyId=41,\n center={x=-12800515.000,y=55700259.000,z=325207.840},\n name='Talemai Moon 3',\n planetarySystemId=0,\n radius=11000\n },\n [42]={\n GM=264870000,\n bodyId=42,\n center={x=-13058408.000,y=55781856.000,z=740177.760},\n name='Talemai Moon 1',\n planetarySystemId=0,\n radius=15000\n },\n [50]={\n GM=499917600,\n bodyId=50,\n center={x=-43902841.780,y=22261034.700,z=-48862386.000},\n name='Feli Moon 1',\n planetarySystemId=0,\n radius=14000\n },\n [70]={\n GM=396912600,\n bodyId=70,\n center={x=58969616.000,y=29797945.000,z=57969449.000},\n name='Sinnen Moon 1',\n planetarySystemId=0,\n radius=17000\n },\n [100]={\n GM=13975172474,\n bodyId=100,\n center={x=98865536.000,y=-13534464.000,z=-934461.990},\n name='Lacobus',\n planetarySystemId=0,\n radius=55650\n },\n [101]={\n GM=264870000,\n bodyId=101,\n center={x=98905288.170,y=-13950921.100,z=-647589.530},\n name='Lacobus Moon 3',\n planetarySystemId=0,\n radius=15000\n },\n [102]={\n GM=444981600,\n bodyId=102,\n center={x=99180968.000,y=-13783862.000,z=-926156.400},\n name='Lacobus Moon 1',\n planetarySystemId=0,\n radius=18000\n },\n [103]={\n GM=211503600,\n bodyId=103,\n center={x=99250052.000,y=-13629215.000,z=-1059341.400},\n name='Lacobus Moon 2',\n planetarySystemId=0,\n radius=14000\n },\n [110]={\n GM=9204742375,\n bodyId=110,\n center={x=14165536.000,y=-85634465.000,z=-934464.300},\n name='Symeon',\n planetarySystemId=0,\n radius=49050\n },\n [120]={\n GM=7135606629,\n bodyId=120,\n center={x=2865536.700,y=-99034464.000,z=-934462.020},\n name='Ion',\n planetarySystemId=0,\n radius=44950\n },\n [121]={\n GM=106830900,\n bodyId=121,\n center={x=2472916.800,y=-99133747.000,z=-1133582.800},\n name='Ion Moon 1',\n planetarySystemId=0,\n radius=11000\n },\n [122]={\n GM=176580000,\n bodyId=122,\n center={x=2995424.500,y=-99275010.000,z=-1378480.700},\n name='Ion Moon 2',\n planetarySystemId=0,\n radius=15000\n } \n }\n}\nend\nfunction PlanetRef() \n--[[ \n Provide coordinate transforms and access to kinematic related parameters\n Author: JayleBreak\n Usage (unit.start):\n PlanetaryReference = require('planetref')\n galaxyReference = PlanetaryReference(referenceTableSource)\n helios = galaxyReference[0] -- PlanetaryReference.PlanetarySystem instance\n alioth = helios[2] -- PlanetaryReference.BodyParameters instance\n Methods:\n PlanetaryReference:getPlanetarySystem - based on planetary system ID.\n PlanetaryReference.isMapPosition - 'true' if an instance of 'MapPosition'\n PlanetaryReference.createBodyParameters - for entry into reference table\n PlanetaryReference.BodyParameters - a class containing a body's information.\n PlanetaryReference.MapPosition - a class for map coordinates\n PlanetaryReference.PlanetarySystem - a container for planetary system info.\n PlanetarySystem:castIntersections - from a position in a given direction.\n PlanetarySystem:closestBody - to the specified coordinates.\n PlanetarySystem:convertToBodyIdAndWorldCoordinates - from map coordinates.\n PlanetarySystem:getBodyParameters - from reference table.\n PlanetarySystem:getPlanetarySystemId - for the instance.\n BodyParameters:convertToWorldCoordinates - from map coordinates\n BodyParameters:convertToMapPosition - from world coordinates\n BodyParameters:getAltitude - of world coordinates\n BodyParameters:getDistance - from center to world coordinates\n BodyParameters:getGravity - at a given position in world coordinates.\n Description\n An instance of the 'PlanetaryReference' \"class\" can contain transform and\n kinematic reference information for all planetary systems in DualUniverse.\n Each planetary system is identified by a numeric identifier. Currently,\n the only planetary system, Helios, has the identifier: zero. This \"class\"\n supports the indexing ('[]') operation which is equivalent to the\n use of the 'getPlanetarySystem' method. It also supports the 'pairs()'\n method for iterating over planetary systems.\n\n An instance of the 'PlanetarySystem' \"class\" contains all reference\n information for a specific system. It supports the indexing ('[]') and\n 'pairs()' functions which allows iteration over each \"body\" in the\n system where the key is the numeric body ID. It also supports the\n 'tostring()' method.\n An instance of the 'BodyParameters' \"class\" contains all reference\n information for a single celestial \"body\" (a moon or planet). It supports\n the 'tostring()' method, and contains the data members:\n planetarySystemId - numeric planetary system ID\n bodyId - numeric body ID\n radius - radius of the body in meters (zero altitude)\n center - world coordinates of the body's center position\n GM - the gravitation parameter (g = GM/radius^2)\n Note that the user is allowed to add custom fields (e.g. body name), but\n should insure that complex table values have the '__tostring' metamethod\n implemented.\n Transform and Kinematics:\n \"World\" coordinates is a cartesian coordinate system with an origin at an\n arbitrary fixed point in a planetary system and with distances measured in\n meters. The coordinates are expressible either as a simple table of 3 values\n or an instance of the 'vec3' class. In either case, the planetary system\n identity is implicit.\n \"Map\" coordinates is a geographic coordinate system with an origin at the\n center of an identified (by a numeric value) celestial body which is a\n member of an identified (also a numeric value) planetary system. Note that\n the convention that latitude, longitude, and altitude values will be the\n position's x, y, and z world coordinates in the special case of body ID 0.\n The kinematic parameters in the reference data permit calculations of the\n gravitational attraction of the celestial body on other objects.\n Reference Data:\n This is an example of reference data with a single entry assigned to\n planetary system ID 0, and body ID 2 ('Alioth'):\n referenceTable = {\n [0] = { [2] = { planetarySystemId = 0,\n bodyId = 2,\n radius = 126068,\n center = vec3({x=-8, y=-8, z=-126303}),\n GM = 1.572199+11 } -- as in F=-GMm/r^2\n }\n }\n ref=PlanetaryReference(referenceTable)\n Collecting Reference Data:\n A combination of information from the \"Map\" screen in the DU user interface,\n and values reported by the DU Lua API can be the source of the reference\n table's data (planetarySystemId, bodyId, and surfaceArea is from the user\n interface):\n referenceTable = {}\n referenceTable[planetarySystemId][bodyId] =\n PlanetaryReference.createBodyParameters(planetarySystemId,\n bodyId,\n surfaceArea,\n core.getConstructWorldPos(),\n core.getWorldVertical(),\n core.getAltitude(),\n core.g())\n Adapting Data Sources:\n Other sources of data can be adapted or converted. An example of adapting a\n table, defined in the file: 'planets.lua', containing information on a single\n planetary system and using celestial body name as the key follows (note that\n a 'name' field is added to the BodyParameters instance transparently after\n construction, and the '__pairs' meta function is required to support the\n 'closestBody' and '__tostring' methods):\n ref=PlanetaryReference(\n {[0] = setmetatable(require('planets'),\n { __index = function(bodies, bodyId)\n for _,v in pairs(bodies) do\n if v and v.bodyId == bodyId then return v end\n end\n return nil\n end,\n __pairs = function(bodies)\n return function(t, k)\n local nk, nv = next(t, k)\n if nv then\n local GM = nv.gravity * nv.radius^2\n local bp = BodyParameters(0,\n nv.id,\n nv.radius,\n nv.pos,\n GM)\n bp.name = nk\n return nk, bp\n end\n return nk, nv\n end, bodies, nil\n end })\n })\n\n Converting Data Sources:\n An instance of 'PlanetaryReference' that has been adapted to a data source\n can be used to convert that source to simple table. For example,\n using the adapted instance shown above:\n load('convertedData=' .. tostring(ref))()\n newRef=PlanetaryReference(convertedData)\n Also See: kepler.lua\n ]]--\n--[[ START OF LOCAL IMPLEMENTATION DETAILS ]]--\n-- Type checks\nlocal function isNumber(n) return type(n) == 'number' end\nlocal function isSNumber(n) return type(tonumber(n)) == 'number' end\nlocal function isTable(t) return type(t) == 'table' end\nlocal function isString(s) return type(s) == 'string' end\nlocal function isVector(v) return isTable(v)\n and isNumber(v.x and v.y and v.z) end\nlocal function isMapPosition(m) return isTable(m) and isNumber(m.latitude and\n m.longitude and\n m.altitude and\n m.bodyId and\n m.systemId) end\n-- Constants\nlocal deg2rad = math.pi/180\nlocal rad2deg = 180/math.pi\nlocal epsilon = 1e-10\nlocal num = ' *([+-]?%d+%.?%d*e?[+-]?%d*)'\nlocal posPattern = '::pos{' .. num .. ',' .. num .. ',' .. num .. ',' ..\n num .. ',' .. num .. '}'\n-- Utilities\nlocal utils = require('cpml.utils')\nlocal vec3 = require('cpml.vec3')\nlocal clamp = utils.clamp\nlocal function float_eq(a,b)\n if a == 0 then return math.abs(b) < 1e-09 end\n if b == 0 then return math.abs(a) < 1e-09 end\n return math.abs(a - b) < math.max(math.abs(a),math.abs(b))*epsilon\nend\nlocal function formatNumber(n)\n local result = string.gsub(\n string.reverse(string.format('%.4f',n)),\n '^0*%.?','')\n return result == '' and '0' or string.reverse(result)\nend\nlocal function formatValue(obj)\n if isVector(obj) then\n return string.format('{x=%.3f,y=%.3f,z=%.3f}', obj.x, obj.y, obj.z)\n end\n if isTable(obj) and not getmetatable(obj) then\n local list = {}\n local nxt = next(obj)\n if type(nxt) == 'nil' or nxt == 1 then -- assume this is an array\n list = obj\n else\n for k,v in pairs(obj) do\n local value = formatValue(v)\n if type(k) == 'number' then\n table.insert(list, string.format('[%s]=%s', k, value))\n else\n table.insert(list, string.format('%s=%s', k, value))\n end\n end\n end\n return string.format('{%s}', table.concat(list, ','))\n end\n if isString(obj) then\n return string.format(\"'%s'\", obj:gsub(\"'\",[[\\']]))\n end\n return tostring(obj)\nend\n-- CLASSES\n-- BodyParameters: Attributes of planetary bodies (planets and moons)\nlocal BodyParameters = {}\nBodyParameters.__index = BodyParameters\nBodyParameters.__tostring =\n function(obj, indent)\n local sep = indent or ''\n local keys = {}\n for k in pairs(obj) do table.insert(keys, k) end\n table.sort(keys)\n local list = {}\n for _, k in ipairs(keys) do\n local value = formatValue(obj[k])\n if type(k) == 'number' then\n table.insert(list, string.format('[%s]=%s', k, value))\n else\n table.insert(list, string.format('%s=%s', k, value))\n end\n end\n if indent then\n return string.format('%s%s',\n indent,\n table.concat(list, ',\\n' .. indent))\n end\n return string.format('{%s}', table.concat(list, ','))\n end\nBodyParameters.__eq = function(lhs, rhs)\n return lhs.planetarySystemId == rhs.planetarySystemId and\n lhs.bodyId == rhs.bodyId and\n float_eq(lhs.radius, rhs.radius) and\n float_eq(lhs.center.x, rhs.center.x) and\n float_eq(lhs.center.y, rhs.center.y) and\n float_eq(lhs.center.z, rhs.center.z) and\n float_eq(lhs.GM, rhs.GM)\n end\nlocal function mkBodyParameters(systemId, bodyId, radius, worldCoordinates, GM)\n -- 'worldCoordinates' can be either table or vec3\n assert(isSNumber(systemId),\n 'Argument 1 (planetarySystemId) must be a number:' .. type(systemId))\n assert(isSNumber(bodyId),\n 'Argument 2 (bodyId) must be a number:' .. type(bodyId))\n assert(isSNumber(radius),\n 'Argument 3 (radius) must be a number:' .. type(radius))\n assert(isTable(worldCoordinates),\n 'Argument 4 (worldCoordinates) must be a array or vec3.' ..\n type(worldCoordinates))\n assert(isSNumber(GM),\n 'Argument 5 (GM) must be a number:' .. type(GM))\n return setmetatable({planetarySystemId = tonumber(systemId),\n bodyId = tonumber(bodyId),\n radius = tonumber(radius),\n center = vec3(worldCoordinates),\n GM = tonumber(GM) }, BodyParameters)\nend\n-- MapPosition: Geographical coordinates of a point on a planetary body.\nlocal MapPosition = {}\nMapPosition.__index = MapPosition\nMapPosition.__tostring = function(p)\n return string.format('::pos{%d,%d,%s,%s,%s}',\n p.systemId,\n p.bodyId,\n formatNumber(p.latitude*rad2deg),\n formatNumber(p.longitude*rad2deg),\n formatNumber(p.altitude))\n end\nMapPosition.__eq = function(lhs, rhs)\n return lhs.bodyId == rhs.bodyId and\n lhs.systemId == rhs.systemId and\n float_eq(lhs.latitude, rhs.latitude) and\n float_eq(lhs.altitude, rhs.altitude) and\n (float_eq(lhs.longitude, rhs.longitude) or\n float_eq(lhs.latitude, math.pi/2) or\n float_eq(lhs.latitude, -math.pi/2))\n end\n-- latitude and longitude are in degrees while altitude is in meters\nlocal function mkMapPosition(overload, bodyId, latitude, longitude, altitude)\n local systemId = overload -- Id or '::pos{...}' string\n if isString(overload) and not longitude and not altitude and\n not bodyId and not latitude then\n systemId, bodyId, latitude, longitude, altitude =\n string.match(overload, posPattern)\n assert(systemId, 'Argument 1 (position string) is malformed.')\n else\n assert(isSNumber(systemId),\n 'Argument 1 (systemId) must be a number:' .. type(systemId))\n assert(isSNumber(bodyId),\n 'Argument 2 (bodyId) must be a number:' .. type(bodyId))\n assert(isSNumber(latitude),\n 'Argument 3 (latitude) must be in degrees:' .. type(latitude))\n assert(isSNumber(longitude),\n 'Argument 4 (longitude) must be in degrees:' .. type(longitude))\n assert(isSNumber(altitude),\n 'Argument 5 (altitude) must be in meters:' .. type(altitude))\n end\n systemId = tonumber(systemId)\n bodyId = tonumber(bodyId)\n latitude = tonumber(latitude)\n longitude = tonumber(longitude)\n altitude = tonumber(altitude)\n if bodyId == 0 then -- this is a hack to represent points in space\n return setmetatable({latitude = latitude,\n longitude = longitude,\n altitude = altitude,\n bodyId = bodyId,\n systemId = systemId}, MapPosition)\n end\n return setmetatable({latitude = deg2rad*clamp(latitude, -90, 90),\n longitude = deg2rad*(longitude % 360),\n altitude = altitude,\n bodyId = bodyId,\n systemId = systemId}, MapPosition)\nend\n-- PlanetarySystem - map body IDs to BodyParameters\nlocal PlanetarySystem = {}\nPlanetarySystem.__index = PlanetarySystem\nPlanetarySystem.__tostring =\n function (obj, indent)\n local sep = indent and (indent .. ' ' )\n local bdylist = {}\n local keys = {}\n for k in pairs(obj) do table.insert(keys, k) end\n table.sort(keys)\n for _, bi in ipairs(keys) do\n bdy = obj[bi]\n local bdys = BodyParameters.__tostring(bdy, sep)\n if indent then\n table.insert(bdylist,\n string.format('[%s]={\\n%s\\n%s}',\n bi, bdys, indent))\n else\n table.insert(bdylist, string.format(' [%s]=%s', bi, bdys))\n end\n end\n if indent then\n return string.format('\\n%s%s%s',\n indent,\n table.concat(bdylist, ',\\n' .. indent),\n indent)\n end\n return string.format('{\\n%s\\n}', table.concat(bdylist, ',\\n'))\n end\nlocal function mkPlanetarySystem(referenceTable)\n local atlas = {}\n local pid\n for _, v in pairs(referenceTable) do\n local id = v.planetarySystemId\n if type(id) ~= 'number' then\n error('Invalid planetary system ID: ' .. tostring(id))\n elseif pid and id ~= pid then\n error('Mismatch planetary system IDs: ' .. id .. ' and '\n .. pid)\n end\n local bid = v.bodyId\n if type(bid) ~= 'number' then\n error('Invalid body ID: ' .. tostring(bid))\n elseif atlas[bid] then\n error('Duplicate body ID: ' .. tostring(bid))\n end\n setmetatable(v.center, getmetatable(vec3.unit_x))\n atlas[bid] = setmetatable(v, BodyParameters)\n pid = id\n end\n return setmetatable(atlas, PlanetarySystem)\nend\n-- PlanetaryReference - map planetary system ID to PlanetarySystem\nPlanetaryReference = {}\nlocal function mkPlanetaryReference(referenceTable)\n return setmetatable({ galaxyAtlas = referenceTable or {} },\n PlanetaryReference)\nend\nPlanetaryReference.__index = \n function(t,i)\n if type(i) == 'number' then\n local system = t.galaxyAtlas[i]\n return mkPlanetarySystem(system)\n end\n return rawget(PlanetaryReference, i)\n end\nPlanetaryReference.__pairs =\n function(obj)\n return function(t, k)\n local nk, nv = next(t, k)\n return nk, nv and mkPlanetarySystem(nv)\n end, obj.galaxyAtlas, nil\n end\nPlanetaryReference.__tostring =\n function (obj)\n local pslist = {}\n for _,ps in pairs(obj or {}) do\n local psi = ps:getPlanetarySystemId()\n local pss = PlanetarySystem.__tostring(ps, ' ')\n table.insert(pslist,\n string.format(' [%s]={%s\\n }', psi, pss))\n end\n return string.format('{\\n%s\\n}\\n', table.concat(pslist,',\\n'))\n end\n--[[ START OF PUBLIC INTERFACE ]]--\n-- PlanetaryReference CLASS METHODS:\n--\n-- BodyParameters - create an instance of BodyParameters class\n-- planetarySystemId [in]: the body's planetary system ID.\n-- bodyId [in]: the body's ID.\n-- radius [in]: the radius in meters of the planetary body.\n-- bodyCenter [in]: the world coordinates of the center (vec3 or table).\n-- GM [in]: the body's standard gravitational parameter.\n-- return: an instance of BodyParameters class.\n--\nPlanetaryReference.BodyParameters = mkBodyParameters\n--\n-- MapPosition - create an instance of the MapPosition class\n-- overload [in]: either a planetary system ID or a position string ('::pos...')\n-- bodyId [in]: (ignored if overload is a position string) the body's ID.\n-- latitude [in]: (ignored if overload is a position string) the latitude.\n-- longitude [in]:(ignored if overload is a position string) the longitude.\n-- altitude [in]: (ignored if overload is a position string) the altitude.\n-- return: the class instance\n--\nPlanetaryReference.MapPosition = mkMapPosition\n--\n-- PlanetarySystem - create an instance of PlanetarySystem class\n-- referenceData [in]: a table (indexed by bodyId) of body reference info.\n-- return: the class instance\n--\nPlanetaryReference.PlanetarySystem = mkPlanetarySystem\n--\n-- createBodyParameters - create an instance of BodyParameters class\n-- planetarySystemId [in]: the body's planetary system ID.\n-- bodyId [in]: the body's ID.\n-- surfaceArea [in]: the body's surface area in square meters.\n-- aPosition [in]: world coordinates of a position near the body.\n-- verticalAtPosition [in]: a vector pointing towards the body center.\n-- altitudeAtPosition [in]: the altitude in meters at the position.\n-- gravityAtPosition [in]: the magnitude of the gravitational acceleration.\n-- return: an instance of BodyParameters class.\n--\nfunction PlanetaryReference.createBodyParameters(planetarySystemId,\n bodyId,\n surfaceArea,\n aPosition,\n verticalAtPosition,\n altitudeAtPosition,\n gravityAtPosition)\n assert(isSNumber(planetarySystemId),\n 'Argument 1 (planetarySystemId) must be a number:' ..\n type(planetarySystemId))\n assert(isSNumber(bodyId),\n 'Argument 2 (bodyId) must be a number:' .. type(bodyId))\n assert(isSNumber(surfaceArea),\n 'Argument 3 (surfaceArea) must be a number:' .. type(surfaceArea))\n assert(isTable(aPosition),\n 'Argument 4 (aPosition) must be an array or vec3:' ..\n type(aPosition))\n assert(isTable(verticalAtPosition),\n 'Argument 5 (verticalAtPosition) must be an array or vec3:' ..\n type(verticalAtPosition))\n assert(isSNumber(altitudeAtPosition),\n 'Argument 6 (altitude) must be in meters:' ..\n type(altitudeAtPosition))\n assert(isSNumber(gravityAtPosition),\n 'Argument 7 (gravityAtPosition) must be number:' ..\n type(gravityAtPosition))\n local radius = math.sqrt(surfaceArea/4/math.pi)\n local distance = radius + altitudeAtPosition\n local center = vec3(aPosition) + distance*vec3(verticalAtPosition)\n local GM = gravityAtPosition * distance * distance\n return mkBodyParameters(planetarySystemId, bodyId, radius, center, GM)\nend\n--\n-- isMapPosition - check for the presence of the 'MapPosition' fields\n-- valueToTest [in]: the value to be checked\n-- return: 'true' if all required fields are present in the input value\n--\nPlanetaryReference.isMapPosition = isMapPosition\n-- PlanetaryReference INSTANCE METHODS:\n--\n-- getPlanetarySystem - get the planetary system using ID or MapPosition as key\n-- overload [in]: either the planetary system ID or a MapPosition that has it.\n-- return: instance of 'PlanetarySystem' class or nil on error\n--\nfunction PlanetaryReference:getPlanetarySystem(overload)\n --if galaxyAtlas then\n local planetarySystemId = overload\n if isMapPosition(overload) then\n planetarySystemId = overload.systemId\n end\n if type(planetarySystemId) == 'number' then\n local system = self.galaxyAtlas[i]\n if system then\n if getmetatable(nv) ~= PlanetarySystem then\n system = mkPlanetarySystem(system)\n end\n return system\n end\n end\n --end\n --return nil\nend\n-- PlanetarySystem INSTANCE METHODS:\n--\n-- castIntersections - Find the closest body that intersects a \"ray cast\".\n-- origin [in]: the origin of the \"ray cast\" in world coordinates\n-- direction [in]: the direction of the \"ray cast\" as a 'vec3' instance.\n-- sizeCalculator [in]: (default: returns 1.05*radius) Returns size given body.\n-- bodyIds[in]: (default: all IDs in system) check only the given IDs.\n-- return: The closest body that blocks the cast or 'nil' if none.\n--\nfunction PlanetarySystem:castIntersections(origin,\n direction,\n sizeCalculator,\n bodyIds)\n local sizeCalculator = sizeCalculator or \n function (body) return 1.05*body.radius end\n local candidates = {}\n if bodyIds then\n for _,i in ipairs(bodyIds) do candidates[i] = self[i] end\n else\n bodyIds = {}\n for k,body in pairs(self) do\n table.insert(bodyIds, k)\n candidates[k] = body\n end\n end\n local function compare(b1,b2)\n local v1 = candidates[b1].center - origin\n local v2 = candidates[b2].center - origin\n return v1:len() < v2:len()\n end\n table.sort(bodyIds, compare)\n local dir = direction:normalize()\n for i, id in ipairs(bodyIds) do\n local body = candidates[id]\n local c_oV3 = body.center - origin\n local radius = sizeCalculator(body)\n local dot = c_oV3:dot(dir)\n local desc = dot^2 - (c_oV3:len2() - radius^2)\n if desc >= 0 then\n local root = math.sqrt(desc)\n local farSide = dot + root\n local nearSide = dot - root\n if nearSide > 0 then\n return body, farSide, nearSide\n elseif farSide > 0 then\n return body, farSide, nil\n end\n end\n end\n return nil, nil, nil\nend\n--\n-- closestBody - find the closest body to a given set of world coordinates\n-- coordinates [in]: the world coordinates of position in space\n-- return: an instance of the BodyParameters object closest to 'coordinates'\n--\nfunction PlanetarySystem:closestBody(coordinates)\n assert(type(coordinates) == 'table', 'Invalid coordinates.')\n local minDistance2, body\n local coord = vec3(coordinates)\n for _,params in pairs(self) do\n local distance2 = (params.center - coord):len2()\n if not body or distance2 < minDistance2 then\n body = params\n minDistance2 = distance2\n end\n end\n return body\nend\n--\n-- convertToBodyIdAndWorldCoordinates - map to body Id and world coordinates\n-- overload [in]: an instance of MapPosition or a position string ('::pos...)\n-- return: a vec3 instance containing the world coordinates or 'nil' on error.\n--\nfunction PlanetarySystem:convertToBodyIdAndWorldCoordinates(overload)\n local mapPosition = overload\n if isString(overload) then\n mapPosition = mkMapPosition(overload)\n end\n if mapPosition.bodyId == 0 then\n return 0, vec3(mapPosition.latitude,\n mapPosition.longitude,\n mapPosition.altitude)\n end\n local params = self:getBodyParameters(mapPosition)\n if params then\n return mapPosition.bodyId,\n params:convertToWorldCoordinates(mapPosition)\n end\nend\n--\n-- getBodyParameters - get or create an instance of BodyParameters class\n-- overload [in]: either an instance of MapPosition or a body's ID.\n-- return: a BodyParameters instance or 'nil' if body ID is not found.\n--\nfunction PlanetarySystem:getBodyParameters(overload)\n local bodyId = overload\n if isMapPosition(overload) then\n bodyId = overload.bodyId\n end\n assert(isSNumber(bodyId),\n 'Argument 1 (bodyId) must be a number:' .. type(bodyId))\n return self[bodyId]\nend\n--\n-- getPlanetarySystemId - get the planetary system ID for this instance\n-- return: the planetary system ID or nil if no planets are in the system.\n--\nfunction PlanetarySystem:getPlanetarySystemId()\n local k, v = next(self)\n return v and v.planetarySystemId\nend\n-- BodyParameters INSTANCE METHODS:\n--\n-- convertToMapPosition - create an instance of MapPosition from coordinates\n-- worldCoordinates [in]: the world coordinates of the map position.\n-- return: an instance of MapPosition class\n--\nfunction BodyParameters:convertToMapPosition(worldCoordinates)\n assert(isTable(worldCoordinates),\n 'Argument 1 (worldCoordinates) must be an array or vec3:' ..\n type(worldCoordinates))\n local worldVec = vec3(worldCoordinates) \n if self.bodyId == 0 then\n return setmetatable({latitude = worldVec.x,\n longitude = worldVec.y,\n altitude = worldVec.z,\n bodyId = 0,\n systemId = self.planetarySystemId}, MapPosition)\n end\n local coords = worldVec - self.center\n local distance = coords:len()\n local altitude = distance - self.radius\n local latitude = 0\n local longitude = 0\n if not float_eq(distance, 0) then\n local phi = math.atan(coords.y, coords.x)\n longitude = phi >= 0 and phi or (2*math.pi + phi)\n latitude = math.pi/2 - math.acos(coords.z/distance)\n end\n return setmetatable({latitude = latitude,\n longitude = longitude,\n altitude = altitude,\n bodyId = self.bodyId,\n systemId = self.planetarySystemId}, MapPosition)\nend\n--\n-- convertToWorldCoordinates - convert a map position to world coordinates\n-- overload [in]: an instance of MapPosition or a position string ('::pos...')\n--\nfunction BodyParameters:convertToWorldCoordinates(overload)\n local mapPosition = isString(overload) and\n mkMapPosition(overload) or overload\n if mapPosition.bodyId == 0 then -- support deep space map position\n return vec3(mapPosition.latitude,\n mapPosition.longitude,\n mapPosition.altitude)\n end\n assert(isMapPosition(mapPosition),\n 'Argument 1 (mapPosition) is not an instance of \"MapPosition\".')\n assert(mapPosition.systemId == self.planetarySystemId,\n 'Argument 1 (mapPosition) has a different planetary system ID.')\n assert(mapPosition.bodyId == self.bodyId,\n 'Argument 1 (mapPosition) has a different planetary body ID.')\n local xproj = math.cos(mapPosition.latitude)\n return self.center + (self.radius + mapPosition.altitude) *\n vec3(xproj*math.cos(mapPosition.longitude),\n xproj*math.sin(mapPosition.longitude),\n math.sin(mapPosition.latitude))\nend\n--\n-- getAltitude - calculate the altitude of a point given in world coordinates.\n-- worldCoordinates [in]: the world coordinates of the point.\n-- return: the altitude in meters\n--\nfunction BodyParameters:getAltitude(worldCoordinates)\n return (vec3(worldCoordinates) - self.center):len() - self.radius\nend\n--\n-- getDistance - calculate the distance to a point given in world coordinates.\n-- worldCoordinates [in]: the world coordinates of the point.\n-- return: the distance in meters\n--\nfunction BodyParameters:getDistance(worldCoordinates)\n return (vec3(worldCoordinates) - self.center):len()\nend\n--\n-- getGravity - calculate the gravity vector induced by the body.\n-- worldCoordinates [in]: the world coordinates of the point.\n-- return: the gravity vector in meter/seconds^2\n--\nfunction BodyParameters:getGravity(worldCoordinates)\n local radial = self.center - vec3(worldCoordinates) -- directed towards body\n local len2 = radial:len2()\n return (self.GM/len2) * radial/math.sqrt(len2)\nend\n-- end of module\nreturn setmetatable(PlanetaryReference,\n { __call = function(_,...)\n return mkPlanetaryReference(...)\n end })\nend\nfunction Keplers()\n --[[ \n Provides methods for computing orbital information for an object\n Usage:\n Kepler = require('autoconf.custom.kepler')\n alioth = Kepler({ GM=157470826617,\n bodyId=2,\n center={x=-8.000,y=-8.000,z=-126303.000},\n name='Alioth',\n planetarySystemId=0,\n radius=126068\n })\n altitude = 6000\n position = '::pos{0,2,0,0,6000}'\n e, o = alioth:escapeAndOrbitalSpeed(altitude)\n orbit = alioth:orbitalParameters(position, {0, o+1, 0})\n print(\"Eccentricity \" .. orbit.eccentricity)\n print(\"Perihelion \" .. orbit.periapsis.altitude)\n print(\"Max. speed \" .. orbit.periapsis.speed)\n print(\"Circular orbit speed \" .. orbit.periapsis.circularOrbitSpeed)\n print(\"Aphelion \" .. orbit.apoapsis.altitude)\n print(\"Min. speed \" .. orbit.apoapsis.speed)\n print(\"Orbital period \" .. orbit.period)\n --- output:\n Eccentricity 0.0018324307017878\n Perihelion 6000.0\n Max. speed 1092.9462297033\n Circular orbit speed 1091.9462297033\n Aphelion 6484.8994605062\n Min. speed 1088.9480596194\n Orbital period 762.02818214049\n Methods:\n Kepler:escapeAndOrbitalSpeed - for a given celestial body and altitude.\n Kepler:orbitalParameters - for a given massless object and a celestial body.\n Description\n The motion of an object in the vicinity of substantially larger mass is\n in the domain of the \"2-body problem\". By assuming the object whose motion\n is of interest is of negligable mass simplifies the calculations of:\n the speed to escape the body, the speed of a circular orbit, and the\n parameters defining the orbit of the object (or the lack of orbit as the\n case may be).\n Orbital Parameters:\n periapsis - the closest approach to the planet\n apoapsis - the furthest point from the planet if in orbit (otherwise nil)\n eccentricity - 0 for circular orbits\n <1 for elliptical orbits\n 1 for parabiolic trajectory\n >1 for hyperbolic trajectory\n period - time (in seconds) to complete an orbit\n Also See: planetref.lua\n]]--\nlocal vec3 = require('cpml.vec3')\nlocal PlanetRef = PlanetRef()\nlocal function isString(s) return type(s) == 'string' end\nlocal function isTable(t) return type(t) == 'table' end\nlocal function float_eq(a,b)\n if a == 0 then return math.abs(b) < 1e-09 end\n if b == 0 then return math.abs(a) < 1e-09 end\n return math.abs(a - b) < math.max(math.abs(a),math.abs(b))*epsilon\nend\nKepler = {}\nKepler.__index = Kepler\n--\n-- escapeAndOrbitalSpeed - speed required to escape and for a circular orbit\n-- altitude [in]: the height of the orbit in meters above \"sea-level\"\n-- return: the speed in m/s needed to escape the celestial body and to orbit it.\n--\nfunction Kepler:escapeAndOrbitalSpeed(altitude)\n assert(self.body)\n -- P = -GMm/r and KE = mv^2/2 (no lorentz factor used)\n -- mv^2/2 = GMm/r\n -- v^2 = 2GM/r\n -- v = sqrt(2GM/r1)\n local distance = altitude + self.body.radius\n if not float_eq(distance, 0) then\n local orbit = math.sqrt(self.body.GM/distance)\n return math.sqrt(2)*orbit, orbit\n end\n return nil, nil\nend\n--\n-- orbitalParameters: determine the orbital elements for a two-body system.\n-- overload [in]: the world coordinates or map coordinates of a massless object.\n-- velocity [in]: The velocity of the massless point object in m/s.\n-- return: the 6 orbital elements for the massless object.\n--\nfunction Kepler:orbitalParameters(overload, velocity)\n assert(self.body)\n assert(isTable(overload) or isString(overload))\n assert(isTable(velocity))\n local pos = (isString(overload) or PlanetRef.isMapPosition(overload)) and\n self.body:convertToWorldCoordinates(overload) or\n vec3(overload)\n local v = vec3(velocity)\n local r = pos - self.body.center\n local v2 = v:len2()\n local d = r:len()\n local mu = self.body.GM\n local e = ((v2 - mu/d)*r - r:dot(v)*v)/mu\n local a = mu/(2*mu/d - v2)\n local ecc = e:len()\n local dir = e:normalize()\n local pd = a*(1-ecc)\n local ad = a*(1+ecc)\n local per = pd*dir + self.body.center\n local apo = ecc <= 1 and -ad*dir + self.body.center or nil\n local trm = math.sqrt(a*mu*(1-ecc*ecc)) \n local Period = apo and 2*math.pi*math.sqrt(a^3/mu)\n -- These are great and all, but, I need more.\n local trueAnomaly = math.acos((e:dot(r))/(ecc*d))\n if r:dot(v) < 0 then\n trueAnomaly = -(trueAnomaly - 2*math.pi)\n end \n -- Apparently... cos(EccentricAnomaly) = (cos(trueAnomaly) + eccentricity)/(1 + eccentricity * cos(trueAnomaly))\n local EccentricAnomaly = math.acos((math.cos(trueAnomaly) + ecc)/(1 + ecc * math.cos(trueAnomaly)))\n -- Then.... apparently if this is below 0, we should add 2pi to it\n -- I think also if it's below 0, we're past the apoapsis?\n local timeTau = EccentricAnomaly\n if timeTau < 0 then\n timeTau = timeTau + 2*math.pi\n end\n -- So... time since periapsis...\n -- Is apparently easy if you get mean anomly. t = M/n where n is mean motion, = 2*pi/Period\n \n \n local MeanAnomaly = timeTau - ecc * math.sin(timeTau)\n local TimeSincePeriapsis = 0\n local TimeToPeriapsis = 0\n local TimeToApoapsis = 0\n if Period ~= nil then\n TimeSincePeriapsis = MeanAnomaly/(2*math.pi/Period)\n \n --system.print(MeanAnomaly .. \" - \" .. TimeSincePeriapsis .. \" - \" .. Period .. \" - \" .. EccentricAnomaly .. \" - \" .. timeTau .. \" - \" .. trueAnomaly)\n -- Mean anom is 0 at periapsis, positive before it... and positive after it.\n -- I guess this is why I needed to use timeTau and not EccentricAnomaly here\n \n TimeToPeriapsis = Period - TimeSincePeriapsis\n TimeToApoapsis = TimeToPeriapsis + Period/2\n if trueAnomaly - math.pi > 0 then -- TBH I think something's wrong in my formulas because I needed this.\n TimeToPeriapsis = TimeSincePeriapsis\n TimeToApoapsis = TimeToPeriapsis + Period/2\n end\n if TimeToApoapsis > Period then\n TimeToApoapsis = TimeToApoapsis - Period\n end\n end\n return { periapsis = { position = per,\n speed = trm/pd,\n circularOrbitSpeed = math.sqrt(mu/pd),\n altitude = pd - self.body.radius},\n apoapsis = apo and\n { position = apo,\n speed = trm/ad,\n circularOrbitSpeed = math.sqrt(mu/ad),\n altitude = ad - self.body.radius},\n currentVelocity = v,\n currentPosition = pos,\n eccentricity = ecc,\n period = Period,\n eccentricAnomaly = EccentricAnomaly,\n meanAnomaly = MeanAnomaly,\n timeToPeriapsis = TimeToPeriapsis,\n timeToApoapsis = TimeToApoapsis\n }\nend\n\nlocal function new(bodyParameters)\n local params = PlanetRef.BodyParameters(bodyParameters.planetarySystemId,\n bodyParameters.bodyId,\n bodyParameters.radius,\n bodyParameters.center,\n bodyParameters.GM)\n return setmetatable({body = params}, Kepler)\nend\nreturn setmetatable(Kepler, { __call = function(_,...) return new(...) end })\nend\nfunction Kinematics()\n --[[ \n DualUniverse kinematic equations\n Author: JayleBreak\n Usage (unit.start):\n Kinematics = require('autoconf.custom.kinematics')\n Methods:\n computeAccelerationTime - \"relativistic\" version of t = (vf - vi)/a\n computeDistanceAndTime - Return distance & time needed to reach final speed.\n computeTravelTime - \"relativistic\" version of t=(sqrt(2ad+v^2)-v)/a\n Description\n DualUniverse increases the effective mass of constructs as their absolute\n speed increases by using the \"lorentz\" factor (from relativity) as the scale\n factor. This results in an upper bound on the absolute speed of constructs\n (excluding \"warp\" drive) that is set to 30 000 KPH (8 333 MPS). This module\n provides utilities for computing some physical quantities taking this\n scaling into account.\n]]--\nlocal Kinematic = {} -- just a namespace\nlocal C = 30000000/3600\nlocal C2 = C*C\nlocal ITERATIONS = 100 -- iterations over engine \"warm-up\" period\nlocal function lorentz(v) return 1/math.sqrt(1 - v*v/C2) end\n--\n-- computeAccelerationTime - \"relativistic\" version of t = (vf - vi)/a\n-- initial [in]: initial (positive) speed in meters per second.\n-- acceleration [in]: constant acceleration until 'finalSpeed' is reached.\n-- final [in]: the speed at the end of the time interval.\n-- return: the time in seconds spent in traversing the distance\n--\nfunction Kinematic.computeAccelerationTime(initial, acceleration, final)\n -- The low speed limit of following is: t=(vf-vi)/a (from: vf=vi+at)\n local k1 = C*math.asin(initial/C)\n return (C * math.asin(final/C) - k1)/acceleration\nend\n--\n-- computeDistanceAndTime - Return distance & time needed to reach final speed.\n-- initial[in]: Initial speed in meters per second.\n-- final[in]: Final speed in meters per second.\n-- restMass[in]: Mass of the construct at rest in Kg.\n-- thrust[in]: Engine's maximum thrust in Newtons.\n-- t50[in]: (default: 0) Time interval to reach 50% thrust in seconds.\n-- brakeThrust[in]: (default: 0) Constant thrust term when braking.\n-- return: Distance (in meters), time (in seconds) required for change.\n--\nfunction Kinematic.computeDistanceAndTime(initial,\n final,\n restMass,\n thrust,\n t50,\n brakeThrust)\n -- This function assumes that the applied thrust is colinear with the\n -- velocity. Furthermore, it does not take into account the influence\n -- of gravity, not just in terms of its impact on velocity, but also\n -- its impact on the orientation of thrust relative to velocity.\n -- These factors will introduce (usually) small errors which grow as\n -- the length of the trip increases.\n t50 = t50 or 0\n brakeThrust = brakeThrust or 0 -- usually zero when accelerating\n local tau0 = lorentz(initial)\n local speedUp = initial <= final\n local a0 = thrust * (speedUp and 1 or -1)/restMass\n local b0 = -brakeThrust/restMass\n local totA = a0+b0\n if speedUp and totA <= 0 or not speedUp and totA >= 0 then\n return -1, -1 -- no solution\n end\n local distanceToMax, timeToMax = 0, 0\n -- If, the T50 time is set, then assume engine is at zero thrust and will\n -- reach full thrust in 2*T50 seconds. Thrust curve is given by:\n -- Thrust: F(z)=(a0*(1+sin(z))+2*b0)/2 where z=pi*(t/t50 - 1)/2\n -- Acceleration is given by F(z)/m(z) where m(z) = m/sqrt(1-v^2/c^2)\n -- or v(z)' = (a0*(1+sin(z))+2*b0)*sqrt(1-v(z)^2/c^2)/2\n if a0 ~= 0 and t50 > 0 then\n -- Closed form solution for velocity exists:\n -- v(t) = -c*tan(w)/sqrt(tan(w)^2+1) => w = -asin(v/c)\n -- w=(pi*t*(a0/2+b0)-a0*t50*sin(pi*t/2/t50)+*pi*c*k1)/pi/c\n -- @ t=0, v(0) = vi\n -- pi*c*k1/pi/c = -asin(vi/c)\n -- k1 = asin(vi/c)\n local k1 = math.asin(initial/C)\n local c1 = math.pi*(a0/2+b0)\n local c2 = a0*t50\n local c3 = C*math.pi\n local v = function(t)\n local w = (c1*t - c2*math.sin(math.pi*t/2/t50) + c3*k1)/c3\n local tan = math.tan(w)\n return C*tan/math.sqrt(tan*tan+1)\n end\n local speedchk = speedUp and function(s) return s >= final end or\n function(s) return s <= final end\n timeToMax = 2*t50\n if speedchk(v(timeToMax)) then\n local lasttime = 0\n while math.abs(timeToMax - lasttime) > 0.5 do\n local t = (timeToMax + lasttime)/2\n if speedchk(v(t)) then\n timeToMax = t \n else\n lasttime = t\n end\n end\n end\n -- There is no closed form solution for distance in this case.\n -- Numerically integrate for time t=0 to t=2*T50 (or less)\n local lastv = initial\n local tinc = timeToMax/ITERATIONS\n for step = 1, ITERATIONS do\n local speed = v(step*tinc)\n distanceToMax = distanceToMax + (speed+lastv)*tinc/2\n lastv = speed\n end\n if timeToMax < 2*t50 then\n return distanceToMax, timeToMax\n end\n initial = lastv\n end\n -- At full thrust, acceleration only depends on the Lorentz factor:\n -- v(t)' = (F/m(v)) = a*sqrt(1-v(t)^2/c^2) where a = a0+b0\n -- -> v = c*sin((at+k1)/c)\n -- @ t=0, v=vi: k1 = c*asin(vi/c)\n -- -> t = (c*asin(v/c) - k1)/a\n -- x(t)' = c*sin((at+k1)/c)\n -- x = k2 - c^2 cos((at+k1)/c)/a\n -- @ t=0, x=0: k2 = c^2 * cos(k1/c)/a\n local k1 = C*math.asin(initial/C)\n local time = (C * math.asin(final/C) - k1)/totA\n local k2 = C2 *math.cos(k1/C)/totA\n local distance = k2 - C2 * math.cos((totA*time + k1)/C)/totA\n return distance+distanceToMax, time+timeToMax\nend\n--\n-- computeTravelTime - \"relativistic\" version of t=(sqrt(2ad+v^2)-v)/a\n-- initialSpeed [in]: initial (positive) speed in meters per second\n-- acceleration [in]: constant acceleration until 'distance' is traversed\n-- distance [in]: the distance traveled in meters\n-- return: the time in seconds spent in traversing the distance\n--\nfunction Kinematic.computeTravelTime(initial, acceleration, distance)\n -- The low speed limit of following is: t=(sqrt(2ad+v^2)-v)/a\n -- (from: d=vt+at^2/2)\n if distance == 0 then return 0 end\n if acceleration > 0 then\n local k1 = C*math.asin(initial/C)\n local k2 = C2*math.cos(k1/C)/acceleration\n return (C*math.acos(acceleration*(k2 - distance)/C2) - k1)/acceleration\n end\n assert(initial > 0, 'Acceleration and initial speed are both zero.')\n return distance/initial\nend\nfunction Kinematic.lorentz(v) return lorentz(v) end\nreturn Kinematic\nend\nPlanetaryReference = PlanetRef()\ngalaxyReference = PlanetaryReference(Atlas())\nKinematic = Kinematics()\nKep = Keplers()\nAutopilotTargetIndex = 0\nAutopilotTargetName = \"None\"\nAutopilotTargetPlanet = nil\nMaxGameVelocity = 8333.05 --export: Max speed for your autopilot in m/s, do not go above 8333.055\nfunction getDistanceDisplayString(distance)\n local su = distance > 100000\n local result = \"\"\n if su then\n -- Convert to SU\n result = round(distance/1000/200,1) .. \" SU\"\n elseif distance < 1000 then\n result = round(distance,1) .. \" M\"\n else\n -- Convert to KM\n result = round(distance/1000,1) .. \" KM\"\n end\n\n return result\nend\nfunction getSpeedDisplayString(speed) -- TODO: Allow options, for now just do kph\n return math.floor(round(speed*3.6,0)+0.5) .. \" km/h\" -- And generally it's not accurate enough to not twitch unless we round 0\nend\nfunction FormatTimeString(seconds)\n local hours = math.floor(seconds/3600)\n local minutes = math.floor(seconds/60%60)\n local seconds = math.floor(seconds%60)\n if seconds < 0 or hours < 0 or minutes < 0 then\n return \"0s\"\n end\n return hours .. \"h \" .. minutes .. \"m\" .. seconds .. \"s\"\nend\nfunction getMagnitudeInDirection(vector, direction)\n --return vec3(vector):project_on(vec3(direction)):len()\n vector = vec3(vector)\n direction = vec3(direction):normalize()\n local result = vector*direction -- To preserve sign, just add them I guess\n return result.x + result.y + result.z\nend\nfunction UpdateAutopilotTarget()\n -- So the indices are weird. I think we need to do a pairs\n if AutopilotTargetIndex == 0 then\n AutopilotTargetName = \"None\"\n AutopilotTargetPlanet = nil\n return true\n end\n local count = 0\n for k,v in pairs(Atlas()[0]) do\n count = count + 1\n if count == AutopilotTargetIndex then\n AutopilotTargetName = v.name\n AutopilotTargetPlanet = galaxyReference[0][k]\n AutopilotTargetCoords = vec3(AutopilotTargetPlanet.center) -- Aim center until we align\n -- Determine the end speed\n _, AutopilotEndSpeed = kepPlanet:escapeAndOrbitalSpeed(AutopilotTargetOrbit - AutopilotTargetPlanet.radius)\n --AutopilotEndSpeed = 0\n --AutopilotPlanetGravity = AutopilotTargetPlanet:getGravity(AutopilotTargetPlanet.center + vec3({1,0,0}) * AutopilotTargetOrbit):len() -- Any direction, at our orbit height\n AutopilotPlanetGravity = 0 -- This is inaccurate unless we integrate and we're not doing that. \n AutopilotAccelerating = false\n AutopilotBraking = false\n AutopilotCruising = false \n Autoilot = false\n AutopilotRealigned = false\n AutopilotStatus = \"Aligning\"\n return true\n end\n end\n system.print(\"Error: Autopilot index was outside the bounds of the target range\")\n return false\nend\nfunction IncrementAutopilotTargetIndex()\n AutopilotTargetIndex = AutopilotTargetIndex + 1\n if AutopilotTargetIndex > tablelength(Atlas()[0]) then \n AutopilotTargetIndex = 0\n end\n UpdateAutopilotTarget()\nend\nfunction DecrementAutopilotTargetIndex()\n AutopilotTargetIndex = AutopilotTargetIndex - 1\n if AutopilotTargetIndex < 0 then \n AutopilotTargetIndex = tablelength(Atlas()[0])\n end\n UpdateAutopilotTarget()\nend\nfunction GetAutopilotTravelTime()\n AutopilotDistance = (AutopilotTargetPlanet.center - vec3(core.getConstructWorldPos())):len()\n local velocity = core.getWorldVelocity() \n local accelDistance, accelTime = Kinematic.computeDistanceAndTime(vec3(velocity):len(),\n MaxGameVelocity, -- From currently velocity to max\n core.getConstructMass(),\n Nav:maxForceForward(),\n warmup, -- T50? Assume none, negligible for this\n 0) -- Brake thrust, none for this\n -- accelDistance now has the amount of distance for which we will be accelerating\n -- Then we need the distance we'd brake from full speed\n -- Note that for some nearby moons etc, it may never reach full speed though.\n local brakeDistance, brakeTime\n if not TurnBurn then \n brakeDistance, brakeTime = GetAutopilotBrakeDistanceAndTime(MaxGameVelocity)\n else\n brakeDistance, brakeTime = GetAutopilotTBBrakeDistanceAndTime(MaxGameVelocity)\n end\n local curBrakeDistance, curBrakeTime\n if not TurnBurn then \n curBrakeDistance, curBrakeTime = GetAutopilotBrakeDistanceAndTime(vec3(velocity):len())\n else\n curBrakeDistance, curBrakeTime = GetAutopilotTBBrakeDistanceAndTime(vec3(velocity):len())\n end\n local cruiseDistance = 0\n local cruiseTime = 0\n \n -- So, time is in seconds\n -- If cruising or braking, use real cruise/brake values\n if brakeDistance + accelDistance < AutopilotDistance then \n -- Add any remaining distance\n cruiseDistance = AutopilotDistance - (brakeDistance + accelDistance)\n cruiseTime = Kinematic.computeTravelTime(8333.0556, 0, cruiseDistance)\n else\n local accelRatio = (AutopilotDistance - brakeDistance)/accelDistance\n accelDistance = AutopilotDistance - brakeDistance -- Accel until we brake\n accelTime = accelTime * accelRatio\n end\n if AutopilotBraking then\n return curBrakeTime\n elseif AutopilotCruising then\n return cruiseTime + curBrakeTime\n else -- If not cruising or braking, assume we'll get to max speed\n return accelTime + brakeTime + cruiseTime\n end\nend\nfunction GetAutopilotBrakeDistanceAndTime(speed)\n return Kinematic.computeDistanceAndTime(speed, AutopilotEndSpeed, core.getConstructMass(), 0, 0, json.decode(unit.getData()).maxBrake - (AutopilotPlanetGravity * core.getConstructMass()))\nend\nfunction GetAutopilotTBBrakeDistanceAndTime(speed) -- Uses thrust and a configured T50\n return Kinematic.computeDistanceAndTime(speed, AutopilotEndSpeed, core.getConstructMass(), Nav:maxForceForward(), warmup, json.decode(unit.getData()).maxBrake - (AutopilotPlanetGravity * core.getConstructMass()))\nend\nfunction round(num, numDecimalPlaces)\n local mult = 10^(numDecimalPlaces or 0)\n return math.floor(num * mult + 0.5) / mult\nend\nfunction tablelength(T)\n local count = 0\n for _ in pairs(T) do count = count + 1 end\n return count\nend\n","filter":{"args":[],"signature":"start()","slotKey":"-2"},"key":"38"},{"code":"Nav:update()","filter":{"args":[],"signature":"update()","slotKey":"-2"},"key":"39"},{"code":"pitchInput = pitchInput - 1","filter":{"args":[{"value":"forward"}],"signature":"actionStart(forward)","slotKey":"-2"},"key":"40"},{"code":"pitchInput = pitchInput + 1","filter":{"args":[{"value":"forward"}],"signature":"actionStop(forward)","slotKey":"-2"},"key":"41"},{"code":"pitchInput = pitchInput + 1","filter":{"args":[{"value":"backward"}],"signature":"actionStart(backward)","slotKey":"-2"},"key":"42"},{"code":"pitchInput = pitchInput - 1","filter":{"args":[{"value":"backward"}],"signature":"actionStop(backward)","slotKey":"-2"},"key":"43"},{"code":"rollInput = rollInput + 1","filter":{"args":[{"value":"right"}],"signature":"actionStart(right)","slotKey":"-2"},"key":"44"},{"code":"rollInput = rollInput + 1","filter":{"args":[{"value":"left"}],"signature":"actionStop(left)","slotKey":"-2"},"key":"45"},{"code":"rollInput = rollInput - 1","filter":{"args":[{"value":"left"}],"signature":"actionStart(left)","slotKey":"-2"},"key":"46"},{"code":"rollInput = rollInput - 1","filter":{"args":[{"value":"right"}],"signature":"actionStop(right)","slotKey":"-2"},"key":"47"},{"code":"Nav.axisCommandManager:updateCommandFromActionStart(axisCommandId.lateral, 1.0)","filter":{"args":[{"value":"straferight"}],"signature":"actionStart(straferight)","slotKey":"-2"},"key":"48"},{"code":"Nav.axisCommandManager:updateCommandFromActionStop(axisCommandId.lateral, -1.0)","filter":{"args":[{"value":"straferight"}],"signature":"actionStop(straferight)","slotKey":"-2"},"key":"49"},{"code":"if(stateHoldLevel=='on') then stateHoldLevel='off' else stateHoldLevel='on' end","filter":{"args":[{"value":"option5"}],"signature":"actionStart(action)","slotKey":"-2"},"key":51}],"methods":[],"events":[]}