InertiaImpact icon

mastermine.lua

InertiaImpact | PRO | 03/08/25 03:55:45 AM UTC (Edited) | 0 ⭐ | 7402 👁️ | Never ⏰ | []
Lua |

120.75 KB

|

None

|

0 👍

/

0 👎

output_dir = ...
if not output_dir then
    output_dir = ''
end
path = shell.resolve(output_dir)
if not fs.isDir(path) then
    error(path .. ' is not a directory')
end
 
files = {
    ["LICENSE"] = [===[MIT License
 
Copyright (c) 2020 MerlinLikeTheWizard
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.]===],
    ["README.md"] = [===[# Mastermine
A fully automated strip mining network for ComputerCraft turtles!
 
Here's all the code for anyone who is interested! Check out the tutorial below for installation instructions.
 
Also, here are steps for a quick install via pastebin:
 
1. Place your advanced computer next to a disk drive with a blank disk in.
2. Run `pastebin get CtcSGkpc mastermine.lua`
3. Run `mastermine disk`
4. Run `disk/hub.lua`
 
## Play with or without PeripheralsPlusOne
 
I highly recommend using the PeripheralsPlusOne and its chunky turtles, but upon popular request I added the ability to disable the need for chunky turtle pairs. Just go to the config and set `use_chunky_turtles = false`
 
## Video description:
 
[![https://www.youtube.com/watch?v=2I2VXl9Pg6Q](https://img.youtube.com/vi/2I2VXl9Pg6Q/0.jpg)](https://www.youtube.com/watch?v=2I2VXl9Pg6Q)
 
## Video tutorial:
 
[![https://www.youtube.com/watch?v=2DTP1LXuiCg](https://img.youtube.com/vi/2DTP1LXuiCg/0.jpg)](https://www.youtube.com/watch?v=2DTP1LXuiCg)
 
## User commands:
 
* `on/go`
* `off/stop`
* `turtle <#> <action>`
* `update <#>`
* `reboot <#>`
* `shutdown <#>`
* `reset <#>`
* `clear <#>`
* `halt <#>`
* `return <#>`
* `hubupdate`
* `hubreboot`
* `hubshutdown`
 
 
use `*` as notation for all turtles
 
 
## Required mods:
 
https://www.curseforge.com/minecraft/mc-mods/cc-tweaked
 
https://github.com/rolandoislas/PeripheralsPlusOne
 
Required by PeripheralsPlusOne: https://www.curseforge.com/minecraft/mc-mods/the-framework]===],
    ["hub.lua"] = [===[if fs.exists('/disk/hub_files/session_id') then
    fs.delete('/disk/hub_files/session_id')
end
if fs.exists('/session_id') then
    fs.copy('/session_id', '/disk/hub_files/session_id')
end
if fs.exists('/disk/hub_files/mine') then
    fs.delete('/disk/hub_files/mine')
end
if fs.exists('/mine') then
    fs.copy('/mine', '/disk/hub_files/mine')
end
 
for _, filename in pairs(fs.list('/')) do
    if filename ~= 'rom' and filename ~= 'disk' and filename ~= 'openp' and filename ~= 'ppp' and filename ~= 'persistent' then
        fs.delete(filename)
    end
end
for _, filename in pairs(fs.list('/disk/hub_files')) do
    fs.copy('/disk/hub_files/' .. filename, '/' .. filename)
end
os.reboot()]===],
    ["pocket.lua"] = [===[local src, dest = ...
 
fs.copy(fs.combine(src, 'pocket_files/update'), fs.combine(dest, 'update'))
file = fs.open(fs.combine(dest, 'hub_id'), 'w')
file.write(os.getComputerID())
file.close()]===],
    ["turtle.lua"] = [===[for _, filename in pairs(fs.list('/')) do
    if filename ~= 'rom' and filename ~= 'disk' and filename ~= 'openp' and filename ~= 'ppp' and filename ~= 'persistent' then
        fs.delete(filename)
    end
end
 
for _, filename in pairs(fs.list('/disk/turtle_files')) do
    fs.copy('/disk/turtle_files/' .. filename, '/' .. filename)
end
 
print("Enter ID of Hub computer to link to: ")
hub_id = tonumber(read())
if hub_id == nil then
    error("Invalid ID")
end
 
file = fs.open('/hub_id', 'w')
file.write(hub_id)
file.close()
 
print("Linked")
 
sleep(1)
os.reboot()]===],
    ["turtle_files/actions.lua"] = [===[inf = basics.inf
str_xyz = basics.str_xyz
 
--lua_print = print
--log_file = fs.open('log.txt', 'w')
--function print(thing)
--    lua_print(thing)
--    log_file.writeLine(thing)
--end
    
 
bumps = {
    north = { 0,  0, -1},
    south = { 0,  0,  1},
    east  = { 1,  0,  0},
    west  = {-1,  0,  0},
}
 
 
left_shift = {
    north = 'west',
    south = 'east',
    east  = 'north',
    west  = 'south',
}
 
 
right_shift = {
    north = 'east',
    south = 'west',
    east  = 'south',
    west  = 'north',
}
 
 
reverse_shift = {
    north = 'south',
    south = 'north',
    east  = 'west',
    west  = 'east',
}
 
 
move = {
    forward = turtle.forward,
    up      = turtle.up,
    down    = turtle.down,
    back    = turtle.back,
    left    = turtle.turnLeft,
    right   = turtle.turnRight
}
 
 
detect = {
    forward = turtle.detect,
    up      = turtle.detectUp,
    down    = turtle.detectDown
}
 
 
inspect = {
    forward = turtle.inspect,
    up      = turtle.inspectUp,
    down    = turtle.inspectDown
}
 
 
dig = {
    forward = turtle.dig,
    up      = turtle.digUp,
    down    = turtle.digDown
}
 
attack = {
    forward = turtle.attack,
    up      = turtle.attackUp,
    down    = turtle.attackDown
}
 
 
getblock = {
    
    up = function(pos, fac)
        if not pos then pos = state.location end
        if not fac then fac = state.orientation end
        return {x = pos.x, y = pos.y + 1, z = pos.z}
    end,
 
    down = function(pos, fac)
        if not pos then pos = state.location end
        if not fac then fac = state.orientation end
        return {x = pos.x, y = pos.y - 1, z = pos.z}
    end,
 
    forward = function(pos, fac)
        if not pos then pos = state.location end
        if not fac then fac = state.orientation end
        local bump = bumps[fac]
        return {x = pos.x + bump[1], y = pos.y + bump[2], z = pos.z + bump[3]}
    end,
    
    back = function(pos, fac)
        if not pos then pos = state.location end
        if not fac then fac = state.orientation end
        local bump = bumps[fac]
        return {x = pos.x - bump[1], y = pos.y - bump[2], z = pos.z - bump[3]}
    end,
    
    left = function(pos, fac)
        if not pos then pos = state.location end
        if not fac then fac = state.orientation end
        local bump = bumps[left_shift[fac]]
        return {x = pos.x + bump[1], y = pos.y + bump[2], z = pos.z + bump[3]}
    end,
    
    right = function(pos, fac)
        if not pos then pos = state.location end
        if not fac then fac = state.orientation end
        local bump = bumps[right_shift[fac]]
        return {x = pos.x + bump[1], y = pos.y + bump[2], z = pos.z + bump[3]}
    end,
}
 
 
function digblock(direction)
    dig[direction]()
    return true
end
 
 
function delay(duration)
    sleep(duration)
    return true
end
 
 
function up()
    return go('up')
end
 
 
function forward()
    return go('forward')
end
 
 
function down()
    return go('down')
end
 
 
function back()
    return go('back')
end
 
 
function left()
    return go('left')
end
 
 
function right()
    return go('right')
end
 
 
function follow_route(route)
    for step in route:gmatch'.' do
        if step == 'u' then
            if not go('up')      then return false end
        elseif step == 'f' then
            if not go('forward') then return false end
        elseif step == 'd' then
            if not go('down')    then return false end
        elseif step == 'b' then
            if not go('back')    then return false end
        elseif step == 'l' then
            if not go('left')    then return false end
        elseif step == 'r' then
            if not go('right')   then return false end
        end
    end
    return true
end
                    
                    
function face(orientation)
    if state.orientation == orientation then
        return true
    elseif right_shift[state.orientation] == orientation then
        if not go('right') then return false end
    elseif left_shift[state.orientation] == orientation then
        if not go('left') then return false end
    elseif right_shift[right_shift[state.orientation]] == orientation then
        if not go('right') then return false end
        if not go('right') then return false end
    else
        return false
    end
    return true
end
 
 
function log_movement(direction)
    if direction == 'up' then
        state.location.y = state.location.y +1
    elseif direction == 'down' then
        state.location.y = state.location.y -1
    elseif direction == 'forward' then
        bump = bumps[state.orientation]
        state.location = {x = state.location.x + bump[1], y = state.location.y + bump[2], z = state.location.z + bump[3]}
    elseif direction == 'back' then
        bump = bumps[state.orientation]
        state.location = {x = state.location.x - bump[1], y = state.location.y - bump[2], z = state.location.z - bump[3]}
    elseif direction == 'left' then
        state.orientation = left_shift[state.orientation]
    elseif direction == 'right' then
        state.orientation = right_shift[state.orientation]
    end
    return true
end
 
 
function go(direction, nodig)
    if not nodig then
        if detect[direction] then
            if detect[direction]() then
                safedig(direction)
            end
        end
    end
    if not move[direction] then
        return false
    end
    if not move[direction]() then
        if attack[direction] then
            attack[direction]()
        end
        return false
    end
    log_movement(direction)
    return true
end
 
 
function go_to_axis(axis, coordinate, nodig)
    local delta = coordinate - state.location[axis]
    if delta == 0 then
        return true
    end
    
    if axis == 'x' then
        if delta > 0 then
            if not face('east') then return false end
        else
            if not face('west') then return false end
        end
    elseif axis == 'z' then
        if delta > 0 then
            if not face('south') then return false end
        else
            if not face('north') then return false end
        end
    end
    
    for i = 1, math.abs(delta) do
        if axis == 'y' then
            if delta > 0 then
                if not go('up', nodig) then return false end
            else
                if not go('down', nodig) then return false end
            end
        else
            if not go('forward', nodig) then return false end
        end
    end
    return true
end
 
 
function go_to(end_location, end_orientation, path, nodig)
    if path then
        for axis in path:gmatch'.' do
            if not go_to_axis(axis, end_location[axis], nodig) then return false end
        end
    elseif end_location.path then
        for axis in end_location.path:gmatch'.' do
            if not go_to_axis(axis, end_location[axis], nodig) then return false end
        end
    else
        return false
    end
    if end_orientation then
        if not face(end_orientation) then return false end
    elseif end_location.orientation then
        if not face(end_location.orientation) then return false end
    end
    return true
end
 
 
function go_route(route, xyzo)
    local xyz_string
    if xyzo then
        xyz_string = str_xyz(xyzo)
    end
    local location_str = basics.str_xyz(state.location)
    while route[location_str] and location_str ~= xyz_string do
        if not go_to(route[location_str], nil, 'xyz') then return false end
        location_str = basics.str_xyz(state.location)
    end
    if xyzo then
        if location_str ~= xyz_string then
            return false
        end
        if xyzo.orientation then
            if not face(xyzo.orientation) then return false end
        end
    end
    return true
end
 
 
function go_to_home()
    state.updated_not_home = nil
    if basics.in_area(state.location, config.locations.home_area) then
        return true
    elseif basics.in_area(state.location, config.locations.greater_home_area) then
        if not go_to_home_exit() then return false end
    elseif basics.in_area(state.location, config.locations.waiting_room_area) then
        if not go_to(config.locations.mine_exit, nil, config.paths.waiting_room_to_mine_exit, true) then return false end
    elseif state.location.y < config.locations.mine_enter.y then
        return false
    end
    if config.locations.main_loop_route[basics.str_xyz(state.location)] then
        if not go_route(config.locations.main_loop_route, config.locations.home_enter) then return false end
    elseif basics.in_area(state.location, config.locations.control_room_area) then
        if not go_to(config.locations.home_enter, nil, config.paths.control_room_to_home_enter, true) then return false end
    else
        return false
    end
    if not forward() then return false end
    while detect.down() do
        if not forward() then return false end
    end
    if not down() then return false end
    if not right() then return false end
    if not right() then return false end
    return true
end
 
 
function go_to_home_exit()
    if basics.in_area(state.location, config.locations.greater_home_area) then
        if not go_to(config.locations.home_exit, nil, config.paths.home_to_home_exit) then return false end
    elseif config.locations.main_loop_route[basics.str_xyz(state.location)] then
        if not go_route(config.locations.main_loop_route, config.locations.home_exit) then return false end
    else
        return false
    end
    return true
end
 
 
function go_to_item_drop()
    if not config.locations.main_loop_route[basics.str_xyz(state.location)] then
        if not go_to_home() then return false end
        if not go_to_home_exit() then return false end
    end
    if not go_route(config.locations.main_loop_route, config.locations.item_drop) then return false end
    return true
end
 
 
function go_to_refuel()
    if not config.locations.main_loop_route[basics.str_xyz(state.location)] then
        if not go_to_home() then return false end
        if not go_to_home_exit() then return false end
    end
    if not go_route(config.locations.main_loop_route, config.locations.refuel) then return false end
    return true
end
 
 
function go_to_waiting_room()
    if not basics.in_area(state.location, config.locations.waiting_room_line_area) then
        if not go_to_home() then return false end
    end
    if not go_to(config.locations.waiting_room, nil, config.paths.home_to_waiting_room) then return false end
    return true
end
 
 
function go_to_mine_enter()
    if not go_route(config.locations.waiting_room_to_mine_enter_route) then return false end
    return true
end
 
 
function go_to_strip(strip)
    if state.location.y < config.locations.mine_enter.y or basics.in_location(state.location, config.locations.mine_enter) then
        if state.type == 'mining' then
            local bump = bumps[strip.orientation]
            strip = {
                x = strip.x + bump[1],
                y = strip.y + bump[2],
                z = strip.z + bump[3],
                orientation = strip.orientation
            }
        end
        if not go_to(strip, nil, config.paths.mine_enter_to_strip) then return false end
        return true
    end
end
 
 
function go_to_mine_exit(strip)
    if state.location.y < config.locations.mine_enter.y or (state.location.x == config.locations.mine_exit.x and state.location.z == config.locations.mine_exit.z) then
        if state.location.x == config.locations.mine_enter.x and state.location.z == config.locations.mine_enter.z then
            -- If directly under mine_enter, shift over to exit
            if not go_to_axis('z', config.locations.mine_exit.z) then return false end
        elseif state.location.x ~= config.locations.mine_exit.x or state.location.z ~= config.locations.mine_exit.z then
            -- If NOT directly under mine_exit go to proper y
            if not go_to_axis('y', strip.y + 1) then return false end
            if state.location.z ~= config.locations.mine_enter.z and strip.z ~= config.locations.mine_enter.z then
                -- If not in main_shaft, find your strip
                if not go_to_axis('x', strip.x) then return false end
            end
            if state.location.x ~= config.locations.mine_exit.x then
                -- If not in strip x = origin, go to main_shaft
                if not go_to_axis('z', config.locations.mine_enter.z) then return false end
            end
        end
        if not go_to(config.locations.mine_exit, nil, 'xzy') then return false end
        return true
    end
end
 
 
function safedig(direction)
    -- DIG IF BLOCK NOT ON BLACKLIST
    if not direction then
        direction = 'forward'
    end
    
    local block_name = ({inspect[direction]()})[2].name
    if block_name then
        for _, word in pairs(config.dig_disallow) do
            if string.find(string.lower(block_name), word) then
                return false
            end
        end
 
        return dig[direction]()
    end
    return true
end
 
 
function dump_items(omit)
    for slot = 1, 16 do
        if turtle.getItemCount(slot) > 0 and ((not omit) or (not omit[turtle.getItemDetail(slot).name])) then
            turtle.select(slot)
            if not turtle.drop() then return false end
        end
    end
    return true
end
    
 
 
function prepare(min_fuel_amount)
    if state.item_count > 0 then
        if not go_to_item_drop() then return false end
        if not dump_items(config.fuelnames) then return false end
    end
    local min_fuel_amount = min_fuel_amount + config.fuel_padding
    if not go_to_refuel() then return false end
    if not dump_items() then return false end
    turtle.select(1)
    if turtle.getFuelLevel() ~= 'unlimited' then
        while turtle.getFuelLevel() < min_fuel_amount do
            if not turtle.suck(math.min(64, math.ceil(min_fuel_amount / config.fuel_per_unit))) then return false end
            turtle.refuel()
        end
    end
    return true
end
 
 
function calibrate()
    -- GEOPOSITION BY MOVING TO ADJACENT BLOCK AND BACK
    local sx, sy, sz = gps.locate()
--    if sx == config.interface.x and sy == config.interface.y and sz == config.interface.z then
--        refuel()
--    end
    if not sx or not sy or not sz then
        return false
    end
    for i = 1, 4 do
        -- TRY TO FIND EMPTY ADJACENT BLOCK
        if not turtle.detect() then
            break
        end
        if not turtle.turnRight() then return false end
    end
    if turtle.detect() then
        -- TRY TO DIG ADJACENT BLOCK
        for i = 1, 4 do
            safedig('forward')
            if not turtle.detect() then
                break
            end
            if not turtle.turnRight() then return false end
        end
        if turtle.detect() then
            return false
        end
    end
    if not turtle.forward() then return false end
    local nx, ny, nz = gps.locate()
    if nx == sx + 1 then
        state.orientation = 'east'
    elseif nx == sx - 1 then
        state.orientation = 'west'
    elseif nz == sz + 1 then
        state.orientation = 'south'
    elseif nz == sz - 1 then
        state.orientation = 'north'
    else
        return false
    end
    state.location = {x = nx, y = ny, z = nz}
    print('Calibrated to ' .. str_xyz(state.location, state.orientation))
    
    back()
    
    if basics.in_area(state.location, config.locations.home_area) then
        face(left_shift[left_shift[config.locations.homes.increment]])
    end
    
    return true
end
 
 
function initialize(session_id, config_values)
    -- INITIALIZE TURTLE
    
    state.session_id = session_id
    
    -- COPY CONFIG DATA INTO MEMORY
    for k, v in pairs(config_values) do
        config[k] = v
    end
    
    -- DETERMINE TURTLE TYPE
    state.peripheral_left = peripheral.getType('left')
    state.peripheral_right = peripheral.getType('right')
    if state.peripheral_left == 'chunkLoader' or state.peripheral_right == 'chunkLoader' or state.peripheral_left == 'chunky' or state.peripheral_right == 'chunky' then
        state.type = 'chunky'
        for k, v in pairs(config.chunky_turtle_locations) do
            config.locations[k] = v
        end
    else
        state.type = 'mining'
        for k, v in pairs(config.mining_turtle_locations) do
            config.locations[k] = v
        end
        if state.peripheral_left == 'modem' then
            state.peripheral_right = 'pick'
        else
            state.peripheral_left = 'pick'
        end
    end
    
    state.request_id = 1
    state.initialized = true
    return true
end
 
 
function getcwd()
    local running_program = shell.getRunningProgram()
    local program_name = fs.getName(running_program)
    return "/" .. running_program:sub(1, #running_program - #program_name)
end
 
 
function pass()
    return true
end
 
 
function dump(direction)
    if not face(direction) then return false end
    if ({inspect.forward()})[2].name ~= 'computercraft:turtle_advanced' then
        return false
    end
    for slot = 1, 16 do
        if turtle.getItemCount(slot) > 0 then
            turtle.select(slot)
            turtle.drop()
        end
    end
    return true
end
 
 
function checkTags(data)
    if type(data.tags) ~= 'table' then
        return false
    end
    if not config.blocktags then
        return false
    end
    for k,v in pairs(data.tags) do
        if config.blocktags[k] then
            return true
        end
    end
    return false
end
 
 
function detect_ore(direction)
    local block = ({inspect[direction]()})[2]
    if config.orenames[block.name] then
        return true
    elseif checkTags(block) then
        return true
    end
    return false
end
 
 
function scan(valid, ores)
    local checked_left  = false
    local checked_right = false
    
    local f = str_xyz(getblock.forward())
    local u = str_xyz(getblock.up())
    local d = str_xyz(getblock.down())
    local l = str_xyz(getblock.left())
    local r = str_xyz(getblock.right())
    local b = str_xyz(getblock.back())
    
    if not valid[f] and valid[f] ~= false then
        valid[f] = detect_ore('forward')
        ores[f] = valid[f]
    end
    if not valid[u] and valid[u] ~= false then
        valid[u] = detect_ore('up')
        ores[u] = valid[u]
    end
    if not valid[d] and valid[d] ~= false then
        valid[d] = detect_ore('down')
        ores[d] = valid[d]
    end
    if not valid[l] and valid[l] ~= false then
        left()
        checked_left = true
        valid[l] = detect_ore('forward')
        ores[l] = valid[l]
    end
    if not valid[r] and valid[r] ~= false then
        right()
        if checked_left then
            right()
        end
        checked_right = true
        valid[r] = detect_ore('forward')
        ores[r] = valid[r]
    end
    if not valid[b] and valid[b] ~= false then
        if checked_right then
            right()
        elseif checked_left then
            left()
        else
            right(2)
        end
        valid[b] = detect_ore('forward')
        ores[b] = valid[b]
    end
end
 
 
function fastest_route(area, pos, fac, end_locations)
    local queue = {}
    local explored = {}
    table.insert(queue,
        {
            coords = {x = pos.x, y = pos.y, z = pos.z},
            facing = fac,
            path = '',
        }
    )
    explored[str_xyz(pos, fac)] = true
 
    while #queue > 0 do
        local node = table.remove(queue, 1)
        if end_locations[str_xyz(node.coords)] or end_locations[str_xyz(node.coords, node.facing)] then
            return node.path
        end
        for _, step in pairs({
                {coords = node.coords,                                facing = left_shift[node.facing],  path = node.path .. 'l'},
                {coords = node.coords,                                facing = right_shift[node.facing], path = node.path .. 'r'},
                {coords = getblock.forward(node.coords, node.facing), facing = node.facing,              path = node.path .. 'f'},
                {coords = getblock.up(node.coords, node.facing),      facing = node.facing,              path = node.path .. 'u'},
                {coords = getblock.down(node.coords, node.facing),    facing = node.facing,              path = node.path .. 'd'},
                }) do
            explore_string = str_xyz(step.coords, step.facing)
            if not explored[explore_string] and (not area or area[str_xyz(step.coords)]) then
                explored[explore_string] = true
                table.insert(queue, step)
            end
        end
    end
end
 
 
function mine_vein(direction)
    if not face(direction) then return false end
    
    -- Log starting location
    local start = str_xyz({x = state.location.x, y = state.location.y, z = state.location.z}, state.orientation)
 
    -- Begin block map
    local valid = {}
    local ores = {}
    valid[str_xyz(state.location)] = true
    valid[str_xyz(getblock.back(state.location, state.orientation))] = false
    for i = 1, config.vein_max do
 
        -- Scan adjacent
        scan(valid, ores)
 
        -- Search for nearest ore
        local route = fastest_route(valid, state.location, state.orientation, ores)
 
        -- Check if there is one
        if not route then
            break
        end
 
        -- Retrieve ore
        turtle.select(1)
        if not follow_route(route) then return false end
        ores[str_xyz(state.location)] = nil
 
    end
 
    if not follow_route(fastest_route(valid, state.location, state.orientation, {[start] = true})) then return false end
 
    if detect.up() then
        safedig('up')
    end
    
    return true
end
 
 
function clear_gravity_blocks()
    for _, direction in pairs({'forward', 'up'}) do
        while config.gravitynames[ ({inspect[direction]()})[2].name ] do
            safedig(direction)
            sleep(1)
        end
    end
    return true
end]===],
    ["turtle_files/basics.lua"] = [===[inf = 1e309
 
bumps = {
    north = { 0,  0, -1},
    south = { 0,  0,  1},
    east  = { 1,  0,  0},
    west  = {-1,  0,  0},
}
 
left_shift = {
    north = 'west',
    south = 'east',
    east  = 'north',
    west  = 'south',
}
 
right_shift = {
    north = 'east',
    south = 'west',
    east  = 'south',
    west  = 'north',
}
 
reverse_shift = {
    north = 'south',
    south = 'north',
    east  = 'west',
    west  = 'east',
}
 
function dprint(thing)
    -- PRINT; IF TABLE PRINT EACH ITEM
    if type(thing) == 'table' then
        for k, v in pairs(thing) do
            print(tostring(k) .. ': ' .. tostring(v))
        end
    else
        print(thing)
    end
    return true
end
 
 
function str_xyz(coords, facing)
    if facing then
        return coords.x .. ',' .. coords.y .. ',' .. coords.z .. ':' .. facing
    else
        return coords.x .. ',' .. coords.y .. ',' .. coords.z
    end
end
 
 
function distance(point_1, point_2)
    return math.abs(point_1.x - point_2.x)
         + math.abs(point_1.y - point_2.y)
         + math.abs(point_1.z - point_2.z)
end
 
 
function in_area(xyz, area)
    return xyz.x <= area.max_x and xyz.x >= area.min_x and xyz.y <= area.max_y and xyz.y >= area.min_y and xyz.z <= area.max_z and xyz.z >= area.min_z
end
 
 
function in_location(xyzo, location)
    for _, axis in pairs({'x', 'y', 'z'}) do
        if location[axis] then
            if location[axis] ~= xyzo[axis] then
                return false
            end
        end
    end
    return true
end]===],
    ["turtle_files/config.lua"] = [===[]===],
    ["turtle_files/mastermine.lua"] = [===[function parse_requests()
    -- PROCESS ALL REDNET REQUESTS
    while #state.requests > 0 do
        local request = table.remove(state.requests, 1)
        sender, message, protocol = request[1], request[2], request[3]
        if message.action == 'shutdown' then
            os.shutdown()
        elseif message.action == 'reboot' then
            os.reboot()
        elseif message.action == 'update' then
            os.run({}, '/update')
        elseif message.request_id == -1 or message.request_id == state.request_id then -- MAKE SURE REQUEST IS CURRENT
            if state.initialized or message.action == 'initialize' then
                print('Directive: ' .. message.action)
                state.busy = true
                state.success = actions[message.action](unpack(message.data)) -- EXECUTE DESIRED FUNCTION WITH DESIRED ARGUMENTS
                state.busy = false
                if not state.success then
                    sleep(1)
                end
                state.request_id = state.request_id + 1
            end
        end
    end
end
 
 
function main()
    state.last_ping = os.clock()
    while true do
        parse_requests()
        sleep(0.3)
    end
end
 
 
main()]===],
    ["turtle_files/receive.lua"] = [===[-- CONTINUOUSLY RECIEVE REDNET MESSAGES
while true do
    signal = {rednet.receive('mastermine')}
    if signal[2].action == 'shutdown' then
        os.shutdown()
    elseif signal[2].action == 'reboot' then
        os.reboot()
    elseif signal[2].action == 'update' then
        os.run({}, '/update')
    else
        table.insert(state.requests, signal)
    end
end]===],
    ["turtle_files/report.lua"] = [===[-- CONTINUOUSLY BROADCAST STATUS REPORTS
hub_id = tonumber(fs.open('/hub_id', 'r').readAll())
 
while true do
 
    state.item_count = 0
    state.empty_slot_count = 16
    for slot = 1, 16 do
        slot_item_count = turtle.getItemCount(slot)
        if slot_item_count > 0 then
            state.empty_slot_count = state.empty_slot_count - 1
            state.item_count = state.item_count + slot_item_count
        end
    end
    
    rednet.send(hub_id, {
            session_id       = state.session_id,
            request_id       = state.request_id,
            turtle_type      = state.type,
            peripheral_left  = state.peripheral_left,
            peripheral_right = state.peripheral_right,
            updated_not_home = state.updated_not_home,
            location         = state.location,
            orientation      = state.orientation,
            fuel_level       = turtle.getFuelLevel(),
            item_count       = state.item_count,
            empty_slot_count = state.empty_slot_count,
            distance         = state.distance,
            strip            = state.strip,
            success          = state.success,
            busy             = state.busy,
        }, 'turtle_report')
    
    sleep(0.5)
    
end]===],
    ["turtle_files/startup.lua"] = [===[-- SET LABEL
os.setComputerLabel('Turtle ' .. os.getComputerID())
 
-- INITIALIZE APIS
if fs.exists('/apis') then
    fs.delete('/apis')
end
fs.makeDir('/apis')
fs.copy('/config.lua', '/apis/config')
fs.copy('/state.lua', '/apis/state')
fs.copy('/basics.lua', '/apis/basics')
fs.copy('/actions.lua', '/apis/actions')
os.loadAPI('/apis/config')
os.loadAPI('/apis/state')
os.loadAPI('/apis/basics')
os.loadAPI('/apis/actions')
 
 
-- OPEN REDNET
for _, side in pairs({'back', 'top', 'left', 'right'}) do
    if peripheral.getType(side) == 'modem' then
        rednet.open(side)
        break
    end
end
 
 
-- IF UPDATED PRINT "UPDATED"
if fs.exists('/updated') then
    fs.delete('/updated')
    print('UPDATED')
    state.updated_not_home = true
end
 
 
-- LAUNCH PROGRAMS AS SEPARATE THREADS
multishell.launch({}, '/report.lua')
multishell.launch({}, '/receive.lua')
multishell.launch({}, '/mastermine.lua')
multishell.setTitle(2, 'report')
multishell.setTitle(3, 'receive')
multishell.setTitle(4, 'mastermine')]===],
    ["turtle_files/state.lua"] = [===[request_id = 1
requests = {}
busy = false]===],
    ["turtle_files/update"] = [===[hub_id = tonumber(fs.open('/hub_id', 'r').readAll())
 
print('Sending update request...')
rednet.send(hub_id, '/disk/turtle_files/', 'update_request')
local sender, message, protocal = rednet.receive('update_package')
 
for _, file_name in pairs(fs.list('/')) do
    if file_name ~= 'rom' and file_name ~= 'persistent' then
        fs.delete(file_name)
    end
end
 
for file_name, file_contents in pairs(message) do
    file = fs.open(file_name, 'w')
    file.write(file_contents)
    file.close()
end
 
os.reboot()]===],
    ["turtle_files/updated"] = [===[]===],
    ["pocket_files/info.lua"] = [===[hub_id = tonumber(fs.open('/hub_id', 'r').readAll())
 
while true do
    sender, hub_state, _ = rednet.receive('hub_report')
    if sender == hub_id then
        term.clear()
        term.setCursorPos(1, 1)
        term.setTextColor(colors.white)
        term.write('POWER: ')
        if hub_state.on then
            term.setTextColor(colors.green)
            print('ON')
        else
            term.setTextColor(colors.red)
            print('OFF')
        end
        term.setTextColor(colors.white)
        term.write('TURTLES PARKED: ')
        if hub_state.turtles_parked >= hub_state.turtle_count then
            term.setTextColor(colors.green)
        else
            term.setTextColor(colors.red)
        end
        term.write(hub_state.turtles_parked)
    end
end]===],
    ["pocket_files/report.lua"] = [===[-- CONTINUOUSLY BROADCAST STATUS REPORTS
hub_id = tonumber(fs.open('/hub_id', 'r').readAll())
 
while true do
    
    local x, y, z = gps.locate()
    
    rednet.send(hub_id, {
            location = {x = x, y = y, z = z},
        }, 'pocket_report')
    
    sleep(0.5)
    
end]===],
    ["pocket_files/startup.lua"] = [===[-- SET LABEL
os.setComputerLabel('pocket ' .. os.getComputerID())
 
 
-- OPEN REDNET
rednet.open('back')
 
 
-- IF UPDATED PRINT "UPDATED"
if fs.exists('/updated') then
    fs.delete('/updated')
    print('UPDATED')
end
 
 
-- LAUNCH PROGRAMS AS SEPARATE THREADS
multishell.launch({}, '/user.lua')
multishell.launch({}, '/info.lua')
multishell.launch({}, '/report.lua')
multishell.setTitle(2, 'usr')
multishell.setTitle(3, 'info')
multishell.setTitle(4, 'rep')]===],
    ["pocket_files/update"] = [===[hub_id = tonumber(fs.open('/hub_id', 'r').readAll())
rednet.open('back')
 
print('Sending update request...')
rednet.broadcast('/disk/pocket_files/', 'update_request')
local sender, message, protocal = rednet.receive('update_package')
 
for _, file_name in pairs(fs.list('/')) do
    if file_name ~= 'rom' and file_name ~= 'disk' and file_name ~= 'persistent' then
        fs.delete(file_name)
    end
end
 
for file_name, file_contents in pairs(message) do
    file = fs.open(file_name, 'w')
    file.write(file_contents)
    file.close()
end
 
os.reboot()]===],
    ["pocket_files/updated"] = [===[]===],
    ["pocket_files/user.lua"] = [===[-- CONTINUOUSLY AWAIT USER INPUT AND PLACE IN TABLE
while true do
    rednet.broadcast(read(), 'user_input')
end]===],
    ["hub_files/basics.lua"] = [===[inf = 1e309
 
bumps = {
    north = { 0,  0, -1},
    south = { 0,  0,  1},
    east  = { 1,  0,  0},
    west  = {-1,  0,  0},
}
 
left_shift = {
    north = 'west',
    south = 'east',
    east  = 'north',
    west  = 'south',
}
 
right_shift = {
    north = 'east',
    south = 'west',
    east  = 'south',
    west  = 'north',
}
 
reverse_shift = {
    north = 'south',
    south = 'north',
    east  = 'west',
    west  = 'east',
}
 
function dprint(thing)
    -- PRINT; IF TABLE PRINT EACH ITEM
    if type(thing) == 'table' then
        for k, v in pairs(thing) do
            print(tostring(k) .. ': ' .. tostring(v))
        end
    else
        print(thing)
    end
    return true
end
 
 
function str_xyz(coords, facing)
    if facing then
        return coords.x .. ',' .. coords.y .. ',' .. coords.z .. ':' .. facing
    else
        return coords.x .. ',' .. coords.y .. ',' .. coords.z
    end
end
 
 
function distance(point_1, point_2)
    return math.abs(point_1.x - point_2.x)
         + math.abs(point_1.y - point_2.y)
         + math.abs(point_1.z - point_2.z)
end
 
 
function in_area(xyz, area)
    return xyz.x <= area.max_x and xyz.x >= area.min_x and xyz.y <= area.max_y and xyz.y >= area.min_y and xyz.z <= area.max_z and xyz.z >= area.min_z
end
 
 
function in_location(xyzo, location)
    for _, axis in pairs({'x', 'y', 'z'}) do
        if location[axis] then
            if location[axis] ~= xyzo[axis] then
                return false
            end
        end
    end
    return true
end]===],
    ["hub_files/config.lua"] = [===[inf = 1e309
 
---==[ MINE ]==---
 
 
-- LOCATION OF THE CENTER OF THE MINE
--     the y value should be set to the height
--     1 above the surface:
--
--            Y
--     ####### #######
--     ####### #######
mine_entrance = {x = 104, y = 76, z = 215}
c = mine_entrance
 
 
-- WHETHER OR NOT TURTLES NEED PAIRS
--     added this because a good number of
--     people were asking for the ability to
--     disale chunky turtles in case they
--     couldn't access the peripherals mod.
--     WARNING: not using chunky turtles will
--     result in narcoleptic turtles!
use_chunky_turtles = false
 
 
-- SPACE IN BLOCKS BETWEEN MINESHAFTS
--     too close means less chance of finding
--     ore veins, too far means longer commute
--     times for turtles.
grid_width = 8
 
 
-- MAXIMUM MINING AMOUNT PER TRIP
-- PER TURTLE
--     most efficient would be to make this
--     number huge, but turtles may be gone a
--     while (plus harder to recall).
mission_length = 150
 
 
-- MAXIMUM BLOCKS A TURTLE MINES IN A
-- SINGLE ORE VEIN
--     veins can contain multiple types of ore
--     and still count as one. also turtles will
--     continue on a vein even when their
--     inventory fills up, so this prevents them
--     losing too many rousources.
vein_max = 64
 
 
-- EXTRA FUEL FOR TURTLES TO BRING ALONG,
-- JUST IN CASE
fuel_padding = 30
 
 
-- FUEL PER ITEM 
--     for coal default is 80. Other fuel sources
--     can be used without changing this number,
--     should be fine.
fuel_per_unit = 80
 
 
-- TIME AFTER LAST PING TO DECLARE TURTLE
-- DISCONNECTED
turtle_timeout = 5
 
 
-- TIME AFTER LAST PING TO DECLARE POCKET
-- COMPUTER DISCONNECTED
pocket_timeout = 5
 
 
-- TIME TO WAIT AFTER SENDING TASK WITH NO
-- RESPONSE TO RESEND
task_timeout = 0.5
 
 
-- EVERY BLOCK NAME CONTAINING ANY OF THESE
-- STRINGS WILL NOT BE MINED
--     e.g. "chest" will prevent "minecraft:trapped_chest".
--     ore types should not be put on this list,
--     but if not desired should be removed from
--     <orenames> below.
dig_disallow = {
    'computer',
    'chest',
    'chair',
}
 
 
mine_levels = {
    -- LEVELS INCLUDED IN THE MINE
    --     turtles will pick randomly with weight
    --     between each listed level.
    --
    -- Level chances should sum to 1.0
    -- e.g.
    -- 
    -- {level = 50, chance = 0.3},
    -- {level = 40, chance = 0.2},
    -- {level = 12, chance = 0.5},
 
    {level = 63, chance = 1.0},
}
 
 
paths = {
    -- THE ORDER IN WHICH TURTLES WILL
    -- TRAVERSE AXES BETWEEN AREAS
    --     recommended not to change this one.
    home_to_home_exit          = 'zyx',
    control_room_to_home_enter = 'yzx',
    home_to_waiting_room       = 'zyx',
    waiting_room_to_mine_exit  = 'yzx',
    mine_enter_to_strip        = 'yxz',
}
 
 
locations = {
    -- THE VARIUS PLACES THE TURTLES MOVE
    -- BETWEEN
    --     coordinates are relative to the
    --     <mine_center> variable. areas are for
    --     altering turtle behavior to prevent
    --     collisions and stuff.
 
     -- THE BLOCK TURTLES WILL GO TO BEFORE
     -- DECENDING
    mine_enter = {x = c.x+0, y = c.y+0, z = c.z+0},
 
     -- THE BLOCK TURTLES WILL COME UP TO
     -- FROM THE MINE
     --     one block higher by default.
    mine_exit = {x = c.x+0, y = c.y+1, z = c.z+1},
 
     -- THE BLOCK TURTLES GO TO IN ORDER
     -- TO ACCESS THE CHEST FOR ITEMS
    item_drop = {x = c.x+2, y = c.y+1, z = c.z+1, orientation = 'east'},
 
     -- THE BLOCK TURTLES GO TO IN ORDER
     -- TO ACCESS THE CHEST FOR FUEL
    refuel = {x = c.x+2, y = c.y+1, z = c.z+0, orientation = 'east'},
 
     -- THE AREA ENCOMPASSING TURTLE HOMES
     --     where they sleep.
    greater_home_area = {
        min_x =  -inf,
        max_x = c.x-3,
        min_y = c.y+0,
        max_y = c.y+1,
        min_z = c.z-1,
        max_z = c.z+2
    },
 
     -- THE ROOM WHERE THE MAGIC HAPPENS
     --     turtles can find there way home from
     --     here.
    control_room_area = {
        min_x = c.x-16,
        max_x = c.x+8,
        min_y = c.y+0,
        max_y = c.y+8,
        min_z = c.z-8,
        max_z = c.z+8
    },
 
     -- WHERE TURTLES QUEUE TO BE PAIRED UP
    waiting_room_line_area = {
        min_x =  -inf,
        max_x = c.x-2,
        min_y = c.y+0,
        max_y = c.y+0,
        min_z =  c.z+0,
        max_z = c.z+1
    },
 
     -- THE AREA ENCOMPASSING BOTH WHERE
     -- TURTLES PAIR UP, AND THE PATH THEY
     -- TAKE TO THE MINE ENTRANCE
    waiting_room_area = {
        min_x = c.x-2,
        max_x = c.x+0,
        min_y = c.y+0,
        max_y = c.y+0,
        min_z =  c.z+0,
        max_z = c.z+1
    },
 
     -- THE LOOP TURTLES GO IN BETWEEN THEIR
     -- HOMES, THE ITEM DROP STATION, AND THE
     -- REFUELING STATION
     --     routes work like linked lists.
     --     keys are current positions, and
     --     values are the associated ajecent
     --     blocks to move to. this loop should
     --     be closed, and it should not be
     --     possible for a collision to occur
     --     between a turtle following the loop,
     --     and a turtle pairing, traveling to
     --     the mine entrance, or any other
     --     movement.
    main_loop_route = {
 
         -- MINING TURTLE HOME ENTER
        [c.x-1 .. ',' .. c.y+1 .. ',' .. c.z-1] = {x = c.x-2, y = c.y+1, z = c.z-1},
 
         -- MINING TURTLE HOME EXIT
        [c.x-2 .. ',' .. c.y+1 .. ',' .. c.z-1] = {x = c.x-2, y = c.y+1, z = c.z+0},
 
         -- CHUNKY TURTLE HOME EXIT
        [c.x-2 .. ',' .. c.y+1 .. ',' .. c.z+0] = {x = c.x-2, y = c.y+1, z = c.z+1},
 
         -- CHUNKY TURTLE HOME ENTER
        [c.x-2 .. ',' .. c.y+1 .. ',' .. c.z+1] = {x = c.x-2, y = c.y+1, z = c.z+2},
 
        [c.x-2 .. ',' .. c.y+1 .. ',' .. c.z+2] = {x = c.x-1, y = c.y+1, z = c.z+2},
        [c.x-1 .. ',' .. c.y+1 .. ',' .. c.z+2] = {x = c.x+0, y = c.y+1, z = c.z+2},
        [c.x+0 .. ',' .. c.y+1 .. ',' .. c.z+2] = {x = c.x+0, y = c.y+1, z = c.z+1},
        [c.x+0 .. ',' .. c.y+1 .. ',' .. c.z+1] = {x = c.x+1, y = c.y+1, z = c.z+1},
 
         -- ITEM DROP STATION
        [c.x+1 .. ',' .. c.y+1 .. ',' .. c.z+1] = {x = c.x+2, y = c.y+1, z = c.z+1},
 
         -- REFUELING STATION
        [c.x+2 .. ',' .. c.y+1 .. ',' .. c.z+1] = {x = c.x+2, y = c.y+1, z = c.z+0},
 
        [c.x+2 .. ',' .. c.y+1 .. ',' .. c.z+0] = {x = c.x+2, y = c.y+1, z = c.z-1},
        [c.x+2 .. ',' .. c.y+1 .. ',' .. c.z-1] = {x = c.x+1, y = c.y+1, z = c.z-1},
        [c.x+1 .. ',' .. c.y+1 .. ',' .. c.z-1] = {x = c.x+0, y = c.y+1, z = c.z-1},
        [c.x+0 .. ',' .. c.y+1 .. ',' .. c.z-1] = {x = c.x-1, y = c.y+1, z = c.z-1},
    },
}
 
 
mining_turtle_locations = {
    -- LOCATIONS THAT ARE SPECIFIC TO
    -- MINING TURTLES
 
     -- TURTLE HOMES
     --     this is where the first turtle parking
     --     spot will be, and each following will
     --     be in the <increment> direction.
    homes = {x = c.x-3, y = c.y+0, z = c.z-3, increment = 'west'},
 
     -- THE AREA ENCOMPASSING THE HOME
     -- LINE, AS WELL AS THE PATH TURTLES
     -- TAKE TO GET TO THEIR HOME
    home_area = {
        min_x = -inf,
        max_x = c.x-3,
        min_y = c.y+0,
        max_y = c.y+0,
        min_z = c.z-1,
        max_z = c.z-1
    },
 
     -- WHERE TURTLES ENTER THE LINE TO
     -- GET TO THEIR HOME
    home_enter = {x = c.x-2, y = c.y+1, z = c.z-1, orientation = 'west'},
 
     -- WHERE TURTLES EXIT THEIR HOMES
    home_exit = {x = c.x-2, y = c.y+1, z = c.z+0},
 
     -- WHERE TURTLES WAIT TO BE PAIRED
    waiting_room = {x = c.x-2, y = c.y+0, z = c.z+0},
 
     -- THE PATH TURTLES WILL TAKE AFTER
     -- PAIRING
    waiting_room_to_mine_enter_route = {
        [c.x-2 .. ',' .. c.y+0 .. ',' .. c.z+0] = {x = c.x-1, y = c.y+0, z = c.z+0},
        [c.x-1 .. ',' .. c.y+0 .. ',' .. c.z+0] = {x = c.x+0, y = c.y+0, z = c.z+0},
    }
}
 
 
chunky_turtle_locations = {
    -- LOCATIONS THAT ARE SPECIFIC TO
    -- MINING TURTLES
 
     -- TURTLE HOMES
     --     this is where the first turtle parking
     --     spot will be, and each following will
     --     be in the <increment> direction.
    homes = {x = c.x-3, y = c.y+0, z = c.z+2, increment = 'west'},
 
     -- THE AREA ENCOMPASSING THE HOME
     -- LINE, AS WELL AS THE PATH TURTLES
     -- TAKE TO GET TO THEIR HOME
    home_area = {
        min_x = -inf,
        max_x = c.x-3,
        min_y = c.y+0,
        max_y = c.y+0,
        min_z = c.z+2,
        max_z = c.z+2
    },
 
     -- WHERE TURTLES ENTER THE LINE TO
     -- GET TO THEIR HOME
    home_enter = {x = c.x-2, y = c.y+1, z = c.z+2, orientation = 'west'},
 
     -- WHERE TURTLES EXIT THEIR HOMES
    home_exit = {x = c.x-2, y = c.y+1, z = c.z+1},
 
     -- WHERE TURTLES WAIT TO BE PAIRED
    waiting_room = {x = c.x-2, y = c.y+0, z = c.z+1},
 
     -- THE PATH TURTLES WILL TAKE AFTER
     -- PAIRING
    waiting_room_to_mine_enter_route = {
        [c.x-2 .. ',' .. c.y+0 .. ',' .. c.z+1] = {x = c.x-1, y = c.y+0, z = c.z+1},
        [c.x-1 .. ',' .. c.y+0 .. ',' .. c.z+1] = {x = c.x-1, y = c.y+0, z = c.z+0},
        [c.x-1 .. ',' .. c.y+0 .. ',' .. c.z+0] = {x = c.x+0, y = c.y+0, z = c.z+0},
    }
}
 
 
gravitynames = {
    -- ALL BLOCKS AFFECTED BY GRAVITY
    --     if a turtle sees these it will take
    --     extra care to make sure they're delt
    --     with. works at least a lot percent of
    --     the time
    ['minecraft:gravel'] = true,
    ['minecraft:sand'] = true,
}
 
 
orenames = {
    -- ALL THE BLOCKS A TURTLE CONSIDERS ORE
    --     a turtle will continue to mine out a
    --     vein until it reaches <vein_max> or
    --     it stops seeing blocks with names in
    --     this list. block names are exact.
    ['BigReactors:YelloriteOre'] = true,
    ['bigreactors:oreyellorite'] = true,
    ['ThermalFoundation:Ore'] = true,
    ['thermalfoundation:ore'] = true,
    ['thermalfoundation:ore_fluid'] = true,
    ['thaumcraft:ore_amber'] = true,
    ['minecraft:coal'] = true,
    ['minecraft:coal_ore'] = true,
    ['minecraft:diamond'] = true,
    ['minecraft:diamond_ore'] = true,
    ['minecraft:dye'] = true,
    ['minecraft:emerald'] = true,
    ['minecraft:emerald_ore'] = true,
    ['minecraft:gold_ore'] = true,
    ['minecraft:iron_ore'] = true,
    ['minecraft:lapis_ore'] = true,
    ['minecraft:redstone'] = true,
    ['minecraft:redstone_ore'] = true,
    ['galacticraftcore:basic_block_core'] = true,
    ['mekanism:oreblock'] = true,
    ['appliedenergistics2:quartz_ore'] = true,
    ['indrev:copper_ore'] = true,
    ['indrev:tin_ore'] = true,
    ['indrev:lead_ore'] = true,
    ['indrev:silver_ore'] = true,
    ['indrev:tungsten_ore'] = true,
    ['modern_industrialization:copper_ore'] = true,
    ['modern_industrialization:tin_ore'] = true,
    ['modern_industrialization:lead_ore'] = true,
    ['modern_industrialization:silver_ore'] = true,
    ['modern_industrialization:nickel_ore'] = true,
    ['techreborn:bauxite_ore'] = true,
    ['techreborn:copper_ore'] = true,
    ['techreborn:galena_ore'] = true,
    ['techreborn:iridium_ore'] = true,
    ['techreborn:lead_ore'] = true,
    ['techreborn:ruby_ore'] = true,
    ['techreborn:sapphire_ore'] = true,
    ['techreborn:silver_ore'] = true,
    ['techreborn:tin_ore'] = true,
    ['powah:uraninite_ore'] = true,
    ['create:zinc_ore'] = true,
    ['minecraft:deepslate_coal_ore'] = true,
    ['minecraft:deepslate_copper_ore'] = true,
    ['minecraft:deepslate_diamond_ore'] = true,
    ['minecraft:deepslate_emerald_ore'] = true,
    ['minecraft:deepslate_gold_ore'] = true,
    ['minecraft:deepslate_iron_ore'] = true,
    ['minecraft:deepslate_lapis_ore'] = true,
    ['minecraft:deepslate_redstone_ore'] = true
}
 
blocktags = {
    -- ALL BLOCKS WITH ONE OF THESE TAGS A TURTLE CONSIDERS ORE
    --     most mods categorize ores with the forge:ores tag.
    --     this is an easy way to detect all but a few ores,
    --     which don't posess this exact tag (for example certus quartzfrom AE2)
    ['forge:ores'] = true,
    -- adds Certus Quartz and Charged Certus Quartz
    ['forge:ores/certus_quartz'] = true
}
 
fuelnames = {
    -- ITEMS THE TURTLE CONSIDERS FUEL
    ['minecraft:coal'] = true,
}
 
 
---==[ SCREEN ]==---
 
 
-- MAXIMUM ZOOM OUT (INVERSE) OF THE
-- MAP SCREEN
monitor_max_zoom_level = 5
 
 
-- DEFAULT ZOOM OF THE MAP SCREEN
--     0 is [1 pixel : 1 block]
default_monitor_zoom_level = 0
 
 
-- CENTER OF THE MAP SCREEN
--     probably want the mine center
default_monitor_location = {x = c.x, z = c.z}]===],
    ["hub_files/events.lua"] = [===[while true do
    event = {os.pullEvent()}
    if event[1] == 'rednet_message' then
        local sender = event[2]
        local message = event[3]
        local protocol = event[4]
        
        if protocol == 'user_input' then
            table.insert(state.user_input, message)
        
        elseif protocol == 'turtle_report' then
            if not state.turtles[sender] then
                state.turtles[sender] = {id = sender}
            end
            state.turtles[sender].data = message
            state.turtles[sender].last_update = os.clock()
        
        elseif protocol == 'pocket_report' then
            if not state.pockets[sender] then
                state.pockets[sender] = {id = sender}
            end
            state.pockets[sender].data = message
            state.pockets[sender].last_update = os.clock()
            
        elseif protocol == 'update_request' then
            if fs.isDir(message) then
                local update_package = {}
                local queue = {''}
                while #queue > 0 do
                    dir_name = table.remove(queue)
                    path_name = fs.combine(message, dir_name)
                    for _, object_name in pairs(fs.list(path_name)) do
                        sub_dir_name = fs.combine(dir_name, object_name)
                        sub_path_name = fs.combine(message, sub_dir_name)
                        if fs.isDir(sub_path_name) then
                            table.insert(queue, sub_dir_name)
                        else
                            local file = fs.open(sub_path_name, 'r')
                            update_package[sub_dir_name] = file.readAll()
                            file.close()
                        end
                    end
                end
                update_package.hub_id = os.getComputerID()
                rednet.send(sender, update_package, 'update_package')
            end
        end
        
    elseif event[1] == 'monitor_touch' then
        if state.monitor_touches then
            table.insert(state.monitor_touches, {x = event[3], y = event[4]})
        end
    end
end]===],
    ["hub_files/monitor.lua"] = [===[menu_lines = {
    '#   # ##### #   # #####',
    '## ##   #   ##  # #',
    '# # #   #   # # # ###',
    '#   #   #   #  ## #',
    '#   # ##### #   # #',
}
 
decimals = {
    [0] = {
        '#####',
        '#   #',
        '#   #',
        '#   #',
        '#####',
    },
    [1] = {
        '###  ',
        '  #  ',
        '  #  ',
        '  #  ',
        '#####',
    },
    [2] = {
        '#####',
        '    #',
        '#####',
        '#    ',
        '#####',
    },
    [3] = {
        '#####',
        '    #',
        '#####',
        '    #',
        '#####',
    },
    [4] = {
        '#   #',
        '#   #',
        '#####',
        '    #',
        '    #',
    },
    [5] = {
        '#####',
        '#    ',
        '#####',
        '    #',
        '#####',
    },
    [6] = {
        '#####',
        '#    ',
        '#####',
        '#   #',
        '#####',
    },
    [7] = {
        '#####',
        '    #',
        '    #',
        '    #',
        '    #',
    },
    [8] = {
        '#####',
        '#   #',
        '#####',
        '#   #',
        '#####',
    },
    [9] = {
        '#####',
        '#   #',
        '#####',
        '    #',
        '    #',
    },
}
 
function debug_print(string)
    term.redirect(monitor.restore_to)
    print(string)
    term.redirect(monitor)
end
 
function turtle_viewer(turtle_ids)
    term.redirect(monitor)
    
    local selected = 1
    
    while true do
        local turtle_id = turtle_ids[selected]
        local turtle = state.turtles[turtle_id]
        
        -- RESOLVE MONITOR TOUCHES, EITHER BY AFFECTING THE DISPLAY OR INSERTING INTO USER_INPUT TABLE
        while #state.monitor_touches > 0 do
            local monitor_touch = table.remove(state.monitor_touches)
            if monitor_touch.x == elements.left.x and monitor_touch.y == elements.left.y then
                selected = math.max(selected - 1, 1)
            elseif monitor_touch.x == elements.right.x and monitor_touch.y == elements.right.y then
                selected = math.min(selected + 1, #turtle_ids)
            elseif monitor_touch.x == elements.viewer_exit.x and monitor_touch.y == elements.viewer_exit.y then
                term.redirect(monitor.restore_to)
                return
            elseif monitor_touch.x == elements.turtle_return.x and monitor_touch.y == elements.turtle_return.y then
                table.insert(state.user_input, 'return ' .. turtle_id)
            elseif monitor_touch.x == elements.turtle_update.x and monitor_touch.y == elements.turtle_update.y then
                table.insert(state.user_input, 'update ' .. turtle_id)
            elseif monitor_touch.x == elements.turtle_reboot.x and monitor_touch.y == elements.turtle_reboot.y then
                table.insert(state.user_input, 'reboot ' .. turtle_id)
            elseif monitor_touch.x == elements.turtle_halt.x and monitor_touch.y == elements.turtle_halt.y then
                table.insert(state.user_input, 'halt ' .. turtle_id)
            elseif monitor_touch.x == elements.turtle_clear.x and monitor_touch.y == elements.turtle_clear.y then
                table.insert(state.user_input, 'clear ' .. turtle_id)
            elseif monitor_touch.x == elements.turtle_reset.x and monitor_touch.y == elements.turtle_reset.y then
                table.insert(state.user_input, 'reset ' .. turtle_id)
            elseif monitor_touch.x == elements.turtle_find.x and monitor_touch.y == elements.turtle_find.y then
                monitor_location.x = turtle.data.location.x
                monitor_location.z = turtle.data.location.z
                monitor_zoom_level = 0
                for level_index, level_and_chance in pairs(config.mine_levels) do
                    if turtle.strip and level_and_chance.level == turtle.strip.y then
                        monitor_level_index = level_index
                        select_mine_level()
                        break
                    end
                end
                term.redirect(monitor.restore_to)
                return
            elseif monitor_touch.x == elements.turtle_forward.x and monitor_touch.y == elements.turtle_forward.y then
                table.insert(state.user_input, 'turtle ' .. turtle_id .. ' go forward')
            elseif monitor_touch.x == elements.turtle_back.x and monitor_touch.y == elements.turtle_back.y then
                table.insert(state.user_input, 'turtle ' .. turtle_id .. ' go back')
            elseif monitor_touch.x == elements.turtle_up.x and monitor_touch.y == elements.turtle_up.y then
                table.insert(state.user_input, 'turtle ' .. turtle_id .. ' go up')
            elseif monitor_touch.x == elements.turtle_down.x and monitor_touch.y == elements.turtle_down.y then
                table.insert(state.user_input, 'turtle ' .. turtle_id .. ' go down')
            elseif monitor_touch.x == elements.turtle_left.x and monitor_touch.y == elements.turtle_left.y then
                table.insert(state.user_input, 'turtle ' .. turtle_id .. ' go left')
            elseif monitor_touch.x == elements.turtle_right.x and monitor_touch.y == elements.turtle_right.y then
                table.insert(state.user_input, 'turtle ' .. turtle_id .. ' go right')
            elseif turtle.data.turtle_type == 'mining' then
                if monitor_touch.x == elements.turtle_dig_up.x and monitor_touch.y == elements.turtle_dig_up.y then
                    table.insert(state.user_input, 'turtle ' .. turtle_id .. ' digblock up')
                elseif monitor_touch.x == elements.turtle_dig.x and monitor_touch.y == elements.turtle_dig.y then
                    table.insert(state.user_input, 'turtle ' .. turtle_id .. ' digblock forward')
                elseif monitor_touch.x == elements.turtle_dig_down.x and monitor_touch.y == elements.turtle_dig_down.y then
                    table.insert(state.user_input, 'turtle ' .. turtle_id .. ' digblock down')
                end
            end
        end
        
        turtle_id = turtle_ids[selected]
        turtle = state.turtles[turtle_id]
        
        background_color = colors.black
        term.setBackgroundColor(background_color)
        monitor.clear()
        
        if turtle.last_update + config.turtle_timeout < os.clock() then
            term.setCursorPos(elements.turtle_lost.x, elements.turtle_lost.y)
            term.setTextColor(colors.red)
            term.write('CONNECTION LOST')
        end
        
        local x_position = elements.turtle_id.x
        for decimal_string in string.format('%04d', turtle_id):gmatch"." do
            for y_offset, line in pairs(decimals[tonumber(decimal_string)]) do
                term.setCursorPos(x_position, elements.turtle_id.y + y_offset - 1)
                for char in line:gmatch"." do
                    if char == '#' then
                        term.setBackgroundColor(colors.green)
                    else
                        term.setBackgroundColor(colors.black)
                    end
                    term.write(' ')
                end
            end
            x_position = x_position + 6
        end
        
        term.setCursorPos(elements.turtle_face.x + 1, elements.turtle_face.y)
        term.setBackgroundColor(colors.yellow)
        term.write('       ')
        term.setCursorPos(elements.turtle_face.x + 1, elements.turtle_face.y + 1)
        term.setBackgroundColor(colors.yellow)
        term.write(' ')
        term.setBackgroundColor(colors.gray)
        term.write('     ')
        term.setBackgroundColor(colors.yellow)
        term.write(' ')
        term.setCursorPos(elements.turtle_face.x + 1, elements.turtle_face.y + 2)
        term.setBackgroundColor(colors.yellow)
        term.write('       ')
        term.setCursorPos(elements.turtle_face.x + 1, elements.turtle_face.y + 3)
        term.setBackgroundColor(colors.yellow)
        term.write('       ')
        term.setCursorPos(elements.turtle_face.x + 1, elements.turtle_face.y + 4)
        term.setBackgroundColor(colors.yellow)
        term.write('       ')
        
        if turtle.data.peripheral_right == 'modem' then
            term.setBackgroundColor(colors.lightGray)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 2)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 3)
            term.write(' ')
        elseif turtle.data.peripheral_right == 'pick' then
            term.setBackgroundColor(colors.cyan)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 2)
            term.write(' ')
            term.setBackgroundColor(colors.brown)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 3)
            term.write(' ')
        elseif turtle.data.peripheral_right == 'chunkLoader' then
            term.setBackgroundColor(colors.gray)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 3)
            term.write(' ')
            term.setBackgroundColor(colors.blue)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 2)
            term.write(' ')
        elseif turtle.data.peripheral_right == 'chunky' then
            term.setBackgroundColor(colors.white)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 3)
            term.write(' ')
            term.setBackgroundColor(colors.red)
            term.setCursorPos(elements.turtle_face.x, elements.turtle_face.y + 2)
            term.write(' ')
        end
        
        if turtle.data.peripheral_left == 'modem' then
            term.setBackgroundColor(colors.lightGray)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 2)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 3)
            term.write(' ')
        elseif turtle.data.peripheral_left == 'pick' then
            term.setBackgroundColor(colors.cyan)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 2)
            term.write(' ')
            term.setBackgroundColor(colors.brown)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 3)
            term.write(' ')
        elseif turtle.data.peripheral_left == 'chunkLoader' then
            term.setBackgroundColor(colors.gray)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 3)
            term.write(' ')
            term.setBackgroundColor(colors.blue)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 2)
            term.write(' ')
        elseif turtle.data.peripheral_left == 'chunky' then
            term.setBackgroundColor(colors.white)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 1)
            term.write(' ')
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 3)
            term.write(' ')
            term.setBackgroundColor(colors.red)
            term.setCursorPos(elements.turtle_face.x + 8, elements.turtle_face.y + 2)
            term.write(' ')
        end
        
        term.setBackgroundColor(background_color)
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y)
        term.setTextColor(colors.white)
        term.write('State: ')
        term.setTextColor(colors.green)
        term.write(turtle.state)
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 1)
        term.setTextColor(colors.white)
        term.write('X: ')
        term.setTextColor(colors.green)
        if turtle.data.location then
            term.write(turtle.data.location.x)
        end
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 2)
        term.setTextColor(colors.white)
        term.write('Y: ')
        term.setTextColor(colors.green)
        if turtle.data.location then
            term.write(turtle.data.location.y)
        end
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 3)
        term.setTextColor(colors.white)
        term.write('Z: ')
        term.setTextColor(colors.green)
        if turtle.data.location then
            term.write(turtle.data.location.z)
        end
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 4)
        term.setTextColor(colors.white)
        term.write('Facing: ')
        term.setTextColor(colors.green)
        term.write(turtle.data.orientation)
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 5)
        term.setTextColor(colors.white)
        term.write('Fuel: ')
        term.setTextColor(colors.green)
        term.write(turtle.data.fuel_level)
        
        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 6)
        term.setTextColor(colors.white)
        term.write('Items: ')
        term.setTextColor(colors.green)
        term.write(turtle.data.item_count)
        
--        term.setCursorPos(elements.turtle_data.x, elements.turtle_data.y + 7)
--        term.setTextColor(colors.white)
--        term.write('Dist: ')
--        term.setTextColor(colors.green)
--        term.write(turtle.data.distance)
        
        term.setTextColor(colors.white)
        
        term.setCursorPos(elements.turtle_return.x, elements.turtle_return.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-RETURN')
        
        term.setCursorPos(elements.turtle_update.x, elements.turtle_update.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-UPDATE')
        
        term.setCursorPos(elements.turtle_reboot.x, elements.turtle_reboot.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-REBOOT')
        
        term.setCursorPos(elements.turtle_halt.x, elements.turtle_halt.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-HALT')
        
        term.setCursorPos(elements.turtle_clear.x, elements.turtle_clear.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-CLEAR')
        
        term.setCursorPos(elements.turtle_reset.x, elements.turtle_reset.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-RESET')
        
        term.setCursorPos(elements.turtle_find.x, elements.turtle_find.y)
        term.setBackgroundColor(colors.green)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.write('-FIND')
        
        term.setCursorPos(elements.turtle_forward.x, elements.turtle_forward.y)
        term.setTextColor(colors.white)
        term.setBackgroundColor(colors.green)
        term.write('^')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-FORWARD')
        
        term.setCursorPos(elements.turtle_back.x, elements.turtle_back.y)
        term.setTextColor(colors.white)
        term.setBackgroundColor(colors.green)
        term.write('V')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-BACK')
        
        term.setCursorPos(elements.turtle_up.x, elements.turtle_up.y)
        term.setTextColor(colors.white)
        term.setBackgroundColor(colors.green)
        term.write('^')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-UP')
        
        term.setCursorPos(elements.turtle_down.x, elements.turtle_down.y)
        term.setTextColor(colors.white)
        term.setBackgroundColor(colors.green)
        term.write('V')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-DOWN')
        
        term.setCursorPos(elements.turtle_left.x, elements.turtle_left.y)
        term.setTextColor(colors.white)
        term.setBackgroundColor(colors.green)
        term.write('<')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-LEFT')
        
        term.setCursorPos(elements.turtle_right.x, elements.turtle_right.y)
        term.setTextColor(colors.white)
        term.setBackgroundColor(colors.green)
        term.write('>')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-RIGHT')
        
        term.setCursorPos(elements.turtle_dig_up.x, elements.turtle_dig_up.y)
        term.setTextColor(colors.white)
        if turtle.data.turtle_type == 'mining' then
            term.setBackgroundColor(colors.green)
        else
            term.setBackgroundColor(colors.gray)
        end
        term.write('^')
        
        term.setCursorPos(elements.turtle_dig.x, elements.turtle_dig.y)
        term.setTextColor(colors.white)
        if turtle.data.turtle_type == 'mining' then
            term.setBackgroundColor(colors.green)
        else
            term.setBackgroundColor(colors.gray)
        end
        term.write('*')
        term.setTextColor(colors.gray)
        term.setBackgroundColor(background_color)
        term.write('-DIG')
        
        term.setCursorPos(elements.turtle_dig_down.x, elements.turtle_dig_down.y)
        term.setTextColor(colors.white)
        if turtle.data.turtle_type == 'mining' then
            term.setBackgroundColor(colors.green)
        else
            term.setBackgroundColor(colors.gray)
        end
        term.write('v')
        
        term.setTextColor(colors.white)
        if selected == 1 then
            term.setBackgroundColor(colors.gray)
        else
            term.setBackgroundColor(colors.green)
        end
        term.setCursorPos(elements.left.x, elements.left.y)
        term.write('<')
        if selected == #turtle_ids then
            term.setBackgroundColor(colors.gray)
        else
            term.setBackgroundColor(colors.green)
        end
        term.setCursorPos(elements.right.x, elements.right.y)
        term.write('>')
        term.setBackgroundColor(colors.red)
        term.setCursorPos(elements.viewer_exit.x, elements.viewer_exit.y)
        term.write('x')
        
        monitor.setVisible(true)
        monitor.setVisible(false)
        
        sleep(sleep_len)
    end
end
 
 
function menu()
    term.redirect(monitor)
    
    while true do
        while #state.monitor_touches > 0 do
            local monitor_touch = table.remove(state.monitor_touches)
            if monitor_touch.x == elements.viewer_exit.x and monitor_touch.y == elements.viewer_exit.y then
                term.redirect(monitor.restore_to)
                return
            elseif monitor_touch.x == elements.menu_toggle.x and monitor_touch.y == elements.menu_toggle.y then
                if state.on then
                    table.insert(state.user_input, 'off')
                else
                    table.insert(state.user_input, 'on')
                end
            elseif monitor_touch.x == elements.menu_update.x and monitor_touch.y == elements.menu_update.y then
                table.insert(state.user_input, 'update')
            elseif monitor_touch.x == elements.menu_return.x and monitor_touch.y == elements.menu_return.y then
                table.insert(state.user_input, 'return')
            elseif monitor_touch.x == elements.menu_reboot.x and monitor_touch.y == elements.menu_reboot.y then
                table.insert(state.user_input, 'reboot')
            elseif monitor_touch.x == elements.menu_halt.x and monitor_touch.y == elements.menu_halt.y then
                table.insert(state.user_input, 'halt')
            elseif monitor_touch.x == elements.menu_clear.x and monitor_touch.y == elements.menu_clear.y then
                table.insert(state.user_input, 'clear')
            elseif monitor_touch.x == elements.menu_reset.x and monitor_touch.y == elements.menu_reset.y then
                table.insert(state.user_input, 'reset')
            end
        end
        
        term.setBackgroundColor(colors.black)
        monitor.clear()
        
        term.setTextColor(colors.white)
        term.setCursorPos(elements.menu_title.x, elements.menu_title.y)
        term.write('MASTER')
        
        for y_offset, line in pairs(menu_lines) do
            term.setCursorPos(elements.menu_title.x, elements.menu_title.y + y_offset)
            for char in line:gmatch"." do
                if char == '#' then
                    if state.on then
                        term.setBackgroundColor(colors.lime)
                    else
                        term.setBackgroundColor(colors.red)
                    end
                else
                    term.setBackgroundColor(colors.black)
                end
                term.write(' ')
            end
        end
        
        term.write('.lua')
        
        term.setBackgroundColor(colors.red)
        term.setCursorPos(elements.viewer_exit.x, elements.viewer_exit.y)
        term.write('x')
        term.setBackgroundColor(colors.green)
        term.setCursorPos(elements.menu_toggle.x, elements.menu_toggle.y)
        term.write('*')
        term.setCursorPos(elements.menu_return.x, elements.menu_return.y)
        term.write('*')
        term.setCursorPos(elements.menu_update.x, elements.menu_update.y)
        term.write('*')
        term.setCursorPos(elements.menu_reboot.x, elements.menu_reboot.y)
        term.write('*')
        term.setCursorPos(elements.menu_halt.x, elements.menu_halt.y)
        term.write('*')
        term.setCursorPos(elements.menu_clear.x, elements.menu_clear.y)
        term.write('*')
        term.setCursorPos(elements.menu_reset.x, elements.menu_reset.y)
        term.write('*')
        term.setBackgroundColor(colors.brown)
        term.setCursorPos(elements.menu_toggle.x + 1, elements.menu_toggle.y)
        term.write('-TOGGLE POWER')
        term.setCursorPos(elements.menu_update.x + 1, elements.menu_update.y)
        term.write('-UPDATE')
        term.setCursorPos(elements.menu_return.x + 1, elements.menu_return.y)
        term.write('-RETURN')
        term.setCursorPos(elements.menu_reboot.x + 1, elements.menu_reboot.y)
        term.write('-REBOOT')
        term.setCursorPos(elements.menu_halt.x + 1, elements.menu_halt.y)
        term.write('-HALT')
        term.setCursorPos(elements.menu_clear.x + 1, elements.menu_clear.y)
        term.write('-CLEAR')
        term.setCursorPos(elements.menu_reset.x + 1, elements.menu_reset.y)
        term.write('-RESET')
        
        monitor.setVisible(true)
        monitor.setVisible(false)
        
        sleep(sleep_len)
    end
end
 
 
function draw_location(location, color)
    if location then
        local pixel = {
            -- x = monitor_width  - math.floor((location.x - min_location.x) / zoom_factor),
            -- y = monitor_height - math.floor((location.z - min_location.z) / zoom_factor),
            x = math.floor((location.x - min_location.x) / zoom_factor),
            y = math.floor((location.z - min_location.z) / zoom_factor),
        }
        if pixel.x >= 1 and pixel.x <= monitor_width and pixel.y >= 1 and pixel.y <= monitor_height then
            if color then
                paintutils.drawPixel(pixel.x, pixel.y, color)
            end
            return pixel
        end
    end
end
    
 
function draw_monitor()
    
    term.redirect(monitor)
    term.setBackgroundColor(colors.black)
    monitor.clear()
    
    zoom_factor = math.pow(2, monitor_zoom_level)
    min_location = {
        x = monitor_location.x - math.floor(monitor_width  * zoom_factor / 2) - 1,
        z = monitor_location.z - math.floor(monitor_height * zoom_factor / 2) - 1,
    }
    
    local mined = {}
    local xz
    for x = min_location.x - ((min_location.x - config.locations.mine_enter.x) % config.grid_width), min_location.x + (monitor_width * zoom_factor), config.grid_width do
        for z = min_location.z, min_location.z + (monitor_height * zoom_factor), zoom_factor do
            xz = x .. ',' .. z
            if not mined[xz] then
                if z > config.locations.mine_enter.z then
                    if monitor_level[x] and monitor_level[x].south.z > z then
                        mined[xz] = true
                        draw_location({x = x, z = z}, colors.lightGray)
                    else
                        draw_location({x = x, z = z}, colors.gray)
                    end
                else
                    if monitor_level[x] and monitor_level[x].north.z < z then
                        mined[xz] = true
                        draw_location({x = x, z = z}, colors.lightGray)
                    else
                        draw_location({x = x, z = z}, colors.gray)
                    end
                end
            end
        end
    end
    
    for x = min_location.x, min_location.x + (monitor_width * zoom_factor), zoom_factor do
        if x > monitor_level.main_shaft.west.x and x < monitor_level.main_shaft.east.x then
            draw_location({x = x, z = config.locations.mine_enter.z}, colors.lightGray)
        else
            draw_location({x = x, z = config.locations.mine_enter.z}, colors.gray)
        end
    end
    
    local pixel
    local special = {}
    
    pixel = draw_location(config.locations.mine_exit, colors.blue)
    if pixel then
        special[pixel.x .. ',' .. pixel.y] = colors.blue
    end
    
    pixel = draw_location(config.locations.mine_enter, colors.blue)
    if pixel then
        special[pixel.x .. ',' .. pixel.y] = colors.blue
    end
    
    -- DRAW STRIP ENDINGS
    for name, strip in pairs(monitor_level) do
        if name ~= 'y' then
            for _, strip_end in pairs(strip) do
                if strip_end.turtles then
                    pixel = draw_location(strip_end, colors.green)
                    if pixel then
                        special[pixel.x .. ',' .. pixel.y] = colors.green
                    end
                end
            end
        end
    end
    
    term.setTextColor(colors.black)
    turtles = {}
    local str_pixel
    for _, turtle in pairs(state.turtles) do
        if turtle.data then
            local location = turtle.data.location
            if location and location.x and location.y then
                pixel = draw_location(location)
                if pixel then
                    term.setCursorPos(pixel.x, pixel.y)
                    str_pixel = pixel.x .. ',' .. pixel.y
                    if special[str_pixel] then
                        term.setBackgroundColor(special[str_pixel])
                    elseif turtle.last_update + config.turtle_timeout < os.clock() then
                        term.setBackgroundColor(colors.red)
                    else
                        term.setBackgroundColor(colors.yellow)
                    end
                    if not turtles[str_pixel] then
                        turtles[str_pixel] = {turtle.id}
                        term.write('-')
                    else
                        table.insert(turtles[str_pixel], turtle.id)
                        if #turtles[str_pixel] <= 9 then
                            term.write(#turtles[str_pixel])
                        else
                            term.write('+')
                        end
                    end
                end
            end
        end
    end
    
    for _, pocket in pairs(state.pockets) do
        local location = pocket.data.location
        if location and location.x and location.y then
            pixel = draw_location(location)
            if pixel then
                term.setCursorPos(pixel.x, pixel.y)
                str_pixel = pixel.x .. ',' .. pixel.y
                if pocket.last_update + config.pocket_timeout < os.clock() then
                    term.setBackgroundColor(colors.red)
                else
                    term.setBackgroundColor(colors.green)
                end
                term.write('M')
            end
        end
    end
    
    term.setTextColor(colors.white)
    term.setBackgroundColor(colors.green)
    term.setCursorPos(elements.menu.x, elements.menu.y)
    term.write('*')
    term.setCursorPos(elements.all_turtles.x, elements.all_turtles.y)
    term.write('*')
    term.setCursorPos(elements.mining_turtles.x, elements.mining_turtles.y)
    term.write('*')
    term.setCursorPos(elements.center.x, elements.center.y)
    term.write('*')
    term.setCursorPos(elements.up.x, elements.up.y)
    term.write('N')
    term.setCursorPos(elements.down.x, elements.down.y)
    term.write('S')
    term.setCursorPos(elements.left.x, elements.left.y)
    term.write('W')
    term.setCursorPos(elements.right.x, elements.right.y)
    term.write('E')
    term.setCursorPos(elements.level_up.x, elements.level_up.y)
    term.write('+')
    term.setCursorPos(elements.level_down.x, elements.level_down.y)
    term.write('-')
    term.setCursorPos(elements.zoom_in.x, elements.zoom_in.y)
    term.write('+')
    term.setCursorPos(elements.zoom_out.x, elements.zoom_out.y)
    term.write('-')
    term.setBackgroundColor(colors.brown)
    term.setCursorPos(elements.level_indicator.x, elements.level_indicator.y)
    term.write(string.format('LEVEL: %3d', monitor_level.y))
    term.setCursorPos(elements.zoom_indicator.x, elements.zoom_indicator.y)
    term.write('ZOOM: ' .. monitor_zoom_level)
    term.setCursorPos(elements.x_indicator.x, elements.x_indicator.y)
    term.write('X: ' .. monitor_location.x)
    term.setCursorPos(elements.z_indicator.x, elements.z_indicator.y)
    term.write('Z: ' .. monitor_location.z)
    term.setCursorPos(elements.center_indicator.x, elements.center_indicator.y)
    term.write('-CENTER')
    term.setCursorPos(elements.menu_indicator.x, elements.menu_indicator.y)
    term.write('-MENU')
    term.setCursorPos(elements.all_indicator.x, elements.all_indicator.y)
    term.write('ALL-')
    term.setCursorPos(elements.mining_indicator.x, elements.mining_indicator.y)
    term.write('MINING-')
    
    term.redirect(monitor.restore_to)
end
 
 
function touch_monitor(monitor_touch)
    if monitor_touch.x == elements.up.x and monitor_touch.y == elements.up.y then
        monitor_location.z = monitor_location.z - zoom_factor
    elseif monitor_touch.x == elements.down.x and monitor_touch.y == elements.down.y then
        monitor_location.z = monitor_location.z + zoom_factor
    elseif monitor_touch.x == elements.left.x and monitor_touch.y == elements.left.y then
        monitor_location.x = monitor_location.x - zoom_factor
    elseif monitor_touch.x == elements.right.x and monitor_touch.y == elements.right.y then
        monitor_location.x = monitor_location.x + zoom_factor
    elseif monitor_touch.x == elements.level_up.x and monitor_touch.y == elements.level_up.y then
        monitor_level_index = math.min(monitor_level_index + 1, #config.mine_levels)
        select_mine_level()
    elseif monitor_touch.x == elements.level_down.x and monitor_touch.y == elements.level_down.y then
        monitor_level_index = math.max(monitor_level_index - 1, 1)
        select_mine_level()
    elseif monitor_touch.x == elements.zoom_in.x and monitor_touch.y == elements.zoom_in.y then
        monitor_zoom_level = math.max(monitor_zoom_level - 1, 0)
    elseif monitor_touch.x == elements.zoom_out.x and monitor_touch.y == elements.zoom_out.y then
        monitor_zoom_level = math.min(monitor_zoom_level + 1, config.monitor_max_zoom_level)
    elseif monitor_touch.x == elements.menu.x and monitor_touch.y == elements.menu.y then
        menu()
    elseif monitor_touch.x == elements.center.x and monitor_touch.y == elements.center.y then
        monitor_location = {x = config.default_monitor_location.x, z = config.default_monitor_location.z}
    elseif monitor_touch.x == elements.all_turtles.x and monitor_touch.y == elements.all_turtles.y then
        local turtle_ids = {}
        for _, turtle in pairs(state.turtles) do
            if turtle.data then
                table.insert(turtle_ids, turtle.id)
            end
        end
        if #turtle_ids then
            turtle_viewer(turtle_ids)
        end
    elseif monitor_touch.x == elements.mining_turtles.x and monitor_touch.y == elements.mining_turtles.y then
        local turtle_ids = {}
        for _, turtle in pairs(state.turtles) do
            if turtle.data and turtle.data.turtle_type == 'mining' then
                table.insert(turtle_ids, turtle.id)
            end
        end
        if #turtle_ids then
            turtle_viewer(turtle_ids)
        end
    else
        local str_pos = monitor_touch.x .. ',' .. monitor_touch.y
        if turtles[str_pos] then
            turtle_viewer(turtles[str_pos])
        end
    end
end
 
 
function init_elements()
    elements = {
        up               = {x = math.ceil(monitor_width / 2), y = 1                            },
        down             = {x = math.ceil(monitor_width / 2), y = monitor_height               },
        left             = {x = 1,                            y = math.ceil(monitor_height / 2)},
        right            = {x = monitor_width,                y = math.ceil(monitor_height / 2)},
        level_up         = {x = monitor_width, y =  1},
        level_down       = {x = monitor_width - 11, y =  1},
        level_indicator  = {x = monitor_width - 10, y =  1},
        zoom_in          = {x = monitor_width, y =  2},
        zoom_out         = {x = monitor_width - 8, y = 2},
        zoom_indicator   = {x = monitor_width - 7, y = 2},
        all_turtles      = {x = monitor_width, y = monitor_height-1},
        all_indicator    = {x = monitor_width - 4, y = monitor_height-1},
        mining_turtles   = {x = monitor_width, y = monitor_height},
        mining_indicator = {x = monitor_width - 7, y = monitor_height},
        menu             = {x =  1, y = monitor_height},
        menu_indicator   = {x =  2, y = monitor_height},
        center           = {x =  1, y =  1},
        center_indicator = {x =  2, y =  1},
        x_indicator      = {x =  1, y =  2},
        z_indicator      = {x =  1, y =  3},
        viewer_exit      = {x =  1, y =  1},
        turtle_face      = {x =  5, y =  2},
        turtle_id        = {x = 16, y =  2},
        turtle_lost      = {x = 13, y =  1},
        turtle_data      = {x =  4, y =  8},
        turtle_return    = {x = 26, y =  8},
        turtle_update    = {x = 26, y =  9},
        turtle_reboot    = {x = 26, y = 10},
        turtle_halt      = {x = 26, y = 11},
        turtle_clear     = {x = 26, y = 12},
        turtle_reset     = {x = 26, y = 13},
        turtle_find      = {x = 26, y = 14},
        turtle_forward   = {x = 10, y = 16},
        turtle_back      = {x = 10, y = 18},
        turtle_up        = {x = 23, y = 16},
        turtle_down      = {x = 23, y = 18},
        turtle_left      = {x =  6, y = 17},
        turtle_right     = {x = 14, y = 17},
        turtle_dig_up    = {x = 31, y = 16},
        turtle_dig       = {x = 31, y = 17},
        turtle_dig_down  = {x = 31, y = 18},
        menu_title       = {x =  9, y =  3},
        menu_toggle      = {x = 10, y = 11},
        menu_update      = {x = 10, y = 13},
        menu_return      = {x = 10, y = 14},
        menu_reboot      = {x = 10, y = 15},
        menu_halt        = {x = 10, y = 16},
        menu_clear       = {x = 10, y = 17},
        menu_reset       = {x = 10, y = 18},
    }
end
 
 
function select_mine_level()
    monitor_level = state.mine[config.mine_levels[monitor_level_index].level]
end
 
 
function step()
    while #state.monitor_touches > 0 do
        touch_monitor(table.remove(state.monitor_touches))
    end
    draw_monitor()
    monitor.setVisible(true)
    monitor.setVisible(false)
    sleep(sleep_len)
end
 
 
function main()
    sleep_len = 0.3
    
    local attached = peripheral.find('monitor')
    
    if not attached then
        error('No monitor connected.')
    end
    
    monitor_size = {attached.getSize()}
    monitor_width = monitor_size[1]
    monitor_height = monitor_size[2]
    
    if monitor_width < 29 or monitor_height < 12 then -- Must be at least that big
        return
    end
    
    monitor = window.create(attached, 1, 1, monitor_width, monitor_height)
    monitor.restore_to = term.current()
    monitor.clear()
    monitor.setVisible(false)
    monitor.setCursorPos(1, 1)
    
    monitor_location = {x = config.locations.mine_enter.x, z = config.locations.mine_enter.z}
    monitor_zoom_level = config.default_monitor_zoom_level
    
    init_elements()
    
    while not state.mine do
        sleep(0.5)
    end
    
    monitor_level_index = 1
    select_mine_level()
    
    state.monitor_touches = {}
    while true do
        local status, caught_error = pcall(step)
        if not status then
            term.redirect(monitor.restore_to)
            error(caught_error)
        end
    end
end
 
 
main()]===],
    ["hub_files/report.lua"] = [===[-- CONTINUOUSLY BROADCAST STATUS REPORTS
while true do
    
    turtles_parked = 0
    turtle_count = 0
    for _, turtle in pairs(state.turtles) do
        if turtle.state == 'park' then
            turtles_parked = turtles_parked + 1
        end
        turtle_count = turtle_count + 1
    end
 
    rednet.broadcast({
            on             = state.on,
            turtles_parked = turtles_parked,
            turtle_count   = turtle_count,
        }, 'hub_report')
    
    sleep(0.5)
    
end]===],
    ["hub_files/session_id"] = [===[29.0]===],
    ["hub_files/startup.lua"] = [===[-- SET LABEL
os.setComputerLabel('Hub')
 
-- INITIALIZE APIS
if fs.exists('/apis') then
    fs.delete('/apis')
end
fs.makeDir('/apis')
fs.copy('/config.lua', '/apis/config')
fs.copy('/state.lua', '/apis/state')
fs.copy('/basics.lua', '/apis/basics')
os.loadAPI('/apis/config')
os.loadAPI('/apis/state')
os.loadAPI('/apis/basics')
 
 
-- OPEN REDNET
for _, side in pairs({'back', 'top', 'left', 'right'}) do
    if peripheral.getType(side) == 'modem' then
        rednet.open(side)
        break
    end
end
 
 
-- IF UPDATED PRINT "UPDATED"
if fs.exists('/updated') then
    fs.delete('/updated')
    print('UPDATED')
    state.updated = true
end
 
 
-- LAUNCH PROGRAMS AS SEPARATE THREADS
multishell.launch({}, '/user.lua')
multishell.launch({}, '/report.lua')
multishell.launch({}, '/monitor.lua')
multishell.launch({}, '/events.lua')
multishell.launch({}, '/whosmineisitanyway.lua')
multishell.setTitle(2, 'user')
multishell.setTitle(3, 'report')
multishell.setTitle(4, 'monitor')
multishell.setTitle(5, 'events')
multishell.setTitle(6, 'whosmine')]===],
    ["hub_files/state.lua"] = [===[user_input = {}
turtles = {}
pockets = {}
homes = {}]===],
    ["hub_files/update"] = [===[os.run({}, '/disk/hub.lua')]===],
    ["hub_files/updated"] = [===[]===],
    ["hub_files/user.lua"] = [===[-- CONTINUOUSLY AWAIT USER INPUT AND PLACE IN TABLE
while true do
    table.insert(state.user_input, read())
end]===],
    ["hub_files/whosmineisitanyway.lua"] = [===[inf = basics.inf
str_xyz = basics.str_xyz
 
 
reverse_shift = {
    north = 'south',
    south = 'north',
    east  = 'west',
    west  = 'east',
}
 
 
function load_mine()
    -- LOAD MINE INTO state.mine FROM /mine/<x,z>/ DIRECTORY
    state.mine_dir_path = '/mine/' .. config.locations.mine_enter.x .. ',' .. config.locations.mine_enter.z .. '/'
    state.mine = {}
    
    if not fs.exists(state.mine_dir_path) then
        fs.makeDir(state.mine_dir_path)
    end
    
    if fs.exists(state.mine_dir_path .. 'on') then
        state.on = true
    end
    
    for _, level_and_chance in pairs(config.mine_levels) do
        local level = level_and_chance.level
        state.mine[level] = {y = level}
    
        -- START WITH AT LEAST A MAIN SHAFT
        state.mine[level].main_shaft = {}
        state.mine[level].main_shaft.west = {name = 'main_shaft', x = config.locations.mine_enter.x, y = level, z = config.locations.mine_enter.z, orientation = 'west'}
        state.mine[level].main_shaft.east = {name = 'main_shaft', x = config.locations.mine_enter.x, y = level, z = config.locations.mine_enter.z, orientation = 'east'}
        
        -- FOR EACH STRIP IN /mine/<x,z>/<level>/ PUT INTO MEMORY
        local level_dir_path = state.mine_dir_path .. level .. '/'
        if not fs.exists(level_dir_path) then
            fs.makeDir(level_dir_path)
        else
            for _, file_name in pairs(fs.list(level_dir_path)) do
                if file_name:sub(1, 1) ~= '.' then
                    local file = fs.open(level_dir_path .. file_name, 'r')
                    if file == nil then
                        error('Failed to open file ' .. level_dir_path .. file_name)
                    else
                        if file_name == 'main_shaft' then
                            local xs = string.gmatch(file.readAll(), '[^,]+')
                            state.mine[level].main_shaft = {}
                            state.mine[level].main_shaft.west = {name = 'main_shaft', x = tonumber(xs()), y = level, z = config.locations.mine_enter.z, orientation = 'west'}
                            state.mine[level].main_shaft.east = {name = 'main_shaft', x = tonumber(xs()), y = level, z = config.locations.mine_enter.z, orientation = 'east'}
                        else
                            local zs = string.gmatch(file.readAll(), '[^,]+')
                            local x = tonumber(file_name)
                            state.mine[level][x] = {}
                            state.mine[level][x].north = {name = x, x = x, y = level, z = tonumber(zs()), orientation = 'north'}
                            state.mine[level][x].south = {name = x, x = x, y = level, z = tonumber(zs()), orientation = 'south'}
                        end
                        file.close()
                    end
                end
            end
        end
    end
    
    state.turtles_dir_path = state.mine_dir_path .. 'turtles/'
    
    if not fs.exists(state.turtles_dir_path) then
        fs.makeDir(state.turtles_dir_path)
    end
    
    local turtle_pairs = {}
    
    for _, turtle_id in pairs(fs.list(state.turtles_dir_path)) do
        if turtle_id:sub(1, 1) ~= '.' then
            turtle_id = tonumber(turtle_id)
            local turtle = {id = turtle_id}
            state.turtles[turtle_id] = turtle
            local turtle_dir_path = state.turtles_dir_path .. turtle_id .. '/'
            if fs.exists(turtle_dir_path .. 'strip') then
                local file = fs.open(turtle_dir_path .. 'strip', 'r')
                if file == nil then
                    error('Failed to open file ' .. turtle_dir_path .. 'strip')
                end
                local strip_args = string.gmatch(file.readAll(), '[^,]+')
 
                local level = tonumber(strip_args())
                local name = strip_args()
                if name ~= 'main_shaft' then
                    name = tonumber(name)
                end
                local orientation = strip_args()
 
                if state.mine[level] and state.mine[level][name] and state.mine[level][name][orientation] then
                    turtle.strip = state.mine[level][name][orientation]
                    if fs.exists(turtle_dir_path .. 'deployed') then
                        file = fs.open(turtle_dir_path .. 'deployed', 'r')
                        if file == nil then
                            error('Failed to open file ' .. turtle_dir_path .. 'deployed')
                        end
                        turtle.steps_left = tonumber(file.readAll())
                        if not turtle_pairs[turtle.strip] then
                            turtle_pairs[turtle.strip] = {}
                        end
                        table.insert(turtle_pairs[turtle.strip], turtle)
                    end
                end
 
            end
            if fs.exists(turtle_dir_path .. 'halt') then
                turtle.state = 'halt'
            end
        end
    end
    
    for strip, turtles in pairs(turtle_pairs) do
        if #turtles == 2 then
            strip.turtles = turtles
            turtles[1].pair = turtles[2]
            turtles[2].pair = turtles[1]
        elseif #turtles == 1 and not config.use_chunky_turtles then
            strip.turtles = turtles
        end
    end
end
 
 
function write_strip(level, name)
    -- RECORD THE STATE OF A STRIP AT A GIVEN level AND name TO /mine/<center>/<level>/<name>
    local file = fs.open(state.mine_dir_path .. level .. '/' .. name, 'w')
    if name == 'main_shaft' then
        file.write(state.mine[level][name].west.x .. ',' .. state.mine[level][name].east.x)
    else
        file.write(state.mine[level][name].north.z .. ',' .. state.mine[level][name].south.z)
    end
    file.close()
end
 
 
function write_turtle_strip(turtle, strip)
    local file = fs.open(state.turtles_dir_path .. turtle.id .. '/strip', 'w')
    file.write(strip.y .. ',' .. strip.name .. ',' .. strip.orientation)
    file.close()
end
 
 
function halt(turtle)
    add_task(turtle, {action = 'pass', end_state = 'halt'})
    fs.open(state.turtles_dir_path .. turtle.id .. '/halt', 'w').close()
end
 
 
function unhalt(turtle)
    fs.delete(state.turtles_dir_path .. turtle.id .. '/halt', 'w')
end
 
 
function update_strip(turtle)
    -- RECORD THAT A STRIP HAS BEEN EXPLORED TO TURTLE'S POSITION
    local strip = turtle.strip
    if strip then
        if strip.orientation == 'north' then
            strip.z = math.min(strip.z, turtle.data.location.z)
        elseif strip.orientation == 'south' then
            strip.z = math.max(strip.z, turtle.data.location.z)
        elseif strip.orientation == 'east' then
            strip.x = math.max(strip.x, turtle.data.location.x)
        elseif strip.orientation == 'west' then
            strip.x = math.min(strip.x, turtle.data.location.x)
        end
        write_strip(strip.y, strip.name)
    end
end
 
 
function expand_mine(level, x)
    if not state.mine[level][x] then
        state.mine[level][x] = {}
        state.mine[level][x].north = {name = x, x = x, y = level, z = config.locations.mine_enter.z, orientation = 'north'}
        state.mine[level][x].south = {name = x, x = x, y = level, z = config.locations.mine_enter.z, orientation = 'south'}
        write_strip(level, x)
    end
end
 
 
function gen_next_strip()
    local level = get_mining_level()
    state.next_strip = get_closest_free_strip(level)
    if state.next_strip then
        state.min_fuel = (basics.distance(state.next_strip, config.locations.mine_enter) + config.mission_length) * 3
    else
        state.min_fuel = nil
    end
end
 
 
function get_closest_free_strip(level)
    local west_x = config.locations.mine_enter.x
    local east_x = config.locations.mine_enter.x
    local offset_x = 0
    local min_x = state.mine[level].main_shaft.west.x
    local max_x = state.mine[level].main_shaft.east.x
    
    local closest_strip
    local distance
    local z_distance
    local min_dist = inf
    
    while west_x >= min_x and east_x <= max_x and offset_x < min_dist do
        for _, x in pairs({west_x, east_x}) do
            for _, z_side in pairs({'north', 'south'}) do
                expand_mine(level, x)
                strip = state.mine[level][x][z_side]
                if not strip.turtles then
                    z_distance = math.abs(strip.z - config.locations.mine_enter.z)
                    distance = z_distance + math.abs(strip.x - config.locations.mine_enter.x)
                    if distance < min_dist then
                        min_dist = distance
                        closest_strip = strip
                    end
                end
            end
        end
        offset_x = offset_x + config.grid_width
        west_x = config.locations.mine_enter.x - offset_x
        east_x = config.locations.mine_enter.x + offset_x
    end
    
    for _, strip in pairs({state.mine[level].main_shaft.west, state.mine[level].main_shaft.east}) do
        if not strip.turtles then
            distance = math.abs(strip.x - config.locations.mine_enter.x)
            if distance <= min_dist then
                min_dist = distance
                closest_strip = strip
            end
        end
    end
    
    return closest_strip
end
 
 
function get_mining_level()
    local n = 0
    local r = math.random()
    for _, level_and_chance in pairs(config.mine_levels) do
        n = n + level_and_chance.chance
        if n > r then
            return level_and_chance.level
        end
    end
end
 
 
function good_on_fuel(mining_turtle, chunky_turtle)
    local fuel_needed = math.ceil(basics.distance(mining_turtle.data.location, config.locations.mine_exit) * 1.5)
    return (mining_turtle.data.fuel_level == "unlimited" or mining_turtle.data.fuel_level > fuel_needed) and ((not config.use_chunky_turtles) or (chunky_turtle.data.fuel_level == "unlimited" or chunky_turtle.data.fuel_level > fuel_needed))
end
 
 
function follow(chunky_turtle)
    add_task(chunky_turtle, {
        action = 'go_to_strip',
        data = {chunky_turtle.strip},
        end_state = 'wait',
    })
end
 
 
function go_mine(mining_turtle)
    update_strip(mining_turtle)
    add_task(mining_turtle, {
        action = 'mine_vein',
        data = {mining_turtle.strip.orientation},
    })
    add_task(mining_turtle, {
        action = 'clear_gravity_blocks',
    })
    if config.use_chunky_turtles then
        add_task(mining_turtle, {
            action = 'go_to_strip',
            data = {mining_turtle.strip},
            end_state = 'wait',
            end_function = follow,
            end_function_args = {mining_turtle.pair},
        })
    else
        add_task(mining_turtle, {
            action = 'go_to_strip',
            data = {mining_turtle.strip},
            end_state = 'wait',
        })
    end
    mining_turtle.steps_left = mining_turtle.steps_left - 1
    local file = fs.open(state.turtles_dir_path .. mining_turtle.id .. '/deployed', 'w')
    file.write(mining_turtle.steps_left)
    file.close()
end
 
 
function free_turtle(turtle)
    if turtle.pair then
        fs.delete(state.turtles_dir_path .. turtle.id .. '/deployed')
        fs.delete(state.turtles_dir_path .. turtle.pair.id .. '/deployed')
        turtle.pair.pair = nil
        turtle.pair = nil
        turtle.strip.turtles = nil
    end
end
 
 
function pair_turtles_finish()
    state.pair_hold = nil
end
 
 
function pair_turtles_send(chunky_turtle)
    add_task(chunky_turtle, {
        action = 'go_to_mine_enter',
        end_function = pair_turtles_finish
    })
    
    add_task(chunky_turtle, {
        action = 'go_to_strip',
        data = {chunky_turtle.strip},
        end_state = 'wait',
    })
end
 
 
function pair_turtles_begin(turtle1, turtle2)
    local mining_turtle
    local chunky_turtle
    if turtle1.data.turtle_type == 'mining' then
        if turtle2.data.turtle_type ~= 'chunky' then
            error('Incompatable turtles')
        end
        mining_turtle = turtle1
        chunky_turtle = turtle2
    elseif turtle1.data.turtle_type == 'chunky' then
        if turtle2.data.turtle_type ~= 'mining' then
            error('Incompatable turtles')
        end
        chunky_turtle = turtle1
        mining_turtle = turtle2
    end
    
    local strip = state.next_strip
    local level = strip.level
    
    if not strip then
        gen_next_strip()
        add_task(mining_turtle, {action = 'pass', end_state = 'idle'})
        add_task(chunky_turtle, {action = 'pass', end_state = 'idle'})
        return
    end
    
    print('Pairing ' .. mining_turtle.id .. ' and ' .. chunky_turtle.id)
    
    mining_turtle.pair = chunky_turtle
    chunky_turtle.pair = mining_turtle
    
    state.pair_hold = {mining_turtle, chunky_turtle}
        
    mining_turtle.steps_left = config.mission_length
    
    strip.turtles = {mining_turtle, chunky_turtle}
    
    for _, turtle in pairs(strip.turtles) do
        turtle.strip = strip
        write_turtle_strip(turtle, strip)
        add_task(turtle, {action = 'pass', end_state = 'trip'})
    end
    
    fs.open(state.turtles_dir_path .. chunky_turtle.id .. '/deployed', 'w').close()
    local file = fs.open(state.turtles_dir_path .. mining_turtle.id .. '/deployed', 'w')
    file.write(mining_turtle.steps_left)
    file.close()
    
    add_task(mining_turtle, {
        action = 'go_to_mine_enter',
        end_function = pair_turtles_send,
        end_function_args = {chunky_turtle}
    })
    
    add_task(mining_turtle, {
        action = 'go_to_strip',
        data = {mining_turtle.strip},
        end_state = 'wait',
    })
    
    gen_next_strip()
end
 
 
function solo_turtle_begin(turtle)
    
    local strip = state.next_strip
    local level = strip.level
    
    if not strip then
        gen_next_strip()
        add_task(turtle, {action = 'pass', end_state = 'idle'})
        return
    end
    
    print('Assigning ' .. turtle.id)
        
    turtle.steps_left = config.mission_length
    
    strip.turtles = {turtle}
    
    for _, turtle in pairs(strip.turtles) do
        turtle.strip = strip
        write_turtle_strip(turtle, strip)
        add_task(turtle, {action = 'pass', end_state = 'trip'})
    end
    
    local file = fs.open(state.turtles_dir_path .. turtle.id .. '/deployed', 'w')
    file.write(turtle.steps_left)
    file.close()
    
    add_task(turtle, {
        action = 'go_to_mine_enter',
    })
    
    add_task(turtle, {
        action = 'go_to_strip',
        data = {turtle.strip},
        end_state = 'wait',
    })
    
    gen_next_strip()
end
 
 
function check_pair_fuel(turtle)
    if state.min_fuel then
        if (turtle.data.fuel_level ~= "unlimited" and turtle.data.fuel_level <= state.min_fuel) then
            add_task(turtle, {action = 'prepare', data = {state.min_fuel}})
        else
            add_task(turtle, {action = 'pass', end_state = 'pair'})
        end
    else
        gen_next_strip()
    end
end
 
 
function send_turtle_up(turtle)
    if turtle.data.location.y < config.locations.mine_enter.y then
        if turtle.strip then
            
            if turtle.data.turtle_type == 'chunky' and turtle.data.location.y == turtle.strip.y then
                add_task(turtle, {action = 'delay', data={3}})
            end
            
            add_task(turtle, {action = 'go_to_mine_exit', data = {turtle.strip}})
        end
    end
end
 
 
function initialize_turtle(turtle)
    local data = {session_id, config}
    
    if turtle.state ~= 'halt' then
        turtle.state = 'lost'
    end
    turtle.task_id = 2
    turtle.tasks = {}
    add_task(turtle, {action = 'initialize', data = data})
end
 
 
function add_task(turtle, task)
    if not task.data then
        task.data = {}
    end
    table.insert(turtle.tasks, task)
end
 
 
function send_tasks(turtle)
    local task = turtle.tasks[1]
    if task then
        local turtle_data = turtle.data
        if turtle_data.request_id == turtle.task_id and turtle.data.session_id == session_id then
            if turtle_data.success then
                if task.end_state then
                    if turtle.state == 'halt' and task.end_state ~= 'halt' then
                        unhalt(turtle)
                    end
                    turtle.state = task.end_state
                end
                if task.end_function then
                    if task.end_function_args then
                        task.end_function(unpack(task.end_function_args))
                    else
                        task.end_function()
                    end
                end
                table.remove(turtle.tasks, 1)
            end
            turtle.task_id = turtle.task_id + 1
        elseif (not turtle_data.busy) and ((not task.epoch) or (task.epoch > os.clock()) or (task.epoch + config.task_timeout < os.clock())) then
            -- ONLY SEND INSTRUCTION AFTER <config.task_timeout> SECONDS HAVE PASSED
            task.epoch = os.clock()
            print(string.format('Sending %s directive to %d', task.action, turtle.id))
            rednet.send(turtle.id, {
                action = task.action,
                data = task.data,
                request_id = turtle_data.request_id
            }, 'mastermine')
        end
    end
end
 
 
function user_input(input)
    -- PROCESS USER INPUT FROM USER_INPUT TABLE
    while #state.user_input > 0 do
        local input = table.remove(state.user_input, 1)
        local next_word = string.gmatch(input, '%S+')
        local command = next_word()
        local turtle_id_string = next_word()
        local turtle_id
        local turtles = {}
        if turtle_id_string and turtle_id_string ~= '*' then
            turtle_id = tonumber(turtle_id_string)
            if state.turtles[turtle_id] then
                turtles = {state.turtles[turtle_id]}
            end
        else
            turtles = state.turtles
        end
        if command == 'turtle' then
            -- SEND COMMAND DIRECTLY TO TURTLE
            local action = next_word()
            local data = {}
            for user_arg in next_word do
                table.insert(data, user_arg)
            end
            for _, turtle in pairs(turtles) do
                halt(turtle)
                add_task(turtle, {
                    action = action,
                    data = data,
                })
            end
        elseif command == 'clear' then
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
            end
        elseif command == 'shutdown' then
            -- REBOOT TURTLE
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
                rednet.send(turtle.id, {
                    action = 'shutdown',
                }, 'mastermine')
            end
        elseif command == 'reboot' then
            -- REBOOT TURTLE
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
                rednet.send(turtle.id, {
                    action = 'reboot',
                }, 'mastermine')
            end
        elseif command == 'update' then
            -- FEED TURTLE DINNER
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
                rednet.send(turtle.id, {
                    action = 'update',
                }, 'mastermine')
            end
        elseif command == 'return' then
            -- BRING TURTLE HOME
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
                halt(turtle)
                send_turtle_up(turtle)
                add_task(turtle, {action = 'go_to_home'})
            end
        elseif command == 'halt' then
            -- HALT TURTLE(S)
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
                halt(turtle)
            end
        elseif command == 'reset' then
            -- HALT TURTLE(S)
            for _, turtle in pairs(turtles) do
                turtle.tasks = {}
                add_task(turtle, {action = 'pass'})
                add_task(turtle, {action = 'pass', end_state = 'lost'})
            end
        elseif command == 'on' or command == 'go' then
            -- ACTIVATE MINING NETWORK
            if not turtle_id_string then
                for _, turtle in pairs(state.turtles) do
                    turtle.tasks = {}
                    add_task(turtle, {action = 'pass'})
                end
                state.on = true
                fs.open(state.mine_dir_path .. 'on', 'w').close()
            end
        elseif command == 'off' or command == 'stop' then
            -- STANDBY MINING NETWORK
            if not turtle_id_string then
                for _, turtle in pairs(state.turtles) do
                    turtle.tasks = {}
                    add_task(turtle, {action = 'pass'})
                    free_turtle(turtle)
                end
                state.on = nil
                fs.delete(state.mine_dir_path .. 'on')
            end
        elseif command == 'hubshutdown' then
            -- STANDBY MINING NETWORK
            if not turtle_id_string then
                os.shutdown()
            end
        elseif command == 'hubreboot' then
            -- STANDBY MINING NETWORK
            if not turtle_id_string then
                os.reboot()
            end
        elseif command == 'hubupdate' then
            -- STANDBY MINING NETWORK
            if not turtle_id_string then
                os.run({}, '/update')
            end
        elseif command == 'debug' then
            -- DEBUG
        end
    end
end
 
 
function command_turtles()
    local turtles_for_pair = {}
    
    for _, turtle in pairs(state.turtles) do
        
        if turtle.data then
        
            if turtle.data.session_id ~= session_id then
                -- BABY TURTLE NEEDS TO LEARN
                if (not turtle.tasks) or (not turtle.tasks[1]) or (not (turtle.tasks[1].action == 'initialize')) then
                    initialize_turtle(turtle)
                end
            end
 
            if #turtle.tasks > 0 then
                -- TURTLE IS BUSY
                send_tasks(turtle)
 
            elseif not turtle.data.location then
                -- TURTLE NEEDS A MAP
                add_task(turtle, {action = 'calibrate'})
 
            elseif turtle.state ~= 'halt' then
 
                if turtle.state == 'park' then
                    -- TURTLE FOUND PARKING
                    if state.on and (config.use_chunky_turtles or turtle.data.turtle_type == 'mining') then
                        add_task(turtle, {action = 'pass', end_state = 'idle'})
                    end
 
                elseif not state.on and turtle.state ~= 'idle' then
                    -- TURTLE HAS TO STOP
                    add_task(turtle, {action = 'pass', end_state = 'idle'})
 
                elseif turtle.state == 'lost' then
                    -- TURTLE IS CONFUSED
                    if turtle.data.location.y < config.locations.mine_enter.y and (turtle.pair or not config.use_chunky_turtles) then
                        add_task(turtle, {action = 'pass', end_state = 'trip'})
                        add_task(turtle, {
                            action = 'go_to_strip',
                            data = {turtle.strip},
                            end_state = 'wait'
                        })
                    else
                        add_task(turtle, {action = 'pass', end_state = 'idle'})
                    end
 
                elseif turtle.state == 'idle' then
                    -- TURTLE IS BORED
                    free_turtle(turtle)
                    if turtle.data.location.y < config.locations.mine_enter.y then
                        send_turtle_up(turtle)
                    elseif not basics.in_area(turtle.data.location, config.locations.control_room_area) then
                        halt(turtle)
                    elseif turtle.data.item_count > 0 or (turtle.data.fuel_level ~= "unlimited" and turtle.data.fuel_level < config.fuel_per_unit) then
                        add_task(turtle, {action = 'prepare', data = {config.fuel_per_unit}})
                    elseif state.on then
                        add_task(turtle, {
                            action = 'go_to_waiting_room',
                            end_function = check_pair_fuel,
                            end_function_args = {turtle},
                        })
                    else
                        add_task(turtle, {action = 'go_to_home', end_state = 'park'})
                    end
 
                elseif turtle.state == 'pair' then
                    -- TURTLE NEEDS A FRIEND
                    if config.use_chunky_turtles then
                        if not state.pair_hold then
                            if not turtle.pair then
                                table.insert(turtles_for_pair, turtle)
                            end
                        else
                            if not (state.pair_hold[1].pair and state.pair_hold[2].pair) then
                                state.pair_hold = nil
                            end
                        end
                    else
                        solo_turtle_begin(turtle)
                    end
 
                elseif turtle.state == 'wait' then
                    -- TURTLE GO DO SOME WORK
                    if turtle.pair then
                        if turtle.data.turtle_type == 'mining' and turtle.pair.state == 'wait' then
                            if turtle.steps_left <= 0 or (turtle.data.empty_slot_count == 0 and turtle.pair.data.empty_slot_count == 0) or not good_on_fuel(turtle, turtle.pair) then
                                add_task(turtle, {action = 'pass', end_state = 'idle'})
                                add_task(turtle.pair, {action = 'pass', end_state = 'idle'})
                            elseif turtle.data.empty_slot_count == 0 then
                                add_task(turtle, {
                                    action = 'dump',
                                    data = {reverse_shift[turtle.strip.orientation]}
                                })
                            else
                                add_task(turtle, {action = 'pass', end_state = 'mine'})
                                add_task(turtle.pair, {action = 'pass', end_state = 'mine'})
                                go_mine(turtle)
                            end
                        end
                    elseif not config.use_chunky_turtles then
                        if turtle.steps_left <= 0 or turtle.data.empty_slot_count == 0 or not good_on_fuel(turtle) then
                            add_task(turtle, {action = 'pass', end_state = 'idle'})
                        else
                            add_task(turtle, {action = 'pass', end_state = 'mine'})
                            go_mine(turtle)
                        end
                    else
                        add_task(turtle, {action = 'pass', end_state = 'idle'})
                    end
                elseif turtle.state == 'mine' then
                    if config.use_chunky_turtles and not turtle.pair then
                        add_task(turtle, {action = 'pass', end_state = 'idle'})
                    end
                end
            end
        end
    end
    if #turtles_for_pair == 2 then
        pair_turtles_begin(turtles_for_pair[1], turtles_for_pair[2])
    end
end
 
 
function main()
    -- INCREASE SESSION ID BY ONE
    if fs.exists('/session_id') then
        session_id = tonumber(fs.open('/session_id', 'r').readAll()) + 1
    else
        session_id = 1
    end
    local file = fs.open('/session_id', 'w')
    file.write(session_id)
    file.close()
    
    -- LOAD MINE INTO MEMORY
    load_mine()
    
    -- FIND THE CLOSEST STRIP
    gen_next_strip()
    
    local cycle = 0
    while true do
        print('Cycle: ' .. cycle)
        user_input()         -- PROCESS USER INPUT
        command_turtles()    -- COMMAND TURTLES
        sleep(0.1)           -- DELAY 0.1 SECONDS
        cycle = cycle + 1
    end
end
 
 
main()]===],
}
 
for k, v in pairs(files) do
    local file = fs.open(fs.combine(path, k), 'w')
    file.write(v)
    file.close()
end
 

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎