Advertisement
Hbomb_79

Missile Controller

May 27th, 2017
182
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 35.29 KB | None | 0 0
  1. --[[
  2.     Built using Titanium Packager (Harry Felton - hbomb79)
  3. ]]
  4.  
  5. local exportDirectory = select( 1, ... ) or ""
  6. local classSource = {
  7.   [ "MAuthentication.ti" ] = "class \"MAuthentication\" abstract() {\
  8.    static = {\
  9.        PERMITTED_DRIVES = { [\"drive_1\"] = true, [\"drive_0\"] = true },\
  10.        PERMITTED_KEYS = { [\"harry\"] = true, [\"rugby\"] = true },\
  11.        DISK_TIMEOUT = 5,\
  12.        BLINK_SIDE = \"top\",\
  13.        LAUNCH_CODE_TIMEOUT = 15,\
  14.        ALERT_SIDE = \"right\",\
  15.        LAUNCH_CODE = \"test\"\
  16.    };\
  17. \
  18.    lastKey = false;\
  19. }\
  20. \
  21. function MAuthentication:MAuthentication()\
  22.    self:on(\"disk\", function( _, event )\
  23.        if MAuthentication.PERMITTED_DRIVES[ event.data[ 2 ] ] then\
  24.            local diskMount = disk.getMountPath( event.data[ 2 ] )\
  25.            if not fs.exists( fs.combine( diskMount, \".key.cfg\" ) ) then\
  26.                self.lastKey = false\
  27.            else\
  28.                local h = fs.open( fs.combine( diskMount, \".key.cfg\" ), \"r\" )\
  29.                local c = h.readAll()\
  30.                h.close()\
  31. \
  32.                if not MAuthentication.PERMITTED_KEYS[ c ] then\
  33.                    self.lastKey = false\
  34.                elseif self.lastKey then\
  35.                    if not self.lastKey[ 3 ][ c ] and os.clock() - self.lastKey[ 1 ] < MAuthentication.DISK_TIMEOUT and event.data[ 2 ] ~= self.lastKey[ 2 ] then\
  36.                        self:prime()\
  37.                    end\
  38. \
  39.                    self.lastKey = false\
  40.                else\
  41.                    self.lastKey = { os.clock(), event.data[ 2 ], { [ c ] = true } }\
  42.                end\
  43.            end\
  44. \
  45.            disk.eject( event.data[ 2 ] )\
  46.        end\
  47.    end)\
  48. end\
  49. \
  50. function MAuthentication:beginNotificationBlink()\
  51.    local t = false\
  52.    self:schedule( function()\
  53.        t = not t\
  54.        redstone.setOutput( MAuthentication.BLINK_SIDE, t )\
  55.    end, 0.2, true, \"notif_blink\" )\
  56. \
  57.    self:schedule( function()\
  58.        self:stopNotificationBlink()\
  59.        self:unprime()\
  60.    end, MAuthentication.LAUNCH_CODE_TIMEOUT, false, \"notif_cancel\" )\
  61. end\
  62. \
  63. function MAuthentication:stopNotificationBlink()\
  64.    self:unschedule( \"notif_blink\" )\
  65.    redstone.setOutput( MAuthentication.BLINK_SIDE, false )\
  66. \
  67.    self:unschedule \"alert_blink\"\
  68.    redstone.setOutput( MAuthentication.ALERT_SIDE, false )\
  69. end\
  70. \
  71. function MAuthentication:unprime( noSwitch )\
  72.    if not noSwitch then self:error(\"Launch Aborted\", \"Launch key was not entered quickly enough. The launch has been aborted to help protect the facility\") end\
  73. \
  74.    self:stopNotificationBlink()\
  75.    self:query \"Input#launch_code\":set {\
  76.        value = \"\",\
  77.        position = 0\
  78.    }\
  79. \
  80.    self.primed = false\
  81. end\
  82. \
  83. function MAuthentication:prime()\
  84.    self:beginNotificationBlink()\
  85.    self.cache.pages:selectPage \"primed\"\
  86. \
  87.    self.primed = true\
  88. end\
  89. \
  90. function MAuthentication:commenceLaunchSequence()\
  91.    if not self.primed then return end\
  92. \
  93.    self.cache.pages:selectPage \"pinging\"\
  94.    self:unschedule \"notif_cancel\"\
  95. \
  96.    self:pingMissiles()\
  97.    self:on(\"ping_complete\", function( _, success )\
  98.        self:off( \"ping_complete\", \"launch_ping_check\" )\
  99.        if not success then\
  100.            self:abortLaunch( true )\
  101.            self:error(\"Launch Aborted\", \"Silos not ready for launch, or offline. Check overhead display before attempting to commence launch.\")\
  102.        else\
  103.            self:syncPosition()\
  104.            self:on(\"sync_complete\", function( _, success )\
  105.                if success then\
  106.                    self.cache.pages:selectPage \"launch\"\
  107. \
  108.                    local t = false\
  109.                    self:schedule( function()\
  110.                        t = not t\
  111.                        redstone.setOutput( MAuthentication.ALERT_SIDE, t )\
  112.                    end, 0.4, true, \"alert_blink\" )\
  113. \
  114.                    local countdown = self:query \"Label#countdown_T\"\
  115.                    local timeRemaining = MissileController.LAUNCH_DELAY\
  116. \
  117.                    countdown:set( \"text\", \"T-\" .. timeRemaining .. \" seconds to launch\" )\
  118.                    self:schedule( function()\
  119.                        timeRemaining = timeRemaining - 1\
  120.                        countdown:set( \"text\", \"T-\" .. timeRemaining .. \" seconds to launch\" )\
  121.                        countdown:setClass(\"RED\", not countdown:hasClass \"RED\" )\
  122. \
  123.                        if timeRemaining == 0 then\
  124.                            self:launch()\
  125.                            self:unschedule \"launch_countdown\"\
  126.                        end\
  127.                    end, 1, true, \"launch_countdown\" )\
  128.                else\
  129.                    self:abortLaunch( true )\
  130.                end\
  131.            end )\
  132.        end\
  133.    end, \"launch_ping_check\")\
  134. end\
  135. \
  136. function MAuthentication:abortLaunch( noMan )\
  137.    self:unprime( true )\
  138.    self:stopNotificationBlink()\
  139. \
  140.    self:unschedule \"launch_countdown\"\
  141. \
  142.    if not noMan then\
  143.        self:error(\"Launch Aborted\", \"Launch was manually aborted\")\
  144.    end\
  145. end\
  146. ",
  147.   [ "MissileController.ti" ] = "local function populateKeyPair( list )\
  148.    local new = {}\
  149.    for i = 1, #list do\
  150.        new[ list[ i ] ] = false\
  151.    end\
  152. \
  153.    return new\
  154. end\
  155. \
  156. --[[\
  157.    WIP\
  158. ]]\
  159. \
  160. class \"MissileController\" extends \"Application\" mixin \"MAuthentication\" {\
  161.    static = {\
  162.        MISSILE_INFO_COMPUTERS = { 11, 12, 13, 14, 15, 16, 23, 24, 19, 20, 21, 22 };\
  163.        LAUNCH_DELAY = 60;\
  164.        LAUNCH_STAGGER_DELAY = 6;\
  165. \
  166.        LAUNCH_REDSTONE_SIDE = \"bottom\";\
  167. \
  168.        REDNET_PROTOCOL = \"RUGBY_MISSILE\";\
  169.        PING_TIMER = 30;\
  170. \
  171.        MISSILE_REDSTONE = { colours.white, colours.orange, colours.magenta, colours.lightBlue, colours.yellow, colours.lime, colours.pink, colours.grey, colours.lightGrey, colours.cyan, colours.purple, colours.blue };\
  172.    };\
  173. \
  174.    backgroundColour = 128;\
  175. \
  176.    cache = {};\
  177.    ping = {\
  178.        unreachable = false;\
  179.        pending = false;\
  180.        reachable = false;\
  181.        launchable = false;\
  182.        untilNext = false;\
  183.    }\
  184. }\
  185. \
  186. --[[\
  187.    @constructor\
  188.    @desc WIP\
  189. ]]\
  190. function MissileController:__init__( ... )\
  191.    self:super( ... )\
  192. \
  193.    self:importFromTML \"missile/ui/master.tml\"\
  194.    self:importTheme( \"master\", \"missile/ui/master.theme\" )\
  195. end\
  196. \
  197. --[[\
  198.    @instance\
  199.    @desc Cache important nodes for use later, avoids using 'query' too often\
  200. ]]\
  201. function MissileController:cacheNodes()\
  202.    self.cache = {\
  203.        monitor = self:query \"Container#monitor\".result[ 1 ],\
  204.        pages = self:query \"PageContainer#master\".result[ 1 ],\
  205.        pingNotif = self:query \"Label#ping_count\",\
  206.        err = self:query \"Page#error\".result[ 1 ]\
  207.    }\
  208. \
  209.    self.cache.pages:selectPage \"home\"\
  210. \
  211.    self:loadPosition()\
  212. end\
  213. \
  214. --[[\
  215.    @instance\
  216.    @desc WIP\
  217. ]]\
  218. function MissileController:queuePing()\
  219.    local c = MissileController.PING_TIMER\
  220.    self:schedule( function()\
  221.        c = c - 1\
  222.        self.cache.pingNotif:set( \"text\", c..\"s\" )\
  223. \
  224.        if c == 0 then\
  225.            self:unschedule \"ping_repeat\"\
  226.            self:pingMissiles()\
  227.        end\
  228.    end, 1, true, \"ping_repeat\" )\
  229. end\
  230. \
  231. --[[\
  232.    @instance\
  233.    @desc Pings the missile sub-computers, returning a table of responsive controllers and a boolean representing whether all are online (launch ready)\
  234.    @return <boolean - allOnline>, <table - responsiveMissiles>\
  235. ]]\
  236. function MissileController:pingMissiles()\
  237.    -- Send a ping to all computers and wait for a response from the computers\
  238.    local missiles, monitor = MissileController.MISSILE_INFO_COMPUTERS, self.cache.monitor\
  239. \
  240.    -- Reset the ping result when we start pinging clients\
  241.    self.ping = { pending = populateKeyPair( missiles ), unreachable = 0, reachable = 0, launchable = 0 }\
  242.    self:checkPings()\
  243.    for i = 1, #missiles do\
  244.        local missileLabel = monitor:query( ( \"Label#%s_missile_status\" ):format( missiles[ i ] ) ).result[ 1 ]\
  245. \
  246.        -- Ping the client\
  247.        rednet.send( missiles[ i ], \"PING\", MissileController.REDNET_PROTOCOL )\
  248.        missileLabel.text = \"PINGING\"\
  249.        missileLabel.classes = {}\
  250.        missileLabel:filterThemes()\
  251. \
  252.        self:schedule( function()\
  253.            -- Missile ping timed out\
  254.            missileLabel:set {\
  255.                text = \"Offline\",\
  256.                classes = { [\"error\"] = true }\
  257.            }\
  258. \
  259.            missileLabel:filterThemes()\
  260. \
  261.            self.ping.pending[ missiles[ i ] ] = nil\
  262.            self.ping.unreachable = self.ping.unreachable + 1\
  263. \
  264.            self:checkPings()\
  265.        end, 1, false, ( \"MISSILE_%s_PING_TIMEOUT\" ):format( missiles[ i ] ) )\
  266.    end\
  267. end\
  268. \
  269. --[[\
  270.    @instance\
  271.    @desc WIP\
  272. ]]\
  273. function MissileController:checkPings()\
  274.    -- Update the status labels\
  275.    local status = self:query \"Label#status\"\
  276.    status:removeClass \"pending\"\
  277.    status:removeClass \"error\"\
  278.    status:removeClass \"OK\"\
  279. \
  280.    self.ready = false\
  281.    if not next( self.ping.pending ) then\
  282.        -- Ping is complete, however clients might be unreachable\
  283.        if self.ping.unreachable > 0 then\
  284.            -- Ping is complete. No silos online\
  285.            status:set( \"text\", \"Missile Silos Not Online\" )\
  286.            status:addClass \"error\"\
  287. \
  288.            self:executeCallbacks( \"ping_complete\", false )\
  289.        elseif self.ping.reachable > 0 then\
  290.            status:set( \"text\", \"Missile Silos Not Ready For Launch\" )\
  291.            status:addClass \"error\"\
  292. \
  293.            self:executeCallbacks( \"ping_complete\", false )\
  294.        elseif self.ping.launchable == #MissileController.MISSILE_INFO_COMPUTERS then\
  295.            -- Ping is complete. All silos accessible\
  296.            status:set( \"text\", \"Ready for Launch\" )\
  297.            status:addClass \"OK\"\
  298. \
  299.            self:executeCallbacks( \"ping_complete\", true )\
  300. \
  301.            self.ready = true\
  302.        end\
  303. \
  304.        self:queuePing()\
  305.    else\
  306.        -- Still pinging the clients\
  307.        status:set( \"text\", \"Syncing Missile Silos\" )\
  308.        status:addClass \"pending\"\
  309. \
  310.        self.cache.pingNotif:set( \"text\", \"PINGING\" )\
  311.    end\
  312. end\
  313. \
  314. function MissileController:error( title, body )\
  315.    local err = self.cache.err\
  316.    err:query \"Label#title\":set(\"text\", title or \"\")\
  317.    err:query \"TextContainer#body\":set(\"text\", body or \"\")\
  318. \
  319.    self.cache.pages:selectPage \"error\"\
  320. end\
  321. \
  322. function MissileController:syncPosition()\
  323.    local d = Thread( function()\
  324.        local f = fs.open(\".location.cfg\", \"r\")\
  325.        local pos = f.readAll()\
  326.        f.close()\
  327. \
  328.        local missiles = MissileController.MISSILE_INFO_COMPUTERS\
  329.        for i = 1, #missiles do\
  330.            rednet.send( missiles[ i ], \"UPDATE_POSITION:PAYLOAD\" .. pos, MissileController.REDNET_PROTOCOL )\
  331. \
  332.            local t, crash = os.startTimer( 1 )\
  333.            while true do\
  334.                local e = { coroutine.yield() }\
  335.                if e[ 1 ] == \"rednet_message\" and e[ 2 ] == missiles[ i ] and e[ 3 ] == \"SUCCESS\" and e[ 4 ] == MissileController.REDNET_PROTOCOL then\
  336.                    os.cancelTimer( t )\
  337.                    break\
  338.                elseif e[ 1 ] == \"timer\" and e[ 2 ] == t then\
  339.                    self:executeCallbacks( \"sync_complete\", false )\
  340.                    self:error(\"Failed to sync silo coordinates\", \"Silo #\" .. i .. \" didn't respond when trying to update the missile location tracking systems -- unsafe to launch, aborting\")\
  341.                    return false\
  342.                end\
  343.            end\
  344.        end\
  345. \
  346.        self:executeCallbacks( \"sync_complete\", true )\
  347.    end )\
  348. \
  349.    self:addThread( d )\
  350.    return d\
  351. end\
  352. \
  353. function MissileController:updatePosition()\
  354.    local f = fs.open(\".location.cfg\", \"w\")\
  355.    f.write( textutils.serialise( { X = self:query \"Input#X_input\".result[ 1 ].value, Z = self:query \"Input#Z_input\".result[ 1 ].value, Y = self:query \"Input#det_input\".result[ 1 ].value } ) )\
  356.    f.close()\
  357. \
  358.    self:syncPosition()\
  359. end\
  360. \
  361. function MissileController:loadPosition()\
  362.    if not fs.exists \".location.cfg\" then return end\
  363. \
  364.    local h = fs.open(\".location.cfg\", \"r\")\
  365.    local positions = textutils.unserialise( h.readAll() )\
  366.    h.close()\
  367. \
  368.    local toSet = { X = \"X_input\", Z = \"Z_input\", Y = \"det_input\"}\
  369.    for prop, query in pairs( toSet ) do\
  370.        local n = self:query( \"Input#\" .. query ).result[ 1 ]\
  371.        n.value = positions[ prop ]\
  372.        n.position = #n.value\
  373.    end\
  374. end\
  375. \
  376. function MissileController:launch()\
  377.    self.cache.pages:selectPage \"launching\"\
  378.    local missiles = MissileController.MISSILE_REDSTONE\
  379.    local launch = self:query \"Label#silo_launch\"\
  380. \
  381.    for i = 1, #missiles do\
  382.        self:schedule(function()\
  383.            redstone.setBundledOutput( MissileController.LAUNCH_REDSTONE_SIDE, missiles[ i ] )\
  384.            launch:set( \"text\", \"Launching Silo #\" .. i )\
  385. \
  386.            if i == #missiles then\
  387.                self:error(\"Launch Success\", \"Missiles have been successfully launched to target location\")\
  388. \
  389.                self:stopNotificationBlink()\
  390.                self:schedule( function() redstone.setBundledOutput( MissileController.LAUNCH_REDSTONE_SIDE, 0 ) end, 1 )\
  391.            end\
  392.        end, ( i - 1 ) * MissileController.LAUNCH_STAGGER_DELAY, false, \"MISSILE_\" .. i )\
  393.    end\
  394. end\
  395. \
  396. --[[\
  397.    @instance\
  398.    @desc WIP\
  399. ]]\
  400. function MissileController:start( ... )\
  401.    self:cacheNodes()\
  402.    self:pingMissiles()\
  403.    self.super:start( ... )\
  404. end\
  405. \
  406. configureConstructor {\
  407.    argumentTypes = {\
  408.        ready = \"boolean\"\
  409.    }\
  410. }",
  411. }
  412. local vfsAssets = {
  413.   [ "missile/startup" ] = "--[[\
  414.    Missile Control Computer\
  415.    ========================\
  416. \
  417.    Controls:\
  418.    ---------\
  419.        - Co-ordinates\
  420.        - Detonation distance\
  421.        - Countdown\
  422.        - Launch\
  423.        - Display\
  424. \
  425.    Copyright (c) Harry Felton 2017\
  426. ]]\
  427. \
  428. local function isInfoComputer( target )\
  429.    for i = 1, #MissileController.MISSILE_INFO_COMPUTERS do\
  430.        if MissileController.MISSILE_INFO_COMPUTERS[ i ] == target then\
  431.            return true\
  432.        end\
  433.    end\
  434. \
  435.    return false\
  436. end\
  437. \
  438. local m, f = peripheral.getNames()\
  439. for i = 1, #m do\
  440.    if peripheral.getType( m[ i ] ) == \"modem\" then\
  441.        rednet.open( m[ i ] )\
  442. \
  443.        f = true\
  444.    end\
  445. end\
  446. \
  447. if not f then error \"no modem\" end\
  448. \
  449. local missile = MissileController():set( \"terminatable\", true )\
  450. local rednet = Thread( function()\
  451.    while true do\
  452.        local message = { coroutine.yield \"rednet_message\" }\
  453.        table.remove( message, 1 )\
  454.        if message[ 3 ] == MissileController.REDNET_PROTOCOL then\
  455.            if isInfoComputer( message[ 1 ] ) then\
  456.                if message[ 2 ]:match \"CLIENT_PING:PAYLOAD\" then\
  457.                    local missileLabel = missile.cache.monitor:query( ( \"Label#%s_missile_status\" ):format( message[ 1 ] ) ).result[ 1 ]\
  458. \
  459.                    if message[ 2 ]:match \"CLIENT_PING:PAYLOAD(.*)\" == \"true\" then\
  460.                        missileLabel:set( \"text\", \"Launchable\" ).classes = { [\"OK\"] = true }\
  461.                        missile.ping.launchable = missile.ping.launchable + 1\
  462.                    else\
  463.                        missileLabel:set( \"text\", \"Not Ready\" ).classes = { [\"pending\"] = true }\
  464.                        missile.ping.reachable = missile.ping.reachable + 1\
  465.                    end\
  466. \
  467.                    missileLabel:filterThemes()\
  468.                    missile.ping.pending[ message[ 1 ] ] = nil\
  469.                    missile:unschedule( ( \"MISSILE_%s_PING_TIMEOUT\" ):format( message[ 1 ] ) )\
  470. \
  471.                    missile:checkPings()\
  472.                elseif message[ 2 ] == \"MASTER_PING\" then\
  473.                    rednet.send( message[ 1 ], \"PING_PONG\", MissileController.REDNET_PROTOCOL )\
  474.                end\
  475.            end\
  476.        end\
  477.    end\
  478. end )\
  479. \
  480. missile:query \"#coord_move\":on(\"trigger\", function()\
  481.    missile:loadPosition()\
  482.    missile.cache.pages:selectPage \"coord\"\
  483. end)\
  484. \
  485. missile:query \"#coord_cancel\":on(\"trigger\", function()\
  486.    missile.cache.pages:selectPage \"home\"\
  487. end)\
  488. \
  489. missile:query \"#coord_confirm\":on(\"trigger\", function( self )\
  490.    missile:on(\"sync_complete\", function( this, success )\
  491.        self.enabled = true\
  492.        if success then missile.cache.pages:selectPage \"home\" end\
  493.    end)\
  494. \
  495.    missile:updatePosition()\
  496.    self.enabled = false\
  497. end)\
  498. \
  499. missile:query \"#home_return\":on(\"trigger\", function()\
  500.    missile.cache.pages:selectPage \"home\"\
  501. end)\
  502. \
  503. missile:query \"#launch_return\":on(\"trigger\", function()\
  504.    missile:unprime( true )\
  505.    missile.cache.pages:selectPage \"home\"\
  506. end)\
  507. \
  508. missile:query \"#launch_code\":on(\"trigger\", function( self )\
  509.    if missile.primed and self.value == MAuthentication.LAUNCH_CODE then\
  510.        self:set { value = \"\", position = 0 }\
  511.        missile:commenceLaunchSequence()\
  512.    else\
  513.        missile:unprime( true )\
  514.        missile:error(\"Incorrect Launch Key\", \"Launch key provided was incorrect. To help protect the facility, the launch has been aborted\")\
  515.    end\
  516. end)\
  517. \
  518. missile:query \"#ABORT\":on(\"trigger\", function( self )\
  519.    missile:abortLaunch()\
  520. end)\
  521. \
  522. missile:addThread( rednet )\
  523. missile:addProjector( Projector( \"main\", \"monitor\", \"monitor_0\" ) )\
  524. \
  525. missile:start()",
  526.   [ "missile/ui/master.theme" ] = "<Any>\
  527.    <!-- <colour>256</colour> -->\
  528. </Any>\
  529. \
  530. <Any class=\"header\">\
  531.    <colour>orange</colour>\
  532. </Any>\
  533. \
  534. <Any class=\"centre\">\
  535.    <X dynamic>( parent.width / 2 ) - self.width / 2</X>\
  536. </Any>\
  537. \
  538. <Button>\
  539.    <horizontalAlign>centre</horizontalAlign>\
  540.    <width dynamic>#self.text + 2</width>\
  541.    <backgroundColour>cyan</backgroundColour>\
  542.    <colour important>white</colour>\
  543. </Button>\
  544. \
  545. <Label class=\"missile_title\">\
  546.    <colour important>orange</colour>\
  547. </Label>\
  548. \
  549. <Label class=\"error\">\
  550.    <colour important>red</colour>\
  551. </Label>\
  552. \
  553. <Label class=\"OK\">\
  554.    <colour important>cyan</colour>\
  555. </Label>\
  556. \
  557. <Label class=\"pending\">\
  558.    <colour important>orange</colour>\
  559. </Label>\
  560. \
  561. <Label id=\"countdown_T\" class=\"RED\">\
  562.    <colour important>white</colour>\
  563.    <backgroundColour important>red</backgroundColour>\
  564. </Label>",
  565.   [ "missile/ui/master.tml" ] = "<Container id=\"monitor\" projector=\"main\" width=\"50\" height=\"19\" backgroundColour=\"128\" colour=\"orange\">\
  566.    <Label class=\"header centre\" colour=\"orange\" Y=\"2\">Missile Control Status Panel</Label>\
  567.    <Label class=\"centre\" Y=\"4\" colour=\"256\" id=\"status\">Starting</Label>\
  568.    <Label colour=\"256\" text=\"...\" X=\"$parent.width - self.width\" id=\"ping_count\"/>\
  569. \
  570.    <Container width=\"$parent.width\" height=\"$parent.height - 7\" Y=7>\
  571.        <Label X=\"2\" Y=\"1\" id=\"11_missile_title\" class=\"missile_title\">Silo 1</Label>\
  572.        <Label id=\"11_missile_status\" Y=\"${#11_missile_title}.Y\" X=\"${#11_missile_title}.X + {#11_missile_title}.width + 1\">Pinging</Label>\
  573. \
  574.        <Label X=\"2\" Y=\"3\" id=\"12_missile_title\" class=\"missile_title\">Silo 2</Label>\
  575.        <Label id=\"12_missile_status\" Y=\"${#12_missile_title}.Y\" X=\"${#12_missile_title}.X + {#12_missile_title}.width + 1\">Pinging</Label>\
  576. \
  577.        <Label X=\"2\" Y=\"5\" id=\"13_missile_title\" class=\"missile_title\">Silo 3</Label>\
  578.        <Label id=\"13_missile_status\" Y=\"${#13_missile_title}.Y\" X=\"${#13_missile_title}.X + {#13_missile_title}.width + 1\">Pinging</Label>\
  579. \
  580.        <Label X=\"2\" Y=\"7\" id=\"14_missile_title\" class=\"missile_title\">Silo 4</Label>\
  581.        <Label id=\"14_missile_status\" Y=\"${#14_missile_title}.Y\" X=\"${#14_missile_title}.X + {#14_missile_title}.width + 1\">Pinging</Label>\
  582. \
  583.        <Label X=\"2\" Y=\"9\" id=\"15_missile_title\" class=\"missile_title\">Silo 5</Label>\
  584.        <Label id=\"15_missile_status\" Y=\"${#15_missile_title}.Y\" X=\"${#15_missile_title}.X + {#15_missile_title}.width + 1\">Pinging</Label>\
  585. \
  586.        <Label X=\"2\" Y=\"11\" id=\"16_missile_title\" class=\"missile_title\">Silo 6</Label>\
  587.        <Label id=\"16_missile_status\" Y=\"${#16_missile_title}.Y\" X=\"${#16_missile_title}.X + {#16_missile_title}.width + 1\">Pinging</Label>\
  588. \
  589.        <Label X=\"${#23_missile_status}.X - self.width - 1\" Y=\"1\" id=\"23_missile_title\" class=\"missile_title\">Silo 7</Label>\
  590.        <Label id=\"23_missile_status\" X=\"$parent.width - self.width - 2\" Y=\"1\">Pinging</Label>\
  591. \
  592.        <Label X=\"${#24_missile_status}.X - self.width - 1\" Y=\"3\" id=\"24_missile_title\" class=\"missile_title\">Silo 8</Label>\
  593.        <Label id=\"24_missile_status\" X=\"$parent.width - self.width - 2\" Y=\"3\">Pinging</Label>\
  594. \
  595.        <Label X=\"${#19_missile_status}.X - self.width - 1\" Y=\"5\" id=\"19_missile_title\" class=\"missile_title\">Silo 9</Label>\
  596.        <Label id=\"19_missile_status\" X=\"$parent.width - self.width - 2\" Y=\"5\">Pinging</Label>\
  597. \
  598.        <Label X=\"${#20_missile_status}.X - self.width - 1\" Y=\"7\" id=\"20_missile_title\" class=\"missile_title\">Silo 10</Label>\
  599.        <Label id=\"20_missile_status\" X=\"$parent.width - self.width - 2\" Y=\"7\">Pinging</Label>\
  600. \
  601.        <Label X=\"${#21_missile_status}.X - self.width - 1\" Y=\"9\" id=\"21_missile_title\" class=\"missile_title\">Silo 11</Label>\
  602.        <Label id=\"21_missile_status\" X=\"$parent.width - self.width - 2\" Y=\"9\">Pinging</Label>\
  603. \
  604.        <Label X=\"${#22_missile_status}.X - self.width - 1\" Y=\"11\" id=\"22_missile_title\" class=\"missile_title\">Silo 12</Label>\
  605.        <Label id=\"22_missile_status\" X=\"$parent.width - self.width - 2\" Y=\"11\">Pinging</Label>\
  606.    </Container>\
  607. </Container>\
  608. \
  609. <Container height=\"3\" width=\"$application.width\" backgroundColour=\"orange\">\
  610.    <Label colour=\"128\">Missile Control Station</Label>\
  611. </Container>\
  612. \
  613. <PageContainer width=\"$application.width\" height=\"$application.height - 2\" id=\"master\" Y=3>\
  614.    <Page id=\"home\">\
  615.        <Label class=\"centre\" Y=4 id=\"status\">...</Label>\
  616. \
  617.        <TextContainer X=\"6\" width=\"$parent.width - 10\" height=\"$parent.height - 7\" Y=\"7\" colour=\"256\" horizontalAlign=\"centre\" text=\"Use two-person authentication to start launch sequence whenever ready.\\nSilo verification and location sync will occur before any launch occurs.\"/>\
  618. \
  619.        <Button class=\"centre\" Y=\"$parent.height - 1\" id=\"coord_move\">Set Co-Ords</Button>\
  620.    </Page>\
  621.    <Page id=\"coord\">\
  622.        <Label class=\"centre\" Y=4 colour=\"1\">Set Co-ordinates</Label>\
  623. \
  624.        <Label Y=6 X=2 id=\"X\" colour=\"lightGrey\">X</Label>\
  625.        <Input Y=\"${Label#X}.Y\" X=\"${Label#X}.X + {Label#X}.width + 2\" id=\"X_input\" width=\"6\" limit=\"6\" backgroundColour=\"lightGrey\"/>\
  626. \
  627.        <Label Y=8 X=2 id=\"Z\" colour=\"lightGrey\">Z</Label>\
  628.        <Input Y=\"${Label#Z}.Y\" X=\"${Label#Z}.X + {Label#Z}.width + 2\" id=\"Z_input\" width=\"6\" limit=\"6\" backgroundColour=\"lightGrey\"/>\
  629. \
  630.        <Label Y=10 X=2 id=\"det\" colour=\"lightGrey\">Detonation Height</Label>\
  631.        <Input Y=\"${Label#det}.Y\" X=\"${Label#det}.X + {Label#det}.width + 2\" width=\"6\" limit=\"6\" id=\"det_input\" backgroundColour=\"lightGrey\"/>\
  632. \
  633.        <Button X=2 backgroundColour=\"red\" id=\"coord_cancel\" Y=\"$parent.height - 1\">Cancel</Button>\
  634.        <Button X=\"${#coord_cancel}.X + {#coord_cancel}.width + 2\" id=\"coord_confirm\" Y=\"$parent.height - 1\">Set</Button>\
  635.    </Page>\
  636.    <Page id=\"primed\">\
  637.        <Label class=\"centre header\" id=\"title\" Y=\"5\" color=\"1\">Missile Silos Primed</Label>\
  638.        <TextContainer id=\"body\" X=\"6\" width=\"$parent.width - 10\" height=\"3\" Y=\"7\" colour=\"256\" horizontalAlign=\"centre\">All twelve silos are primed for launch. Enter the launch code to initiate launch sequence</TextContainer>\
  639. \
  640.        <Input backgroundColour=\"lightGrey\" width=\"20\" limit=\"64\" Y=\"11\" id=\"launch_code\" class=\"centre\" mask=\"*\"/>\
  641.        <Button class=\"centre\" id=\"launch_return\" Y=\"$parent.height - 1\" backgroundColour=\"red\">Cancel</Button>\
  642.    </Page>\
  643.    <Page id=\"launch\">\
  644.        <Label class=\"centre header\" Y=\"5\" color=\"1\">LAUNCH COMMENCED</Label>\
  645.        <TextContainer id=\"body\" text=\"Commencing launch. Clear the area immediately!\\n\\nCancel the launch IMMEDIATELY if a fault is detected\" Y=\"7\" X=\"6\" width=\"$parent.width - 10\" height=\"$parent.height - 10\" colour=\"256\" horizontalAlign=\"centre\"/>\
  646.        <Label text=\"T-60 seconds to launch\" Y=\"$parent.height - 1\" colour=\"red\" class=\"centre\" id=\"countdown_T\"/>\
  647.        <Button class=\"centre\" id=\"ABORT\" Y=\"$parent.height - 3\" backgroundColour=\"red\">ABORT LAUNCH</Button>\
  648.    </Page>\
  649.    <Page id=\"launching\">\
  650.        <Label class=\"centre header\" Y=\"5\" color=\"1\">LAUNCHING</Label>\
  651.        <TextContainer id=\"body\" text=\"LAUNCH PHASE REACHED -- UN-ABORTABLE.\\n\\nEVACUATE ALL PERSONNEL, FATAL CONDITIONS TO FOLLOW\" Y=\"7\" X=\"6\" width=\"$parent.width - 10\" height=\"$parent.height - 10\" colour=\"256\" horizontalAlign=\"centre\"/>\
  652.        <Label text=\"\" Y=\"$parent.height - 1\" colour=\"red\" class=\"centre\" id=\"silo_launch\"/>\
  653.    </Page>\
  654.    <Page id=\"pinging\">\
  655.        <Label class=\"centre header\" Y=\"5\" color=\"1\">Validating Silos</Label>\
  656.        <TextContainer id=\"body\" text=\"Checking Silo launch state, and syncing locational tracking information before commencing launch countdown\" Y=\"7\" X=\"6\" width=\"$parent.width - 10\" height=\"$parent.height - 10\" colour=\"256\" horizontalAlign=\"centre\"/>\
  657.    </Page>\
  658.    <Page id=\"error\">\
  659.        <Label class=\"centre header\" id=\"title\" Y=\"5\" color=\"1\">...</Label>\
  660.        <TextContainer id=\"body\" X=\"6\" width=\"$parent.width - 10\" height=\"$parent.height - 7\" Y=\"7\" colour=\"256\" horizontalAlign=\"centre\">...</TextContainer>\
  661. \
  662.        <Button class=\"centre\" id=\"home_return\" Y=\"$parent.height - 1\">Return</Button>\
  663.    </Page>\
  664. </PageContainer>",
  665. }local env = type( getfenv ) == "function" and getfenv() or _ENV or _G
  666. if env.TI_VFS_RAW then env = env.TI_VFS_RAW end
  667. local fallbackFS = env.fs
  668. local RAW = setmetatable({
  669.     fs = setmetatable( {}, { __index = _G["fs"] }),
  670.     os = setmetatable( {}, { __index = _G["os"] } )
  671. }, { __index = env })
  672.  
  673. local VFS = RAW["fs"]
  674. local VFS_ENV = setmetatable({},{__index = function( _, key )
  675.     if key == "TI_VFS_RAW" then return RAW end
  676.     return RAW[ key ]
  677. end})
  678.  
  679. VFS_ENV._G = env
  680. VFS_ENV._ENV = env
  681.  
  682. local VFS_DIRS = {
  683.   missile = true,
  684.   [ "missile/ui" ] = true,
  685. }
  686. local matches = { ["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%", ["*"] = "[^/]*", ["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-" }
  687.  
  688.  
  689. function VFS_ENV.load(src, name, mode, env)
  690.     return load( src, name or '(load)', mode, env or VFS_ENV )
  691. end
  692.  
  693. function VFS_ENV.loadstring(src, name)
  694.     return VFS_ENV.load( src, name, 't', VFS_ENV )
  695. end
  696.  
  697. function VFS_ENV.loadfile(file, env)
  698.     local _ENV = VFS_ENV
  699.     local h = fs.open( file, "r" )
  700.     if h then
  701.         local fn, e = load(h.readAll(), fs.getName(file), 't', env or VFS_ENV)
  702.         h.close()
  703.         return fn, e
  704.     end
  705.  
  706.     return nil, 'File not found'
  707. end
  708. if type( setfenv ) == "function" then setfenv( VFS_ENV.loadfile, VFS_ENV ) end
  709.  
  710. function VFS_ENV.os.run( _tEnv, _sPath, ... )
  711.     local _ENV = VFS_ENV
  712.     local tArgs, tEnv = { ... }, _tEnv
  713.  
  714.     setmetatable( tEnv, { __index = VFS_ENV } )
  715.     local fnFile, err = loadfile( _sPath, tEnv )
  716.     if fnFile then
  717.         local ok, err = pcall( function()
  718.             fnFile( table.unpack( tArgs ) )
  719.         end )
  720.         if not ok then
  721.             if err and err ~= "" then
  722.                 printError( err )
  723.             end
  724.             return false
  725.         end
  726.         return true
  727.     end
  728.     if err and err ~= "" then
  729.         printError( err )
  730.     end
  731.     return false
  732. end
  733.  
  734. local tAPIsLoading = {}
  735. function VFS_ENV.os.loadAPI( _sPath )
  736.     local _ENV, sName = VFS_ENV, fs.getName( _sPath )
  737.     if tAPIsLoading[sName] == true then
  738.         printError( "API "..sName.." is already being loaded" )
  739.         return false
  740.     end
  741.     tAPIsLoading[sName] = true
  742.  
  743.     local tEnv = setmetatable( {}, { __index = VFS_ENV } )
  744.     local fnAPI, err = loadfile( _sPath, tEnv )
  745.     if fnAPI then
  746.         local ok, err = pcall( fnAPI )
  747.         if not ok then
  748.             printError( err )
  749.             tAPIsLoading[sName] = nil
  750.             return false
  751.         end
  752.     else
  753.         printError( err )
  754.         tAPIsLoading[sName] = nil
  755.         return false
  756.     end
  757.  
  758.     local tAPI = {}
  759.     for k,v in pairs( tEnv ) do if k ~= "_ENV" then tAPI[k] =  v end end
  760.  
  761.     VFS_ENV[sName], tAPIsLoading[sName] = tAPI, nil
  762.     return true
  763. end
  764.  
  765. VFS_ENV.os.loadAPI "/rom/apis/io"
  766. function VFS_ENV.dofile(file)
  767.     local _ENV = VFS_ENV
  768.     local fn, e = loadfile(file, VFS_ENV)
  769.  
  770.     if fn then return fn()
  771.     else error(e, 2) end
  772. end
  773. if type( setfenv ) == "function" then setfenv( VFS_ENV.dofile, VFS_ENV ) end
  774.  
  775. function VFS.open( path, mode )
  776.     path = fs.combine( "", path )
  777.     if vfsAssets[ path ] then
  778.         if mode == "w" or mode == "wb" or mode == "a" or mode == "ab" then
  779.             return error("Cannot open file in mode '"..tostring( mode ).."'. File is inside of Titanium VFS and is read only")
  780.         end
  781.  
  782.         local content, handle = vfsAssets[ path ], {}
  783.         if mode == "rb" then
  784.             handle.read = function()
  785.                 if #content == 0 then return end
  786.                 local b = content:sub( 1, 1 ):byte()
  787.  
  788.                 content = content:sub( 2 )
  789.                 return b
  790.             end
  791.         end
  792.  
  793.         handle.readLine = function()
  794.             if #content == 0 then return end
  795.  
  796.             local line, rest = content:match "^([^\n\r]*)(.*)$"
  797.  
  798.             content = rest and rest:gsub("^[\n\r]", "") or ""
  799.             return line or content
  800.         end
  801.  
  802.         handle.readAll = function()
  803.             if #content == 0 then return end
  804.  
  805.             local c = content
  806.             content = ""
  807.  
  808.             return c
  809.         end
  810.  
  811.         handle.close = function() content = "" end
  812.  
  813.         return handle
  814.     else return fallbackFS.open( fs.combine( exportDirectory, path ), mode ) end
  815. end
  816.  
  817. function VFS.isReadOnly( path )
  818.     path = fs.combine( "", path )
  819.     if vfsAssets[ path ] then return true end
  820.  
  821.     return fallbackFS.isReadOnly( fs.combine( exportDirectory, path ) )
  822. end
  823.  
  824. function VFS.getSize( path )
  825.     return vfsAssets[ path ] and #vfsAssets[ path ] or fallbackFS.getSize( path )
  826. end
  827.  
  828. function VFS.list( target )
  829.     target = fs.combine( "", target )
  830.     local list = fallbackFS.isDir( target ) and fallbackFS.list( target ) or {}
  831.  
  832.     local function addResult( res )
  833.         for i = 1, #list do if list[ i ] == res then return end end
  834.         list[ #list + 1 ] = res
  835.     end
  836.  
  837.     if VFS_DIRS[ target ] then
  838.         for path in pairs( vfsAssets ) do
  839.             if path:match( ("^%s/"):format( target ) ) then
  840.                 addResult( path:match( ("^%s/([^/]+)"):format( target ) ) )
  841.             end
  842.         end
  843.     elseif target == "" then
  844.         for path in pairs( vfsAssets ) do addResult( path:match "^([^/]+)" ) end
  845.     end
  846.  
  847.     return list
  848. end
  849.  
  850. function VFS.find( target )
  851.     target = fs.combine( "", target )
  852.     local list = fallbackFS.find( target ) or {}
  853.  
  854.     target = ("^(%s)(.*)$"):format( target:gsub( ".", matches ) )
  855.     for path in pairs( vfsAssets ) do
  856.         local res, tail = path:match( target )
  857.         if res and ( tail == "" or tail:sub( 1, 1 ) == "/" ) then
  858.             local isMatch
  859.             for i = 1, #list do if list[ i ] == res then isMatch = true; break end end
  860.  
  861.             if not isMatch then list[ #list + 1 ] = res end
  862.         end
  863.     end
  864.  
  865.     return list
  866. end
  867.  
  868. function VFS.isDir( path )
  869.     path = fs.combine( "", path )
  870.     return VFS_DIRS[ path ] or fallbackFS.isDir( fs.combine( exportDirectory, path ) )
  871. end
  872.  
  873. function VFS.exists( path )
  874.     path = fs.combine( "", path )
  875.     if vfsAssets[ path ] or VFS_DIRS[ path ] then return true end
  876.  
  877.     return fallbackFS.exists( fs.combine( exportDirectory, path ) )
  878. end
  879. if not fs.exists( "/.tpm/bin/tpm" ) then
  880.     local h = http.get "https://gitlab.com/hbomb79/Titanium-Package-Manager/raw/master/tpm"
  881.     if not h then return error "Failed to download TPM" end
  882.  
  883.     local f = fs.open( "/.tpm/bin/tpm", "w" )
  884.     f.write( h.readAll() )
  885.     h.close()
  886.     f.close()
  887. end
  888.  
  889. local ok, err = loadfile "/.tpm/bin/tpm"
  890. if not ok then return error("Failed to load TPM '"..tostring( err ).."'") end
  891.  
  892. ok( "fetch" )
  893. ok( "--disposable", "--depend", shell.getRunningProgram(), "install", "Titanium:latest" )
  894. local FAILURE = "Failed to execute Titanium package. Latest Titanium version cannot be found %s (/.tpm/cache)"
  895. if not fs.exists("/.tpm/cache") then
  896.     return error( FAILURE:format "because TPM cache is missing" )
  897. end
  898.  
  899. local cacheHandle = fs.open("/.tpm/cache", "r")
  900. local cache = textutils.unserialise( cacheHandle.readAll() )
  901. cacheHandle.close()
  902.  
  903. if not cache then
  904.     return error( FAILURE:format "because TPM cache is malformed" )
  905. elseif not cache.Titanium then
  906.     return error( FAILURE:format "because TPM cache missing Titanium version information" )
  907. end
  908. if not VFS_ENV.Titanium then VFS_ENV.dofile( "/.tpm/packages/Titanium/"..cache.Titanium[1] ) end
  909. local ti = VFS_ENV.Titanium
  910. if not ti then
  911.     return error "Failed to execute Titanium package. Titanium is not loaded. Please load Titanium before executing this package (or use a --titanium-init), or repackage this application using the --titanium flag."
  912. end
  913.  
  914. local loaded = {}
  915. local function loadClass( name, source )
  916.     if not source then return error( "Failed to load class '"..name.."'. No source found within class assets" )
  917.     elseif loaded[ name ] then return end
  918.  
  919.     local className = name:gsub( "%..*", "" )
  920.     if not ti.getClass( className ) then
  921.         local output, err = ( VFS_ENV or _G ).loadstring( source, name )
  922.         if not output or err then return error( "Failed to load Lua chunk. File '"..name.."' has a syntax error: "..tostring( err ), 0 ) end
  923.  
  924.         local ok, err = pcall( output )
  925.         if not ok or err then return error( "Failed to execute Lua chunk. File '"..name.."' crashed: "..tostring( err ), 0 ) end
  926.  
  927.         local class = ti.getClass( className )
  928.         if class then
  929.             if not class:isCompiled() then class:compile() end
  930.             loaded[ name ] = true
  931.         else return error( "File '"..name.."' failed to create class '"..className.."'" ) end
  932.     else
  933.         print( "WARNING: Class " .. className .. " failed to load because a class with the same name already exists." )
  934.     end
  935. end
  936.  
  937. ti.setClassLoader(function( c )
  938.     local name = classSource[ c .. ".lua" ] and c .. ".lua" or c .. ".ti"
  939.     loadClass( name, classSource[ name ] )
  940. end)
  941. for name, source in pairs( classSource ) do
  942.     loadClass( name, source )
  943. end
  944.  
  945. local fn, err = VFS_ENV.loadfile "missile/startup"
  946. if fn then fn()
  947. else return error( "Failed to run file from bundle vfs: "..tostring( err ) ) end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement