[READ-ONLY] Mirror of https://github.com/thoda-dev/du-elevator. a generic elevator script for Dual Universe
0

Configure Feed

Select the types of activity you want to include in your feed.

v1.4.0 - dynamic bookmarks and new closest planet computing

Thomas (Aug 27, 2023, 7:22 PM +0200) f7e561af f9d19212

+134 -20
+13 -4
README.MD
··· 25 25 You must link the following elements to the remote control (order doesn't matter, the script will detect them automatically): 26 26 - Link the core to the remote control 27 27 - Link the fuel tank to the remote control (only if you want to see the fuel gauge) 28 - - Link the databank to the remote control (not required bu will imporved the alignement of the construct when landing) 28 + - Link the databank to the remote control (not required) 29 + - databank is used to store the initial position and orientation of the elevator. Using it will increase precision of the elevator. 30 + - storing bookmarks dynamically with the commands require the databank. 29 31 30 32 ## Setup 31 33 ··· 58 60 59 61 You can set bookmarks to save altitudes with a name. Edit the script and go to Unit > OnStart, you will see a Bookmark list (table) at the top of the script. You can edit the list to add your custom points. The first element will be used ad the base altitude when the elevator is landed on the floor. 60 62 61 - Navigating using bookmarks : 63 + It's also possible to use the command `save:<name>` in the chat to save the current position with the specified name and `delete` to delete the current selected bookmark. (see Chat Command section below) 64 + 65 + Navigating using bookmarks (based on the default game key bindings): 62 66 - you can navigate in the list in the HUD with your keyboard keys arrows up and down 67 + - using arrow up and down with Ctrl (or strg depending on the language of your keyboard) will move the selected bookmark up or down in the list 63 68 - pressing the key arrow right will start the travel to the selected bookmark 64 69 65 70 Current selected bookmark is displayed in blue, the current bookmark the elevator is going to is displayed in green. 66 71 67 72 ### Chat Command 68 73 69 - - open the "lua" chat channel and type `goto:<altitude>` to go to the specified altitude with `<altitude>` the altitude in meters, if the altitude is missing or invalid, it will send you the `BaseAltitude` 74 + open the "lua" chat channel and type one of these commands : 75 + - `goto:<altitude>` to go to the specified altitude with `<altitude>` the altitude in meters, if the altitude is missing or invalid, it will send you the `BaseAltitude` 76 + - `save:<name>` to save the current position with `<name>` the name of the bookmark 77 + - `delete` to delete the current selected bookmark 70 78 71 79 ### Other keys 72 80 73 81 - Option 1 (default Alt+1): save the current position and orientation as initial values in the databank 74 - - Option 2 (default Alt+2): save the current planet id in the databank (used to compute distance and positions when really far from the planet) 75 82 76 83 # Customize the HUD 77 84 ··· 104 111 # Coming Soon 105 112 106 113 - screen support to display position informations and bookmarks and navigate by clicking on it 114 + - manual control mode to be able to control the elevator with the keyboard (like a normal ship) 115 + - replacing altitude bookmarks by positions to be able to use the elevator as an autopilot browsing threw waypoints 107 116 - ~~support for emitter to be able to send a signal to a specific channel to close a door to lock the elevator in position~~ This feature will not be done but replaced by a more generic system to manage several events than can be used to interact with any thing in the construct (doors, lights, etc...) or on another construct (like a door on the floor) via an emitter. 108 117 109 118 # Support or donation
+120 -15
Source/Unit/OnStart.lua
··· 210 210 displayName = ' >> ' .. bookmark.name 211 211 class = 'movingto' 212 212 end 213 - html = html .. '<div class="' .. class .. '"><div>' .. displayName .. '</div><div>' .. format_number(bookmark.altitude) .. 'm</div></div>' 213 + html = html .. '<div class="' .. class .. '"><div>' .. displayName .. '</div><div>' .. format_number((utils.round(bookmark.altitude*100)/100)) .. 'm</div></div>' 214 214 end 215 215 html = html .. '</div>' 216 216 end ··· 234 234 --[[ 235 235 Version Management 236 236 ]] 237 - local version = "V 1.3.4" 237 + local version = "V 1.4.0" 238 238 local log_split = "=================================================" 239 239 --printing version in lua chat 240 240 system.print(log_split)local a=""local b=math.ceil((50-#version-2)/2)for c=1,b,1 do a=a..'='end;a=a.." "..version.." "for c=1,b,1 do a=a..'='end;system.print(a)system.print(log_split) ··· 251 251 Unminified Source available at: https://github.com/Jericho1060/DualUniverse/blob/master/ElementsMetatable/FuelTank.lua 252 252 ]] 253 253 FuelTank={__index={name='',percentage=0,timeLeft=0,lastRefresh=0,fuelType='rocket',refresh=function(self)local a=system.getUtcTime()if lastRefresh~=a then local b=self.getClass():lower()if b:find('atmo')then self.fuelType="atmo"elseif b:find('space')then self.fuelType="space"end;local c=self.getWidgetData()local d=json.decode(c)self.name=d.name;self.percentage=d.percentage;self.timeLeft=tonumber(d.timeLeft)self.lastRefresh=a end end,getName=function(self)self:refresh()return self.name end,getPercentFilled=function(self)self:refresh()return self.percentage end,getSecondsLeft=function(self)self:refresh()return self.timeLeft end,getFuelType=function(self)self:refresh()return self.fuelType end,getTimeLeftString=function(self)local e=self:getSecondsLeft()if e==nil or e<=0 then return"-"end;days=string.format("%2.f",math.floor(e/(3600*24)))hours=string.format("%2.f",math.floor(e/3600-days*24))mins=string.format("%2.f",math.floor(e/60-hours*60-days*24*60))secs=string.format("%2.f",math.floor(e-hours*3600-days*24*60*60-mins*60))str=""if tonumber(days)>0 then str=str..days.."d "end;if tonumber(hours)>0 then str=str..hours.."h "end;if tonumber(mins)>0 then str=str..mins.."m "end;if tonumber(secs)>0 then str=str..secs.."s"end;return str end,dump=function(self)self:refresh()system.print('{name="'..self.name..'",percentage='..self.percentage..',timeLeft='..self.timeLeft..',fuelType="'..self.fuelType..'"}')end}} 254 + 254 255 --[[ 255 256 Detecting elements linked 256 257 ]] ··· 305 306 shiftPressed = false 306 307 jumpDelta = 0 307 308 baseAcceleration = 0.8 309 + BrakePressed = false 308 310 309 311 Nav = Navigator.new(system, core, unit) 310 312 Nav.axisCommandManager:setupCustomTargetSpeedRanges(axisCommandId.longitudinal, {100, 500, 1000, 2000,3000, 5000}) ··· 382 384 if __DEBUG then system.print('Planet ID: ' .. PlanetID) end 383 385 end 384 386 387 + function getClosestBodyIdFromAtlas() 388 + local closestBody = nil 389 + local closestDistance = 0 390 + local constructPos = vec3(construct.getWorldPosition()) 391 + for _, body in pairs(atlas[0]) do 392 + local distance = (vec3(body.center) - constructPos):len() 393 + if closestBody == nil or distance < closestDistance then 394 + closestBody = body.id 395 + closestDistance = distance 396 + end 397 + end 398 + return closestBody 399 + end 400 + 401 + function mergeBookmarksFromDatabank() 402 + if (databank ~= nil) and databank.hasKey('Bookmarks') then 403 + local str = databank.getStringValue('Bookmarks') 404 + local bookmarks_data = Serializer:parse(str) 405 + for _, bookmark in pairs(bookmarks_data) do 406 + local exists = false 407 + for _, b in pairs(Bookmarks) do 408 + if b.name == bookmark.name then 409 + exists = true 410 + break 411 + end 412 + end 413 + if not exists then 414 + Bookmarks[#Bookmarks+1] = bookmark 415 + end 416 + end 417 + databank.setStringValue('Bookmarks', Serializer:stringify(Bookmarks)) 418 + end 419 + end 420 + 421 + mergeBookmarksFromDatabank() 422 + 385 423 --[[ 386 424 Compute planet atmosphere altitude 387 425 ]] ··· 418 456 longitudinalSpeed = 0, 419 457 coreAltitude = TargetAltitude, 420 458 altitude = TargetAltitude, 421 - planetData = nil, 459 + planetData = atlas[0][getClosestBodyIdFromAtlas()], 422 460 direction = 'Stabilizing', 423 461 atmosphereDistance = 0, 424 462 atmosphereAltitude = 0, ··· 426 464 currentMaxSpeed = MaxSpeed, 427 465 } 428 466 429 - storeReferencePlanet(false) 430 - 431 467 --computing the distance from the planet sea level as target altitude 432 468 if ElevatorData.planetData ~= nil then 433 469 TargetAltitude = computePlanetSeaDistance(ElevatorData.planetData, construct.getWorldPosition()) ··· 435 471 end 436 472 437 473 --base selected bookmark is 0 -> none selected as index 0 doesn't exist in lua 438 - selectedBookmarkIndex = 0 474 + selectedBookmarkIndex = 1 439 475 440 476 --[[ 441 477 init the HUD ··· 765 801 Actions 766 802 ]] 767 803 local systemActionsStart = {} 804 + local systemActionsLoop = {} 805 + local systemActionsStop = {} 768 806 769 807 systemActionsStart[Script.system.ACTIONS.DOWN] = function () 770 - if selectedBookmarkIndex < #Bookmarks then 771 - selectedBookmarkIndex = selectedBookmarkIndex + 1 808 + if BrakePressed then 809 + if selectedBookmarkIndex < #Bookmarks then 810 + local temp = Bookmarks[selectedBookmarkIndex] 811 + Bookmarks[selectedBookmarkIndex] = Bookmarks[selectedBookmarkIndex + 1] 812 + selectedBookmarkIndex = selectedBookmarkIndex + 1 813 + Bookmarks[selectedBookmarkIndex] = temp 814 + else 815 + selectedBookmarkIndex = 1 816 + end 772 817 else 773 - selectedBookmarkIndex = 1 818 + if selectedBookmarkIndex < #Bookmarks then 819 + selectedBookmarkIndex = selectedBookmarkIndex + 1 820 + else 821 + selectedBookmarkIndex = 1 822 + end 823 + if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end 774 824 end 775 - if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end 776 825 end 777 826 systemActionsStart[Script.system.ACTIONS.UP] = function () 778 - if selectedBookmarkIndex > 1 then 779 - selectedBookmarkIndex = selectedBookmarkIndex - 1 827 + if BrakePressed then 828 + if selectedBookmarkIndex > 1 then 829 + local temp = Bookmarks[selectedBookmarkIndex] 830 + Bookmarks[selectedBookmarkIndex] = Bookmarks[selectedBookmarkIndex - 1] 831 + selectedBookmarkIndex = selectedBookmarkIndex - 1 832 + Bookmarks[selectedBookmarkIndex] = temp 833 + else 834 + selectedBookmarkIndex = #Bookmarks 835 + end 780 836 else 781 - selectedBookmarkIndex = #Bookmarks 837 + if selectedBookmarkIndex > 1 then 838 + selectedBookmarkIndex = selectedBookmarkIndex - 1 839 + else 840 + selectedBookmarkIndex = #Bookmarks 841 + end 782 842 end 783 843 if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end 784 844 end ··· 794 854 systemActionsStart[Script.system.ACTIONS.OPTION_1] = function() 795 855 storeIniPosAndForward(true) 796 856 end 797 - systemActionsStart[Script.system.ACTIONS.OPTION_2] = function() 798 - storeReferencePlanet(true) 857 + systemActionsStart[Script.system.ACTIONS.BRAKE] = function() 858 + BrakePressed = true 859 + end 860 + systemActionsStop[Script.system.ACTIONS.BRAKE] = function() 861 + BrakePressed = false 799 862 end 800 863 Script.system:onActionStart(systemActionsStart) --loading all "actionStart" functions 864 + Script.system:onActionLoop(systemActionsLoop) --loading all "actionLoop" functions 865 + Script.system:onActionStop(systemActionsStop) --loading all "actionStop" functions 801 866 802 867 --[[ 803 868 Chat Commands ··· 808 873 TargetAltitude = tonumber(text:split(':')[2]) or BaseAltitute 809 874 brakeInput = 0 810 875 system.print('Target Altitude set to ' .. TargetAltitude .. 'm') 876 + elseif text:lower():find('save:') then 877 + if databank == nil then 878 + system.print('Please, install a databank to save bookmarks') 879 + return 880 + end 881 + if databank.hasKey('Bookmarks') then 882 + local str = databank.getStringValue('Bookmarks') 883 + local bookmarks_data = Serializer:parse(str) 884 + 885 + end 886 + local name = text:split(':')[2] 887 + for _, bookmark in pairs(Bookmarks) do 888 + if bookmark.name == name then 889 + system.print('Bookmark "' .. name .. '"" already exists') 890 + return 891 + end 892 + end 893 + if name ~= nil then 894 + local altitude = ElevatorData.altitude 895 + local bookmark = { 896 + name = name, 897 + altitude = altitude 898 + } 899 + Bookmarks[#Bookmarks+1] = bookmark 900 + databank.setStringValue('Bookmarks', Serializer:stringify(Bookmarks)) 901 + system.print('Bookmark ' .. name .. ' saved at ' .. altitude .. 'm') 902 + else 903 + system.print('Please, provide a name for the bookmark') 904 + end 905 + elseif text:lower() == 'delete' then 906 + if databank == nil then 907 + system.print('Please, install a databank to manage bookmarks with commands') 908 + return 909 + end 910 + if Bookmarks[selectedBookmarkIndex] then 911 + --delete selected bookmark 912 + local name = Bookmarks[selectedBookmarkIndex].name 913 + table.remove(Bookmarks, selectedBookmarkIndex) 914 + databank.setStringValue('Bookmarks', Serializer:stringify(Bookmarks)) 915 + end 811 916 else 812 917 system.print('Unknown command') 813 918 end
+1 -1
config.json
··· 1 - {"events":[],"handlers":[{"code":"Script.system:update()","filter":{"args":[],"signature":"onUpdate()","slotKey":"-4"},"key":"9"},{"code":"Script.system:inputText(text)","filter":{"args":[{"variable":"*"}],"signature":"onInputText(text)","slotKey":"-4"},"key":"10"},{"code":"Script.system:flush()","filter":{"args":[],"signature":"onFlush()","slotKey":"-4"},"key":"11"},{"code":"Script.system:actionStop(action)","filter":{"args":[{"variable":"*"}],"signature":"onActionStop(action)","slotKey":"-4"},"key":"12"},{"code":"Script.system:actionStart(action)","filter":{"args":[{"variable":"*"}],"signature":"onActionStart(action)","slotKey":"-4"},"key":"13"},{"code":"Script.system:actionLoop(action)","filter":{"args":[{"variable":"*"}],"signature":"onActionLoop(action)","slotKey":"-4"},"key":"14"},{"code":"Script.player:parentChanged(oldId, newId)","filter":{"args":[{"variable":"*"},{"variable":"*"}],"signature":"onParentChanged(oldId,newId)","slotKey":"-3"},"key":"3"},{"code":"Script.construct:VRStationEntered(id)","filter":{"args":[{"variable":"*"}],"signature":"onVRStationEntered(id)","slotKey":"-2"},"key":"4"},{"code":"Script.construct:undocked(id)","filter":{"args":[{"variable":"*"}],"signature":"onUndocked(id)","slotKey":"-2"},"key":"5"},{"code":"Script.construct:PvPTimer(active)","filter":{"args":[{"variable":"*"}],"signature":"onPvPTimer(active)","slotKey":"-2"},"key":"6"},{"code":"Script.construct:playerBoarded(id)","filter":{"args":[{"variable":"*"}],"signature":"onPlayerBoarded(id)","slotKey":"-2"},"key":"7"},{"code":"Script.construct:constructDocked(id)","filter":{"args":[{"variable":"*"}],"signature":"onConstructDocked(id)","slotKey":"-2"},"key":"8"},{"code":"Script.unit:timer(tag)","filter":{"args":[{"variable":"*"}],"signature":"onTimer(tag)","slotKey":"-1"},"key":"0"},{"code":"Script.unit:stop()","filter":{"args":[],"signature":"onStop()","slotKey":"-1"},"key":"1"},{"code":"--[[\r\n DU-ELEVATOR by Jericho\r\n]]\r\n\r\n--[[\r\n Bookmarks: you can store several altitudes here to navigate easily\r\n]]\r\nlocal Bookmarks = {\r\n { name = 'Start Point', altitude = 294 },\r\n { name = 'Hovering', altitude = 300 },\r\n { name = 'Floor 1', altitude = 350 },\r\n { name = 'Floor 2', altitude = 500 },\r\n { name = 'Space 1', altitude = 5000 },\r\n { name = 'Space 2', altitude = 6000 },\r\n { name = 'Space 3', altitude = 10000 },\r\n { name = 'Space 4', altitude = 20000 },\r\n { name = 'Space 5', altitude = 50000 },\r\n { name = 'Space 6', altitude = 200000 },\r\n}\r\n\r\n--[[\r\n LUA PARAMETERS\r\n]]\r\n__DEBUG = false --export: Debug mode, will print more information in the console\r\nShowParentingWidget = false --export: Show the parenting widget\r\nShowControlUnitWidget = false --export: show the default widget of the construct\r\nDisplayAtlasData = true --export: show the Atlas widget with closest planet informations\r\n\r\n--[[\r\n HUD Rendering\r\n]]\r\nfunction RENDER_HUD(ElevatorData)\r\n --[[\r\n That function is used for rendering the HUD,\r\n You can update it or replace all its content to customise it.\r\n You can also remove its content if you don't want a HUD.\r\n It's not affecting in any way how the elevator is working and it's just a display\r\n \r\n ElevatorData is an object containing values that are refresh all the time.\r\n ElevatorData.isBreaking - boolean - if the brakes a are enabled or not.\r\n ElevatorData.verticalSpeed - number - the absolute vertical speed of the elevator in m/s.\r\n ElevatorData.verticalSpeedSigned - number - the real vertical speed of the elevator in m/s. if <0 the elevator is falling.\r\n ElevatorData.lateralSpeed - numver - the absolute lateral speed of the elevator in m/s.\r\n ElevatorData.longitudinalSpeed - number - the absolute Longitudinal speed of the elevator in m/s.\r\n ElevatorData.coreAltitude - number - the altitude from sea level returned by the core in meters. (warning: this altitude in space when far from planets is 0)\r\n ElevatorData.altitude - number - computed altitude from the distance between the construct and the sea level of the closest planet. (not 0 when in space)\r\n ElevatorData.atmosphereDistance - number - computed distance from the construct to the atmosphere of the closest planet\r\n ElevatorData.atmosphereAltitude - number - computed altitude of the atmosphere of the closest planet.\r\n ElevatorData.atmoMaxSpeed - number - the maximum speed of the elevator in m/s when in atmosphere. (anti burn security)\r\n ElevatorData.currentMaxSpeed - number - the maximum speed of the elevator in m/s. (50km/h in space)\r\n ElevatorData.planetData - table - Atlas Data of the closest planet. See atlas in game file for the structure (Game Directory\\data\\lua\\atlas.lua)\r\n ]]\r\n --locale for Translation\r\n local locale = system.getLocale()\r\n localeIndex = 1 --default to english\r\n if locale == 'fr-FR' then\r\n localeIndex = 2 --french index in atlas\r\n elseif locale == 'de-DE' then\r\n localeIndex = 3 --german index in atlas\r\n end\r\n --direction if the elevator from the speed\r\n local direction = 'Stabilizing'\r\n if ElevatorData.verticalSpeedSigned > 0 then\r\n direction = 'Up'\r\n elseif ElevatorData.verticalSpeedSigned < 0 then\r\n direction = 'Down'\r\n end\r\n --braking status text for hud\r\n local brakeStatus = 'Off'\r\n if ElevatorData.isBreaking then\r\n brakeStatus = 'On'\r\n end\r\n --html rendering\r\n local html = [[\r\n <style>\r\n * {font-size:1vh !important;}\r\n .hud {\r\n position: absolute;\r\n left: 5vh;\r\n top: 5vh;\r\n right: 5vh;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: left;\r\n align-items: left;\r\n }\r\n .widget_container {\r\n border: 2px solid orange;\r\n border-radius:.5vh;\r\n background-color: rgba(0, 0, 0, .5);\r\n display: flex;\r\n flex-direction: column;\r\n padding:.5vh;\r\n margin-top:1vh;\r\n }\r\n .widget_container div {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n }\r\n .widget_container div div {\r\n margin:.25vh;\r\n }\r\n .widget_container div div:first-child {\r\n text-transform: uppercase;\r\n font-weight: bold;\r\n }\r\n .selected {\r\n color: teal;\r\n }\r\n .movingto {\r\n color: green;\r\n }\r\n .atmo .gauge {\r\n background-color: #22d3ee;\r\n }\r\n .atmo .gauge_label {\r\n color: #155e75;\r\n }\r\n .space .gauge {\r\n background-color: #fbbf24;\r\n }\r\n .space .gauge_label {\r\n color: #92400e;\r\n }\r\n .rocket .gauge {\r\n background-color: #a78bfa;\r\n }\r\n .rocket .gauge_label {\r\n color: #5b21b6;\r\n }\r\n .gauge_container {\r\n display:block;\r\n width:100%;\r\n min-width:10vw;\r\n position: relative;\r\n border: 1px solid black;\r\n background-color: rgba(0, 0, 0, .75);\r\n height: 1.5vh;\r\n }\r\n .gauge {\r\n position: absolute;\r\n z-index:1;\r\n top:-.25vh;\r\n left:-.25vh;\r\n height:100%;\r\n margin:0;\r\n background-color: cyan;\r\n }\r\n .gauge_label {\r\n position:relative;\r\n display:block;\r\n z-index:10;\r\n margin:0;\r\n padding:0;\r\n width:100%;\r\n text-align:center;\r\n }\r\n </style>\r\n <div class=\"hud\">\r\n <div class=\"widget_container\">\r\n <div>\r\n <div>Base Altitude</div><div>]] .. format_number(utils.round(BaseAltitute)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Target Altitude</div><div>]] .. format_number(utils.round(TargetAltitude)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Current Altitude</div><div>]] .. format_number(utils.round(ElevatorData.altitude)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Atmosphere Distance</div><div>]] .. format_number(utils.round(ElevatorData.atmosphereDistance)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Maximum Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.currentMaxSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Vertical Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.verticalSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Lateral Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.lateralSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Longitudinal Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.longitudinalSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Direction</div><div>]] .. ElevatorData.direction .. [[</div>\r\n </div>\r\n <div>\r\n <div>Brake</div><div>]] .. brakeStatus .. [[</div>\r\n </div>\r\n </div>\r\n ]]\r\n if #fuelTanks > 0 then\r\n html = html .. '<div class=\"widget_container\">'\r\n for _, tank in pairs(fuelTanks) do\r\n html = html .. '<div><div>' .. tank:getName() .. '</div><div>' .. tank:getTimeLeftString() .. '</div></div><div class=\"gauge_container ' .. tank:getFuelType() .. '\"><div class=\"gauge\" style=\"width:' .. utils.round(tank:getPercentFilled()) .. '%;\"></div><div class=\"gauge_label\"><div style=\"margin-left:auto;margin-right:auto;padding:0;margin-top:-.25vh;\">' .. format_number(utils.round(tank:getPercentFilled())) .. '%</div></div></div>'\r\n end\r\n html = html .. '</div>'\r\n end\r\n if #Bookmarks > 0 then\r\n html = html .. '<div class=\"widget_container\"><div><div style=\"text-align:center !important;border-bottom:1px solid black;width:100%;\">Bookmarks</div></div>'\r\n for index, bookmark in ipairs(Bookmarks) do\r\n local class=''\r\n if selectedBookmarkIndex == index then\r\n class = 'selected'\r\n end\r\n local displayName = bookmark.name\r\n if selectedBookmarkIndex > 0 and bookmark.altitude == TargetAltitude then\r\n displayName = ' >> ' .. bookmark.name\r\n class = 'movingto'\r\n end\r\n html = html .. '<div class=\"' .. class .. '\"><div>' .. displayName .. '</div><div>' .. format_number(bookmark.altitude) .. 'm</div></div>'\r\n end\r\n html = html .. '</div>'\r\n end\r\n if DisplayAtlasData and (ElevatorData.planetData ~= nil) then\r\n html = html .. '<div class=\"widget_container\">'\r\n html = html .. '<div><div style=\"text-align:center;border-bottom:1px solid black;width:100%;\">Atlas Data</div></div>'\r\n --html = html .. '<div><img src=\"/'..ElevatorData.planetData.iconPath..'\" style=\"width:10vh;height:10vh;\"></div>' --image is blinking in hud, you can display it here but by default i'm removing it\r\n html = html .. '<div><div>Planet Name</div><div>'..ElevatorData.planetData.name[localeIndex]..'</div></div>'\r\n html = html .. '<div><div>Planet Type</div><div>'..ElevatorData.planetData.type[localeIndex]..'</div></div>'\r\n html = html .. '<div><div>Planet gravity</div><div>'..ElevatorData.planetData.gravity..'m/s²</div></div>'\r\n html = html .. '<div><div>Planet Surface Average Altitude</div><div>'..ElevatorData.planetData.surfaceAverageAltitude..'m</div></div>'\r\n html = html .. '<div><div>Planet Surface Min Altitude</div><div>'..ElevatorData.planetData.surfaceMinAltitude..'m</div></div>'\r\n html = html .. '<div><div>Planet Surface Max Altitude</div><div>'..ElevatorData.planetData.surfaceMaxAltitude..'m</div></div>'\r\n html = html .. '<div><div>Planet Max Altitude For Satic Core</div><div>'..ElevatorData.planetData.maxStaticAltitude..'m</div></div>'\r\n html = html .. '</div>'\r\n end\r\n html = html .. '</div>'\r\n system.setScreen(html) --render the HUD\r\nend\r\n\r\n--[[\r\n Version Management\r\n]]\r\nlocal version = \"V 1.3.4\"\r\nlocal log_split = \"=================================================\"\r\n--printing version in lua chat\r\nsystem.print(log_split)local a=\"\"local b=math.ceil((50-#version-2)/2)for c=1,b,1 do a=a..'='end;a=a..\" \"..version..\" \"for c=1,b,1 do a=a..'='end;system.print(a)system.print(log_split)\r\n--[[\r\n Lua Serializer by Jericho\r\n Version: 1.0.0\r\n Source available at: https://github.com/Jericho1060/DualUniverse/blob/master/Serializer/Serializer.lua\r\n]]\r\nSerializer={__index={stringify=function(self,a)if type(a)=='number'then return self:stringifyNumber(a)elseif type(a)=='string'then return self:stringifyString(a)elseif type(a)=='table'then return self:stringifyTable(a)elseif type(a)=='boolean'then return self:stringifyBoolean(a)end;return nil end,parse=function(self,b)return load(\"return \"..b)()end,stringifyTableKey=function(self,c)if type(c)=='number'then return'['..self:stringifyNumber(c)..']'end;return c end,stringifyNumber=function(self,d)return tostring(d)end,stringifyString=function(self,b)return string.format('%q',b):gsub('\\\\\\n','\\n'):gsub('\\\\\\r','\\r'):gsub('\\\\\\t','\\t')end,stringifyTable=function(self,e)local b='{'local f=1;local g=self:tableLength(e)for h,i in pairs(e)do b=b..self:stringifyTableKey(h)..'='..self:stringify(i)if f<g then b=b..','end;f=f+1 end;return b..'}'end,stringifyBoolean=function(self,j)return tostring(j)end,tableLength=function(self,e)local k=0;for l in pairs(e)do k=k+1 end;return k end}}\r\nSerializer = setmetatable({}, Serializer)\r\n--[[\r\n Fuel Tanks Metatable By Jericho\r\n Version: 1.0.0\r\n Unminified Source available at: https://github.com/Jericho1060/DualUniverse/blob/master/ElementsMetatable/FuelTank.lua\r\n]]\r\nFuelTank={__index={name='',percentage=0,timeLeft=0,lastRefresh=0,fuelType='rocket',refresh=function(self)local a=system.getUtcTime()if lastRefresh~=a then local b=self.getClass():lower()if b:find('atmo')then self.fuelType=\"atmo\"elseif b:find('space')then self.fuelType=\"space\"end;local c=self.getWidgetData()local d=json.decode(c)self.name=d.name;self.percentage=d.percentage;self.timeLeft=tonumber(d.timeLeft)self.lastRefresh=a end end,getName=function(self)self:refresh()return self.name end,getPercentFilled=function(self)self:refresh()return self.percentage end,getSecondsLeft=function(self)self:refresh()return self.timeLeft end,getFuelType=function(self)self:refresh()return self.fuelType end,getTimeLeftString=function(self)local e=self:getSecondsLeft()if e==nil or e<=0 then return\"-\"end;days=string.format(\"%2.f\",math.floor(e/(3600*24)))hours=string.format(\"%2.f\",math.floor(e/3600-days*24))mins=string.format(\"%2.f\",math.floor(e/60-hours*60-days*24*60))secs=string.format(\"%2.f\",math.floor(e-hours*3600-days*24*60*60-mins*60))str=\"\"if tonumber(days)>0 then str=str..days..\"d \"end;if tonumber(hours)>0 then str=str..hours..\"h \"end;if tonumber(mins)>0 then str=str..mins..\"m \"end;if tonumber(secs)>0 then str=str..secs..\"s\"end;return str end,dump=function(self)self:refresh()system.print('{name=\"'..self.name..'\",percentage='..self.percentage..',timeLeft='..self.timeLeft..',fuelType=\"'..self.fuelType..'\"}')end}}\r\n--[[\r\n Detecting elements linked\r\n]]\r\ncore = nil\r\nfuelTanks = {}\r\ndatabank = nil\r\nfor slot_name, slot in pairs(unit) do\r\n if\r\n type(slot) == \"table\"\r\n and type(slot.export) == \"table\"\r\n and slot.getClass\r\n then\r\n local class = slot.getClass():lower()\r\n if class:find(\"coreunit\") then\r\n core = slot\r\n elseif class:find(\"fuelcontainer\") then\r\n fuelTanks[#fuelTanks + 1] = setmetatable(slot, FuelTank)\r\n elseif class:find(\"databank\") then\r\n databank = slot\r\n end\r\n end\r\nend\r\n\r\n--[[\r\n stopping the script if required elements are not linked\r\n]]\r\nif core == nil then\r\n system.print('Core unit is not linked, exiting the script')\r\n unit.exit()\r\nend\r\nif databank == nil then\r\n system.print('Databank is not linked')\r\nend\r\n\r\n--[[\r\n Atlas\r\n]]\r\natlas = require\"atlas\"\r\n\r\n--[[\r\n Default Unit Start Mechanics\r\n]]\r\npitchInput = 0\r\npitchInputFromDevice = 0\r\nrollInput = 0\r\nyawInput = 0\r\nverticalStrafeInput = 0\r\nlateralStrafeInput = 0\r\nbrakeInput = 1 --braking by default\r\ngoingBack = false\r\ngoingForward = false\r\nshiftPressed = false\r\njumpDelta = 0\r\nbaseAcceleration = 0.8\r\n\r\nNav = Navigator.new(system, core, unit)\r\nNav.axisCommandManager:setupCustomTargetSpeedRanges(axisCommandId.longitudinal, {100, 500, 1000, 2000,3000, 5000})\r\nNav.axisCommandManager:setTargetGroundAltitude(0)\r\nunit.deactivateGroundEngineAltitudeStabilization()\r\n\r\n-- Parenting widget\r\nif ShowParentingWidget then\r\n parentingPanelId = system.createWidgetPanel(\"Docking\")\r\n parentingWidgetId = system.createWidget(parentingPanelId,\"parenting\")\r\n system.addDataToWidget(unit.getWidgetDataId(),parentingWidgetId)\r\nend\r\nif not ShowControlUnitWidget then\r\n unit.hideWidget()\r\nend\r\n\r\n--[[\r\n Storing base position and orientation of the elevator\r\n]]\r\nfunction storeIniPosAndForward(force)\r\n if force == nil then force = false end\r\n ConstructInitPos = construct.getWorldPosition()\r\n --replace that pos by a hand written value to be sure the construct will always realign the same position\r\n --ConstructInitPos = {-1231461.6525868,1201334.4594833,-2617227.3718653}\r\n if __DEBUG then system.print('ConstructInitPos: ' .. json.encode(ConstructInitPos)) end\r\n BaseForward = construct.getWorldForward()\r\n --replace that pos by a hand written value to be sure the construct will always realign the same forward orientation\r\n --BaseForward = {-0.085554607212543,0.2445342447567,-0.9658600687807}\r\n if __DEBUG then system.print('BaseForward: ' .. json.encode(BaseForward)) end\r\n if databank ~= nil then\r\n if databank.hasKey('ConstructInitPos') and not force then\r\n ConstructInitPos = Serializer:parse(databank.getStringValue('ConstructInitPos'))\r\n system.print(\"ConstructInitPos loaded from databank\")\r\n else\r\n local toStore = Serializer:stringify(ConstructInitPos)\r\n databank.setStringValue('ConstructInitPos', toStore)\r\n system.print(\"ConstructInitPos stored in databank\")\r\n system.print(toStore)\r\n end\r\n if databank.hasKey('BaseForward') and not force then\r\n BaseForward = Serializer:parse(databank.getStringValue('BaseForward'))\r\n system.print(\"BaseForward loaded from databank\")\r\n else\r\n local toStore = Serializer:stringify(BaseForward)\r\n databank.setStringValue('BaseForward', toStore)\r\n system.print(\"BaseForward stored in databank\")\r\n system.print(toStore)\r\n end\r\n else\r\n system.print(\"No Databank detected\")\r\n end\r\n BaseForward = vec3(BaseForward)\r\nend\r\n\r\n--[[\r\n Storing Planet ID in Databank to be sure we always have the origin planet store even where too far from planet (1su or more)\r\n]]\r\nfunction storeReferencePlanet(force)\r\n if force == nil then force = false end\r\n local PlanetID = nil\r\n if databank.hasKey('PlanetID') and not force then\r\n PlanetID = databank.getIntValue('PlanetID')\r\n else\r\n PlanetID = core.getCurrentPlanetId()\r\n if PlanetID ~= nil then\r\n databank.setIntValue('PlanetID', PlanetID)\r\n system.print(\"Planet ID stored in databank: \" .. PlanetID)\r\n end\r\n end\r\n if PlanetID == nil then\r\n system.print(\"WARNING: no planet detected, you may be too far away in spance, please, restart the elevator from the planet surface\")\r\n else\r\n ElevatorData.planetData = atlas[0][PlanetID]\r\n end\r\n if __DEBUG then system.print('Planet ID: ' .. PlanetID) end\r\nend\r\n\r\n--[[\r\n Compute planet atmosphere altitude\r\n]]\r\nfunction computePlanetAtmoAltitude(planetData)\r\n return planetData.atmosphereRadius - planetData.radius\r\nend\r\n\r\n--[[\r\n --compute distance from a planet sea level\r\n]]\r\nfunction computePlanetSeaDistance(planetData, constructWorldPosition)\r\n return (vec3(planetData.center) - vec3(constructWorldPosition)):len() - planetData.radius\r\nend\r\n\r\nConstructInitPos = nil\r\nBaseForward = nil\r\nstoreIniPosAndForward()\r\nif ConstructInitPos == nil or BaseForward == nil then\r\n system.print(\"An Error occurs during Init. Try removing the databank, remove its dynamic properties and install it again ...\")\r\nend\r\nBaseAltitute = Bookmarks[1].altitude --getting the base altitude from the 1st bookmark\r\n--init a value to store the target altitude\r\nTargetAltitude = core.getAltitude() --by default to the start altitude to avoid falling down if in the air\r\n\r\nMaxSpeed = construct.getFrictionBurnSpeed() -- for security to avoid burning if going too fast\r\nMaxSpeedOnPlanet = MaxSpeed\r\nif __DEBUG then system.print('MaxSpeed: ' .. (MaxSpeed*3.6) .. 'km/h') end\r\n\r\nElevatorData = {\r\n isBreaking = brakeInput == 1,\r\n verticalSpeed = 0,\r\n verticalSpeedSigned = 0,\r\n lateralSpeed = 0,\r\n longitudinalSpeed = 0,\r\n coreAltitude = TargetAltitude,\r\n altitude = TargetAltitude,\r\n planetData = nil,\r\n direction = 'Stabilizing',\r\n atmosphereDistance = 0,\r\n atmosphereAltitude = 0,\r\n atmoMaxSpeed = MaxSpeedOnPlanet,\r\n currentMaxSpeed = MaxSpeed,\r\n}\r\n\r\nstoreReferencePlanet(false)\r\n\r\n--computing the distance from the planet sea level as target altitude\r\nif ElevatorData.planetData ~= nil then\r\n TargetAltitude = computePlanetSeaDistance(ElevatorData.planetData, construct.getWorldPosition())\r\n ElevatorData.atmosphereAltitude = computePlanetAtmoAltitude(ElevatorData.planetData)\r\nend\r\n\r\n--base selected bookmark is 0 -> none selected as index 0 doesn't exist in lua\r\nselectedBookmarkIndex = 0\r\n\r\n--[[\r\n init the HUD\r\n]]\r\nsystem.showScreen(true)\r\n\r\n--[[\r\n Kinematics functions by Jaylebreak\r\n Source available at https://gitlab.com/JayleBreak/dualuniverse/-/blob/master/DUflightfiles/autoconf/custom/kinematics.lua\r\n]]\r\nfunction computeAccelerationTime(initial, acceleration, final) return (final - initial)/acceleration end\r\nfunction computeDistanceAndTime(initial, final, mass, thrust, t50, brakeThrust)\r\n t50 = t50 or 0\r\n brakeThrust = brakeThrust or 0\r\n local speedUp = initial < final\r\n local a0 = thrust / (speedUp and mass or -mass)\r\n local b0 = -brakeThrust/mass\r\n local totA = a0+b0\r\n if initial == final then\r\n return 0, 0\r\n elseif speedUp and totA <= 0 or not speedUp and totA >= 0 then\r\n return -1, -1\r\n end\r\n local distanceToMax, timeToMax = 0, 0\r\n if a0 ~= 0 and t50 > 0 then\r\n local c1 = math.pi/t50/2\r\n local v = function(t)\r\n return a0*(t/2 - t50*math.sin(c1*t)/math.pi) + b0*t + initial\r\n end\r\n local speedchk = speedUp and function(s) return s >= final end or function(s) return s <= final end\r\n timeToMax = 2*t50\r\n if speedchk(v(timeToMax)) then\r\n local lasttime = 0\r\n while math.abs(timeToMax - lasttime) > 0.25 do\r\n local t = (timeToMax + lasttime)/2\r\n if speedchk(v(t)) then\r\n timeToMax = t\r\n else\r\n lasttime = t\r\n end\r\n end\r\n end\r\n local K = 2*a0*t50^2/math.pi^2\r\n distanceToMax = K*(math.cos(c1*timeToMax) - 1) + (a0+2*b0)*timeToMax^2/4 + initial*timeToMax\r\n if timeToMax < 2*t50 then\r\n return distanceToMax, timeToMax\r\n end\r\n initial = v(timeToMax)\r\n end\r\n local a = a0+b0\r\n local t = computeAccelerationTime(initial, a, final)\r\n local d = initial*t + a*t*t/2\r\n return distanceToMax+d, timeToMax+t\r\nend\r\n\r\n--[[\r\n Compute angle between vectors by Jericho\r\n]]\r\nfunction signedAngleBetween(vec1, vec2, planeNormal)\r\n local normVec1 = vec1:project_on_plane(planeNormal):normalize()\r\n local normVec2 = vec2:normalize()\r\n local v1v2dot = normVec1:dot(normVec2)\r\n local angle = math.acos(utils.clamp(v1v2dot,-1,1))\r\n local crossProduct = vec1:cross(vec2)\r\n if crossProduct:dot(planeNormal) < 0 then\r\n return -angle\r\n end\r\n return angle\r\nend\r\n--[[\r\n\tformatting numbers by adding a space between thousands by Jericho\r\n]]\r\nfunction format_number(a)local b=a;while true do b,k=string.gsub(b,\"^(-?%d+)(%d%d%d)\",'%1 %2')if k==0 then break end end;local c=string.sub(b,-2)if c=='.0'then b=string.sub(b,1,b:len()-2)end;return b end\r\n--[[\r\n DU-LUA-Framework by Jericho\r\n Permit to code easier by grouping most of the code in a single event Unit > Start\r\n Unminified Source available here: https://github.com/Jericho1060/du-lua-framework\r\n]]--\r\nlocal a=system.print;local b=error;local c=pcall;local d=assert;local e=coroutine;local f=e.create;local g=e.status;local h=e.resume;local i=\"dead\"local j=\"suspended\"function runFunction(k,l,...)local m,n=c(k,...)if not m then b(l..n)end end;local o={__index={cos={update={},flush={}},fns={update={},flush={},action={start={},stop={},loop={}},inputText=nil,start=nil,stop=nil},ACTIONS={FORWARD=\"forward\",BACKWARD=\"backward\",YAW_LEFT=\"yawleft\",YAW_RIGHT=\"yawright\",STRAFE_LEFT=\"strafeleft\",STRAFE_RIGHT=\"straferight\",LEFT=\"left\",RIGHT=\"right\",UP=\"up\",DOWN=\"down\",GROUND_ALTITUDE_UP=\"groundaltitudeup\",GROUND_ALTITUDE_DOWN=\"groundaltitudedown\",LEFT_ALT=\"lalt\",LEFT_SHIFT=\"lshift\",GEAR=\"gear\",LIGHT=\"light\",BRAKE=\"brake\",OPTION_1=\"option1\",OPTION_2=\"option2\",OPTION_3=\"option3\",OPTION_4=\"option4\",OPTION_5=\"option5\",OPTION_6=\"option6\",OPTION_7=\"option7\",OPTION_8=\"option8\",OPTION_9=\"option9\",OPTION_10=\"option10\",OPTION_11=\"option11\",OPTION_12=\"option12\",OPTION_13=\"option13\",OPTION_14=\"option14\",OPTION_15=\"option15\",OPTION_16=\"option16\",OPTION_17=\"option17\",OPTION_18=\"option18\",OPTION_19=\"option19\",OPTION_20=\"option20\",OPTION_21=\"option21\",OPTION_22=\"option22\",OPTION_23=\"option23\",OPTION_24=\"option24\",OPTION_25=\"option25\",OPTION_26=\"option26\",OPTION_27=\"option27\",OPTION_28=\"option28\",OPTION_29=\"option29\",LEFT_MOUSE=\"leftmouse\",STOP_ENGINES=\"stopengines\",SPEED_UP=\"speedup\",SPEED_DOWN=\"speeddown\",ANTIGRAVITY=\"antigravity\",BOOSTER=\"booster\"},main={update=f(function()end),flush=f(function()end)},update=function(self)local p=g(self.main.update)if p==i then self.main.update=f(function()self:runUpdate()end)elseif p==j then d(h(self.main.update))end end,flush=function(self)local p=g(self.main.flush)if p==i then self.main.flush=f(function()self:runFlush()end)elseif p==j then d(h(self.main.flush))end end,action=function(self,q,r)if self.fns.action[q][r]then runFunction(self.fns.action[q][r],\"System Action \"..q..\" Error: \")end end,actionStart=function(self,r)self:action('start',r)end,actionStop=function(self,r)self:action('stop',r)end,actionLoop=function(self,r)self:action('loop',r)end,inputText=function(self,s)if self.fns.inputText then runFunction(self.fns.inputText,\"System Input Text Error: \",s)end end,runUpdate=function(self)for t,u in pairs(self.cos.update)do local m=g(u)if m==i then self.cos.update[t]=f(self.fns.update[t])elseif m==j then d(h(u))end end end,runFlush=function(self)for t,u in pairs(self.cos.flush)do local m=g(u)if m==i then self.cos.flush[t]=f(self.fns.flush[t])elseif m==j then d(h(u))end end end,onUpdate=function(self,v)for t,w in pairs(v)do self.fns.update[t]=w;self.cos.update[t]=f(w)end end,onFlush=function(self,v)for t,w in pairs(v)do self.fns.flush[t]=w;self.cos.flush[t]=f(w)end end,onAction=function(self,q,v)for t,w in pairs(v)do self.fns.action[q][t]=w end end,onActionStart=function(self,v)self:onAction(\"start\",v)end,onActionStop=function(self,v)self:onAction(\"stop\",v)end,onActionLoop=function(self,v)self:onAction(\"loop\",v)end,onInputText=function(self,k)self.fns.inputText=k end}}local x={__index={timers={},stopFn=function()end,timer=function(self,y)if self.timers[y]then runFunction(self.timers[y],\"Unit Timer \"..y..\" Error: \")end end,setTimer=function(self,y,z,k)self.timers[y]=k;unit.setTimer(y,z)end,stopTimer=function(self,y)unit.stopTimer(y)self.timers[y]=nil end,onStop=function(self,k)self.stopFn=k end,stop=function(self)if self.stopFn then runFunction(self.stopFn,\"Unit Stop Error: \")end end}}local A={__index={parentChangedFn=function(B,C)end,onParentChange=function(self,k)self.parentChangedFn=k end,parentChanged=function(self,B,C)if self.parentChangedFn then runFunction(self.parentChangedFn,\"Player Parent Changed Error: \",B,C)end end}}local D={__index={dockedFn=function(E)end,onDocked=function(self,k)self.dockedFn=k end,docked=function(self,E)if self.dockedFn then runFunction(self.dockedFn,\"Construct Docked Error: \",E)end end,undockedFn=function(E)end,onUndocked=function(self,k)self.undockedFn=k end,undocked=function(self,E)if self.undockedFn then runFunction(self.undockedFn,\"Construct Undocked Error: \",E)end end,playerBoardedFn=function(E)end,onPlayerBoarded=function(self,k)self.playerBoardedFn=k end,playerBoarded=function(self,E)if self.playerBoardedFn then runFunction(self.playerBoardedFn,\"Construct Player Boarded Error: \",E)end end,VRStationEnteredFn=function(E)end,onVRStationEntered=function(self,k)self.VRStationEnteredFn=k end,VRStationEntered=function(self,E)if self.VRStationEnteredFn then runFunction(self.VRStationEnteredFn,\"Construct VR Station Entered Error: \",E)end end,constructDockedFn=function(E)end,onConstructDocked=function(self,k)self.constructDockedFn=k end,constructDocked=function(self,E)if self.constructDockedFn then runFunction(self.constructDockedFn,\"Construct Construct Docked Error: \",E)end end,PvPTimerFn=function(F)end,onPvPTimer=function(self,k)self.PvPTimerFn=k end,PvPTimer=function(self,F)if self.PvPTimerFn then runFunction(self.PvPTimerFn,\"Construct PvP Timer Error: \",F)end end}}DU_Framework={__index={system=setmetatable({},o),unit=setmetatable({},x),player=setmetatable({},A),construct=setmetatable({},D)}}\r\n\r\nScript = {}\r\nsetmetatable(Script, DU_Framework)\r\n\r\n--[[\r\n String Helpers For Lua By Jericho\r\n]]\r\nString = {\r\n __index = {\r\n split = function(self, delimiter)\r\n local result = {}\r\n for match in (self..delimiter):gmatch(\"(.-)\"..delimiter) do\r\n table.insert(result, match)\r\n end\r\n return result\r\n end\r\n }\r\n}\r\nstring = setmetatable(string, String)\r\nstring.__index = string\r\n\r\nlocal systemOnFlush = {\r\n function()\r\n local yawSpeedFactor = 0.1 --export: the auto yaw speed multiplier\r\n local yawAccelerationFactor = 3\r\n local lateralAntiDriftFactor = 1\r\n local lateralStrafeFactor = 5\r\n local brakeSpeedFactor = 1\r\n local brakeFlatFactor = 4\r\n local autoBrakeSpeed = 15\r\n -- validate params\r\n brakeSpeedFactor = math.max(brakeSpeedFactor, 0.01)\r\n brakeFlatFactor = math.max(brakeFlatFactor, 0.01)\r\n yawSpeedFactor = math.max(yawSpeedFactor, 0.01)\r\n yawAccelerationFactor = math.max(yawAccelerationFactor, 0.01)\r\n --init all the PIDs as global if first flush\r\n if (rollPID == nil) then rollPID = pid.new(0.2, 0, 10) end\r\n if (pitchPID == nil) then pitchPID = pid.new(0.2, 0, 10) end\r\n if (yawPID == nil) then yawPID = pid.new(0.2, 0, 10) end\r\n if (lateralPID == nil) then lateralPID = pid.new(0.2, 0, 10) end\r\n if (longitudinalPID == nil) then longitudinalPID = pid.new(0.2, 0, 10) end\r\n if (distancePID == nil) then distancePID = pid.new(0.2, 0, 10) end\r\n\r\n -- final inputs\r\n if unit.isMouseDirectControlActivated() then\r\n -- in direct control, we tweak the pitch to behave inbetween virtual joystick and direct control\r\n -- this helps a lot for ground construct control\r\n pitchInputFromDevice = utils.clamp(pitchInputFromDevice + system.getControlDeviceForwardInput() * system.getActionUpdateDeltaTime(), -1.0, 1.0)\r\n else\r\n pitchInputFromDevice = system.getControlDeviceForwardInput()\r\n end\r\n local finalPitchInput = pitchInput + pitchInputFromDevice\r\n local finalRollInput = rollInput + system.getControlDeviceYawInput()\r\n local finalYawInput = yawInput - system.getControlDeviceLeftRightInput()\r\n local combinedRollYawInput = utils.clamp(finalRollInput - finalYawInput, -1.0, 1.0);\r\n local finalVerticalStrafeInput = verticalStrafeInput\r\n local finalLateralStrafeInput = lateralStrafeInput;\r\n local finalBrakeInput = brakeInput\r\n\r\n -- Axis\r\n local worldVertical = vec3(core.getWorldVertical())\r\n local worldRight = vec3(core.getWorldRight())\r\n local worldForward = vec3(core.getWorldForward())\r\n local constructUp = vec3(construct.getWorldOrientationUp())\r\n local constructForward = vec3(construct.getWorldOrientationForward())\r\n local constructRight = vec3(construct.getWorldOrientationRight())\r\n local constructVelocity = vec3(construct.getWorldVelocity())\r\n local constructVelocityDir = vec3(construct.getWorldVelocity()):normalize()\r\n local constructAngularVelocity = vec3(construct.getWorldAngularVelocity())\r\n local constructYawVelocity = constructAngularVelocity:dot(constructUp)\r\n local constructWorldPosition = vec3(construct.getWorldPosition())\r\n local constructTargetPosition = vec3(ConstructInitPos)\r\n\r\n -- Engine commands\r\n local keepCollinearity = 0 -- for easier reading\r\n local dontKeepCollinearity = 1 -- for easier reading\r\n local tolerancePercentToSkipOtherPriorities = 1 -- if we are within this tolerance (in%), we don't go to the next priorities\r\n\r\n --fake braking value\r\n local canBrake = false\r\n\r\n -- keeping the start position alignement\r\n local StrafeSpeedFactor = 1 --export: useg to increase the force of the alignement, decrease the value if the alignement is too strong or increase it if it's too slow\r\n local positionDifference = constructTargetPosition - constructWorldPosition\r\n local lateralOffset = positionDifference:project_on(constructRight):len() * utils.sign(positionDifference:dot(constructRight))\r\n ElevatorData.lateralSpeed = constructVelocity:project_on(worldRight):len()\r\n local lateralDistance = math.abs(lateralOffset)\r\n local longitudinalOffset = positionDifference:project_on(constructForward):len() * utils.sign(positionDifference:dot(constructForward))\r\n ElevatorData.longitudinalSpeed = constructVelocity:project_on(worldForward):len()\r\n local longitudinalDistance = math.abs(longitudinalOffset)\r\n if (lateralDistance < .25) and (longitudinalDistance < .25) then canBrake = true end\r\n if ((ElevatorData.lateralSpeed*3.6) > lateralDistance) or ((ElevatorData.longitudinalSpeed*3.6) > longitudinalDistance) then canBrake = true end\r\n lateralPID:inject(lateralOffset)\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.lateral, lateralPID:get() * StrafeSpeedFactor)\r\n longitudinalPID:inject(longitudinalOffset)\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, longitudinalPID:get() * StrafeSpeedFactor)\r\n\r\n -- Rotation\r\n local currentRollDeg = getRoll(worldVertical, constructForward, constructRight)\r\n local currentPitchDeg = -math.asin(constructForward:dot(worldVertical)) * constants.rad2deg\r\n local targetRollDeg = 0\r\n local targetPitchDeg = 0\r\n local targetYawDeg = signedAngleBetween(BaseForward,constructForward,constructUp)*180/math.pi\r\n rollPID:inject(targetRollDeg - currentRollDeg)\r\n pitchPID:inject(targetPitchDeg - currentPitchDeg)\r\n yawPID:inject(-targetYawDeg*yawSpeedFactor)\r\n\r\n local constructTargetAngularVelocity = rollPID:get() * constructForward + pitchPID:get() * constructRight + yawPID:get() * constructUp\r\n\r\n Nav:setEngineTorqueCommand('torque', constructTargetAngularVelocity, keepCollinearity, 'airfoil', '', '', tolerancePercentToSkipOtherPriorities)\r\n\r\n -- moving up or down from TargetAltitude\r\n ElevatorData.verticalSpeed = constructVelocity:project_on(worldVertical):len()\r\n ElevatorData.verticalSpeedSigned = ElevatorData.verticalSpeed * -utils.sign(constructVelocity:dot(worldVertical))\r\n local brakeDistance = 0\r\n local brakeDistanceToAtmosphere = 0\r\n local maxBrake = construct.getMaxBrake()\r\n ElevatorData.coreAltitude = core.getAltitude()\r\n ElevatorData.atmosphereAltitude = computePlanetAtmoAltitude(ElevatorData.planetData)\r\n ElevatorData.altitude = computePlanetSeaDistance(ElevatorData.planetData, constructWorldPosition)\r\n ElevatorData.atmosphereDistance = ElevatorData.altitude - ElevatorData.atmosphereAltitude\r\n local isInAtmosphere = ElevatorData.atmosphereDistance <= 0\r\n local targetIsInAtmosphere = TargetAltitude <= ElevatorData.atmosphereAltitude\r\n if maxBrake ~= nil then\r\n local g = core.getGravityIntensity()\r\n if ElevatorData.verticalSpeedSigned < 0 then\r\n g = ElevatorData.planetData.gravity --when falling, apply the max gravity to the braking computing to increase braking\r\n end\r\n local inertialMass = construct.getInertialMass()\r\n local speedSign = utils.sign(ElevatorData.verticalSpeedSigned)\r\n brakeDistanceToAtmosphere, _ = computeDistanceAndTime(ElevatorData.verticalSpeed, ElevatorData.atmoMaxSpeed, inertialMass, 0, 0, maxBrake - (g * inertialMass * speedSign))\r\n brakeDistance, _ = computeDistanceAndTime(ElevatorData.verticalSpeed, 0, inertialMass, 0, 0, maxBrake - (g * inertialMass * speedSign))\r\n end\r\n if isInAtmosphere or (targetIsInAtmosphere and (brakeDistanceToAtmosphere > ElevatorData.atmosphereDistance)) then\r\n ElevatorData.currentMaxSpeed = ElevatorData.atmoMaxSpeed\r\n else\r\n ElevatorData.currentMaxSpeed = 50000/3.6 -- 50km/h in m/s = 13888.88888888889....\r\n end\r\n local distance = TargetAltitude - ElevatorData.altitude\r\n local targetDistance = utils.sign(distance) * (math.abs(distance)-brakeDistance)\r\n distancePID:inject(targetDistance)\r\n if distancePID:get() > 0.25 or ElevatorData.verticalSpeedSigned < -ElevatorData.currentMaxSpeed then --using a 0.25 meter deadband for stabilizing\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.vertical, 1)\r\n ElevatorData.direction = 'up'\r\n if ElevatorData.verticalSpeedSigned < 0 then\r\n finalBrakeInput = 1\r\n end\r\n elseif distancePID:get() < -0.25 or ElevatorData.verticalSpeedSigned > ElevatorData.currentMaxSpeed then\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.vertical, -1)\r\n ElevatorData.direction = 'down'\r\n if ElevatorData.verticalSpeedSigned > 0 then\r\n finalBrakeInput = 1\r\n end\r\n else\r\n Nav.axisCommandManager:resetCommand(axisCommandId.vertical)\r\n ElevatorData.direction = 'stabilizing'\r\n end\r\n\r\n --Brakes\r\n if (math.abs(distance) < (ElevatorData.verticalSpeed*3.6) and canBrake) --speed control\r\n or (ElevatorData.verticalSpeed >= ElevatorData.currentMaxSpeed) --anti burn speed\r\n or ((math.abs(distancePID:get()) < .25) and canBrake) --altitude reached with 0.25m deadband\r\n or ((brakeDistance > math.abs(distance)) and canBrake) --braking distance\r\n or (finalBrakeInput == 0 and autoBrakeSpeed > 0 and Nav.axisCommandManager.throttle == 0 and constructVelocity:len() < autoBrakeSpeed)\r\n then\r\n finalBrakeInput = 1\r\n --use engines to help braking\r\n if ElevatorData.verticalSpeedSigned < 0 and isInAtmosphere then\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.vertical, 1)\r\n end\r\n end\r\n ElevatorData.isBreaking = (finalBrakeInput == 1)\r\n local brakeAcceleration = -finalBrakeInput * (brakeSpeedFactor * constructVelocity + brakeFlatFactor * constructVelocityDir)\r\n\r\n Nav:setEngineForceCommand('brake', brakeAcceleration)\r\n\r\n -- AutoNavigation regroups all the axis command by 'TargetSpeed'\r\n local autoNavigationEngineTags = ''\r\n local autoNavigationAcceleration = vec3()\r\n local autoNavigationUseBrake = false\r\n\r\n -- Longitudinal Translation\r\n local longitudinalEngineTags = 'thrust analog longitudinal'\r\n local longitudinalCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.longitudinal)\r\n if (longitudinalCommandType == axisCommandType.byThrottle) then\r\n local longitudinalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(longitudinalEngineTags,axisCommandId.longitudinal)\r\n Nav:setEngineForceCommand(longitudinalEngineTags, longitudinalAcceleration, keepCollinearity)\r\n elseif (longitudinalCommandType == axisCommandType.byTargetSpeed) then\r\n local longitudinalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.longitudinal)\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. longitudinalEngineTags\r\n autoNavigationAcceleration = autoNavigationAcceleration + longitudinalAcceleration\r\n if (Nav.axisCommandManager:getTargetSpeed(axisCommandId.longitudinal) == 0 or -- we want to stop\r\n Nav.axisCommandManager:getCurrentToTargetDeltaSpeed(axisCommandId.longitudinal) < - Nav.axisCommandManager:getTargetSpeedCurrentStep(axisCommandId.longitudinal) * 0.5) -- if the longitudinal velocity would need some braking\r\n then\r\n autoNavigationUseBrake = true\r\n end\r\n end\r\n\r\n -- Lateral Translation\r\n local lateralStrafeEngineTags = 'thrust analog lateral'\r\n local lateralCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.lateral)\r\n if (lateralCommandType == axisCommandType.byThrottle) then\r\n local lateralStrafeAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(lateralStrafeEngineTags,axisCommandId.lateral)\r\n Nav:setEngineForceCommand(lateralStrafeEngineTags, lateralStrafeAcceleration, keepCollinearity)\r\n elseif (lateralCommandType == axisCommandType.byTargetSpeed) then\r\n local lateralAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.lateral)\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. lateralStrafeEngineTags\r\n autoNavigationAcceleration = autoNavigationAcceleration + lateralAcceleration\r\n end\r\n\r\n -- Vertical Translation\r\n local verticalStrafeEngineTags = 'thrust analog vertical'\r\n local verticalCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.vertical)\r\n if (verticalCommandType == axisCommandType.byThrottle) then\r\n local verticalStrafeAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(verticalStrafeEngineTags,axisCommandId.vertical)\r\n Nav:setEngineForceCommand(verticalStrafeEngineTags, verticalStrafeAcceleration, keepCollinearity, 'airfoil', 'ground', '', tolerancePercentToSkipOtherPriorities)\r\n elseif (verticalCommandType == axisCommandType.byTargetSpeed) then\r\n local verticalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.vertical)\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. verticalStrafeEngineTags\r\n autoNavigationAcceleration = autoNavigationAcceleration + verticalAcceleration\r\n end\r\n\r\n -- Auto Navigation (Cruise Control)\r\n if (autoNavigationAcceleration:len() > constants.epsilon) then\r\n if (brakeInput ~= 0 or autoNavigationUseBrake or math.abs(constructVelocityDir:dot(constructForward)) < 0.95) -- if the velocity is not properly aligned with the forward\r\n then\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ', brake'\r\n end\r\n Nav:setEngineForceCommand(autoNavigationEngineTags, autoNavigationAcceleration, dontKeepCollinearity, '', '', '', tolerancePercentToSkipOtherPriorities)\r\n end\r\n\r\n -- Rockets\r\n Nav:setBoosterCommand('rocket_engine')\r\n end\r\n}\r\n\r\nlocal systemOnUpdate = {\r\n function ()\r\n Nav:update()\r\n RENDER_HUD(ElevatorData)\r\n end\r\n}\r\n\r\nScript.system:onUpdate(systemOnUpdate)\r\nScript.system:onFlush(systemOnFlush)\r\n\r\n--[[\r\n Actions\r\n]]\r\nlocal systemActionsStart = {}\r\n\r\nsystemActionsStart[Script.system.ACTIONS.DOWN] = function ()\r\n if selectedBookmarkIndex < #Bookmarks then\r\n selectedBookmarkIndex = selectedBookmarkIndex + 1\r\n else\r\n selectedBookmarkIndex = 1\r\n end\r\n if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.UP] = function ()\r\n if selectedBookmarkIndex > 1 then\r\n selectedBookmarkIndex = selectedBookmarkIndex - 1\r\n else\r\n selectedBookmarkIndex = #Bookmarks\r\n end\r\n if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.STRAFE_RIGHT] = function ()\r\n brakeInput = 0\r\n if selectedBookmarkIndex > 0 then\r\n TargetAltitude = Bookmarks[selectedBookmarkIndex].altitude\r\n if __DEBUG then system.print('Target Altitude: ' .. TargetAltitude) end\r\n else\r\n system.print('No bookmark selected')\r\n end\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.OPTION_1] = function()\r\n storeIniPosAndForward(true)\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.OPTION_2] = function()\r\n storeReferencePlanet(true)\r\nend\r\nScript.system:onActionStart(systemActionsStart) --loading all \"actionStart\" functions\r\n\r\n--[[\r\n Chat Commands\r\n]]\r\n\r\nScript.system:onInputText(function (text)\r\n if text:lower():find('goto:') then\r\n TargetAltitude = tonumber(text:split(':')[2]) or BaseAltitute\r\n brakeInput = 0\r\n system.print('Target Altitude set to ' .. TargetAltitude .. 'm')\r\n else\r\n system.print('Unknown command')\r\n end\r\nend)\r\n","filter":{"args":[],"signature":"onStart()","slotKey":"-1"},"key":"2"}],"methods":[],"slots":{"0":{"name":"slot1","type":{"events":[],"methods":[]}},"1":{"name":"slot2","type":{"events":[],"methods":[]}},"2":{"name":"slot3","type":{"events":[],"methods":[]}},"3":{"name":"slot4","type":{"events":[],"methods":[]}},"4":{"name":"slot5","type":{"events":[],"methods":[]}},"5":{"name":"slot6","type":{"events":[],"methods":[]}},"6":{"name":"slot7","type":{"events":[],"methods":[]}},"7":{"name":"slot8","type":{"events":[],"methods":[]}},"8":{"name":"slot9","type":{"events":[],"methods":[]}},"9":{"name":"slot10","type":{"events":[],"methods":[]}},"-5":{"name":"library","type":{"events":[],"methods":[]}},"-4":{"name":"system","type":{"events":[],"methods":[]}},"-3":{"name":"player","type":{"events":[],"methods":[]}},"-2":{"name":"construct","type":{"events":[],"methods":[]}},"-1":{"name":"unit","type":{"events":[],"methods":[]}}}} 1 + {"events":[],"handlers":[{"code":"Script.system:update()","filter":{"args":[],"signature":"onUpdate()","slotKey":"-4"},"key":"9"},{"code":"Script.system:inputText(text)","filter":{"args":[{"variable":"*"}],"signature":"onInputText(text)","slotKey":"-4"},"key":"10"},{"code":"Script.system:flush()","filter":{"args":[],"signature":"onFlush()","slotKey":"-4"},"key":"11"},{"code":"Script.system:actionStop(action)","filter":{"args":[{"variable":"*"}],"signature":"onActionStop(action)","slotKey":"-4"},"key":"12"},{"code":"Script.system:actionStart(action)","filter":{"args":[{"variable":"*"}],"signature":"onActionStart(action)","slotKey":"-4"},"key":"13"},{"code":"Script.system:actionLoop(action)","filter":{"args":[{"variable":"*"}],"signature":"onActionLoop(action)","slotKey":"-4"},"key":"14"},{"code":"Script.player:parentChanged(oldId, newId)","filter":{"args":[{"variable":"*"},{"variable":"*"}],"signature":"onParentChanged(oldId,newId)","slotKey":"-3"},"key":"3"},{"code":"Script.construct:VRStationEntered(id)","filter":{"args":[{"variable":"*"}],"signature":"onVRStationEntered(id)","slotKey":"-2"},"key":"4"},{"code":"Script.construct:undocked(id)","filter":{"args":[{"variable":"*"}],"signature":"onUndocked(id)","slotKey":"-2"},"key":"5"},{"code":"Script.construct:PvPTimer(active)","filter":{"args":[{"variable":"*"}],"signature":"onPvPTimer(active)","slotKey":"-2"},"key":"6"},{"code":"Script.construct:playerBoarded(id)","filter":{"args":[{"variable":"*"}],"signature":"onPlayerBoarded(id)","slotKey":"-2"},"key":"7"},{"code":"Script.construct:constructDocked(id)","filter":{"args":[{"variable":"*"}],"signature":"onConstructDocked(id)","slotKey":"-2"},"key":"8"},{"code":"Script.unit:timer(tag)","filter":{"args":[{"variable":"*"}],"signature":"onTimer(tag)","slotKey":"-1"},"key":"0"},{"code":"Script.unit:stop()","filter":{"args":[],"signature":"onStop()","slotKey":"-1"},"key":"1"},{"code":"--[[\r\n DU-ELEVATOR by Jericho\r\n]]\r\n\r\n--[[\r\n Bookmarks: you can store several altitudes here to navigate easily\r\n]]\r\nlocal Bookmarks = {\r\n { name = 'Start Point', altitude = 294 },\r\n { name = 'Hovering', altitude = 300 },\r\n { name = 'Floor 1', altitude = 350 },\r\n { name = 'Floor 2', altitude = 500 },\r\n { name = 'Space 1', altitude = 5000 },\r\n { name = 'Space 2', altitude = 6000 },\r\n { name = 'Space 3', altitude = 10000 },\r\n { name = 'Space 4', altitude = 20000 },\r\n { name = 'Space 5', altitude = 50000 },\r\n { name = 'Space 6', altitude = 200000 },\r\n}\r\n\r\n--[[\r\n LUA PARAMETERS\r\n]]\r\n__DEBUG = false --export: Debug mode, will print more information in the console\r\nShowParentingWidget = false --export: Show the parenting widget\r\nShowControlUnitWidget = false --export: show the default widget of the construct\r\nDisplayAtlasData = true --export: show the Atlas widget with closest planet informations\r\n\r\n--[[\r\n HUD Rendering\r\n]]\r\nfunction RENDER_HUD(ElevatorData)\r\n --[[\r\n That function is used for rendering the HUD,\r\n You can update it or replace all its content to customise it.\r\n You can also remove its content if you don't want a HUD.\r\n It's not affecting in any way how the elevator is working and it's just a display\r\n \r\n ElevatorData is an object containing values that are refresh all the time.\r\n ElevatorData.isBreaking - boolean - if the brakes a are enabled or not.\r\n ElevatorData.verticalSpeed - number - the absolute vertical speed of the elevator in m/s.\r\n ElevatorData.verticalSpeedSigned - number - the real vertical speed of the elevator in m/s. if <0 the elevator is falling.\r\n ElevatorData.lateralSpeed - numver - the absolute lateral speed of the elevator in m/s.\r\n ElevatorData.longitudinalSpeed - number - the absolute Longitudinal speed of the elevator in m/s.\r\n ElevatorData.coreAltitude - number - the altitude from sea level returned by the core in meters. (warning: this altitude in space when far from planets is 0)\r\n ElevatorData.altitude - number - computed altitude from the distance between the construct and the sea level of the closest planet. (not 0 when in space)\r\n ElevatorData.atmosphereDistance - number - computed distance from the construct to the atmosphere of the closest planet\r\n ElevatorData.atmosphereAltitude - number - computed altitude of the atmosphere of the closest planet.\r\n ElevatorData.atmoMaxSpeed - number - the maximum speed of the elevator in m/s when in atmosphere. (anti burn security)\r\n ElevatorData.currentMaxSpeed - number - the maximum speed of the elevator in m/s. (50km/h in space)\r\n ElevatorData.planetData - table - Atlas Data of the closest planet. See atlas in game file for the structure (Game Directory\\data\\lua\\atlas.lua)\r\n ]]\r\n --locale for Translation\r\n local locale = system.getLocale()\r\n localeIndex = 1 --default to english\r\n if locale == 'fr-FR' then\r\n localeIndex = 2 --french index in atlas\r\n elseif locale == 'de-DE' then\r\n localeIndex = 3 --german index in atlas\r\n end\r\n --direction if the elevator from the speed\r\n local direction = 'Stabilizing'\r\n if ElevatorData.verticalSpeedSigned > 0 then\r\n direction = 'Up'\r\n elseif ElevatorData.verticalSpeedSigned < 0 then\r\n direction = 'Down'\r\n end\r\n --braking status text for hud\r\n local brakeStatus = 'Off'\r\n if ElevatorData.isBreaking then\r\n brakeStatus = 'On'\r\n end\r\n --html rendering\r\n local html = [[\r\n <style>\r\n * {font-size:1vh !important;}\r\n .hud {\r\n position: absolute;\r\n left: 5vh;\r\n top: 5vh;\r\n right: 5vh;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: left;\r\n align-items: left;\r\n }\r\n .widget_container {\r\n border: 2px solid orange;\r\n border-radius:.5vh;\r\n background-color: rgba(0, 0, 0, .5);\r\n display: flex;\r\n flex-direction: column;\r\n padding:.5vh;\r\n margin-top:1vh;\r\n }\r\n .widget_container div {\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: space-between;\r\n }\r\n .widget_container div div {\r\n margin:.25vh;\r\n }\r\n .widget_container div div:first-child {\r\n text-transform: uppercase;\r\n font-weight: bold;\r\n }\r\n .selected {\r\n color: teal;\r\n }\r\n .movingto {\r\n color: green;\r\n }\r\n .atmo .gauge {\r\n background-color: #22d3ee;\r\n }\r\n .atmo .gauge_label {\r\n color: #155e75;\r\n }\r\n .space .gauge {\r\n background-color: #fbbf24;\r\n }\r\n .space .gauge_label {\r\n color: #92400e;\r\n }\r\n .rocket .gauge {\r\n background-color: #a78bfa;\r\n }\r\n .rocket .gauge_label {\r\n color: #5b21b6;\r\n }\r\n .gauge_container {\r\n display:block;\r\n width:100%;\r\n min-width:10vw;\r\n position: relative;\r\n border: 1px solid black;\r\n background-color: rgba(0, 0, 0, .75);\r\n height: 1.5vh;\r\n }\r\n .gauge {\r\n position: absolute;\r\n z-index:1;\r\n top:-.25vh;\r\n left:-.25vh;\r\n height:100%;\r\n margin:0;\r\n background-color: cyan;\r\n }\r\n .gauge_label {\r\n position:relative;\r\n display:block;\r\n z-index:10;\r\n margin:0;\r\n padding:0;\r\n width:100%;\r\n text-align:center;\r\n }\r\n </style>\r\n <div class=\"hud\">\r\n <div class=\"widget_container\">\r\n <div>\r\n <div>Base Altitude</div><div>]] .. format_number(utils.round(BaseAltitute)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Target Altitude</div><div>]] .. format_number(utils.round(TargetAltitude)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Current Altitude</div><div>]] .. format_number(utils.round(ElevatorData.altitude)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Atmosphere Distance</div><div>]] .. format_number(utils.round(ElevatorData.atmosphereDistance)) .. [[m</div>\r\n </div>\r\n <div>\r\n <div>Maximum Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.currentMaxSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Vertical Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.verticalSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Lateral Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.lateralSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Longitudinal Speed</div><div>]] .. format_number(math.abs(utils.round(ElevatorData.longitudinalSpeed*3.6))) .. [[km/h</div>\r\n </div>\r\n <div>\r\n <div>Direction</div><div>]] .. ElevatorData.direction .. [[</div>\r\n </div>\r\n <div>\r\n <div>Brake</div><div>]] .. brakeStatus .. [[</div>\r\n </div>\r\n </div>\r\n ]]\r\n if #fuelTanks > 0 then\r\n html = html .. '<div class=\"widget_container\">'\r\n for _, tank in pairs(fuelTanks) do\r\n html = html .. '<div><div>' .. tank:getName() .. '</div><div>' .. tank:getTimeLeftString() .. '</div></div><div class=\"gauge_container ' .. tank:getFuelType() .. '\"><div class=\"gauge\" style=\"width:' .. utils.round(tank:getPercentFilled()) .. '%;\"></div><div class=\"gauge_label\"><div style=\"margin-left:auto;margin-right:auto;padding:0;margin-top:-.25vh;\">' .. format_number(utils.round(tank:getPercentFilled())) .. '%</div></div></div>'\r\n end\r\n html = html .. '</div>'\r\n end\r\n if #Bookmarks > 0 then\r\n html = html .. '<div class=\"widget_container\"><div><div style=\"text-align:center !important;border-bottom:1px solid black;width:100%;\">Bookmarks</div></div>'\r\n for index, bookmark in ipairs(Bookmarks) do\r\n local class=''\r\n if selectedBookmarkIndex == index then\r\n class = 'selected'\r\n end\r\n local displayName = bookmark.name\r\n if selectedBookmarkIndex > 0 and bookmark.altitude == TargetAltitude then\r\n displayName = ' >> ' .. bookmark.name\r\n class = 'movingto'\r\n end\r\n html = html .. '<div class=\"' .. class .. '\"><div>' .. displayName .. '</div><div>' .. format_number((utils.round(bookmark.altitude*100)/100)) .. 'm</div></div>'\r\n end\r\n html = html .. '</div>'\r\n end\r\n if DisplayAtlasData and (ElevatorData.planetData ~= nil) then\r\n html = html .. '<div class=\"widget_container\">'\r\n html = html .. '<div><div style=\"text-align:center;border-bottom:1px solid black;width:100%;\">Atlas Data</div></div>'\r\n --html = html .. '<div><img src=\"/'..ElevatorData.planetData.iconPath..'\" style=\"width:10vh;height:10vh;\"></div>' --image is blinking in hud, you can display it here but by default i'm removing it\r\n html = html .. '<div><div>Planet Name</div><div>'..ElevatorData.planetData.name[localeIndex]..'</div></div>'\r\n html = html .. '<div><div>Planet Type</div><div>'..ElevatorData.planetData.type[localeIndex]..'</div></div>'\r\n html = html .. '<div><div>Planet gravity</div><div>'..ElevatorData.planetData.gravity..'m/s²</div></div>'\r\n html = html .. '<div><div>Planet Surface Average Altitude</div><div>'..ElevatorData.planetData.surfaceAverageAltitude..'m</div></div>'\r\n html = html .. '<div><div>Planet Surface Min Altitude</div><div>'..ElevatorData.planetData.surfaceMinAltitude..'m</div></div>'\r\n html = html .. '<div><div>Planet Surface Max Altitude</div><div>'..ElevatorData.planetData.surfaceMaxAltitude..'m</div></div>'\r\n html = html .. '<div><div>Planet Max Altitude For Satic Core</div><div>'..ElevatorData.planetData.maxStaticAltitude..'m</div></div>'\r\n html = html .. '</div>'\r\n end\r\n html = html .. '</div>'\r\n system.setScreen(html) --render the HUD\r\nend\r\n\r\n--[[\r\n Version Management\r\n]]\r\nlocal version = \"V 1.4.0\"\r\nlocal log_split = \"=================================================\"\r\n--printing version in lua chat\r\nsystem.print(log_split)local a=\"\"local b=math.ceil((50-#version-2)/2)for c=1,b,1 do a=a..'='end;a=a..\" \"..version..\" \"for c=1,b,1 do a=a..'='end;system.print(a)system.print(log_split)\r\n--[[\r\n Lua Serializer by Jericho\r\n Version: 1.0.0\r\n Source available at: https://github.com/Jericho1060/DualUniverse/blob/master/Serializer/Serializer.lua\r\n]]\r\nSerializer={__index={stringify=function(self,a)if type(a)=='number'then return self:stringifyNumber(a)elseif type(a)=='string'then return self:stringifyString(a)elseif type(a)=='table'then return self:stringifyTable(a)elseif type(a)=='boolean'then return self:stringifyBoolean(a)end;return nil end,parse=function(self,b)return load(\"return \"..b)()end,stringifyTableKey=function(self,c)if type(c)=='number'then return'['..self:stringifyNumber(c)..']'end;return c end,stringifyNumber=function(self,d)return tostring(d)end,stringifyString=function(self,b)return string.format('%q',b):gsub('\\\\\\n','\\n'):gsub('\\\\\\r','\\r'):gsub('\\\\\\t','\\t')end,stringifyTable=function(self,e)local b='{'local f=1;local g=self:tableLength(e)for h,i in pairs(e)do b=b..self:stringifyTableKey(h)..'='..self:stringify(i)if f<g then b=b..','end;f=f+1 end;return b..'}'end,stringifyBoolean=function(self,j)return tostring(j)end,tableLength=function(self,e)local k=0;for l in pairs(e)do k=k+1 end;return k end}}\r\nSerializer = setmetatable({}, Serializer)\r\n--[[\r\n Fuel Tanks Metatable By Jericho\r\n Version: 1.0.0\r\n Unminified Source available at: https://github.com/Jericho1060/DualUniverse/blob/master/ElementsMetatable/FuelTank.lua\r\n]]\r\nFuelTank={__index={name='',percentage=0,timeLeft=0,lastRefresh=0,fuelType='rocket',refresh=function(self)local a=system.getUtcTime()if lastRefresh~=a then local b=self.getClass():lower()if b:find('atmo')then self.fuelType=\"atmo\"elseif b:find('space')then self.fuelType=\"space\"end;local c=self.getWidgetData()local d=json.decode(c)self.name=d.name;self.percentage=d.percentage;self.timeLeft=tonumber(d.timeLeft)self.lastRefresh=a end end,getName=function(self)self:refresh()return self.name end,getPercentFilled=function(self)self:refresh()return self.percentage end,getSecondsLeft=function(self)self:refresh()return self.timeLeft end,getFuelType=function(self)self:refresh()return self.fuelType end,getTimeLeftString=function(self)local e=self:getSecondsLeft()if e==nil or e<=0 then return\"-\"end;days=string.format(\"%2.f\",math.floor(e/(3600*24)))hours=string.format(\"%2.f\",math.floor(e/3600-days*24))mins=string.format(\"%2.f\",math.floor(e/60-hours*60-days*24*60))secs=string.format(\"%2.f\",math.floor(e-hours*3600-days*24*60*60-mins*60))str=\"\"if tonumber(days)>0 then str=str..days..\"d \"end;if tonumber(hours)>0 then str=str..hours..\"h \"end;if tonumber(mins)>0 then str=str..mins..\"m \"end;if tonumber(secs)>0 then str=str..secs..\"s\"end;return str end,dump=function(self)self:refresh()system.print('{name=\"'..self.name..'\",percentage='..self.percentage..',timeLeft='..self.timeLeft..',fuelType=\"'..self.fuelType..'\"}')end}}\r\n\r\n--[[\r\n Detecting elements linked\r\n]]\r\ncore = nil\r\nfuelTanks = {}\r\ndatabank = nil\r\nfor slot_name, slot in pairs(unit) do\r\n if\r\n type(slot) == \"table\"\r\n and type(slot.export) == \"table\"\r\n and slot.getClass\r\n then\r\n local class = slot.getClass():lower()\r\n if class:find(\"coreunit\") then\r\n core = slot\r\n elseif class:find(\"fuelcontainer\") then\r\n fuelTanks[#fuelTanks + 1] = setmetatable(slot, FuelTank)\r\n elseif class:find(\"databank\") then\r\n databank = slot\r\n end\r\n end\r\nend\r\n\r\n--[[\r\n stopping the script if required elements are not linked\r\n]]\r\nif core == nil then\r\n system.print('Core unit is not linked, exiting the script')\r\n unit.exit()\r\nend\r\nif databank == nil then\r\n system.print('Databank is not linked')\r\nend\r\n\r\n--[[\r\n Atlas\r\n]]\r\natlas = require\"atlas\"\r\n\r\n--[[\r\n Default Unit Start Mechanics\r\n]]\r\npitchInput = 0\r\npitchInputFromDevice = 0\r\nrollInput = 0\r\nyawInput = 0\r\nverticalStrafeInput = 0\r\nlateralStrafeInput = 0\r\nbrakeInput = 1 --braking by default\r\ngoingBack = false\r\ngoingForward = false\r\nshiftPressed = false\r\njumpDelta = 0\r\nbaseAcceleration = 0.8\r\nBrakePressed = false\r\n\r\nNav = Navigator.new(system, core, unit)\r\nNav.axisCommandManager:setupCustomTargetSpeedRanges(axisCommandId.longitudinal, {100, 500, 1000, 2000,3000, 5000})\r\nNav.axisCommandManager:setTargetGroundAltitude(0)\r\nunit.deactivateGroundEngineAltitudeStabilization()\r\n\r\n-- Parenting widget\r\nif ShowParentingWidget then\r\n parentingPanelId = system.createWidgetPanel(\"Docking\")\r\n parentingWidgetId = system.createWidget(parentingPanelId,\"parenting\")\r\n system.addDataToWidget(unit.getWidgetDataId(),parentingWidgetId)\r\nend\r\nif not ShowControlUnitWidget then\r\n unit.hideWidget()\r\nend\r\n\r\n--[[\r\n Storing base position and orientation of the elevator\r\n]]\r\nfunction storeIniPosAndForward(force)\r\n if force == nil then force = false end\r\n ConstructInitPos = construct.getWorldPosition()\r\n --replace that pos by a hand written value to be sure the construct will always realign the same position\r\n --ConstructInitPos = {-1231461.6525868,1201334.4594833,-2617227.3718653}\r\n if __DEBUG then system.print('ConstructInitPos: ' .. json.encode(ConstructInitPos)) end\r\n BaseForward = construct.getWorldForward()\r\n --replace that pos by a hand written value to be sure the construct will always realign the same forward orientation\r\n --BaseForward = {-0.085554607212543,0.2445342447567,-0.9658600687807}\r\n if __DEBUG then system.print('BaseForward: ' .. json.encode(BaseForward)) end\r\n if databank ~= nil then\r\n if databank.hasKey('ConstructInitPos') and not force then\r\n ConstructInitPos = Serializer:parse(databank.getStringValue('ConstructInitPos'))\r\n system.print(\"ConstructInitPos loaded from databank\")\r\n else\r\n local toStore = Serializer:stringify(ConstructInitPos)\r\n databank.setStringValue('ConstructInitPos', toStore)\r\n system.print(\"ConstructInitPos stored in databank\")\r\n system.print(toStore)\r\n end\r\n if databank.hasKey('BaseForward') and not force then\r\n BaseForward = Serializer:parse(databank.getStringValue('BaseForward'))\r\n system.print(\"BaseForward loaded from databank\")\r\n else\r\n local toStore = Serializer:stringify(BaseForward)\r\n databank.setStringValue('BaseForward', toStore)\r\n system.print(\"BaseForward stored in databank\")\r\n system.print(toStore)\r\n end\r\n else\r\n system.print(\"No Databank detected\")\r\n end\r\n BaseForward = vec3(BaseForward)\r\nend\r\n\r\n--[[\r\n Storing Planet ID in Databank to be sure we always have the origin planet store even where too far from planet (1su or more)\r\n]]\r\nfunction storeReferencePlanet(force)\r\n if force == nil then force = false end\r\n local PlanetID = nil\r\n if databank.hasKey('PlanetID') and not force then\r\n PlanetID = databank.getIntValue('PlanetID')\r\n else\r\n PlanetID = core.getCurrentPlanetId()\r\n if PlanetID ~= nil then\r\n databank.setIntValue('PlanetID', PlanetID)\r\n system.print(\"Planet ID stored in databank: \" .. PlanetID)\r\n end\r\n end\r\n if PlanetID == nil then\r\n system.print(\"WARNING: no planet detected, you may be too far away in spance, please, restart the elevator from the planet surface\")\r\n else\r\n ElevatorData.planetData = atlas[0][PlanetID]\r\n end\r\n if __DEBUG then system.print('Planet ID: ' .. PlanetID) end\r\nend\r\n\r\nfunction getClosestBodyIdFromAtlas()\r\n local closestBody = nil\r\n local closestDistance = 0\r\n local constructPos = vec3(construct.getWorldPosition())\r\n for _, body in pairs(atlas[0]) do\r\n local distance = (vec3(body.center) - constructPos):len()\r\n if closestBody == nil or distance < closestDistance then\r\n closestBody = body.id\r\n closestDistance = distance\r\n end\r\n end\r\n return closestBody\r\nend\r\n\r\nfunction mergeBookmarksFromDatabank()\r\n if (databank ~= nil) and databank.hasKey('Bookmarks') then\r\n local str = databank.getStringValue('Bookmarks')\r\n local bookmarks_data = Serializer:parse(str)\r\n for _, bookmark in pairs(bookmarks_data) do\r\n local exists = false\r\n for _, b in pairs(Bookmarks) do\r\n if b.name == bookmark.name then\r\n exists = true\r\n break\r\n end\r\n end\r\n if not exists then\r\n Bookmarks[#Bookmarks+1] = bookmark\r\n end\r\n end\r\n databank.setStringValue('Bookmarks', Serializer:stringify(Bookmarks))\r\n end\r\nend\r\n\r\nmergeBookmarksFromDatabank()\r\n\r\n--[[\r\n Compute planet atmosphere altitude\r\n]]\r\nfunction computePlanetAtmoAltitude(planetData)\r\n return planetData.atmosphereRadius - planetData.radius\r\nend\r\n\r\n--[[\r\n --compute distance from a planet sea level\r\n]]\r\nfunction computePlanetSeaDistance(planetData, constructWorldPosition)\r\n return (vec3(planetData.center) - vec3(constructWorldPosition)):len() - planetData.radius\r\nend\r\n\r\nConstructInitPos = nil\r\nBaseForward = nil\r\nstoreIniPosAndForward()\r\nif ConstructInitPos == nil or BaseForward == nil then\r\n system.print(\"An Error occurs during Init. Try removing the databank, remove its dynamic properties and install it again ...\")\r\nend\r\nBaseAltitute = Bookmarks[1].altitude --getting the base altitude from the 1st bookmark\r\n--init a value to store the target altitude\r\nTargetAltitude = core.getAltitude() --by default to the start altitude to avoid falling down if in the air\r\n\r\nMaxSpeed = construct.getFrictionBurnSpeed() -- for security to avoid burning if going too fast\r\nMaxSpeedOnPlanet = MaxSpeed\r\nif __DEBUG then system.print('MaxSpeed: ' .. (MaxSpeed*3.6) .. 'km/h') end\r\n\r\nElevatorData = {\r\n isBreaking = brakeInput == 1,\r\n verticalSpeed = 0,\r\n verticalSpeedSigned = 0,\r\n lateralSpeed = 0,\r\n longitudinalSpeed = 0,\r\n coreAltitude = TargetAltitude,\r\n altitude = TargetAltitude,\r\n planetData = atlas[0][getClosestBodyIdFromAtlas()],\r\n direction = 'Stabilizing',\r\n atmosphereDistance = 0,\r\n atmosphereAltitude = 0,\r\n atmoMaxSpeed = MaxSpeedOnPlanet,\r\n currentMaxSpeed = MaxSpeed,\r\n}\r\n\r\n--computing the distance from the planet sea level as target altitude\r\nif ElevatorData.planetData ~= nil then\r\n TargetAltitude = computePlanetSeaDistance(ElevatorData.planetData, construct.getWorldPosition())\r\n ElevatorData.atmosphereAltitude = computePlanetAtmoAltitude(ElevatorData.planetData)\r\nend\r\n\r\n--base selected bookmark is 0 -> none selected as index 0 doesn't exist in lua\r\nselectedBookmarkIndex = 1\r\n\r\n--[[\r\n init the HUD\r\n]]\r\nsystem.showScreen(true)\r\n\r\n--[[\r\n Kinematics functions by Jaylebreak\r\n Source available at https://gitlab.com/JayleBreak/dualuniverse/-/blob/master/DUflightfiles/autoconf/custom/kinematics.lua\r\n]]\r\nfunction computeAccelerationTime(initial, acceleration, final) return (final - initial)/acceleration end\r\nfunction computeDistanceAndTime(initial, final, mass, thrust, t50, brakeThrust)\r\n t50 = t50 or 0\r\n brakeThrust = brakeThrust or 0\r\n local speedUp = initial < final\r\n local a0 = thrust / (speedUp and mass or -mass)\r\n local b0 = -brakeThrust/mass\r\n local totA = a0+b0\r\n if initial == final then\r\n return 0, 0\r\n elseif speedUp and totA <= 0 or not speedUp and totA >= 0 then\r\n return -1, -1\r\n end\r\n local distanceToMax, timeToMax = 0, 0\r\n if a0 ~= 0 and t50 > 0 then\r\n local c1 = math.pi/t50/2\r\n local v = function(t)\r\n return a0*(t/2 - t50*math.sin(c1*t)/math.pi) + b0*t + initial\r\n end\r\n local speedchk = speedUp and function(s) return s >= final end or function(s) return s <= final end\r\n timeToMax = 2*t50\r\n if speedchk(v(timeToMax)) then\r\n local lasttime = 0\r\n while math.abs(timeToMax - lasttime) > 0.25 do\r\n local t = (timeToMax + lasttime)/2\r\n if speedchk(v(t)) then\r\n timeToMax = t\r\n else\r\n lasttime = t\r\n end\r\n end\r\n end\r\n local K = 2*a0*t50^2/math.pi^2\r\n distanceToMax = K*(math.cos(c1*timeToMax) - 1) + (a0+2*b0)*timeToMax^2/4 + initial*timeToMax\r\n if timeToMax < 2*t50 then\r\n return distanceToMax, timeToMax\r\n end\r\n initial = v(timeToMax)\r\n end\r\n local a = a0+b0\r\n local t = computeAccelerationTime(initial, a, final)\r\n local d = initial*t + a*t*t/2\r\n return distanceToMax+d, timeToMax+t\r\nend\r\n\r\n--[[\r\n Compute angle between vectors by Jericho\r\n]]\r\nfunction signedAngleBetween(vec1, vec2, planeNormal)\r\n local normVec1 = vec1:project_on_plane(planeNormal):normalize()\r\n local normVec2 = vec2:normalize()\r\n local v1v2dot = normVec1:dot(normVec2)\r\n local angle = math.acos(utils.clamp(v1v2dot,-1,1))\r\n local crossProduct = vec1:cross(vec2)\r\n if crossProduct:dot(planeNormal) < 0 then\r\n return -angle\r\n end\r\n return angle\r\nend\r\n--[[\r\n\tformatting numbers by adding a space between thousands by Jericho\r\n]]\r\nfunction format_number(a)local b=a;while true do b,k=string.gsub(b,\"^(-?%d+)(%d%d%d)\",'%1 %2')if k==0 then break end end;local c=string.sub(b,-2)if c=='.0'then b=string.sub(b,1,b:len()-2)end;return b end\r\n--[[\r\n DU-LUA-Framework by Jericho\r\n Permit to code easier by grouping most of the code in a single event Unit > Start\r\n Unminified Source available here: https://github.com/Jericho1060/du-lua-framework\r\n]]--\r\nlocal a=system.print;local b=error;local c=pcall;local d=assert;local e=coroutine;local f=e.create;local g=e.status;local h=e.resume;local i=\"dead\"local j=\"suspended\"function runFunction(k,l,...)local m,n=c(k,...)if not m then b(l..n)end end;local o={__index={cos={update={},flush={}},fns={update={},flush={},action={start={},stop={},loop={}},inputText=nil,start=nil,stop=nil},ACTIONS={FORWARD=\"forward\",BACKWARD=\"backward\",YAW_LEFT=\"yawleft\",YAW_RIGHT=\"yawright\",STRAFE_LEFT=\"strafeleft\",STRAFE_RIGHT=\"straferight\",LEFT=\"left\",RIGHT=\"right\",UP=\"up\",DOWN=\"down\",GROUND_ALTITUDE_UP=\"groundaltitudeup\",GROUND_ALTITUDE_DOWN=\"groundaltitudedown\",LEFT_ALT=\"lalt\",LEFT_SHIFT=\"lshift\",GEAR=\"gear\",LIGHT=\"light\",BRAKE=\"brake\",OPTION_1=\"option1\",OPTION_2=\"option2\",OPTION_3=\"option3\",OPTION_4=\"option4\",OPTION_5=\"option5\",OPTION_6=\"option6\",OPTION_7=\"option7\",OPTION_8=\"option8\",OPTION_9=\"option9\",OPTION_10=\"option10\",OPTION_11=\"option11\",OPTION_12=\"option12\",OPTION_13=\"option13\",OPTION_14=\"option14\",OPTION_15=\"option15\",OPTION_16=\"option16\",OPTION_17=\"option17\",OPTION_18=\"option18\",OPTION_19=\"option19\",OPTION_20=\"option20\",OPTION_21=\"option21\",OPTION_22=\"option22\",OPTION_23=\"option23\",OPTION_24=\"option24\",OPTION_25=\"option25\",OPTION_26=\"option26\",OPTION_27=\"option27\",OPTION_28=\"option28\",OPTION_29=\"option29\",LEFT_MOUSE=\"leftmouse\",STOP_ENGINES=\"stopengines\",SPEED_UP=\"speedup\",SPEED_DOWN=\"speeddown\",ANTIGRAVITY=\"antigravity\",BOOSTER=\"booster\"},main={update=f(function()end),flush=f(function()end)},update=function(self)local p=g(self.main.update)if p==i then self.main.update=f(function()self:runUpdate()end)elseif p==j then d(h(self.main.update))end end,flush=function(self)local p=g(self.main.flush)if p==i then self.main.flush=f(function()self:runFlush()end)elseif p==j then d(h(self.main.flush))end end,action=function(self,q,r)if self.fns.action[q][r]then runFunction(self.fns.action[q][r],\"System Action \"..q..\" Error: \")end end,actionStart=function(self,r)self:action('start',r)end,actionStop=function(self,r)self:action('stop',r)end,actionLoop=function(self,r)self:action('loop',r)end,inputText=function(self,s)if self.fns.inputText then runFunction(self.fns.inputText,\"System Input Text Error: \",s)end end,runUpdate=function(self)for t,u in pairs(self.cos.update)do local m=g(u)if m==i then self.cos.update[t]=f(self.fns.update[t])elseif m==j then d(h(u))end end end,runFlush=function(self)for t,u in pairs(self.cos.flush)do local m=g(u)if m==i then self.cos.flush[t]=f(self.fns.flush[t])elseif m==j then d(h(u))end end end,onUpdate=function(self,v)for t,w in pairs(v)do self.fns.update[t]=w;self.cos.update[t]=f(w)end end,onFlush=function(self,v)for t,w in pairs(v)do self.fns.flush[t]=w;self.cos.flush[t]=f(w)end end,onAction=function(self,q,v)for t,w in pairs(v)do self.fns.action[q][t]=w end end,onActionStart=function(self,v)self:onAction(\"start\",v)end,onActionStop=function(self,v)self:onAction(\"stop\",v)end,onActionLoop=function(self,v)self:onAction(\"loop\",v)end,onInputText=function(self,k)self.fns.inputText=k end}}local x={__index={timers={},stopFn=function()end,timer=function(self,y)if self.timers[y]then runFunction(self.timers[y],\"Unit Timer \"..y..\" Error: \")end end,setTimer=function(self,y,z,k)self.timers[y]=k;unit.setTimer(y,z)end,stopTimer=function(self,y)unit.stopTimer(y)self.timers[y]=nil end,onStop=function(self,k)self.stopFn=k end,stop=function(self)if self.stopFn then runFunction(self.stopFn,\"Unit Stop Error: \")end end}}local A={__index={parentChangedFn=function(B,C)end,onParentChange=function(self,k)self.parentChangedFn=k end,parentChanged=function(self,B,C)if self.parentChangedFn then runFunction(self.parentChangedFn,\"Player Parent Changed Error: \",B,C)end end}}local D={__index={dockedFn=function(E)end,onDocked=function(self,k)self.dockedFn=k end,docked=function(self,E)if self.dockedFn then runFunction(self.dockedFn,\"Construct Docked Error: \",E)end end,undockedFn=function(E)end,onUndocked=function(self,k)self.undockedFn=k end,undocked=function(self,E)if self.undockedFn then runFunction(self.undockedFn,\"Construct Undocked Error: \",E)end end,playerBoardedFn=function(E)end,onPlayerBoarded=function(self,k)self.playerBoardedFn=k end,playerBoarded=function(self,E)if self.playerBoardedFn then runFunction(self.playerBoardedFn,\"Construct Player Boarded Error: \",E)end end,VRStationEnteredFn=function(E)end,onVRStationEntered=function(self,k)self.VRStationEnteredFn=k end,VRStationEntered=function(self,E)if self.VRStationEnteredFn then runFunction(self.VRStationEnteredFn,\"Construct VR Station Entered Error: \",E)end end,constructDockedFn=function(E)end,onConstructDocked=function(self,k)self.constructDockedFn=k end,constructDocked=function(self,E)if self.constructDockedFn then runFunction(self.constructDockedFn,\"Construct Construct Docked Error: \",E)end end,PvPTimerFn=function(F)end,onPvPTimer=function(self,k)self.PvPTimerFn=k end,PvPTimer=function(self,F)if self.PvPTimerFn then runFunction(self.PvPTimerFn,\"Construct PvP Timer Error: \",F)end end}}DU_Framework={__index={system=setmetatable({},o),unit=setmetatable({},x),player=setmetatable({},A),construct=setmetatable({},D)}}\r\n\r\nScript = {}\r\nsetmetatable(Script, DU_Framework)\r\n\r\n--[[\r\n String Helpers For Lua By Jericho\r\n]]\r\nString = {\r\n __index = {\r\n split = function(self, delimiter)\r\n local result = {}\r\n for match in (self..delimiter):gmatch(\"(.-)\"..delimiter) do\r\n table.insert(result, match)\r\n end\r\n return result\r\n end\r\n }\r\n}\r\nstring = setmetatable(string, String)\r\nstring.__index = string\r\n\r\nlocal systemOnFlush = {\r\n function()\r\n local yawSpeedFactor = 0.1 --export: the auto yaw speed multiplier\r\n local yawAccelerationFactor = 3\r\n local lateralAntiDriftFactor = 1\r\n local lateralStrafeFactor = 5\r\n local brakeSpeedFactor = 1\r\n local brakeFlatFactor = 4\r\n local autoBrakeSpeed = 15\r\n -- validate params\r\n brakeSpeedFactor = math.max(brakeSpeedFactor, 0.01)\r\n brakeFlatFactor = math.max(brakeFlatFactor, 0.01)\r\n yawSpeedFactor = math.max(yawSpeedFactor, 0.01)\r\n yawAccelerationFactor = math.max(yawAccelerationFactor, 0.01)\r\n --init all the PIDs as global if first flush\r\n if (rollPID == nil) then rollPID = pid.new(0.2, 0, 10) end\r\n if (pitchPID == nil) then pitchPID = pid.new(0.2, 0, 10) end\r\n if (yawPID == nil) then yawPID = pid.new(0.2, 0, 10) end\r\n if (lateralPID == nil) then lateralPID = pid.new(0.2, 0, 10) end\r\n if (longitudinalPID == nil) then longitudinalPID = pid.new(0.2, 0, 10) end\r\n if (distancePID == nil) then distancePID = pid.new(0.2, 0, 10) end\r\n\r\n -- final inputs\r\n if unit.isMouseDirectControlActivated() then\r\n -- in direct control, we tweak the pitch to behave inbetween virtual joystick and direct control\r\n -- this helps a lot for ground construct control\r\n pitchInputFromDevice = utils.clamp(pitchInputFromDevice + system.getControlDeviceForwardInput() * system.getActionUpdateDeltaTime(), -1.0, 1.0)\r\n else\r\n pitchInputFromDevice = system.getControlDeviceForwardInput()\r\n end\r\n local finalPitchInput = pitchInput + pitchInputFromDevice\r\n local finalRollInput = rollInput + system.getControlDeviceYawInput()\r\n local finalYawInput = yawInput - system.getControlDeviceLeftRightInput()\r\n local combinedRollYawInput = utils.clamp(finalRollInput - finalYawInput, -1.0, 1.0);\r\n local finalVerticalStrafeInput = verticalStrafeInput\r\n local finalLateralStrafeInput = lateralStrafeInput;\r\n local finalBrakeInput = brakeInput\r\n\r\n -- Axis\r\n local worldVertical = vec3(core.getWorldVertical())\r\n local worldRight = vec3(core.getWorldRight())\r\n local worldForward = vec3(core.getWorldForward())\r\n local constructUp = vec3(construct.getWorldOrientationUp())\r\n local constructForward = vec3(construct.getWorldOrientationForward())\r\n local constructRight = vec3(construct.getWorldOrientationRight())\r\n local constructVelocity = vec3(construct.getWorldVelocity())\r\n local constructVelocityDir = vec3(construct.getWorldVelocity()):normalize()\r\n local constructAngularVelocity = vec3(construct.getWorldAngularVelocity())\r\n local constructYawVelocity = constructAngularVelocity:dot(constructUp)\r\n local constructWorldPosition = vec3(construct.getWorldPosition())\r\n local constructTargetPosition = vec3(ConstructInitPos)\r\n\r\n -- Engine commands\r\n local keepCollinearity = 0 -- for easier reading\r\n local dontKeepCollinearity = 1 -- for easier reading\r\n local tolerancePercentToSkipOtherPriorities = 1 -- if we are within this tolerance (in%), we don't go to the next priorities\r\n\r\n --fake braking value\r\n local canBrake = false\r\n\r\n -- keeping the start position alignement\r\n local StrafeSpeedFactor = 1 --export: useg to increase the force of the alignement, decrease the value if the alignement is too strong or increase it if it's too slow\r\n local positionDifference = constructTargetPosition - constructWorldPosition\r\n local lateralOffset = positionDifference:project_on(constructRight):len() * utils.sign(positionDifference:dot(constructRight))\r\n ElevatorData.lateralSpeed = constructVelocity:project_on(worldRight):len()\r\n local lateralDistance = math.abs(lateralOffset)\r\n local longitudinalOffset = positionDifference:project_on(constructForward):len() * utils.sign(positionDifference:dot(constructForward))\r\n ElevatorData.longitudinalSpeed = constructVelocity:project_on(worldForward):len()\r\n local longitudinalDistance = math.abs(longitudinalOffset)\r\n if (lateralDistance < .25) and (longitudinalDistance < .25) then canBrake = true end\r\n if ((ElevatorData.lateralSpeed*3.6) > lateralDistance) or ((ElevatorData.longitudinalSpeed*3.6) > longitudinalDistance) then canBrake = true end\r\n lateralPID:inject(lateralOffset)\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.lateral, lateralPID:get() * StrafeSpeedFactor)\r\n longitudinalPID:inject(longitudinalOffset)\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.longitudinal, longitudinalPID:get() * StrafeSpeedFactor)\r\n\r\n -- Rotation\r\n local currentRollDeg = getRoll(worldVertical, constructForward, constructRight)\r\n local currentPitchDeg = -math.asin(constructForward:dot(worldVertical)) * constants.rad2deg\r\n local targetRollDeg = 0\r\n local targetPitchDeg = 0\r\n local targetYawDeg = signedAngleBetween(BaseForward,constructForward,constructUp)*180/math.pi\r\n rollPID:inject(targetRollDeg - currentRollDeg)\r\n pitchPID:inject(targetPitchDeg - currentPitchDeg)\r\n yawPID:inject(-targetYawDeg*yawSpeedFactor)\r\n\r\n local constructTargetAngularVelocity = rollPID:get() * constructForward + pitchPID:get() * constructRight + yawPID:get() * constructUp\r\n\r\n Nav:setEngineTorqueCommand('torque', constructTargetAngularVelocity, keepCollinearity, 'airfoil', '', '', tolerancePercentToSkipOtherPriorities)\r\n\r\n -- moving up or down from TargetAltitude\r\n ElevatorData.verticalSpeed = constructVelocity:project_on(worldVertical):len()\r\n ElevatorData.verticalSpeedSigned = ElevatorData.verticalSpeed * -utils.sign(constructVelocity:dot(worldVertical))\r\n local brakeDistance = 0\r\n local brakeDistanceToAtmosphere = 0\r\n local maxBrake = construct.getMaxBrake()\r\n ElevatorData.coreAltitude = core.getAltitude()\r\n ElevatorData.atmosphereAltitude = computePlanetAtmoAltitude(ElevatorData.planetData)\r\n ElevatorData.altitude = computePlanetSeaDistance(ElevatorData.planetData, constructWorldPosition)\r\n ElevatorData.atmosphereDistance = ElevatorData.altitude - ElevatorData.atmosphereAltitude\r\n local isInAtmosphere = ElevatorData.atmosphereDistance <= 0\r\n local targetIsInAtmosphere = TargetAltitude <= ElevatorData.atmosphereAltitude\r\n if maxBrake ~= nil then\r\n local g = core.getGravityIntensity()\r\n if ElevatorData.verticalSpeedSigned < 0 then\r\n g = ElevatorData.planetData.gravity --when falling, apply the max gravity to the braking computing to increase braking\r\n end\r\n local inertialMass = construct.getInertialMass()\r\n local speedSign = utils.sign(ElevatorData.verticalSpeedSigned)\r\n brakeDistanceToAtmosphere, _ = computeDistanceAndTime(ElevatorData.verticalSpeed, ElevatorData.atmoMaxSpeed, inertialMass, 0, 0, maxBrake - (g * inertialMass * speedSign))\r\n brakeDistance, _ = computeDistanceAndTime(ElevatorData.verticalSpeed, 0, inertialMass, 0, 0, maxBrake - (g * inertialMass * speedSign))\r\n end\r\n if isInAtmosphere or (targetIsInAtmosphere and (brakeDistanceToAtmosphere > ElevatorData.atmosphereDistance)) then\r\n ElevatorData.currentMaxSpeed = ElevatorData.atmoMaxSpeed\r\n else\r\n ElevatorData.currentMaxSpeed = 50000/3.6 -- 50km/h in m/s = 13888.88888888889....\r\n end\r\n local distance = TargetAltitude - ElevatorData.altitude\r\n local targetDistance = utils.sign(distance) * (math.abs(distance)-brakeDistance)\r\n distancePID:inject(targetDistance)\r\n if distancePID:get() > 0.25 or ElevatorData.verticalSpeedSigned < -ElevatorData.currentMaxSpeed then --using a 0.25 meter deadband for stabilizing\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.vertical, 1)\r\n ElevatorData.direction = 'up'\r\n if ElevatorData.verticalSpeedSigned < 0 then\r\n finalBrakeInput = 1\r\n end\r\n elseif distancePID:get() < -0.25 or ElevatorData.verticalSpeedSigned > ElevatorData.currentMaxSpeed then\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.vertical, -1)\r\n ElevatorData.direction = 'down'\r\n if ElevatorData.verticalSpeedSigned > 0 then\r\n finalBrakeInput = 1\r\n end\r\n else\r\n Nav.axisCommandManager:resetCommand(axisCommandId.vertical)\r\n ElevatorData.direction = 'stabilizing'\r\n end\r\n\r\n --Brakes\r\n if (math.abs(distance) < (ElevatorData.verticalSpeed*3.6) and canBrake) --speed control\r\n or (ElevatorData.verticalSpeed >= ElevatorData.currentMaxSpeed) --anti burn speed\r\n or ((math.abs(distancePID:get()) < .25) and canBrake) --altitude reached with 0.25m deadband\r\n or ((brakeDistance > math.abs(distance)) and canBrake) --braking distance\r\n or (finalBrakeInput == 0 and autoBrakeSpeed > 0 and Nav.axisCommandManager.throttle == 0 and constructVelocity:len() < autoBrakeSpeed)\r\n then\r\n finalBrakeInput = 1\r\n --use engines to help braking\r\n if ElevatorData.verticalSpeedSigned < 0 and isInAtmosphere then\r\n Nav.axisCommandManager:setThrottleCommand(axisCommandId.vertical, 1)\r\n end\r\n end\r\n ElevatorData.isBreaking = (finalBrakeInput == 1)\r\n local brakeAcceleration = -finalBrakeInput * (brakeSpeedFactor * constructVelocity + brakeFlatFactor * constructVelocityDir)\r\n\r\n Nav:setEngineForceCommand('brake', brakeAcceleration)\r\n\r\n -- AutoNavigation regroups all the axis command by 'TargetSpeed'\r\n local autoNavigationEngineTags = ''\r\n local autoNavigationAcceleration = vec3()\r\n local autoNavigationUseBrake = false\r\n\r\n -- Longitudinal Translation\r\n local longitudinalEngineTags = 'thrust analog longitudinal'\r\n local longitudinalCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.longitudinal)\r\n if (longitudinalCommandType == axisCommandType.byThrottle) then\r\n local longitudinalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(longitudinalEngineTags,axisCommandId.longitudinal)\r\n Nav:setEngineForceCommand(longitudinalEngineTags, longitudinalAcceleration, keepCollinearity)\r\n elseif (longitudinalCommandType == axisCommandType.byTargetSpeed) then\r\n local longitudinalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.longitudinal)\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. longitudinalEngineTags\r\n autoNavigationAcceleration = autoNavigationAcceleration + longitudinalAcceleration\r\n if (Nav.axisCommandManager:getTargetSpeed(axisCommandId.longitudinal) == 0 or -- we want to stop\r\n Nav.axisCommandManager:getCurrentToTargetDeltaSpeed(axisCommandId.longitudinal) < - Nav.axisCommandManager:getTargetSpeedCurrentStep(axisCommandId.longitudinal) * 0.5) -- if the longitudinal velocity would need some braking\r\n then\r\n autoNavigationUseBrake = true\r\n end\r\n end\r\n\r\n -- Lateral Translation\r\n local lateralStrafeEngineTags = 'thrust analog lateral'\r\n local lateralCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.lateral)\r\n if (lateralCommandType == axisCommandType.byThrottle) then\r\n local lateralStrafeAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(lateralStrafeEngineTags,axisCommandId.lateral)\r\n Nav:setEngineForceCommand(lateralStrafeEngineTags, lateralStrafeAcceleration, keepCollinearity)\r\n elseif (lateralCommandType == axisCommandType.byTargetSpeed) then\r\n local lateralAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.lateral)\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. lateralStrafeEngineTags\r\n autoNavigationAcceleration = autoNavigationAcceleration + lateralAcceleration\r\n end\r\n\r\n -- Vertical Translation\r\n local verticalStrafeEngineTags = 'thrust analog vertical'\r\n local verticalCommandType = Nav.axisCommandManager:getAxisCommandType(axisCommandId.vertical)\r\n if (verticalCommandType == axisCommandType.byThrottle) then\r\n local verticalStrafeAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromThrottle(verticalStrafeEngineTags,axisCommandId.vertical)\r\n Nav:setEngineForceCommand(verticalStrafeEngineTags, verticalStrafeAcceleration, keepCollinearity, 'airfoil', 'ground', '', tolerancePercentToSkipOtherPriorities)\r\n elseif (verticalCommandType == axisCommandType.byTargetSpeed) then\r\n local verticalAcceleration = Nav.axisCommandManager:composeAxisAccelerationFromTargetSpeed(axisCommandId.vertical)\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ' , ' .. verticalStrafeEngineTags\r\n autoNavigationAcceleration = autoNavigationAcceleration + verticalAcceleration\r\n end\r\n\r\n -- Auto Navigation (Cruise Control)\r\n if (autoNavigationAcceleration:len() > constants.epsilon) then\r\n if (brakeInput ~= 0 or autoNavigationUseBrake or math.abs(constructVelocityDir:dot(constructForward)) < 0.95) -- if the velocity is not properly aligned with the forward\r\n then\r\n autoNavigationEngineTags = autoNavigationEngineTags .. ', brake'\r\n end\r\n Nav:setEngineForceCommand(autoNavigationEngineTags, autoNavigationAcceleration, dontKeepCollinearity, '', '', '', tolerancePercentToSkipOtherPriorities)\r\n end\r\n\r\n -- Rockets\r\n Nav:setBoosterCommand('rocket_engine')\r\n end\r\n}\r\n\r\nlocal systemOnUpdate = {\r\n function ()\r\n Nav:update()\r\n RENDER_HUD(ElevatorData)\r\n end\r\n}\r\n\r\nScript.system:onUpdate(systemOnUpdate)\r\nScript.system:onFlush(systemOnFlush)\r\n\r\n--[[\r\n Actions\r\n]]\r\nlocal systemActionsStart = {}\r\nlocal systemActionsLoop = {}\r\nlocal systemActionsStop = {}\r\n\r\nsystemActionsStart[Script.system.ACTIONS.DOWN] = function ()\r\n if BrakePressed then\r\n if selectedBookmarkIndex < #Bookmarks then\r\n local temp = Bookmarks[selectedBookmarkIndex]\r\n Bookmarks[selectedBookmarkIndex] = Bookmarks[selectedBookmarkIndex + 1]\r\n selectedBookmarkIndex = selectedBookmarkIndex + 1\r\n Bookmarks[selectedBookmarkIndex] = temp\r\n else\r\n selectedBookmarkIndex = 1\r\n end\r\n else\r\n if selectedBookmarkIndex < #Bookmarks then\r\n selectedBookmarkIndex = selectedBookmarkIndex + 1\r\n else\r\n selectedBookmarkIndex = 1\r\n end\r\n if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end\r\n end\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.UP] = function ()\r\n if BrakePressed then\r\n if selectedBookmarkIndex > 1 then\r\n local temp = Bookmarks[selectedBookmarkIndex]\r\n Bookmarks[selectedBookmarkIndex] = Bookmarks[selectedBookmarkIndex - 1]\r\n selectedBookmarkIndex = selectedBookmarkIndex - 1\r\n Bookmarks[selectedBookmarkIndex] = temp\r\n else\r\n selectedBookmarkIndex = #Bookmarks\r\n end\r\n else\r\n if selectedBookmarkIndex > 1 then\r\n selectedBookmarkIndex = selectedBookmarkIndex - 1\r\n else\r\n selectedBookmarkIndex = #Bookmarks\r\n end\r\n end\r\n if __DEBUG then system.print('Selected Bookmark Index: ' .. selectedBookmarkIndex) end\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.STRAFE_RIGHT] = function ()\r\n brakeInput = 0\r\n if selectedBookmarkIndex > 0 then\r\n TargetAltitude = Bookmarks[selectedBookmarkIndex].altitude\r\n if __DEBUG then system.print('Target Altitude: ' .. TargetAltitude) end\r\n else\r\n system.print('No bookmark selected')\r\n end\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.OPTION_1] = function()\r\n storeIniPosAndForward(true)\r\nend\r\nsystemActionsStart[Script.system.ACTIONS.BRAKE] = function()\r\n BrakePressed = true\r\nend\r\nsystemActionsStop[Script.system.ACTIONS.BRAKE] = function()\r\n BrakePressed = false\r\nend\r\nScript.system:onActionStart(systemActionsStart) --loading all \"actionStart\" functions\r\nScript.system:onActionLoop(systemActionsLoop) --loading all \"actionLoop\" functions\r\nScript.system:onActionStop(systemActionsStop) --loading all \"actionStop\" functions\r\n\r\n--[[\r\n Chat Commands\r\n]]\r\n\r\nScript.system:onInputText(function (text)\r\n if text:lower():find('goto:') then\r\n TargetAltitude = tonumber(text:split(':')[2]) or BaseAltitute\r\n brakeInput = 0\r\n system.print('Target Altitude set to ' .. TargetAltitude .. 'm')\r\n elseif text:lower():find('save:') then\r\n if databank == nil then\r\n system.print('Please, install a databank to save bookmarks')\r\n return\r\n end\r\n if databank.hasKey('Bookmarks') then\r\n local str = databank.getStringValue('Bookmarks')\r\n local bookmarks_data = Serializer:parse(str)\r\n\r\n end\r\n local name = text:split(':')[2]\r\n for _, bookmark in pairs(Bookmarks) do\r\n if bookmark.name == name then\r\n system.print('Bookmark \"' .. name .. '\"\" already exists')\r\n return\r\n end\r\n end\r\n if name ~= nil then\r\n local altitude = ElevatorData.altitude\r\n local bookmark = {\r\n name = name,\r\n altitude = altitude\r\n }\r\n Bookmarks[#Bookmarks+1] = bookmark\r\n databank.setStringValue('Bookmarks', Serializer:stringify(Bookmarks))\r\n system.print('Bookmark ' .. name .. ' saved at ' .. altitude .. 'm')\r\n else\r\n system.print('Please, provide a name for the bookmark')\r\n end\r\n elseif text:lower() == 'delete' then\r\n if databank == nil then\r\n system.print('Please, install a databank to manage bookmarks with commands')\r\n return\r\n end\r\n if Bookmarks[selectedBookmarkIndex] then\r\n --delete selected bookmark\r\n local name = Bookmarks[selectedBookmarkIndex].name\r\n table.remove(Bookmarks, selectedBookmarkIndex)\r\n databank.setStringValue('Bookmarks', Serializer:stringify(Bookmarks))\r\n end\r\n else\r\n system.print('Unknown command')\r\n end\r\nend)\r\n","filter":{"args":[],"signature":"onStart()","slotKey":"-1"},"key":"2"}],"methods":[],"slots":{"0":{"name":"slot1","type":{"events":[],"methods":[]}},"1":{"name":"slot2","type":{"events":[],"methods":[]}},"2":{"name":"slot3","type":{"events":[],"methods":[]}},"3":{"name":"slot4","type":{"events":[],"methods":[]}},"4":{"name":"slot5","type":{"events":[],"methods":[]}},"5":{"name":"slot6","type":{"events":[],"methods":[]}},"6":{"name":"slot7","type":{"events":[],"methods":[]}},"7":{"name":"slot8","type":{"events":[],"methods":[]}},"8":{"name":"slot9","type":{"events":[],"methods":[]}},"9":{"name":"slot10","type":{"events":[],"methods":[]}},"-5":{"name":"library","type":{"events":[],"methods":[]}},"-4":{"name":"system","type":{"events":[],"methods":[]}},"-3":{"name":"player","type":{"events":[],"methods":[]}},"-2":{"name":"construct","type":{"events":[],"methods":[]}},"-1":{"name":"unit","type":{"events":[],"methods":[]}}}}