Advertisement
joebodo

miningBoss.lua

Apr 17th, 2014
176
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 33.63 KB | None | 0 0
  1. os.loadAPI('apis.lua')
  2.  
  3. Peripheral.wrap("wireless_modem")
  4.  
  5. local args = { ... }
  6. local options = {
  7.   chunks    = { arg = 'c', type = 'number', value = -1,
  8.                 desc = 'Number of chunks to mine' },
  9.   depth     = { arg = 'd', type = 'number', value = 9000,
  10.                 desc = 'Mining depth' },
  11.   plug      = { arg = 'f', type = 'flag',   value = false,
  12.                 desc = 'Fill in top hole' },
  13.   logMethod = { arg = 'l', type = 'string', value = 'wireless',
  14.                 desc = 'Logging: file, screen' },
  15.   help      = { arg = 'h', type = 'flag',   value = false,
  16.                 desc = 'Displays the options' }
  17. }
  18.  
  19. local miners = {}
  20. local pickupQueue = {}
  21. local workQueue = {}
  22. local miningStatus = {
  23.   fuel = 0,
  24.   activity = nil,
  25.   count = 0,
  26.   version = 'v1.3a',
  27.   prompt = 'm for menu'
  28. }
  29.  
  30. local mining = {
  31.   status = 'idle',
  32.   firstTurtle,
  33.   diameter = 1,
  34.   chunkIndex = 0,
  35.   chunks = -1,
  36.   depth = 9000,
  37.   holes = 0,
  38.   plug = false
  39. }
  40.  
  41. function getChunkCoordinates(diameter, index, x, y)
  42.   local dirs = { -- circumference of grid
  43.     { xd =  0, yd =  1, heading = 1 }, -- south
  44.     { xd = -1, yd =  0, heading = 2 },
  45.     { xd =  0, yd = -1, heading = 3 },
  46.     { xd =  1, yd =  0, heading = 0 }  -- east
  47.   }
  48.   -- always move east when entering the next diameter
  49.   if index == 0 then
  50.     dirs[4].x = x + 16
  51.     dirs[4].y = y
  52.     return dirs[4]
  53.   end
  54.   dir = dirs[math.floor(index / (diameter - 1)) + 1]
  55.   dir.x = x + dir.xd * 16
  56.   dir.y = y + dir.yd * 16
  57.   return dir
  58. end
  59.  
  60. function getChunkLoadedBox()
  61.   local ax, ay = getCornerOf(TL2.getNamedLocation('chunkborder'))
  62.   if TL2.getNamedLocation('chunkloader1') then
  63.     ax, ay = getCornerOf(TL2.getNamedLocation('chunkloader1'))
  64.   end
  65.  
  66.   local bx, by = getCornerOf(TL2.getNamedLocation('chunkloader2'))
  67.   local boundingBox = {
  68.     ax = math.min(ax, bx),
  69.     ay = math.min(ay, by),
  70.     bx = math.max(ax, bx)+15,
  71.     by = math.max(ay, by)+15
  72.   }
  73.   return boundingBox
  74. end
  75.  
  76. function getBoreLocations(x, y)
  77.  
  78.   local locations = {}
  79.  
  80.   while true do
  81.     local a = math.abs(y)
  82.     local b = math.abs(x)
  83.  
  84.     if x > 0 and y > 0 or
  85.        x < 0 and y < 0 then
  86.        -- rotate coords
  87.        a = math.abs(x)
  88.        b = math.abs(y)
  89.     end
  90.     if (a % 5 == 0 and b % 5 == 0) or
  91.        (a % 5 == 2 and b % 5 == 1) or
  92.        (a % 5 == 4 and b % 5 == 2) or
  93.        (a % 5 == 1 and b % 5 == 3) or
  94.        (a % 5 == 3 and b % 5 == 4) then
  95.        table.insert(locations, { x = x, y = y })
  96.     end
  97.     if y % 2 == 0 then -- forward dir
  98.       if (x + 1) % 16 == 0 then
  99.         y = y + 1
  100.       else
  101.         x = x + 1
  102.       end
  103.     else
  104.       if (x - 1) % 16 == 15 then
  105.         if (y + 1) % 16 == 0 then
  106.           break
  107.         end
  108.         y = y + 1
  109.       else
  110.         x = x - 1
  111.       end
  112.     end
  113.   end
  114.   return locations
  115. end
  116.  
  117. -- get the bore location closest to the miner
  118. local function getClosestLocation(points, b)
  119.   local key = 1
  120.   local distance = 9000
  121.   for k,a in pairs(points) do
  122.     -- try and avoid floating point operation
  123.     local d = math.max(a.x, b.x) - math.min(a.x, b.x) +
  124.               math.max(a.y, b.y) - math.min(a.y, b.y)
  125.  
  126.     if d < distance then
  127.       d = math.sqrt(
  128.         math.pow(a.x - b.x, 2) + math.pow(a.y - b.y, 2))
  129.       if d < distance then
  130.         key = k
  131.         distance = d
  132.         if distance <= 1 then
  133.           break
  134.         end
  135.       end
  136.     end
  137.   end
  138.   return table.remove(points, key)
  139. end
  140.  
  141. function getCornerOf(c)
  142.   return math.floor(c.x / 16) * 16, math.floor(c.y / 16) * 16
  143. end
  144.  
  145. function showSetup(message)
  146.   message = message or ''
  147.   errorPage.message = message
  148.   UI.pager:setPage('error')
  149. end
  150.  
  151. function releaseMiners()
  152.  
  153.   if mining.status ~= 'mining' then -- chunk loaders are already placed if mining
  154.     local chunkloadersNeeded = 2
  155.     if  TL2.getNamedLocation('chunkloader1') then
  156.       chunkloadersNeeded = 1
  157.     end
  158.     if  TL2.getNamedLocation('chunkloader2') then
  159.       chunkloadersNeeded = chunkloadersNeeded - 1
  160.     end
  161.     if turtle.getItemCount(1) < chunkloadersNeeded then
  162.       showSetup('Not enough chunk loaders in slot 1')
  163.       return false
  164.     end
  165.   end
  166.  
  167.   local maxMiners = turtle.getItemCount(2)
  168.   if maxMiners == 0 then
  169.     showSetup('No ender chests in slot 2')
  170.   end
  171.  
  172.   if not mining.firstTurtle then
  173.     local firstTurtle
  174.  
  175.     if maxMiners == 1 then
  176.       for i = 16, 3, -1 do
  177.         if turtle.getItemCount(i) == 1 then
  178.           firstTurtle = i
  179.           break
  180.         end
  181.       end
  182.     else
  183.       for i = 3, 16 do
  184.         local itemCount = turtle.getItemCount(i)
  185.         if itemCount == 0 then
  186.           break
  187.         end
  188.         if itemCount > 1 then
  189.           if itemCount < maxMiners then
  190.             maxMiners = itemCount
  191.           end
  192.         else
  193.           firstTurtle = i
  194.           break
  195.         end
  196.       end
  197.     end
  198.  
  199.     if not firstTurtle then
  200.       showSetup('No turtles found in inventory')
  201.       return false
  202.     end
  203. Logger.log('miningBoss', 'firstTurtle: ' .. firstTurtle)
  204. Logger.log('miningBoss', 'maxMiners: ' .. maxMiners)
  205.  
  206.     -- check resources
  207.     local minerCount = 0
  208.     for i = firstTurtle, 16 do
  209.       if turtle.getItemCount(i) == 1 then
  210.         minerCount = minerCount + 1
  211.       end
  212.     end
  213. Logger.log('miningBoss', 'minerCount: ' .. minerCount)
  214.     if minerCount > maxMiners then
  215.       for i = 3, firstTurtle-1 do
  216.         if turtle.getItemCount(i) == maxMiners then
  217.           showSetup('Not enough resources in slot ' .. i)
  218.           return false
  219.         end
  220.       end
  221.     end
  222.     -- sanity checking
  223.     for i = firstTurtle, 16 do
  224.       if turtle.getItemCount(i) > 1 then
  225.         showSetup('Invalid setup')
  226.         return false
  227.       end
  228.     end
  229.     mining.firstTurtle = firstTurtle
  230.   end
  231.  
  232.   for i = mining.firstTurtle, 16 do
  233.     if turtle.getItemCount(i) == 1 then
  234.       queueWork(51, 'releaseMiner-' .. i, 'releaseMiner', { slot = i })
  235.     end
  236.   end
  237.  
  238.   return true
  239. end
  240.  
  241. function updateStatus(miner, status)
  242.  
  243.   local function getIndicator(value, past)
  244.     local ind = ' '
  245.     if past then
  246.       if value > past then
  247.         ind = '^'
  248.       elseif value < past then
  249.         ind = 'v'
  250.       end
  251.     end
  252.     return ind
  253.   end
  254.  
  255.   miner.statusD = miner.xStatus
  256.   if miner.xStatus ~= 'deployed' then
  257.     miner.statusD = miner.xStatus
  258.     if miner.xStatus == 'pickup' then
  259.       miner.statusD = 'pkup'
  260.     end
  261.   end
  262.  
  263.   if not miner.lastFuel then
  264.     miner.lastFuel = miner.fuel
  265.   end
  266.   miner.fuelD = string.format("%s%dk",
  267.     getIndicator(math.floor(miner.fuel/1024), math.floor(miner.lastFuel/1024)),
  268.     math.floor(miner.fuel/1024))
  269.   miner.lastFuel = miner.fuel
  270.  
  271.   miner.depthD = string.format("%s%d",
  272.     getIndicator(status.z, miner.lastLocation.z),
  273.     status.z)
  274.   miner.lastLocation = miner.location
  275.   miner.coordsD = string.format("%d,%d", status.x, status.y)
  276.  
  277.   miner.timestamp = os.clock()
  278.  
  279.   local timer = Event.getNamedTimer('statusTimer')
  280.   if not timer or not Event.isTimerActive(timer) then
  281.     Event.addNamedTimer('statusTimer', 1, false, function()
  282.       if statusPage.enabled then
  283.         statusPage:draw()
  284.       end
  285.     end)
  286.   end
  287. end
  288.  
  289. function releaseMiner(slot)
  290.  
  291.   if mining.status ~= 'mining' then
  292.     return true
  293.   end
  294.  
  295.   showActivity('Releasing miner')
  296.   local x, y = getCornerOf(TL2.getNamedLocation('chunkloader2'), 16, 16, 1)
  297.   local box = TL2.pointToBox({ x = x, y = y, z = 0 }, 16, 16, 1)
  298.   while true do
  299.     local deployPt = TL2.createPoint(TL2.getState())
  300.     deployPt.x = deployPt.x - 2
  301.     deployPt.y = deployPt.y - 2
  302.     local deployBox = TL2.pointToBox(deployPt, 4, 4, 1)
  303.     TL2.boxContain(box, deployBox)
  304.     if not TL2.pointInBox(TL2.getState(), deployBox) or TL2.isTurtleAt('bottom') then
  305.       local r = Util.random(15)
  306.       TL2.goto(deployBox.ax + math.floor(r / 4), deployBox.ay + r % 4, 0)
  307.     end
  308.     if not turtle.detectDown() then
  309.       break
  310.     end
  311.     if not TL2.isTurtleAt('bottom') and TL2.digDown() then
  312.       break
  313.     end
  314.   end
  315.  
  316.   if turtle.getItemCount(slot) ~= 1 then
  317.     showActivity('Not a turtle')
  318.     return true
  319.   end
  320.  
  321.   for i = 2, mining.firstTurtle - 1 do
  322.     if turtle.getItemCount(i) == 0 then
  323.       showActivity('Not enough resources')
  324.       return true
  325.     end
  326.   end
  327.  
  328.   -- place miner
  329.   if not TL2.placeDown(slot) then
  330.     -- someone took out a turtle from the inventory :(
  331.     return true
  332.   end
  333.   os.sleep(.1)
  334.  
  335.   -- give miner resources
  336.   for i = 2, mining.firstTurtle - 1 do
  337.     TL2.select(i)
  338.     turtle.dropDown(1)
  339.   end
  340.  
  341.   peripheral.call('bottom', 'turnOn')
  342.  
  343.   if not Util.tryTimed(5,
  344.     function()
  345.       local id = peripheral.call('bottom', 'getID')
  346.       if id then
  347.         local pt = TL2.createPoint(TL2.getState())
  348.         pt.heading = TL2.getState().heading
  349.         miners[id] = {
  350.           id = id,
  351.           status = 'idle',
  352.           xStatus = 'new',
  353.           deployLocation = pt,
  354.           location = pt,
  355.           lastLocation = pt,
  356.           name = 'turtle_' .. tostring(id)
  357.         }
  358.         TLC.requestState(id)
  359.         return true
  360.       end
  361.       os.sleep(.1)
  362.       return false
  363.     end) then
  364.     -- turtle won't start or this is not a turtle
  365.     TL2.select(slot)
  366.     turtle.digDown()
  367.   end
  368.  
  369.   --pager:setPage('status')
  370.   return true
  371. end
  372.  
  373. TLC.registerAction('releaseMiner', function(args)
  374.   return releaseMiner(args.slot)
  375. end)
  376.  
  377. function collectMiner(miner)
  378.   showActivity('Picking up turtle')
  379.  
  380.   if not TL2.pointInBox(miner.location, getChunkLoadedBox()) then
  381.     shutdownCommand(miner)
  382.     Logger.log('miningBoss', 'shutting down ' .. miner.id .. ' too far away')
  383.     return true
  384.   end
  385.  
  386.   TL2.goto(miner.location.x, miner.location.y, 0)
  387.  
  388.   if not TL2.isTurtleAt('bottom') then
  389.     queueWork(3,  'rescue-' .. miner.id, 'rescueMiner', miner)
  390. --[[
  391.       Logger.log('shutting down ' .. miner.id .. ' unable to locate')
  392.       TLC.sendAction(miner.id, 'shutdown', location)
  393.       miners[miner.id] = nil
  394. --]]
  395.     return true
  396.   end
  397.  
  398.   local id = peripheral.call('bottom', 'getID')
  399.   if id and miners[id] then
  400.     local miner = miners[id]
  401.     local slots = TL2.getInventory()
  402.     for i = 2, mining.firstTurtle-1 do
  403.       TL2.select(i)
  404.       if turtle.suckDown() then
  405.         slots[i].qty = slots[i].qty + 1
  406.       end
  407.     end
  408.     TL2.reconcileInventory(slots)
  409.     local slot = TL2.selectOpenSlot(3)
  410.     miners[miner.id] = nil
  411.     if not slot then
  412.       -- too many turtles for inventory
  413.       peripheral.call('bottom', 'shutdown')
  414.       Logger.log('miningBoss', 'shutting down ' .. id .. ' no room')
  415.     elseif turtle.digDown() then
  416.       if mining.status == 'mining' and miner.fuel > 1024 then
  417.         -- check to see if we mined up something other than a turtle
  418.         if turtle.getItemCount(slot) == 1 then
  419.           releaseMiner(slot)
  420.         end
  421.       end
  422.     end
  423.     UI.pager:getCurrentPage():draw()
  424.     if Util.size(miners) == 0 then
  425.       UI.pager:setPage('menu')
  426.       local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex
  427.       if mining.status == 'recalling' and
  428.          mining.chunks ~= -1 and
  429.          chunks >= mining.chunks then
  430.         -- automatically exit if we exceeded requested diameter
  431.         Event.exitPullEvents()
  432.       end
  433.     end
  434.   end
  435.   return true
  436. end
  437.  
  438. function setStatus(status)
  439.   mining.status = status
  440.   showActivity()
  441. end
  442.  
  443. function showActivity(currentActivity)
  444.   if currentActivity then
  445.     miningStatus.activity = currentActivity
  446.     Logger.log('miningBoss', currentActivity)
  447.   else
  448.     miningStatus.activity = mining.status
  449.   end
  450.   miningStatus.count = Util.size(miners)
  451.  
  452.   if UI.pager:getCurrentPage().statusBar then
  453.     UI.pager:getCurrentPage().statusBar:draw()
  454.   end
  455. end
  456.  
  457. optionsPage = UI.Page({
  458.   titleBar = UI.TitleBar({
  459.     title = 'Mining Options',
  460.     previousPage = true
  461.   }),
  462.   form = UI.Form({
  463.     fields = {
  464.       { label = 'Chunks', key = 'chunks',display = UI.Form.D.entry, limit = 4,
  465.         help = 'Number of chunks to mine' },
  466.       { label = 'Depth', key = 'depth', display = UI.Form.D.entry, limit = 3,
  467.         help = 'Leave blank for unlimited' },
  468.       { label = 'Fill top hole', key = 'plug', display = UI.Form.D.chooser,
  469.         choices = {
  470.           { name = 'No', value = 'n' },
  471.           { name = 'Yes', value = 'y' },
  472.         },
  473.         width = 7, help = '' },
  474.       { text = 'Accept', event = 'accept', display = UI.Form.D.button,
  475.           x = 5, y = 5, width = 10 },
  476.       { text = 'Cancel', event = 'cancel', display = UI.Form.D.button,
  477.           x = 19, y = 5, width = 10 }
  478.     },
  479.     labelWidth = 18,
  480.     valueWidth = UI.term.width-18,
  481.     x = 5,
  482.     y = 4,
  483.     height = UI.term.height-4
  484.   }),
  485.   statusBar = UI.StatusBar()
  486. })
  487.  
  488. function optionsPage:enable()
  489.   local t = {
  490.     plug = 'n',
  491.     depth = '',
  492.     chunks = '',
  493.   }
  494.  
  495.   if mining.plug then
  496.     t.plug = 'y'
  497.   end
  498.   if mining.depth ~= 9000 then
  499.     t.depth = tostring(mining.depth)
  500.   end
  501.   if mining.chunks ~= -1 then
  502.     t.chunks = tostring(mining.chunks)
  503.   end
  504.  
  505.   self.form:setValues(t)
  506.   self:focusFirst()
  507. end
  508.  
  509. function optionsPage:eventHandler(event)
  510.  
  511.   if event.type == 'focus_change' then
  512.     self.statusBar:setStatus(event.focused.help)
  513.     self.statusBar:draw()
  514.  
  515.   elseif event.type == 'cancel' then
  516.     UI.pager:setPreviousPage()
  517.  
  518.   elseif event.type == 'accept' then
  519.     local values = self.form.values
  520.  
  521.     values.chunks = tonumber(values.chunks)
  522.     if not values.chunks or not tonumber(values.chunks) then
  523.       values.chunks = -1
  524.     end
  525.  
  526.     values.depth = tonumber(values.depth)
  527.     if not values.depth or not tonumber(values.depth) then
  528.       values.depth = 9000
  529.     end
  530.  
  531.     if values.plug == 'Y' or values.plug == 'y' then
  532.       values.plug = true
  533.     else
  534.       values.plug = false
  535.     end
  536.  
  537.     mining.depth = values.depth
  538.     mining.chunks = values.chunks
  539.     mining.plug = values.plug
  540.  
  541.     UI.pager:setPreviousPage()
  542.   end
  543.  
  544.   return UI.Page.eventHandler(self, event)
  545. end
  546.  
  547.  
  548. --[[ -- menuPage -- ]]--
  549. menuPage = UI.Page({
  550.   titleBar = UI.TitleBar({
  551.     title = 'Turtle Mining Swarm ' .. miningStatus.version
  552.   }),
  553.   menu = UI.Menu({
  554.     centered = true,
  555.     y = 4,
  556.     width = UI.term.width,
  557.     menuItems = {
  558.       { prompt = 'Deploy Miners', event = 'deploy' },
  559.       { prompt = 'Options',       event = 'options' },
  560.       { prompt = 'Stop mining',   event = 'stop' },
  561.       { prompt = 'Status',        event = 'status' },
  562.       { prompt = 'Help',          event = 'help' },
  563.       { prompt = 'Quit',          event = 'quit' }
  564.     }
  565.   }),
  566. --[[
  567.   progressWindow = UI.Window({
  568.     x = UI.term.width - 20,
  569.     y = 3,
  570.     width = 18,
  571.     height = 9,
  572.     backgroundColor = colors.blue,
  573.     totalHolesProgressBar = UI.VerticalMeter({
  574.       y = 7,
  575.       x = 2,
  576.       height = 8
  577.     }),
  578.     chunkHolesProgressBar = UI.VerticalMeter({
  579.       y = 10,
  580.       x = 2,
  581.       height = 8
  582.     }),
  583.   }),
  584. --]]
  585.   statusBar = UI.StatusBar({
  586.     columns = {
  587.       { '', 'activity', UI.term.width - 13 },
  588.       { '', 'fuel', 10 }
  589.     },
  590.     status = miningStatus
  591.   })
  592. })
  593.  
  594. function menuPage:enable()
  595.   local fuel = turtle.getFuelLevel()
  596.   if fuel > 9999 then
  597.     miningStatus.fuel = string.format('Fuel: %dk', math.floor(fuel/1000))
  598.   else
  599.     miningStatus.fuel = 'Fuel: ' .. turtle.getFuelLevel()
  600.   end
  601. end
  602.  
  603. function menuPage:eventHandler(event)
  604.   if event.type == 'deploy' then
  605.     if releaseMiners() then
  606.       setStatus('mining')
  607.       UI.pager:setPage('status')
  608.       if not TL2.getNamedLocation('chunkloader2') then
  609.         TL2.gotoNamedLocation('chunkborder')
  610.         TL2.placeUp(1)
  611.         TL2.saveNamedLocation('chunkloader2', 0, 0, 0)
  612.         Message.broadcast('alive')
  613.       end
  614.     end
  615.  
  616.   elseif event.type == 'options' then
  617.     UI.pager:setPage('options')
  618.  
  619.   elseif event.type == 'stop' then
  620.     if mining.status == 'mining' then
  621.       UI.pager:setPage('status')
  622.       setStatus('recalling')
  623.     end
  624.  
  625.   elseif event.type == 'status' then
  626.     UI.pager:setPage('status')
  627.  
  628.   elseif event.type == 'help' then
  629.     showSetup()
  630.  
  631.   elseif event.type == 'quit' then
  632.     Event.exitPullEvents()
  633.     return true
  634.   end
  635.  
  636.   return UI.Page.eventHandler(self, event)
  637. end
  638.  
  639. --[[ -- statusPage -- ]]--
  640. statusPage = UI.Page({
  641.   grid = UI.Grid({
  642.     columns = {
  643.       { 'Name', 'name', 13 },
  644.       { 'Fuel', 'fuelD', 5 },
  645.       { 'Depth', 'depthD', 6 },
  646.       --{ 'Coords', 'coordsD', 6 },
  647.       { 'Time', 'timeD', 5 },
  648.       { '', 'statusD', 4 }
  649.     },
  650.     sortColumn = 'name',
  651.     pageSize = 11,
  652.     width = UI.term.width,
  653.     t = miners,
  654.     sizeMethod = 'count'
  655.   }),
  656.   statusBar = UI.StatusBar({
  657.     columns = {
  658.       { '', 'activity', UI.term.width - 13 },
  659.       { '', 'prompt', 10 }
  660.     },
  661.     --backgroundColor = colors.blue,
  662.     status = miningStatus
  663.   })
  664. })
  665.  
  666. function statusPage:eventHandler(event)
  667.   if event.type == 'key' then
  668.     if event.key == 'm' or event.key == 'q' or event.key == 'enter' then
  669.       UI.pager:setPage('menu')
  670.       return true
  671.     end
  672.   end
  673.   return self.grid:eventHandler(event)
  674. end
  675.  
  676. function statusPage:draw()
  677.   for _,miner in pairs(miners) do
  678.     if miner.timestamp then
  679.       miner.timeD =
  680.         string.format("%ds",
  681.         math.floor(os.clock()-miner.timestamp))
  682.     end
  683.   end
  684.   self.grid:draw()
  685.   self.statusBar:draw()
  686. end
  687.  
  688. --[[-- errorPage --]]--
  689. errorPage = UI.Page({
  690.   titleBar = UI.TitleBar({
  691.     title = 'Configuration'
  692.   }),
  693.   window = UI.Window({
  694.     y = 2,
  695.     height = UI.term.height -1,
  696.     backgroundColor = colors.blue,
  697.     focus = function() end
  698.   })
  699. })
  700.  
  701. function errorPage:draw()
  702.   self.titleBar:draw()
  703.   self.window:draw()
  704.   self.window:setCursorPos(1, 1)
  705.   self.window:print(self.message)
  706.   self.window:setCursorPos(1, 3)
  707.   self.window:print('Slot 1: 2 chunk loaders')
  708.   self.window:print('Slot 2: ender chests ')
  709.   self.window:print('Slot 3 through x: resources that should not be mined')
  710.   self.window:print('Place turtles in the remainder of the slots immediately following the resources')
  711.   self.window:print('')
  712.   self.window:print('Press any key to continue')
  713. end
  714.  
  715. function errorPage:eventHandler(event)
  716.   if event.type == 'key' then
  717.     UI.pager:setPage('menu')
  718.   end
  719. end
  720.  
  721. -- tell the miner to unload chest, go to pickup plane and report when ready
  722. function pickupCommand(miner)
  723.   if miner.xStatus ~= 'pickup' then
  724.     TLC.sendActions(miner.id, {
  725.       { action = 'enderChestUnload', args = { slots = mining.firstTurtle-2 }},
  726.       { action = 'gotoZ', args = {
  727.           z = 0, digStrategy = 'cautious', mode = 'destructive' }},
  728.       { action = 'status' }
  729.     })
  730.     miner.xStatus = 'pickup'
  731.   end
  732. end
  733.  
  734. function shutdownCommand(miner)
  735.   miners[miner.id] = nil
  736.   TLC.sendActions(miner.id, {
  737.     { action = 'enderChestUnload', args = { slots = mining.firstTurtle-2 }},
  738.     { action = 'shutdown' }
  739.   })
  740. end
  741.  
  742. function transferFuelCommand(miner)
  743.   TLC.sendActions(miner.id, {
  744.     { action = 'gotoZ', args = {
  745.           z = 0, digStrategy = 'cautious', mode = 'destructive' }},
  746.     { action = 'transferFuel' }
  747.   })
  748. end
  749.  
  750. function boreCommand(miner)
  751.   local bore = getClosestLocation(mining.locations,
  752.      miner.location)
  753.  
  754.   mining.holes = mining.holes + 1 -- for status monitor only
  755.  
  756.   miner.location.x = bore.x
  757.   miner.location.y = bore.y
  758.   miner.xStatus = 'busy'
  759.  
  760.   local actions = {
  761.     { action = 'gotoZ', args = {
  762.         z = -1, -- make sure he is on the correct plane
  763.         digStrategy = 'cautious',
  764.         mode = 'destructive' }},
  765.     { action = 'plug', args = {
  766.         plug = mining.plug }},
  767.     { action = 'goto', args = {
  768.         x = bore.x, y = bore.y, z = -1,
  769.         digStrategy = 'cautious',
  770.         mode = 'destructive' }},
  771.     { action = 'gotoZ', args = {
  772.         z = -2,
  773.         digStrategy = 'cautious',
  774.         mode = 'destructive' }},
  775.     { action = 'bore', args = {
  776.         depth = mining.depth }},
  777.     { action = 'status' }
  778.   }
  779.   TLC.sendActions(miner.id, actions)
  780.   saveState()
  781. end
  782.  
  783. TLC.registerAction('refuelFromMiner', function(miner)
  784.   local slot = TL2.selectOpenSlot()
  785.   if slot and TL2.pointInBox(miner.location, getChunkLoadedBox()) then
  786.     showActivity('Refueling')
  787.     TL2.goto(miner.location.x, miner.location.y)
  788.     transferFuelCommand(miner)
  789.     Event.waitForEvent('turtle_inventory', 3)
  790.     turtle.select(slot)
  791.     turtle.refuel(64)
  792.     if menuPage.enabled then
  793.       menuPage:draw()
  794.     end
  795.   end
  796.   return true
  797. end)
  798.  
  799. TLC.registerAction('rescueMiner', function(miner)
  800.  
  801.   --if miner.xStatus ~= 'lost' then
  802.     --return true
  803.   --end
  804.  
  805.   local distance
  806.   Util.tryTimes(5, function()
  807.     Message.send(miner.id, 'alive')
  808.      _,_,_,distance = Message.waitForMessage('isAlive', 1, miner.id)
  809.     return distance
  810.   end)
  811.  
  812.   if not distance then
  813.     if not miner.retryCount then
  814.       miner.retryCount = 0
  815.     end
  816.     miner.retryCount = miner.retryCount + 1
  817.     if miner.retryCount > 3 then
  818.       shutdownCommand(miner)
  819.       Logger.log('miningBoss', 'shutting down ' .. miner.id .. ' unable to locate')
  820.     else
  821.       queueWork(3,  'rescue-' .. miner.id, 'rescueMiner', miner)
  822.     end
  823.     return true
  824.   end
  825.  
  826.   local boundingBox = getChunkLoadedBox()
  827.  
  828.   showActivity('Rescuing turtle')
  829.   --pager:setPage('status')
  830.   local location = TLC.tracker(miner.id, distance, true, boundingBox)
  831.   if location then
  832.     -- miners start out 1 below boss plane
  833.     location.z = - location.z + 1
  834.     TLC.sendAction(miner.id, 'setLocation', location)
  835.   end
  836.  
  837.   if not location or (args.fuel == 0 and location.z ~= 0) then
  838.     -- not in a place we can pick up (don't want to leave plane)
  839.     shutdownCommand(miner)
  840.     Logger.log('miningBoss', 'shutting down ' .. miner.id .. ' no fuel or too far away')
  841.   else
  842.     miner.xStatus = 'lost'
  843.     pickupCommand(miner)
  844.   end
  845.  
  846.   UI.pager:getCurrentPage():draw()
  847.   Message.broadcast('alive')
  848.  
  849.   return true
  850. end)
  851.  
  852. Message.addHandler('turtleStatus', function(h, id, msg, distance)
  853.  
  854.   local status = msg.contents
  855.  
  856. --Logger.log(string.format('%d %s', id, status.status))
  857.  
  858.   local miner = miners[id]
  859.  
  860. --if miner then
  861. --Logger.log('xstatus: ' .. miner.xStatus)
  862. --end
  863.  
  864.   if not miner then
  865.     Message.send(id, 'alive')
  866.     if mining.status ~= 'mining' or status.status ~= 'idle' then
  867.       return
  868.     end
  869.     miners[id] = {
  870.       id = id,
  871.       xStatus = 'lost',
  872.       lastLocation = { x = 0, y = 0, z = 0 }
  873.     }
  874.     miner = miners[id]
  875.     if status.x == 0 and status.y == 0 and status.z == 0 then
  876.       queueWork(3,  'rescue-' .. miner.id, 'rescueMiner', miner)
  877.     elseif TL2.pointInBox(status, getChunkLoadedBox()) then
  878.       miner.name = tostring(status.name)
  879.       miner.status = status.status
  880.       miner.fuel = status.fuel
  881.       miner.location = { x = status.x, y = status.y, z = status.z }
  882.       pickupCommand(miner)
  883.       return
  884.     else
  885.       queueWork(3,  'rescue-' .. miner.id, 'rescueMiner', miner)
  886.     end
  887.   end
  888.  
  889.   miner.name = tostring(status.name)
  890.   miner.status = status.status
  891.   miner.fuel = status.fuel
  892.   miner.location = { x = status.x, y = status.y, z = status.z }
  893.  
  894.   if miner.xStatus == 'new' then
  895.     -- just deployed
  896.     TLC.sendAction(id, 'setLocation', miner.deployLocation)
  897.     miner.location = miner.deployLocation
  898.     miner.xStatus = 'deployed' -- needed ?? shouldn't it be 'idle'
  899.   end
  900.  
  901.   if miner.xStatus == 'lost' then
  902.     -- ignore
  903.  
  904.   elseif miner.xStatus == 'pickup' then
  905.     if not pickupQueue[miner.id] then
  906.       pickupQueue[miner.id] = miner
  907.       queueWork(50, 'checkMiners', 'checkMiners')
  908.     end
  909.  
  910.   elseif miner.status == 'idle' then
  911.  
  912.     if mining.status == 'recalling' then
  913.       pickupCommand(miner)
  914.  
  915.     elseif mining.status == 'mining' and TL2.getState().status == 'idle' then
  916.  
  917.       if msg.contents.fuel > 1024 then
  918.         if #mining.locations == 0 then
  919.           if not isQueued('moveChunkLoaders') then
  920.             queueWork(75, 'moveChunkLoaders', 'moveChunkLoaders')
  921.           end
  922.         else
  923.           if turtle.getFuelLevel() < 15000 and status.reserveFuel > 0 then
  924.             TLC.performAction('refuelFromMiner', miner)
  925.           end
  926.           boreCommand(miner)
  927.         end
  928.       else
  929.         Logger.log('miningBoss', 'miner ' .. miner.id .. ' low fuel')
  930.         pickupCommand(miner)
  931.       end
  932.     end
  933.   end
  934.   updateStatus(miner, status)
  935. --Logger.log('end xstatus: ' .. miner.xStatus)
  936. end)
  937.  
  938. function placeChunkLoader(direction, x, y, heading)
  939.   if direction == 'up' then
  940.     TL2.goto(x, y, 0)
  941.     TL2.placeUp(1)
  942.     TL2.saveNamedLocation('chunkloader1', x, y, 0)
  943.   else
  944.     TL2.goto(x, y, 0, heading)
  945.     TL2.up()
  946.     TL2.place(1)
  947.     TL2.down()
  948.     local heading = TL2.getHeadingInfo()
  949.     TL2.saveNamedLocation('chunkloader2',
  950.       x + heading.xd, y + heading.yd, 0)
  951.   end
  952. end
  953.  
  954. function collectChunkLoaders()
  955.   showActivity('Collecting chunk loaders')
  956.   if TL2.gotoNamedLocation('chunkloader1') then
  957.     TL2.emptySlot(1)
  958.     TL2.select(1)
  959.     turtle.digUp()
  960.   end
  961.   if TL2.gotoNamedLocation('chunkloader2') then
  962.     TL2.select(1)
  963.     turtle.digUp()
  964.   end
  965.   showActivity()
  966. end
  967.  
  968. function outsideBox(c, box)
  969.   return c.x < box.ax or c.y < box.ay or c.x > box.bx or c.y > box.by
  970. end
  971.  
  972. function collectChunkLoader(namedLocation)
  973.   local c = TL2.getNamedLocation(namedLocation)
  974.   if c then
  975.     local cb = TL2.getNamedLocation('chunkborder')
  976.     local b = { ax = cb.x, ay = cb.y, bx = cb.x + 15, by = cb.y + 15 }
  977.     if outsideBox(c, b) then
  978.       local x, y = c.x, c.y
  979.       if c.x < cb.x then
  980.         x = math.max(c.x, cb.x)
  981.       elseif c.x > cb.x+15 then
  982.         x = math.min(c.x, cb.x+15)
  983.       end
  984.       if c.y < cb.y then
  985.         y = math.max(c.y, cb.y)
  986.       elseif c.y > cb.y+15 then
  987.         y = math.min(c.y, cb.y+15)
  988.       end
  989.       TL2.goto(x, y)
  990.       TL2.headTowards(c)
  991.       TL2.up()
  992.       TL2.select(1)
  993.       turtle.dig()
  994.       TL2.down()
  995.     else
  996.       TL2.gotoPoint(c)
  997.       TL2.select(1)
  998.       turtle.digUp()
  999.     end
  1000.   end
  1001. end
  1002.  
  1003. TLC.registerAction('setLocation', function()
  1004.   Logger.log('miningBoss', 'ignoring set location')
  1005.   return true
  1006. end)
  1007.  
  1008. -- rise above to eliminate the rednet deadzone
  1009. TLC.registerAction('gps', function()
  1010.  
  1011.   -- become a mini-gps
  1012.   Message.broadcast('alive')
  1013.  
  1014.   local x, y = getCornerOf(TL2.getNamedLocation('chunkborder'))
  1015.  
  1016.   local function broadcastPosition()
  1017.     local pt = TL2.createPoint(TL2.getState())
  1018.     pt.z = pt.z + 1
  1019.     Message.broadcast('position', pt)
  1020.   end
  1021.  
  1022.   TL2.goto(x + 8, y + 5, 86)
  1023.   -- drop down a couple of blocks in case we hit a bedrock roof (nether)
  1024.   TL2.gotoZ(TL2.getState().z - 4)
  1025.   local z = TL2.getState().z
  1026.   broadcastPosition()
  1027.  
  1028.   TL2.goto(x + 8, y + 11, z)
  1029.   broadcastPosition()
  1030.  
  1031.   TL2.goto(x + 5, y + 8, z-1)
  1032.   broadcastPosition()
  1033.  
  1034.   TL2.goto(x + 11, y + 8, z-1)
  1035.   broadcastPosition()
  1036.  
  1037.   queueWork(2,  'descend', 'descend')
  1038.   mining.status = 'mining'
  1039.   return true
  1040. end)
  1041.  
  1042. TLC.registerAction('descend', function()
  1043.   if TL2.getState().z ~= 0 then
  1044.     local x, y = getCornerOf(TL2.getNamedLocation('chunkborder'))
  1045.     TL2.goto(x + 8, y + 8, 0)
  1046.   end
  1047.   return true
  1048. end)
  1049.  
  1050. TLC.registerAction('moveChunkLoaders', function()
  1051.   showActivity('Moving chunk loaders')
  1052.  
  1053.   local x, y = getCornerOf(TL2.getNamedLocation('chunkborder'))
  1054.   local points = math.pow(mining.diameter, 2) - math.pow(mining.diameter-2, 2)
  1055.   mining.chunkIndex = mining.chunkIndex + 1
  1056.  
  1057.   if mining.chunkIndex >= points then
  1058.     mining.diameter = mining.diameter + 2
  1059.     mining.chunkIndex = 0
  1060.   end
  1061.  
  1062.   if mining.chunks ~= -1 then
  1063.     local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex
  1064.     if chunks >= mining.chunks then
  1065.       setStatus('recalling')
  1066.       return true
  1067.     end
  1068.   end
  1069.  
  1070.   local nc = getChunkCoordinates(mining.diameter, mining.chunkIndex, x, y)
  1071.  
  1072.   local cl1 = { x = x + nc.xd * 15, y = y + nc.yd * 15 }
  1073.   local cl2 = { x = x + nc.xd * 15, y = y + nc.yd * 15 }
  1074.  
  1075.   -- brute force calculations
  1076.   if nc.heading == 0 then
  1077.     cl1.y = cl1.y + 1
  1078.   elseif nc.heading == 1 then
  1079.     cl1.x = cl1.x + 1
  1080.   elseif nc.heading == 2 then
  1081.     cl1.x = nc.x + 16
  1082.     cl1.y = nc.y
  1083.     cl2.x = nc.x + 16
  1084.     cl2.y = nc.y + 1
  1085.   elseif nc.heading == 3 then
  1086.     cl1.x = nc.x + 15
  1087.     cl1.y = nc.y + 16
  1088.     cl2.x = nc.x + 14
  1089.     cl2.y = nc.y + 16
  1090.   end
  1091.  
  1092.   -- collect prev chunk's loader
  1093.   collectChunkLoader('chunkloader1')
  1094.  
  1095.   local ocl = TL2.getNamedLocation('chunkloader2')
  1096.   if cl1.x == ocl.x and cl1.y == ocl.y then
  1097.     -- turning a corner - no need to move 2nd chunk loader
  1098.     TL2.saveNamedLocation('chunkloader1', cl1.x, cl1.y, 0)
  1099.   else
  1100.     -- place at edge of loaded chunk
  1101.     -- now 2 in chunk
  1102.     placeChunkLoader('up', cl1.x, cl1.y)
  1103.  
  1104.       -- get the first one
  1105.     collectChunkLoader('chunkloader2')
  1106.   end
  1107.  
  1108.   -- place in next chunk
  1109.   placeChunkLoader('front', cl2.x, cl2.y, nc.heading)
  1110.  
  1111.   mining.locations = getBoreLocations(nc.x, nc.y)
  1112.  
  1113.   -- enter next chunk
  1114.   TL2.gotoPoint(TL2.getNamedLocation('chunkloader2'))
  1115.   TL2.saveNamedLocation('chunkborder', nc.x, nc.y, 0, 0)
  1116.   queueWork(50, 'checkMiners', 'checkMiners')
  1117.   return true
  1118. end)
  1119.  
  1120. -- clear state every time we move
  1121. -- if the server shuts down during movement, we cannot resume
  1122. -- 99.9% of the time, we should be idle
  1123. TLC.registerAction('clearState', function()
  1124.   fs.delete('mining.state')
  1125.   return true
  1126. end)
  1127. TLC.actionChainStart('clearState')
  1128.  
  1129. function saveState()
  1130.   local state = TL2.getState()
  1131.   mining.x = state.x
  1132.   mining.y = state.y
  1133.   mining.heading = state.heading
  1134.   mining.namedLocations = TL2.getMemory().locations
  1135.  
  1136.   fs.delete('mining.state')
  1137.   Util.writeTable('mining.state', mining)
  1138. end
  1139.  
  1140. TLC.registerAction('saveState', function()
  1141.   saveState()
  1142.   showActivity()
  1143.   return true
  1144. end)
  1145. TLC.actionChainEnd('saveState')
  1146.  
  1147. TLC.registerAction('checkMiners', function()
  1148.  
  1149.   local chunkborder = TL2.getNamedLocation('chunkborder')
  1150.  
  1151.   -- pickup any that are ready
  1152.   if Util.size(pickupQueue) > 0 then
  1153.     if TL2.selectOpenSlot(3) or mining.status == 'recalling' then
  1154.       for _,miner in pairs(pickupQueue) do
  1155.         -- pickup any miners in the deployment location
  1156.         if miner.location.x == chunkborder.x and miner.location.y == chunkborder.y then
  1157.           miner.distance = 0
  1158.         else
  1159.           miner.distance = TL2.calculateDistance(TL2.getState(), miner.location)
  1160.         end
  1161.       end
  1162.       local k,miner = Util.first(pickupQueue,
  1163.         function(a,b) return a.distance < b.distance end)
  1164.       table.remove(pickupQueue, k)
  1165.       collectMiner(miner)
  1166.       queueWork(50, 'checkMiners', 'checkMiners')
  1167.       return true
  1168.     end
  1169.   end
  1170.  
  1171.   return true
  1172. end)
  1173.  
  1174. Message.addHandler('getMiningStatus', function(h, id, msg)
  1175.   Message.send(id, 'miningStatus', { state = mining, status = miningStatus })
  1176. end)
  1177.  
  1178. function enableWirelessLogging()
  1179.   Message.broadcast('logClient')
  1180.   local _, id = Message.waitForMessage('logServer', 1)
  1181.   if not id then
  1182.     return false
  1183.   end
  1184.   Logger.setWirelessLogging(id)
  1185.   return true
  1186. end
  1187.  
  1188. function resume()
  1189.   if not fs.exists('mining.state') then
  1190.     return false
  1191.   end
  1192.  
  1193.   local tmining = Util.readTable('mining.state')
  1194.  
  1195.   if not tmining.namedLocations or
  1196.      not tmining.x or
  1197.      Util.size(tmining.namedLocations) == 0 then
  1198.     return false
  1199.   end
  1200.  
  1201.   local state = TL2.getState()
  1202.  
  1203.   state.x = tmining.x
  1204.   state.y = tmining.y
  1205.   state.heading = tmining.heading
  1206.   TL2.getMemory().locations = tmining.namedLocations
  1207.   mining = tmining
  1208.   miningStatus.activity = 'Resuming mining'
  1209.   -- release any miners that we did not release previously
  1210.   if mining.status == 'mining' then
  1211.     --releaseMiners()
  1212.     UI.pager:setPage('status')
  1213.   end
  1214.   queueWork(1, 'gps', 'gps')
  1215.   mining.status = 'resuming'
  1216.  
  1217.   return true
  1218. end
  1219.  
  1220. function getWork()
  1221.   local k,work = Util.first(workQueue, function(a,b) return a.priority < b.priority end)
  1222.   if k then
  1223.     workQueue[k] = nil
  1224.     return work
  1225.   end
  1226. end
  1227.  
  1228. Event.addHandler('workReady', function()
  1229.   if TL2.getState().status ~= 'idle' then
  1230.     Logger.log('miningBoss', 'busy')
  1231.     Event.queueTimedEvent('endTimer', 2.5, 'workReady')
  1232.     return
  1233.   end
  1234.  
  1235.   Logger.log('miningBoss', 'Queue size: ' .. Util.size(workQueue))
  1236.   local work = getWork()
  1237.   if work then
  1238.     TLC.performAction(work.action, work.actionArgs)
  1239.     if Util.size(workQueue) > 0 then
  1240.       Event.queueTimedEvent('endTimer', 2.5, 'workReady')
  1241.     end
  1242.   end
  1243. end)
  1244.  
  1245. function isQueued(name)
  1246.   return workQueue[name]
  1247. end
  1248.  
  1249. function queueWork(priority, name, action, actionArgs)
  1250.   if not workQueue[name] then
  1251.     workQueue[name] = {
  1252.       priority = priority,
  1253.       name = name,
  1254.       action = action,
  1255.       actionArgs = actionArgs
  1256.     }
  1257.   end
  1258.   Event.queueTimedEvent('endTimer', 2.5, 'workReady')
  1259.   Logger.log('miningBoss', 'Queuing: ' .. name)
  1260. end
  1261.  
  1262. TL2.setMode('destructive')
  1263. TL2.setDigStrategy('wasteful')
  1264.  
  1265. UI.pager:setPages({
  1266.   [ 'menu'    ] = menuPage,
  1267.   [ 'status'  ] = statusPage,
  1268.   [ 'options' ] = optionsPage,
  1269.   [ 'error'   ] = errorPage
  1270. })
  1271.  
  1272. if not Util.getOptions(options, args) then
  1273.   return
  1274. end
  1275.  
  1276. mining.depth = options.depth.value
  1277. mining.chunks = options.chunks.value
  1278. mining.plug = options.plug.value
  1279.  
  1280. if turtle.getFuelLevel() < 1000 then
  1281.   error('Add at least 1000 fuel before starting')
  1282. end
  1283.  
  1284. Logger.disable()
  1285. UI.pager:setPage('menu')
  1286.  
  1287. if not resume() then
  1288.   -- set the reference plane to 2 above deployment location
  1289.   TL2.getState().z = -2
  1290.   TL2.saveNamedLocation('chunkborder', 0, 0, 0, 0)
  1291.   mining.locations = getBoreLocations(0, 0)
  1292. end
  1293.  
  1294. Logger.filter('event', 'rednet_send', 'rednet_receive', 'debug')
  1295.  
  1296. if options.logMethod.value == 'wireless' then
  1297.   Message.enableWirelessLogging()
  1298. elseif options.logMethod.value == 'file' then
  1299.   Logger.setFileLogging('mine.log')
  1300. elseif options.logMethod.value == 'screen' then
  1301.   Logger.setScreenLogging()
  1302. end
  1303.  
  1304. TLC.pullEvents('miningBoss')
  1305.  
  1306. fs.delete('mining.state')
  1307. collectChunkLoaders()
  1308. TL2.gotoZ(-2)
  1309. --TL2.goto(0, 0, -2, 0)
  1310. UI.term:reset()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement