Last active
February 14, 2021 01:43
-
-
Save DavidGoldman/f73492ec45a27358dcfcc3aea1b08964 to your computer and use it in GitHub Desktop.
Minecraft Strip Miner (Minecraft 1.12 requires Plethora Peripherals, Minecraft 1.7.10 requires Open Peripherals)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ Strip Miner by Davidee inspired by https://pastebin.com/6qmBBLpz ]]-- | |
--[[ History at https://gist.github.com/DavidGoldman/f73492ec45a27358dcfcc3aea1b08964 ]] | |
--[[ Do what you will with the script. ]]-- | |
--[[ This turtle script is smart enough to mine veins of a hardcoded set of ores. ]]-- | |
--[[ Cheers! ]]-- | |
local version = "1.0.6" | |
-- Logging | |
local ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF = 1, 2, 3, 4, 5, 6, 7 | |
local LOG_PREFIXES = { "", "[Debug] ", "[Info] ", "[Warn] ", "[Error] ", "[Fatal] ", "" } | |
local log_f_handle = nil | |
local logging_level = ALL | |
local function log(level, msg) | |
if level < logging_level then | |
return | |
end | |
if log_f_handle == nil then | |
log_f_handle = fs.open("miner.log", "w") | |
end | |
log_f_handle.writeLine(LOG_PREFIXES[level] .. msg) | |
log_f_handle.flush() | |
end | |
local function closeLog() | |
if log_f_handle ~= nil then | |
log_f_handle.close() | |
log_f_handle = nil | |
end | |
end | |
-- Sorted pairs function from https://www.lua.org/pil/19.3.html | |
local function pairsByKeys(t, f) | |
local a = {} | |
for key in pairs(t) do | |
table.insert(a, key) | |
end | |
table.sort(a, f) | |
local i = 0 -- Iterator variable | |
local iter = function () -- Iterator function | |
i = i + 1 | |
if a[i] == nil then | |
return nil | |
else | |
return a[i], t[a[i]] | |
end | |
end | |
return iter | |
end | |
-- http://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value | |
local function deepcopy(o, seen) | |
seen = seen or {} | |
if o == nil then return nil end | |
if seen[o] then return seen[o] end | |
local no | |
if type(o) == 'table' then | |
no = {} | |
seen[o] = no | |
for k, v in next, o, nil do | |
no[deepcopy(k, seen)] = deepcopy(v, seen) | |
end | |
setmetatable(no, deepcopy(getmetatable(o), seen)) | |
else -- number, string, boolean, etc | |
no = o | |
end | |
return no | |
end | |
-- Stack data structure | |
local function stack_new() | |
return { _size = 0, _storage = {} } | |
end | |
local function stack_getSize(this) | |
return this._size | |
end | |
local function stack_apply(this, fn, context) | |
local size = this._size | |
local storage = this._storage | |
for i=size,1,-1 do | |
fn(context, storage[i]) | |
end | |
end | |
local function stack_reverse_apply(this, fn, context) | |
local size = this._size | |
local storage = this._storage | |
for i=1,size do | |
fn(context, storage[i]) | |
end | |
end | |
local function stack_push(this, val) | |
this._size = this._size + 1 | |
table.insert(this._storage, val) | |
end | |
local function stack_peek(this) | |
local size = this._size | |
if size == 0 then | |
return nil | |
else | |
return this._storage[size] | |
end | |
end | |
local function stack_pop(this) | |
local size = this._size | |
if size == 0 then | |
return nil | |
else | |
this._size = size - 1 | |
return table.remove(this._storage) | |
end | |
end | |
local function stack_pop_until(this, idx, fn) | |
local size = this._size | |
local storage = this._storage | |
for i=size,idx+1,-1 do | |
fn(i, table.remove(storage)) | |
end | |
this._size = idx | |
return stack_peek(this) | |
end | |
local function stack_copy(this) | |
local size = this._size | |
local my_storage = this._storage | |
local copy_storage = {} | |
for i=1,size do | |
table.insert(copy_storage, my_storage[i]) | |
end | |
return { _size = size, _storage = copy_storage } | |
end | |
local function stack_reverseCopy(this) | |
local size = this._size | |
local my_storage = this._storage | |
local copy_storage = {} | |
for i=size,1,-1 do | |
table.insert(copy_storage, my_storage[i]) | |
end | |
return { _size = size, _storage = copy_storage } | |
end | |
-- End stack | |
-- DIR = 0 (forward, neg z), 1 (right, pos x), 2 (backward, pos z), 3 (left, neg x) | |
local FORWARD, RIGHT, BACKWARD, LEFT = 0, 1, 2, 3 | |
local function dir_left(dir) | |
return (dir - 1) % 4 | |
end | |
local function dir_right(dir) | |
return (dir + 1) % 4 | |
end | |
local function dir_offset(dir, offset_dir) | |
return (dir + offset_dir) % 4 | |
end | |
local function dir_forward(dir, x, z) | |
if dir == FORWARD then | |
return x, z - 1 | |
elseif dir == RIGHT then | |
return x + 1, z | |
elseif dir == BACKWARD then | |
return x, z + 1 | |
else | |
return x - 1, z | |
end | |
end | |
local function dir_backward(dir, x, z) | |
if dir == FORWARD then | |
return x, z + 1 | |
elseif dir == RIGHT then | |
return x - 1, z | |
elseif dir == BACKWARD then | |
return x, z - 1 | |
else | |
return x + 1, z | |
end | |
end | |
-- End DIR helpers | |
-- Coord helpers | |
local function dir_coords_toString(x, y, z, dir) | |
return string.format("%d-%d-%d:%d", x, y, z, dir) | |
end | |
local function coords_toString(x, y, z) | |
return string.format("%d-%d-%d", x, y, z) | |
end | |
local function coords_getMovementStringsFromPos(x, y, z, dir) | |
local up = dir_coords_toString(x, y + 1, z, dir) | |
local down = dir_coords_toString(x, y - 1, z, dir) | |
local xf, zf = dir_forward(dir, x, z) | |
local forward = dir_coords_toString(xf, y, zf, dir) | |
local xb, zb = dir_backward(dir, x, z) | |
local backward = dir_coords_toString(xb, y, zb, dir) | |
return forward, backward, up, down | |
end | |
local function coords_getMovementStrings(state) | |
return coords_getMovementStringsFromPos(state.x, state.y, state.z, state.dir) | |
end | |
-- End Coord helpers | |
-- Tracker data structure used to keep track of all movement to go home via a moon walk (backwards). | |
-- All coordinates are relative to the turtle's starting position (the origin). | |
local function tracker_new(track_all_states) | |
local stack = stack_new() | |
stack_push(stack, { x = 0, y = 0, z = 0, dir = 0, cost = 0, action = "START"}) | |
return { _stack = stack, _movementTable = {}, _track_states = track_all_states, _sx = 0, _sy = 0, _sz = 0, _sdir = 0 } | |
end | |
local function tracker_newStartingFromOld(old_tracker) | |
local stack = stack_new() | |
local state = stack_peek(old_tracker._stack) | |
stack_push(stack, { x = state.x, y = state.y, z = state.z, dir = state.dir, cost = 0, action = "START" }) | |
return { _stack = stack, _movementTable = {}, _track_states = true, _sx = state.x, _sy = state.y, _sz = state.z, _sdir = state.dir } | |
end | |
local function _addToMovementTable(movementTable, key, value) | |
movementTable[key] = movementTable[key] or value | |
end | |
local function _removeFromMovementTable(movementTable, key, value) | |
if movementTable[key] == value then | |
movementTable[key] = nil | |
end | |
end | |
local function tracker_updateForState(this, state) | |
local stack = this._stack | |
local movementTable = this._movementTable | |
-- If we don't track all states, only keep track of the current position (not even the starting position). | |
if not this._track_states then | |
stack_pop(stack) | |
stack_push(stack, state) | |
return | |
end | |
-- Check if this movement can be traced back. | |
local coord_str = dir_coords_toString(state.x, state.y, state.z, state.dir) | |
local idx = movementTable[coord_str] | |
if idx ~= nil and idx ~= stack_getSize(stack) then | |
local prev_state = stack_pop_until(stack, idx, function(cur_index, cur_state) | |
-- Remove potential movements | |
local f, b, u, d = coords_getMovementStrings(cur_state) | |
_removeFromMovementTable(movementTable, f, cur_index) | |
_removeFromMovementTable(movementTable, b, cur_index) | |
_removeFromMovementTable(movementTable, u, cur_index) | |
_removeFromMovementTable(movementTable, d, cur_index) | |
end | |
) | |
-- Update proper action and cost for this state | |
state.cost = prev_state.cost + 1 | |
local f, b, u, d = coords_getMovementStrings(prev_state) | |
if coord_str == f then | |
state.action = "FORWARD" | |
elseif coord_str == b then | |
state.action = "BACKWARD" | |
elseif coord_str == u then | |
state.action = "UP" | |
else | |
state.action = "DOWN" | |
end | |
end | |
-- Push new state on to stack now that any previous ones have been removed | |
stack_push(stack, state) | |
-- Add potential movements from this state to table | |
local idx = stack_getSize(stack) | |
f, b, u, d = coords_getMovementStrings(state) | |
_addToMovementTable(movementTable, f, idx) | |
_addToMovementTable(movementTable, b, idx) | |
_addToMovementTable(movementTable, u, idx) | |
_addToMovementTable(movementTable, d, idx) | |
end | |
local function tracker_left(this) | |
local stack = this._stack | |
local last_state = stack_peek(stack) | |
local new_state = { | |
x = last_state.x, | |
y = last_state.y, | |
z = last_state.z, | |
dir = dir_left(last_state.dir), | |
cost = last_state.cost, | |
action = "LEFT" | |
} | |
tracker_updateForState(this, new_state) | |
end | |
local function tracker_right(this) | |
local stack = this._stack | |
local last_state = stack_peek(stack) | |
local new_state = { | |
x = last_state.x, | |
y = last_state.y, | |
z = last_state.z, | |
dir = dir_right(last_state.dir), | |
cost = last_state.cost, | |
action = "RIGHT" | |
} | |
tracker_updateForState(this, new_state) | |
end | |
local function tracker_up(this) | |
local stack = this._stack | |
local last_state = stack_peek(stack) | |
local new_state = { | |
x = last_state.x, | |
y = last_state.y + 1, | |
z = last_state.z, | |
dir = last_state.dir, | |
cost = last_state.cost + 1, | |
action = "UP" | |
} | |
tracker_updateForState(this, new_state) | |
end | |
local function tracker_down(this) | |
local stack = this._stack | |
local last_state = stack_peek(stack) | |
local new_state = { | |
x = last_state.x, | |
y = last_state.y - 1, | |
z = last_state.z, | |
dir = last_state.dir, | |
cost = last_state.cost + 1, | |
action = "DOWN" | |
} | |
tracker_updateForState(this, new_state) | |
end | |
local function tracker_forward(this) | |
local stack = this._stack | |
local last_state = stack_peek(stack) | |
local new_x, new_z = dir_forward(last_state.dir, last_state.x, last_state.z) | |
local new_state = { | |
x = new_x, | |
y = last_state.y, | |
z = new_z, | |
dir = last_state.dir, | |
cost = last_state.cost + 1, | |
action = "FORWARD" | |
} | |
tracker_updateForState(this, new_state) | |
end | |
local function tracker_backward(this) | |
local stack = this._stack | |
local last_state = stack_peek(stack) | |
local new_x, new_z = dir_backward(last_state.dir, last_state.x, last_state.z) | |
local new_state = { | |
x = new_x, | |
y = last_state.y, | |
z = new_z, | |
dir = last_state.dir, | |
cost = last_state.cost + 1, | |
action = "BACKWARD" | |
} | |
tracker_updateForState(this, new_state) | |
end | |
-- Rewind by iterating through the stack. | |
-- WARNING: You can't rewind a tracker while it is active! | |
local function tracker_rewind(this, fn, context) | |
log(DEBUG, "Rewinding") | |
stack_apply(this._stack, fn, context) | |
log(DEBUG, "Done Rewinding") | |
end | |
local function tracker_getState(this) | |
return stack_peek(this._stack) | |
end | |
local function tracker_getStartingPosAndDir(this) | |
return this._sx, this._sy, this._sz, this._sdir | |
end | |
local function tracker_getDir(this) | |
local state = stack_peek(this._stack) | |
if state ~= nil then | |
return state.dir | |
else | |
return 0 | |
end | |
end | |
local function tracker_getPosAndDir(this) | |
local state = stack_peek(this._stack) | |
if state ~= nil then | |
return state.x, state.y, state.z, state.dir | |
else | |
return 0, 0, 0, 0 | |
end | |
end | |
-- End tracker | |
-- Terminal helpers | |
local function writeString(msg, x, y) | |
term.setCursorPos(x, y) | |
write(msg) | |
end | |
local function writeCenteredString(msg, y) | |
local width = term.getSize() | |
writeString(msg, math.ceil((width - string.len(msg) + 1) / 2), y) | |
end | |
local function writePaddedString(msg, x, y, padded_length) | |
term.setCursorPos(x, y) | |
if padded_length <= string.len(msg) then | |
write(msg) | |
else | |
write(msg .. string.rep(" ", padded_length - string.len(msg))) | |
end | |
end | |
local function resetScreen() | |
term.clear() | |
writeCenteredString("Strip Miner", 1) | |
writeCenteredString("For the automation extraordinaire", 2) | |
writeString("By Davidee, version " .. version, 1, 13) | |
term.setCursorPos(1, 4) | |
end | |
local function checkedInputNumber(x, y, min, max) | |
local input = min | |
repeat | |
writePaddedString(" ", x, y, (term.getSize() - x)) | |
term.setCursorPos(x, y) | |
input = tonumber(read()) | |
until (input ~= nil and input >= min and input <= max) | |
return input | |
end | |
local function checkedInputTwoEnum(x, y, value_a, value_b) | |
local short_a = string.lower(string.sub(value_a, 1, 1)) | |
local short_b = string.lower(string.sub(value_b, 1, 1)) | |
local suggestion = value_a .. " (" .. short_a .. ") / " .. value_b .. " (" .. short_b .. "): " | |
local input = short_a | |
repeat | |
writePaddedString(suggestion, x, y, term.getSize()) | |
term.setCursorPos(string.len(suggestion) + 1, y) -- TODO: Verify | |
input = read() | |
if input == "" then | |
input = "NO" -- sadly there is no 'continue' in Lua, so we do this instead | |
else | |
input = string.lower(string.sub(input, 1, 1)) | |
end | |
until (input == short_a or input == short_b) | |
return input == short_a | |
end | |
local function clearLines(y_min, y_max) | |
for l=y_min, y_max do | |
writePaddedString("", 1, l, term.getSize()) | |
end | |
end | |
local function pause(pause_handler, msg) | |
if pause_handler then | |
pause_handler(msg) | |
else | |
print(msg) | |
os.pullEvent("key") | |
end | |
end | |
-- Prefer an over-estimation here. | |
local function estimateFuelCostToMineBlocks(num_blocks) | |
return 2 * num_blocks | |
end | |
-- We can have 3 different trackers at once: | |
-- * Primary tracker (we're mining in the strip mine) | |
-- * Return to position tracker (only possible when to_origin = false) | |
-- * Offroad tracker (e.g. we got caught up mining some coal) | |
local function estimateReturnFuelCost(state, to_origin) | |
local offroad_estimate = 0 | |
if state.offroad_tracker ~= nil then | |
local offroad_state = tracker_getState(state.offroad_tracker) | |
if offroad_state ~= nil then | |
offroad_estimate = offroad_state.cost | |
end | |
end | |
-- Going from a place to the origin. Ignore return to position tracker. | |
if to_origin then | |
local px, py, pz = tracker_getPosAndDir(state.primary_tracker) | |
local primary_estimate = math.abs(px) + math.abs(py) + math.abs(pz) + 20 -- "Estimate" | |
return primary_estimate + offroad_estimate | |
end | |
-- Going back to a previous position. Use the cur_tracker (return to position). | |
local sx, sy, sz = tracker_getStartingPosAndDir(state.cur_tracker) | |
local x, y, z = tracker_getPosAndDir(state.cur_tracker) | |
local return_estimate = math.abs(x - sx) + math.abs(y - sy) + math.abs(z - sz) + 20 -- "Estimate" | |
return return_estimate + offroad_estimate | |
end | |
-- Forward declared | |
local decrementFuel | |
local MOVEMENT_TRIES = 20 | |
-- Functions below use a different type of 'state' than above. Stateful props. | |
local function turnLeft(state) | |
if turtle.turnLeft() then | |
tracker_left(state.cur_tracker) | |
end | |
end | |
local function turnRight(state) | |
if turtle.turnRight() then | |
tracker_right(state.cur_tracker) | |
end | |
end | |
local function turn(state, right) | |
if right then | |
turnRight(state) | |
else | |
turnLeft(state) | |
end | |
end | |
local function safeDigUp() | |
local dug = false | |
while turtle.detectUp() do | |
if not turtle.digUp() then | |
break -- Digging up failed for some reason. What gives? Did a player break the block? | |
else | |
dug = true | |
end | |
sleep(0.45) -- Wait for gravel to fall | |
end | |
return dug | |
end | |
local function moveUp(state) | |
for i=1, MOVEMENT_TRIES do | |
if turtle.up() then | |
tracker_up(state.cur_tracker) | |
decrementFuel(state) | |
break | |
end | |
if turtle.detectUp() then | |
safeDigUp() | |
else -- Must be a mob. | |
turtle.attackUp() | |
sleep(0.1) -- Mobs are invincible for a short time. | |
end | |
end | |
end | |
local function moveDown(state) | |
for i=1, MOVEMENT_TRIES do | |
if turtle.down() then | |
tracker_down(state.cur_tracker) | |
decrementFuel(state) | |
break | |
end | |
if turtle.detectDown() then | |
turtle.digDown() | |
else -- Must be a mob. | |
turtle.attackDown() | |
sleep(0.1) -- Mobs are invincible for a short time. | |
end | |
end | |
end | |
local function moveForward(state) | |
for i=1, MOVEMENT_TRIES do | |
if turtle.forward() then | |
tracker_forward(state.cur_tracker) | |
decrementFuel(state) | |
break | |
end | |
if turtle.detect() then | |
turtle.dig() | |
else -- Must be a mob. | |
turtle.attack() | |
sleep(0.1) -- Mobs are invincible for a short time. | |
end | |
end | |
end | |
local function moveBackward(state) | |
if turtle.back() then -- Moon walk! | |
tracker_backward(state.cur_tracker) | |
decrementFuel(state) | |
else -- There might be something behind us. Rotate to fix it. | |
turnRight(state) | |
turnRight(state) | |
moveForward(state) -- This will try to break the block. | |
turnRight(state) | |
turnRight(state) | |
end | |
end | |
local function rotate(state, cur_dir, desired_dir) | |
if cur_dir == desired_dir then | |
return | |
end | |
if dir_right(cur_dir) == desired_dir then | |
turnRight(state) | |
elseif dir_left(cur_dir) == desired_dir then | |
turnLeft(state) | |
else | |
turnRight(state) | |
turnRight(state) | |
end | |
end | |
local function moveY(state, from, to, dir) | |
local diff = to - from | |
if diff >= 0 then | |
for i = 1, diff do | |
moveUp(state) | |
end | |
else | |
for i = 1, math.abs(diff) do | |
moveDown(state) | |
end | |
end | |
return dir | |
end | |
local function moveX(state, from, to, cur_dir, opt_dig_up_and_down) | |
local diff = to - from | |
local new_dir = cur_dir | |
if diff > 0 then -- dir = 1 (right, pos x) | |
new_dir = RIGHT | |
elseif diff < 0 then -- dir = 3 (left, neg x) | |
new_dir = LEFT | |
end | |
diff = math.abs(diff) | |
rotate(state, cur_dir, new_dir) | |
for i = 1, diff do | |
moveForward(state) | |
if opt_dig_up_and_down then | |
safeDigUp() | |
turtle.digDown() | |
end | |
end | |
return new_dir | |
end | |
local function moveZ(state, from, to, cur_dir, opt_dig_up_and_down) | |
local diff = to - from | |
local new_dir = cur_dir | |
if diff > 0 then -- dir = 2 (backward, pos z) | |
new_dir = BACKWARD | |
elseif diff < 0 then -- dir = 0 (forward, neg z) | |
new_dir = FORWARD | |
end | |
diff = math.abs(diff) | |
rotate(state, cur_dir, new_dir) | |
for i = 1, diff do | |
moveForward(state) | |
if opt_dig_up_and_down then | |
safeDigUp() | |
turtle.digDown() | |
end | |
end | |
return new_dir | |
end | |
local function makeSet(t) | |
local s = {} | |
for _, v in pairs(t) do | |
s[v] = true | |
end | |
return s | |
end | |
local ORE_ID_SET = makeSet({ | |
-- Vanilla ores | |
"minecraft:gold_ore", "minecraft:iron_ore", "minecraft:coal_ore", "minecraft:lapis_ore", | |
"minecraft:diamond_ore", "minecraft:redstone_ore", "minecraft:emerald_ore", | |
-- IC2 ores | |
"IC2:blockOreCopper", "IC2:blockOreTin", "IC2:blockOreUran", "IC2:blockOreLead", | |
"ic2:resource", | |
-- Applied Energistics 2 ores | |
"appliedenergistics2:tile.OreQuartz", "appliedenergistics2:tile.OreQuartzCharged", | |
"appliedenergistics2:quartz_ore", "appliedenergistics2:charged_quartz_ore", | |
-- Forestry | |
"Forestry:resources", | |
"forestry:resources", | |
-- GrowthCraft | |
"growthcraft:salt_ore", | |
-- BigReactors | |
"BigReactors:YelloriteOre", | |
"bigreactors:oreyellorite", | |
-- Railcraft | |
"Railcraft:ore", | |
"railcraft:ore", | |
-- ThermalFoundation | |
"ThermalFoundation:Ore", | |
"thermalfoundation:ore", | |
-- BluePower | |
"bluepower:amethyst_ore", | |
-- ProjectRed Exploration | |
"ProjRed|Exploration:projectred.exploration.ore", | |
"projectred-exploration:ore", | |
-- Biomes O' Plenty | |
"biomesoplenty:gem_ore" | |
}) | |
local ACTION_TABLE = { LEFT = turnLeft, RIGHT = turnRight, UP = moveUp, DOWN = moveDown, FORWARD = moveForward, BACKWARD = moveBackward } | |
local REV_ACTION_TABLE = { LEFT = turnRight, RIGHT = turnLeft, UP = moveDown, DOWN = moveUp, FORWARD = moveBackward, BACKWARD = moveForward } | |
local function doAction(state, action) | |
if ACTION_TABLE[action] then | |
ACTION_TABLE[action](state) | |
return true | |
end | |
return false | |
end | |
local function doReverseAction(state, action) | |
if REV_ACTION_TABLE[action] then | |
REV_ACTION_TABLE[action](state) | |
return true | |
end | |
return false | |
end | |
local function doStateAction(state, tracker_state) | |
local times = tracker_state.distance or 1 | |
for i=1,times do | |
doAction(state, tracker_state.action) | |
end | |
end | |
local function doReverseStateAction(state, tracker_state) | |
local times = tracker_state.distance or 1 | |
for i=1,times do | |
doReverseAction(state, tracker_state.action) | |
end | |
end | |
decrementFuel = function(state) | |
if state.fuel ~= "unlimited" then | |
state.fuel = state.fuel - 1 | |
end | |
end | |
local function isChestPresent(side) | |
local peri = peripheral.wrap(side) | |
if peri == nil then | |
return false | |
end | |
-- Support for Plethora Peripherals, verify it's a chest from the size. | |
if peri.list ~= nil and peri.size ~= nil then | |
return peri.size() > 10 | |
end | |
-- Legacy support for Open Peripherals. | |
if peri.listSources ~= nil then | |
local sources = peri.listSources() | |
return not not sources.inventory and not not sources["inventory-world"] | |
end | |
end | |
local function moveItemIntoChest(chest, dir_from_chest, slot, opt_amount) | |
-- Support for Plethora Peripherals. | |
if chest.pullItems ~= nil then | |
return chest.pullItems(dir_from_chest, slot, opt_amount) | |
end | |
-- Legacy support for Open Peripherals. | |
return chest.pullItem(dir_from_chest, slot, opt_amount) | |
end | |
-- Legacy support for Open Peripherals: check if we can actually move into the chest. | |
local function testChestMove(chest, dir_from_chest) | |
local status = pcall(moveItemIntoChest, chest, dir_from_chest, 0) | |
return status | |
end | |
local function detectChestDirection(side, optional_dir_from_chest) | |
if not isChestPresent(side) then | |
return nil | |
end | |
local chest = peripheral.wrap(side) | |
-- Support for Plethora Peripherals, check the transfer locations. | |
if chest.getTransferLocations ~= nil then | |
local locations = chest.getTransferLocations() | |
local found_loc = nil | |
for _, dir in pairs(locations) do | |
if optional_dir_from_chest == dir then | |
return dir | |
end | |
-- Ignore "self" see it refers to the chest itself, not the turtle. | |
if found_loc == nil and dir ~= "self" then | |
found_loc = dir | |
end | |
end | |
return found_loc | |
end | |
-- Legacy support for Open Peripherals. | |
if optional_dir_from_chest ~= nil and testChestMove(chest, optional_dir_from_chest) then | |
return optional_dir_from_chest | |
end | |
local DIRECTIONS = { "NORTH", "SOUTH", "WEST", "EAST", "DOWN", "UP" } | |
for _, dir in ipairs(DIRECTIONS) do | |
if testChestMove(chest, dir) then | |
return dir | |
end | |
end | |
return nil | |
end | |
-- Legacy: unavailable with Plethora Peripherals. OK because this is just an optimization. | |
-- Returns true if chest.condenseItems() worked, potentially trying multiple times. | |
-- | |
-- For some reason, it has been failing in dropItemsIntoChest sometimes with error | |
-- "Java Exception Thrown: java.lang.RuntimeException: You are not attached to this Computer" | |
local function safeChestCondense(chest) | |
if chest.condenseItems == nil then | |
return true | |
end | |
log(DEBUG, "Condensing items in chest") | |
local success, result_or_err = pcall(chest.condenseItems) | |
if not success then | |
log(ERROR, "safeChestCondense: " .. result_or_err) | |
-- Try again 5 times. | |
for i=1, 5 do | |
sleep(1) | |
success, result_or_err = pcall(chest.condenseItems) | |
if success then | |
break | |
else | |
log(ERROR, string.format("safeChestCondense: Retry #%d failed", i)) | |
end | |
end | |
end | |
if success then | |
log(DEBUG, "Chest condensed") | |
end | |
return success | |
end | |
-- Returns true iff all items were transferred over. | |
local function dropItemsIntoChest(chest, dir_from_chest, starting_slot, items_to_keep) | |
-- Legacy: condense the chest if supported. | |
safeChestCondense(chest) | |
items_to_keep = deepcopy(items_to_keep) | |
for s=starting_slot, 16 do | |
local item_detail = turtle.getItemDetail(s) | |
if item_detail ~= nil then | |
local item_count = item_detail.count | |
local deposit_count = item_count | |
local item_id = item_detail.name | |
local to_keep_info = items_to_keep[item_id] | |
if to_keep_info and (not to_keep_info.damage or to_keep_info.damage == item_detail.damage) then | |
local keep_count = to_keep_info.count | |
if item_count >= keep_count then | |
deposit_count = item_count - keep_count | |
items_to_keep[item_id] = nil | |
else | |
deposit_count = 0 | |
to_keep_info.count = keep_count - item_count | |
end | |
end | |
if deposit_count > 0 then | |
local success, result_or_err = pcall(moveItemIntoChest, chest, dir_from_chest, s, deposit_count) | |
if not success then | |
log(ERROR, string.format("dropItemsIntoChest: error %s", result_or_err)) | |
return false | |
end | |
if result_or_err < deposit_count then | |
log(ERROR, string.format("dropItemsIntoChest: only deposited %d / %d", result_or_err, deposit_count)) | |
return false | |
end | |
end | |
end | |
end | |
return true | |
end | |
local function fetchStackFromChest(chest, dir_from_chest, chest_slot, amount, turtle_slot) | |
-- Support for Plethora Peripherals. | |
if chest.pushItems ~= nil then | |
return chest.pushItems(dir_from_chest, chest_slot, amount, turtle_slot) | |
end | |
-- Legacy support for Open Peripherals. | |
return chest.pushItem(dir_from_chest, chest_slot, amount, turtle_slot) | |
end | |
-- Returns an iterator for the items in the chest. | |
local function chestIterator(chest) | |
-- Support for Plethora Peripherals. | |
if chest.list ~= nil then | |
local list = chest.list() | |
local list_iter = pairsByKeys(list) | |
local iter = function () -- Iterator function | |
local slot, partial_info = list_iter() | |
if slot == nil then | |
return nil | |
end | |
local item_info = chest.getItemMeta(slot) | |
return slot, item_info | |
end | |
return iter | |
end | |
-- Legacy support for Open Peripherals. | |
local stacks = chest.getAllStacks() | |
local stacks_iter = pairsByKeys(stacks) | |
local iter = function () -- Iterator function | |
local slot, proxy = stacks_iter() | |
if slot == nil then | |
return nil | |
end | |
local item_info = proxy.select() | |
local converted_info = { count = item_info.qty, damage = item_info.dmg, name = item_info.id, maxCount = item_info.max_size } | |
return slot, converted_info | |
end | |
return iter | |
end | |
-- Returns how many of the item we now have in the specified slot. | |
local function fetchItemFromChestIntoSlot(chest, dir_from_chest, item_id, turtle_slot, opt_amount) | |
local slot_item_detail = turtle.getItemDetail(turtle_slot) | |
local slot_qty = 0 | |
local slot_dmg = -1 | |
if slot_item_detail ~= nil then | |
if slot_item_detail.name ~= item_id then | |
-- TODO: Decide what to do! | |
else | |
slot_qty = slot_item_detail.count | |
slot_dmg = slot_item_detail.damage | |
end | |
end | |
if slot_qty == opt_amount then | |
return opt_amount | |
end | |
log(DEBUG, string.format("Fetching some %s", item_id)) | |
-- Legacy: condense the chest if supported. Condensing should make it easier to fetch full stacks. | |
safeChestCondense(chest) | |
for slot, item_info in chestIterator(chest) do | |
-- Make sure item IDs and dmg match | |
if item_info.name == item_id and (slot_dmg == -1 or item_info.damage == slot_dmg) then | |
opt_amount = opt_amount or item_info.maxCount | |
local count = item_info.count | |
local countNeeded = opt_amount - slot_qty -- Figure out how much we need. | |
if countNeeded > 0 then | |
local takeCount = math.min(countNeeded, count) -- Can't take more than is there. | |
local receivedCount = fetchStackFromChest(chest, dir_from_chest, slot, takeCount, turtle_slot) | |
slot_qty = slot_qty + receivedCount | |
-- If we have enough now, immediately return, otherwise we need to keep iterating. | |
if slot_qty == opt_amount then | |
return slot_qty | |
end | |
else | |
break | |
end | |
end | |
end | |
return slot_qty | |
end | |
-- Returns -1 or an empty turtle slot. | |
local function getEmptySlot() | |
for s=1, 16 do | |
if turtle.getItemCount(s) == 0 then | |
return s | |
end | |
end | |
return -1 | |
end | |
-- Reset turtle item selection to slot 1 so mined blocks will be stacked more efficiently. | |
local function resetItemSelection() | |
turtle.select(1) | |
end | |
-- Returns true if the given item is now selected. | |
local function selectItem(item_id) | |
local cur_item_detail = turtle.getItemDetail() | |
if cur_item_detail ~= nil and cur_item_detail.name == item_id then | |
return true | |
end | |
for s=1, 16 do | |
local slot_item_detail = turtle.getItemDetail(s) | |
if slot_item_detail ~= nil and slot_item_detail.name == item_id then | |
turtle.select(s) | |
return true | |
end | |
end | |
return false | |
end | |
-- Returns slot (or -1), quantity | |
local function fetchItemFromChest(chest, dir_from_chest, item_id, opt_amount) | |
local empty_slot = getEmptySlot() | |
if empty_slot ~= -1 then | |
local qty = fetchItemFromChestIntoSlot(chest, dir_from_chest, item_id, empty_slot, opt_amount) | |
if qty > 0 then | |
return empty_slot, qty | |
end | |
end | |
return -1, 0 | |
end | |
local MAX_TORCH_SLOTS = 1 | |
-- Returns the torch slots. | |
local function takeTorches(state, chest) | |
local torch_slots = { } | |
for i=1, MAX_TORCH_SLOTS do | |
local slot, qty = fetchItemFromChest(chest, state.dir_from_chest, "minecraft:torch") | |
if slot ~= -1 then | |
table.insert(torch_slots, slot) | |
else | |
break | |
end | |
end | |
return torch_slots | |
end | |
local function refuelFromChest(state, chest, fuel_wanted) | |
if state.fuel >= fuel_wanted then | |
return | |
end | |
while state.fuel < fuel_wanted do | |
local num_coal_needed = math.ceil((fuel_wanted - state.fuel) / 80) | |
local slot, qty = fetchItemFromChest(chest, state.dir_from_chest, "minecraft:coal", num_coal_needed) | |
if slot ~= -1 then | |
turtle.select(slot) | |
turtle.refuel() | |
resetItemSelection() | |
state.fuel = turtle.getFuelLevel() | |
num_coal_needed = math.ceil((fuel_wanted - state.fuel) / 80) | |
else | |
break -- There's no more coal! | |
end | |
end | |
end | |
-- Returns how much more fuel it wants (0 if success) | |
local function refuel(state, chest) | |
if state.fuel == "unlimited" then | |
return 0 | |
end | |
-- TODO: Improve this, this estimation is coarse and over-estimates. | |
local return_estimate = estimateReturnFuelCost(state, false) | |
local blocks_for_full_inv = 1024 -- 64 * 16 | |
local full_inv_estimate = estimateFuelCostToMineBlocks(blocks_for_full_inv) | |
local blocks_to_travel_a_tunnel = 2 * state.tunnel_length + state.tunnel_gap + 2 -- Estimate | |
local blocks_for_remaining_tunnels = (state.num_tunnels - state.cur_tunnel + 1) * blocks_to_travel_a_tunnel | |
local remaining_blocks_estimate = estimateFuelCostToMineBlocks(blocks_for_remaining_tunnels) | |
local estimate = math.min(full_inv_estimate, remaining_blocks_estimate) | |
estimate = 2 * (estimate + return_estimate) -- Double it for no good reason! | |
refuelFromChest(state, chest, estimate) | |
return math.max(0, estimate - state.fuel) | |
end | |
local function interactWithChest(state, side, is_done) | |
local chest = peripheral.wrap(side) | |
-- Empty our inventory, but try to keep some cobblestone if we're not done. | |
-- This is so we can patch things up when we mine ores. | |
local items_to_keep = {} | |
if not is_done then | |
items_to_keep["minecraft:cobblestone"] = { count = 32 } | |
end | |
log(DEBUG, "Dropping items into chest...") | |
local empty_inv = dropItemsIntoChest(chest, state.dir_from_chest, 1, items_to_keep) | |
while not empty_inv do | |
pause(state.pause_handler, "Chest is full!") | |
empty_inv = dropItemsIntoChest(chest, state.dir_from_chest, 1, items_to_keep) | |
end | |
if is_done then | |
return | |
end | |
-- Refuel if needed. | |
log(DEBUG, "Checking refuel...") | |
local fuel_needed = refuel(state, chest) | |
while fuel_needed ~= 0 do | |
pause(state.pause_handler, string.format("Need %d more fuel!", fuel_needed)) | |
fuel_needed = refuel(state, chest) | |
end | |
-- Get torches. | |
log(DEBUG, "Taking torches...") | |
local torch_slots = takeTorches(state, chest) | |
while table.getn(torch_slots) == 0 do | |
pause(state.pause_handler, "Chest is out of torches!") | |
torch_slots = takeTorches(state, chest) | |
end | |
state.torch_slots = torch_slots | |
-- Try to get some cobblestone to use for filling in holes left by mining ores if we don't have | |
-- some already. | |
if not is_done and not selectItem("minecraft:cobblestone") then | |
fetchItemFromChest(chest, state.dir_from_chest, "minecraft:cobblestone", 32) | |
end | |
resetItemSelection() | |
end | |
local function isOutOfTorches(state) | |
return table.getn(state.torch_slots) == 0 | |
end | |
local function needsFuel(state) | |
return state.fuel ~= "unlimited" and estimateReturnFuelCost(state, true) > state.fuel | |
end | |
local function isInventoryFull(starting_slot) | |
for s=starting_slot, 16 do | |
if turtle.getItemCount(s) == 0 then | |
return false | |
end | |
end | |
return true | |
end | |
local function manageInventory(state) | |
if isInventoryFull(1) or needsFuel(state) or isOutOfTorches(state) then | |
local old_tracker = state.cur_tracker | |
local return_tracker = tracker_newStartingFromOld(old_tracker) | |
state.cur_tracker = return_tracker | |
-- If the tracker was an offroad tracker, we need to rewind it first. | |
if state.offroad_tracker ~= nil then | |
tracker_rewind(state.offroad_tracker, doReverseStateAction, state) | |
end | |
-- Figure out how to go back. | |
local x, y, z, dir = tracker_getPosAndDir(state.cur_tracker) | |
dir = moveY(state, y, 1, dir) | |
dir = moveX(state, x, 0, dir) | |
dir = moveZ(state, z, 0, dir) | |
moveDown(state) -- As we start at 0 but above moved to 1 | |
rotate(state, dir, BACKWARD) -- dir = 2 (backward, pos z) | |
-- Use the chest. | |
interactWithChest(state, "front", false) | |
-- Need a new tracker as we can't rewind while active. | |
local temp_tracker = tracker_newStartingFromOld(return_tracker) | |
state.cur_tracker = temp_tracker | |
tracker_rewind(return_tracker, doReverseStateAction, state) | |
state.cur_tracker = old_tracker | |
end | |
end | |
local function digMoveForwardDigVertical(state) | |
turtle.dig() | |
moveForward(state) | |
safeDigUp() | |
turtle.digDown() | |
manageInventory(state) | |
end | |
local function shouldPlaceTorch(blocks_from_torch, block, tunnel_length) | |
if blocks_from_torch >= 8 then | |
return true | |
end | |
local num_blocks_left = tunnel_length - block | |
-- If we place it with no blocks left, it'll be broken when he digs the edges. | |
return num_blocks_left == 2 and blocks_from_torch >= 4 | |
end | |
local function placeTorch(state) | |
local torch_slots = state.torch_slots | |
local cur_slot = torch_slots[table.getn(torch_slots)] | |
local qty = turtle.getItemCount(cur_slot) | |
local right_tunnels = state.right_tunnels | |
local _, y = tracker_getPosAndDir(state.cur_tracker) | |
-- The side that we place it depends on whether we're doing tunnels on left or right. | |
turn(state, right_tunnels) | |
moveY(state, y, 0) | |
turtle.select(cur_slot) | |
local result = turtle.place() | |
if result and qty == 1 then | |
table.remove(torch_slots) | |
end | |
resetItemSelection() | |
moveY(state, 0, y) | |
turn(state, not right_tunnels) | |
return result | |
end | |
-- Forward declared | |
local checkBlockForward | |
local checkBlockDir | |
local function getUpCoordString(state) | |
local x, y, z = tracker_getPosAndDir(state.cur_tracker) | |
return coords_toString(x, y + 1, z) | |
end | |
local function getDownCoordString(state) | |
local x, y, z = tracker_getPosAndDir(state.cur_tracker) | |
return coords_toString(x, y - 1, z) | |
end | |
local function getDirCoordString(state, offset_dir) | |
local x, y, z, dir = tracker_getPosAndDir(state.cur_tracker) | |
dir = dir_offset(dir, offset_dir) | |
x, z = dir_forward(dir, x, z) | |
return coords_toString(x, y, z) | |
end | |
local function createOffroadTracker(state) | |
if state.offroad_tracker == nil then | |
state.offroad_tracker = tracker_newStartingFromOld(state.cur_tracker) | |
state.cur_tracker = state.offroad_tracker | |
return true | |
end | |
return false | |
end | |
local function possiblyDestroyOffroadTracker(state, destroy) | |
if destroy then | |
state.offroad_tracker = nil | |
state.cur_tracker = state.primary_tracker | |
end | |
end | |
-- TODO: Try to make this non-recursive. | |
local function checkBlockUp(state) | |
local succ, info = turtle.inspectUp() | |
if succ and ORE_ID_SET[info.name] then | |
if not safeDigUp() then | |
return | |
end | |
local created_tracker = createOffroadTracker(state) | |
moveUp(state) | |
manageInventory(state) | |
local checked_table = state.checked_table | |
local start_dir = tracker_getDir(state.cur_tracker) | |
local u, f, r = getUpCoordString(state), getDirCoordString(state, FORWARD), getDirCoordString(state, RIGHT) | |
local b, l = getDirCoordString(state, BACKWARD), getDirCoordString(state, LEFT) | |
-- Check em. | |
local uc, fc, rc = checked_table[u], checked_table[f], checked_table[r] | |
local bc, lc = checked_table[b], checked_table[l] | |
-- Mark them all checked. | |
local all_strings = { u, f, r, b, l } | |
for _, v in ipairs(all_strings) do | |
checked_table[v] = true | |
end | |
if not uc then | |
checkBlockUp(state) | |
end | |
if not fc then | |
checkBlockForward(state) | |
end | |
if not rc then | |
checkBlockDir(state, dir_offset(start_dir, RIGHT)) | |
end | |
if not bc then | |
checkBlockDir(state, dir_offset(start_dir, BACKWARD)) | |
end | |
if not lc then | |
checkBlockDir(state, dir_offset(start_dir, LEFT)) | |
end | |
rotate(state, tracker_getDir(state.cur_tracker), start_dir) | |
moveDown(state) | |
possiblyDestroyOffroadTracker(state, created_tracker) | |
if selectItem("minecraft:cobblestone") then | |
turtle.placeUp() | |
resetItemSelection() | |
end | |
end | |
end | |
local function checkBlockDown(state) | |
local succ, info = turtle.inspectDown() | |
if succ and ORE_ID_SET[info.name] then | |
if not turtle.digDown() then | |
return | |
end | |
local created_tracker = createOffroadTracker(state) | |
moveDown(state) | |
manageInventory(state) | |
local checked_table = state.checked_table | |
local start_dir = tracker_getDir(state.cur_tracker) | |
local d, f, r = getDownCoordString(state), getDirCoordString(state, FORWARD), getDirCoordString(state, RIGHT) | |
local b, l = getDirCoordString(state, BACKWARD), getDirCoordString(state, LEFT) | |
-- Check em. | |
local dc, fc, rc = checked_table[d], checked_table[f], checked_table[r] | |
local bc, lc = checked_table[b], checked_table[l] | |
-- Mark them all checked. | |
local all_strings = { d, f, r, b, l } | |
for _, v in ipairs(all_strings) do | |
checked_table[v] = true | |
end | |
if not dc then | |
checkBlockDown(state) | |
end | |
if not fc then | |
checkBlockForward(state) | |
end | |
if not rc then | |
checkBlockDir(state, dir_offset(start_dir, RIGHT)) | |
end | |
if not bc then | |
checkBlockDir(state, dir_offset(start_dir, BACKWARD)) | |
end | |
if not lc then | |
checkBlockDir(state, dir_offset(start_dir, LEFT)) | |
end | |
rotate(state, tracker_getDir(state.cur_tracker), start_dir) | |
moveUp(state) | |
possiblyDestroyOffroadTracker(state, created_tracker) | |
if selectItem("minecraft:cobblestone") then | |
turtle.placeDown() | |
resetItemSelection() | |
end | |
end | |
end | |
checkBlockForward = function(state) | |
local succ, info = turtle.inspect() | |
if succ and ORE_ID_SET[info.name] then | |
if not turtle.dig() then | |
return | |
end | |
local created_tracker = createOffroadTracker(state) | |
moveForward(state) | |
manageInventory(state) | |
local checked_table = state.checked_table | |
local start_dir = tracker_getDir(state.cur_tracker) | |
local u, f, r = getUpCoordString(state), getDirCoordString(state, FORWARD), getDirCoordString(state, RIGHT) | |
local d, l = getDownCoordString(state), getDirCoordString(state, LEFT) | |
-- Check em. | |
local uc, fc, rc = checked_table[u], checked_table[f], checked_table[r] | |
local dc, lc = checked_table[d], checked_table[l] | |
-- Mark them all checked. | |
local all_strings = { u, f, r, d, l } | |
for _, v in ipairs(all_strings) do | |
checked_table[v] = true | |
end | |
if not uc then | |
checkBlockUp(state) | |
end | |
if not dc then | |
checkBlockDown(state) | |
end | |
if not fc then | |
checkBlockForward(state) | |
end | |
if not rc then | |
checkBlockDir(state, dir_offset(start_dir, RIGHT)) | |
end | |
if not lc then | |
checkBlockDir(state, dir_offset(start_dir, LEFT)) | |
end | |
rotate(state, tracker_getDir(state.cur_tracker), start_dir) | |
moveBackward(state) | |
possiblyDestroyOffroadTracker(state, created_tracker) | |
if selectItem("minecraft:cobblestone") then | |
turtle.place() | |
resetItemSelection() | |
end | |
end | |
end | |
checkBlockDir = function(state, dir) | |
rotate(state, tracker_getDir(state.cur_tracker), dir) | |
checkBlockForward(state) | |
end | |
local function maybeCheckBlockForward(state) | |
local forward_str = getDirCoordString(state, FORWARD) | |
local checked_table = state.checked_table | |
if not checked_table[forward_str] then | |
checked_table[forward_str] = true | |
checkBlockForward(state) | |
end | |
end | |
local function maybeCheckBlockDown(state) | |
local down_str = getDownCoordString(state) | |
local checked_table = state.checked_table | |
if not checked_table[down_str] then | |
checked_table[down_str] = true | |
checkBlockDown(state) | |
end | |
end | |
local function maybeCheckBlockUp(state) | |
local up_str = getUpCoordString(state) | |
local checked_table = state.checked_table | |
if not checked_table[up_str] then | |
checked_table[up_str] = true | |
checkBlockUp(state) | |
end | |
end | |
local function maybeCheckBlockForwardAndDown(state) | |
maybeCheckBlockForward(state) | |
maybeCheckBlockDown(state) | |
end | |
local function maybeCheckBlockForwardAndUp(state) | |
maybeCheckBlockForward(state) | |
maybeCheckBlockUp(state) | |
end | |
-- Mark next 6 tunnel blocks checked before we get to them. | |
local function markNext6BlocksChecked(state, right) | |
local checked_table = state.checked_table | |
local x, y, z, dir = tracker_getPosAndDir(state.cur_tracker) | |
-- Forward 3 blocks | |
x, z = dir_forward(dir, x, z) | |
for i=0, 2 do | |
checked_table[coords_toString(x, y + i, z)] = true | |
end | |
-- Forward-(right) 3 blocks | |
local offset_dir = right and RIGHT or LEFT | |
x, z = dir_forward(dir_offset(dir, offset_dir), x, z) | |
for i=0, 2 do | |
checked_table[coords_toString(x, y + i, z)] = true | |
end | |
end | |
local function mineTunnelAndOres(state) | |
local tunnel_length = state.tunnel_length | |
local right_tunnels = state.right_tunnels | |
local blocks_from_torch = -1 | |
state.checked_table = {} | |
for length=1, tunnel_length do | |
-- Right tunnels turn right initially, while left tunnels turn left initially | |
local is_odd_length = (length % 2 == 1) | |
local goes_l_to_r = (is_odd_length == right_tunnels) | |
markNext6BlocksChecked(state, goes_l_to_r) | |
moveForward(state) | |
turn(state, not goes_l_to_r) | |
maybeCheckBlockForwardAndDown(state) | |
moveUp(state) | |
maybeCheckBlockForward(state) | |
moveUp(state) | |
maybeCheckBlockForwardAndUp(state) | |
manageInventory(state) | |
-- Now mine the other side | |
turn(state, goes_l_to_r) | |
turn(state, goes_l_to_r) | |
moveForward(state) | |
maybeCheckBlockForwardAndUp(state) | |
moveDown(state) | |
maybeCheckBlockForward(state) | |
moveDown(state) | |
maybeCheckBlockForwardAndDown(state) | |
turn(state, not goes_l_to_r) | |
manageInventory(state) | |
-- Handles placing torches (only on even lengths) | |
if not is_odd_length then | |
if blocks_from_torch == -1 or shouldPlaceTorch(blocks_from_torch + 2, length, tunnel_length) then | |
if placeTorch(state) then | |
blocks_from_torch = 0 | |
end | |
else | |
blocks_from_torch = blocks_from_torch + 2 | |
end | |
end | |
end | |
end | |
local function mineAndCheckVertical(state, should_go_up) | |
if should_go_up then | |
maybeCheckBlockForwardAndDown(state) | |
moveUp(state) | |
maybeCheckBlockForward(state) | |
moveUp(state) | |
maybeCheckBlockForwardAndUp(state) | |
else | |
maybeCheckBlockForwardAndUp(state) | |
moveDown(state) | |
maybeCheckBlockForward(state) | |
moveDown(state) | |
maybeCheckBlockForwardAndDown(state) | |
end | |
end | |
local function markCurrent3BlocksChecked(state, should_go_up) | |
local checked_table = state.checked_table | |
local x, y, z, dir = tracker_getPosAndDir(state.cur_tracker) | |
-- Up/Down 3 blocks | |
for i=0, 2 do | |
local offset = i | |
if not should_go_up then | |
offset = 0 - offset | |
end | |
checked_table[coords_toString(x, y + offset, z)] = true | |
end | |
end | |
local function mineTunnelConnectorAndOres(state) | |
local tunnel_gap = state.tunnel_gap | |
local right_tunnels = state.right_tunnels | |
if state.cur_tunnel % 2 == 1 then -- Odd tunnel num --> turn (tunnel dir), forward, dig, start in left | |
for i=1, tunnel_gap + 3 do | |
markCurrent3BlocksChecked(state, i % 2 == 1) | |
mineAndCheckVertical(state, i % 2 == 1) | |
turn(state, right_tunnels) | |
moveForward(state) | |
turn(state, not right_tunnels) | |
end | |
mineAndCheckVertical(state, (tunnel_gap + 4) % 2 == 1) | |
turn(state, right_tunnels) | |
mineAndCheckVertical(state, (tunnel_gap + 5) % 2 == 1) | |
turn(state, right_tunnels) | |
else -- Even tunnel num --> turn (opp tunnel dir), dig, go backwards to start in left | |
turn(state, not right_tunnels) | |
moveBackward(state) | |
turn(state, right_tunnels) | |
for i=1, tunnel_gap + 3 do | |
markCurrent3BlocksChecked(state, i % 2 == 1) | |
mineAndCheckVertical(state, i % 2 == 1) | |
turn(state, not right_tunnels) | |
moveForward(state) | |
turn(state, right_tunnels) | |
end | |
mineAndCheckVertical(state, (tunnel_gap + 4) % 2 == 1) | |
turn(state, not right_tunnels) | |
mineAndCheckVertical(state, (tunnel_gap + 5) % 2 == 1) | |
-- Move back to left. | |
moveBackward(state) | |
turn(state, not right_tunnels) | |
end | |
-- Move back down. | |
local _, y = tracker_getPosAndDir(state.cur_tracker) | |
moveY(state, y, 0) | |
end | |
local function mineTunnelZigZag(state) | |
local tunnel_length = state.tunnel_length | |
local right_tunnels = state.right_tunnels | |
local blocks_from_torch = -1 | |
-- Move up so we can mine up and down. | |
local _, y = tracker_getPosAndDir(state.cur_tracker) | |
moveY(state, y, 1) | |
for length=1, tunnel_length do | |
digMoveForwardDigVertical(state) | |
-- Right tunnels turn right initially, while left tunnels turn left initially. | |
local is_odd_length = (length % 2 == 1) | |
if is_odd_length == right_tunnels then | |
turnRight(state) | |
digMoveForwardDigVertical(state) | |
turnLeft(state) | |
else | |
turnLeft(state) | |
digMoveForwardDigVertical(state) | |
turnRight(state) | |
end | |
-- Handles placing torches (only on even lengths) | |
if not is_odd_length then | |
if blocks_from_torch == -1 or shouldPlaceTorch(blocks_from_torch + 2, length, tunnel_length) then | |
if placeTorch(state) then | |
blocks_from_torch = 0 | |
end | |
else | |
blocks_from_torch = blocks_from_torch + 2 | |
end | |
end | |
end | |
end | |
local function mineTunnelConnector(state) | |
local tunnel_gap = state.tunnel_gap | |
local right_tunnels = state.right_tunnels | |
local _, y = tracker_getPosAndDir(state.cur_tracker) | |
moveY(state, y, 1) | |
if state.cur_tunnel % 2 == 1 then -- Odd tunnel num --> turn (tunnel dir), forward, dig, start in left | |
turn(state, right_tunnels) | |
moveForward(state) | |
for i=1, tunnel_gap + 2 do | |
digMoveForwardDigVertical(state) | |
end | |
turn(state, right_tunnels) | |
else -- Even tunnel num --> turn (opp tunnel dir), dig, go backwards to start in left | |
turn(state, not right_tunnels) | |
for i=1, tunnel_gap + 2 do | |
digMoveForwardDigVertical(state) | |
end | |
moveBackward(state) | |
turn(state, not right_tunnels) | |
end | |
moveY(state, 1, y) | |
end | |
function stripMine(num_tunnels, tunnel_length, tunnel_gap, right_tunnels, opt_pause_handler, opt_dir_from_chest_to_turtle) | |
-- Verify the dir | |
local dir_from_chest_to_turtle = detectChestDirection("back", opt_dir_from_chest_to_turtle) | |
if dir_from_chest_to_turtle == nil then | |
return false | |
end | |
log(DEBUG, "Detected chest direction: " .. dir_from_chest_to_turtle) | |
-- Tunnel length must be even so we can end at the same side as we start | |
if tunnel_length % 2 == 1 then | |
tunnel_length = tunnel_length + 1 | |
end | |
-- The primary tracker only tracks the current position. | |
local tracker = tracker_new(false) | |
local state = { | |
num_tunnels = num_tunnels, | |
tunnel_length = tunnel_length, | |
tunnel_gap = tunnel_gap, | |
right_tunnels = right_tunnels, | |
cur_tunnel = 1, | |
pause_handler = opt_pause_handler, | |
fuel = turtle.getFuelLevel(), | |
cur_tracker = tracker, | |
primary_tracker = tracker, | |
dir_from_chest = dir_from_chest_to_turtle, | |
torch_slots = { } | |
} | |
-- Use the chest, but face it first so it's obvious what's going on | |
rotate(state, FORWARD, BACKWARD) | |
interactWithChest(state, "front", false) | |
rotate(state, BACKWARD, FORWARD) | |
-- Dig tunnels | |
for tunnel=1, num_tunnels do | |
-- Set state | |
state.cur_tunnel = tunnel | |
mineTunnelAndOres(state) | |
if tunnel ~= num_tunnels then | |
mineTunnelConnectorAndOres(state) | |
end | |
end | |
-- Go back to start. | |
local x, y, z, dir = tracker_getPosAndDir(state.cur_tracker) | |
dir = moveY(state, y, 1, dir) | |
dir = moveZ(state, z, 0, dir) | |
dir = moveX(state, x, 0, dir, true) | |
-- As we start at 0 but above moved to 1 | |
moveDown(state) | |
-- Use the chest, but face it first so it's obvious what's going on | |
rotate(state, dir, BACKWARD) | |
interactWithChest(state, "front", true) | |
rotate(state, BACKWARD, FORWARD) | |
return true | |
end | |
local function pauseUI(msg) | |
writeCenteredString("ATTENTION", 4) | |
writeCenteredString(msg, 7) | |
writeCenteredString("Press any key to try again...", 10) | |
os.pullEvent("key") | |
resetScreen() | |
end | |
function stripMineWithUI() | |
resetScreen() | |
writeString("Number of tunnels: ", 1, 4) | |
local num_tunnels = checkedInputNumber(35, 4, 1, 1000) | |
writeString("Tunnel length: ", 1, 5) | |
local tunnel_length = checkedInputNumber(35, 5, 1, 10000) | |
local tunnel_gap = 0 | |
local right_tunnels = true | |
if num_tunnels > 1 then | |
writeString("Number of blocks between tunnels: ", 1, 6) | |
tunnel_gap = checkedInputNumber(35, 6, 1, 100) | |
writeString("Dig tunnels to the left or the right?", 1, 8) | |
right_tunnels = checkedInputTwoEnum(1, 9, "Right", "Left") | |
end | |
resetScreen() | |
writeString("Please place a chest behind the turtle", 1, 4) | |
writeString("and put some torches and coal in it.", 1, 5) | |
sleep(1.5) | |
writeCenteredString("Press any key to start...", 9) | |
os.pullEvent("key") | |
resetScreen() | |
if not stripMine(num_tunnels, tunnel_length, tunnel_gap, right_tunnels, pauseUI, "north") then | |
write("FATAL: Unable to detect chest") | |
else | |
write("** Strip Miner complete **") | |
end | |
clearLines(8, 13) | |
term.setCursorPos(1, 8) | |
closeLog() | |
end | |
stripMineWithUI() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment