Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[ smartTurtle API version 2.0 by seiterarch - Save as sTurtle.
- This is a complete overhaul of the sTurtle API - more integrity, less unavoidable sanity checking.
- Position tracking is now persistent, using the same basis as Minecraft. The file "position" will be used.
- slot tracking is session-persistent starting from the first use of sTurtle.select
- bypass = true will skip input sterilization for most functions - this may cause errors.
- License: This code is completely free to use and distribute. Attribution would be nice, but I'm not too bothered.]]
- assert(turtle, "sTurtle must be loaded on a turtle.")
- --Define position ring and slot tracking variable.
- --It might be worth re-factoring these with metatables, but this should be possible later without changing the interface.
- local pos = {0, 0, 0, 0}
- local currentSlot = 1
- --Some useful values and tables
- local DIRECTIONS = {"forward", "right", "back", "left", "up", "down"}
- local FACES = {"front", "right", "back", "left", "top", "bottom"}
- local TIMEOUT = 20 --Number of failed attacks before a turtle gives up trying to move. Roughly 10ths of a second.
- --[[ Utility Functions ]]
- --Sterilization functions. A bad argument causes false to be returned (to ease debugging).
- --If nil(or false) is passed then the default is returned.
- --Directions and faces are given by an entry of the respective list or the index of said entry.
- function nForm(n, default, modulus) --Integers. If modulus (an integer) is defined then result is modular.
- if n then
- n = tonumber(n)
- if not n then return false end
- else
- return default
- end
- n = math.floor(n)
- --Find the basis of n's equivalence class in Zmod(modulus)
- if modulus then
- m = nForm(modulus)
- assert(m, "nForm received bad modulus argument")
- n = n % m
- end
- return n
- end
- function dirForm(dir, default) --Cardinal directions.
- if dir then
- for i=1, 6 do
- if dir == i or dir == DIRECTIONS[i] then
- return DIRECTIONS[i]
- end
- end
- else
- return default
- end
- return false
- end
- function faceForm(face, default) --Computer faces. Mostly useful for redstone.
- if face then
- for i=1, 6 do
- if face == i or face == FACES[i] then
- return FACES[i]
- end
- end
- else
- return default
- end
- return false
- end
- function slotForm(slot, default) --Inventory slot index. Values outside the range return are mapped onto it in case you want to do math.
- if slot then
- slot = nForm(slot, default, 16)
- if slot == 0 then slot = 16 end
- return slot --false will be passed if nForm returned false
- else
- return default
- end
- end
- --Direction and face conversion and inversion functions may be useful. dirRev is for inverting the direction of a turn
- --If sterilization is bypassed, an argument of nil (or false) will pass through.
- function dirToFace(dir, bypass)
- if not bypass then
- dir = dirForm(dir)
- assert(dir, "sTurtle.dirToFace received invalid argument")
- elseif not dir then
- return dir
- end
- local face = nil
- for i=1,6 do
- if dir == DIRECTIONS[i] then
- return FACES[i]
- end
- end
- error("sTurtle.dirToFace() received invalid argument")
- end
- function faceToDir(face, bypass)
- if not bypass then
- face = faceForm(face)
- assert(face, "sTurtle.faceToDir() received invalid argument")
- elseif not face then
- return face
- end
- for i=1,6 do
- if face == FACES[i] then
- return DIRECTIONS[i]
- end
- end
- error("sTurtle.faceToDir() received invalid argument")
- end
- function dirInv(dir, bypass)
- if not bypass then
- dir = dirForm(dir)
- assert(dir, "sTurtle.dirInv received invalid argument")
- end
- if dir == DIRECTIONS[1] then dir = DIRECTIONS[3]
- elseif dir == DIRECTIONS[2] then dir = DIRECTIONS[4]
- elseif dir == DIRECTIONS[3] then dir = DIRECTIONS[1]
- elseif dir == DIRECTIONS[4] then dir = DIRECTIONS[2]
- elseif dir == DIRECTIONS[5] then dir = DIRECTIONS[6]
- elseif dir == DIRECTIONS[6] then dir = DIRECTIONS[5]
- elseif dir then
- error("sTurtle.dirInv received invalid argument")
- end
- return dir
- end
- function faceInv(face, bypass)
- if not bypass then
- face = faceForm(face)
- assert(face, "sTurtle.faceInv received invalid argument")
- elseif not face then
- return face
- end
- --Convert argument to direction, invert and then return to face
- return dirToFace(dirInv(faceToDir(face, true), true), true)
- end
- function dirRev(dir, bypass)
- if not bypass then
- dir = dirForm(dir)
- assert(dir, "sTurtle.dirRev received invalid argument")
- end
- if dir == DIRECTIONS[1] then dir = DIRECTIONS[1]
- elseif dir == DIRECTIONS[2] then dir = DIRECTIONS[4]
- elseif dir == DIRECTIONS[3] then dir = DIRECTIONS[3]
- elseif dir == DIRECTIONS[4] then dir = DIRECTIONS[2]
- elseif dir == DIRECTIONS[5] then dir = DIRECTIONS[5]
- elseif dir == DIRECTIONS[6] then dir = DIRECTIONS[6]
- elseif dir then
- error("sTurtle.dirRev received invalid argument")
- end
- return dir
- end
- --Lets you change the default TIMEOUT if you so wish. Not currently persistant across reboot.
- function setTIMEOUT(x)
- TIMEOUT = nForm(x, TIMEOUT)
- end
- --[[ Data storage functions ]]
- --Save and load functions for pos. Custom file names can be passed (excluding false and nil).
- --Intended to be used for storing common positions only; /position is continually updated.
- function savePos(name)
- if name then
- name = tostring(name)
- else
- name = "position"
- end
- local f = fs.open(name, 'w')
- f.write(textutils.serialize(pos))
- f.close()
- end
- function loadPos(name)
- if name then
- name = tostring(name)
- else
- name = "position"
- end
- assert(fs.exists(name), name .. " does not exist.")
- local f = fs.open(name, 'r')
- pos = textutils.unserialize(f.readAll())
- f.close()
- print("Position data loaded.")
- end
- --Set and retrieve functions for pos
- --Passing nil for any of setPos' arguments causes them to remain as they were. (This also happens for invalid args.)
- function setPos(x, y, z, f)
- pos[1] = nForm(x) or pos[1]
- pos[2] = nForm(y) or pos[2]
- pos[3] = nForm(z) or pos[3]
- pos[4] = nForm(f, nil, 4) or pos[4]
- savePos()
- end
- function getPos()
- return pos[1], pos[2], pos[3], pos[4]
- end
- --[[ Movement Functions ]]
- --Turn functions. Positions updated/saved before motion as this leaves the smallest window for failure.
- --turn() takes a direction (string) relative to current facing, or a number of right turns.
- function turn(dir, bypass)
- if type(dir) == "number" then
- dir = dirForm(nForm(dir, nil, 4) + 1)
- elseif not bypass then
- dir = dirForm(dir, "right")
- assert(dir, "sTurtle.turn received an invalid argument.")
- end
- if dir == 'right' then
- pos[4] = (pos[4] + 1) % 4
- savePos()
- turtle.turnRight()
- elseif dir == 'back' then
- pos[4] = (pos[4] + 1) % 4
- savePos()
- turtle.turnRight()
- pos[4] = (pos[4] + 1) % 4
- savePos()
- turtle.turnRight()
- elseif dir == 'left' then
- pos[4] = (pos[4] + 3) % 4
- savePos()
- turtle.turnLeft()
- end
- end
- --turnTo() takes destination f co-ord.
- function turnTo(f, bypass)
- if not bypass then
- f = nForm(f, 0)
- assert(f, "sTurtle.turnTo received an invalid argument.")
- end
- local diff = (f - pos[4]) % 4
- if diff == 1 then turn("right", true)
- elseif diff == 2 then turn("back", true)
- elseif diff == 3 then turn("left", true)
- end
- savePos()
- end
- --Basic cardinal direction movement.
- function forward(n, bypass)
- --Sterilize inputs if bypass is not true.
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.forward received an invalid argument.")
- end
- for i=1, n do
- if not(turtle.forward()) then
- return false
- end
- --Position tracking
- if pos[4]==0 then pos[3] = pos[3]+1
- elseif pos[4]==1 then pos[1] = pos[1]-1
- elseif pos[4]==2 then pos[3] = pos[3]-1
- elseif pos[4]==3 then pos[1] = pos[1]+1
- end
- --Record movement
- savePos()
- end
- return true
- end
- function back(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.back received an invalid argument.")
- end
- for i=1, n do
- if not(turtle.back()) then
- return false
- end
- if pos[4]==0 then pos[3] = pos[3]+1
- elseif pos[4]==1 then pos[1] = pos[1]-1
- elseif pos[4]==2 then pos[3] = pos[3]-1
- elseif pos[4]==3 then pos[1] = pos[1]+1
- end
- savePos()
- end
- return true
- end
- function up(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.up received an invalid argument.")
- end
- for i=1, n do
- if not(turtle.up()) then
- return false
- end
- pos[2] = pos[2] + 1
- savePos()
- end
- return true
- end
- function down(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.down received an invalid argument.")
- end
- for i=1, n do
- if not(turtle.down()) then
- return false
- end
- pos[2] = pos[2] - 1
- savePos()
- end
- return true
- end
- --Strafes
- function left(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.left received an invalid argument.")
- end
- local result = false
- turn("left", true)
- result = forward(n, true)
- turn("right", true)
- return result
- end
- function right(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.right received an invalid argument.")
- end
- local result = false
- turn("right", true)
- result = forward(n, true)
- turn("left", true)
- return result
- end
- --Abbreviated versions of the above for ease of use and some backwards compatability
- function f(n, bypass)
- return forward(n, bypass)
- end
- function b(n, bypass)
- return back(n, bypass)
- end
- function u(n, bypass)
- return up(n, bypass)
- end
- function d(n, bypass)
- return down(n, bypass)
- end
- function l(n, bypass)
- return left(n, bypass)
- end
- function r(n, bypass)
- return right(n, bypass)
- end
- --Destructive movement functions. Return false if turtle encounters an immovable obstacle, otherwise true.
- --If an obstacle is encountered, turtle will attempt to dig or attack depending on the presence of blocks.
- function mine(n, bypass) --forward
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.mine received an invalid argument.")
- end
- local delay = 0
- for i=1, n do
- while not forward(1, true) do
- if turtle.detect() and not turtle.dig() then
- return false
- elseif not turtle.attack() then
- delay = delay + 1
- if delay >= TIMEOUT then
- return false
- end
- sleep(0.1)
- end
- end
- delay = 0
- end
- return true
- end
- function mineUp(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.mineUp received an invalid argument.")
- end
- local delay = 0
- for i=1, n do
- while not up(1, true) do
- if turtle.detectUp() and not turtle.digUp() then
- return false
- elseif not turtle.attackUp() then
- delay = delay + 1
- if delay >= TIMEOUT then
- return false
- end
- sleep(0.1)
- end
- end
- delay = 0
- end
- return true
- end
- function mineDown(n, bypass)
- if not bypass then
- n = nForm(n, 1)
- assert(n, "sTurtle.mineDown received an invalid argument.")
- end
- delay = 0
- for i=1, n do
- while not down(1, true) do
- if turtle.detectDown() and not turtle.digDown() then
- return false
- elseif not turtle.attackDown() then
- delay = delay + 1
- if delay >= TIMEOUT then
- return false
- end
- sleep(0.1)
- end
- end
- delay = 0
- end
- return true
- end
- --[[ Inventory functions ]]
- --Select function that accepts nil for no change and keeps track of current slot.
- --Slot tracking is not persistent across reboot.
- function select(slot, bypass)
- if not bypass then
- slot = slotForm(slot)
- if slot == false then
- error("sTurtle.select received an invalid argument.")
- end
- end
- if not slot then return true end
- local result = turtle.select(slot)
- currentSlot = slot
- return result
- end
- function getCurrentSlot()
- return currentSlot
- end
- --Pass an array of slots to to get the total itemCount across the array. Duplicate entries counted twice. Nil allowed.
- function countAll(slotList, bypass)
- assert(type(slotList) == "table", "sTurtle.countAll recieved non-array argument.")
- if not bypass then
- for i,slot in ipairs(slotList) do
- slotList[i] = slotForm(slot)
- if slotList[i] == false then
- error("sTurtle.countAll received an invalid argument.")
- end
- end
- end
- local count = 0
- for i, slot in ipairs(slotList) do
- count = count + turtle.getItemCount(slot)
- end
- return count
- end
- --Returns an array of slots with the same contents as the given slot.
- function getMatchingSlots(slot, bypass)
- if not bypass then
- slot = slotForm(slot)
- if slot == false then
- error("sTurtle.getMatchingSlots received an invalid argument.")
- end
- end
- --create some useful variables
- local initialSlot = currentSlot
- local nextIndex = 1
- local slots = {}
- for i=1,16 do
- if i ~= slot then
- select(i, true)
- if turtle.compareTo(slot) then
- slots[nextIndex] = i
- nextIndex = nextIndex + 1
- end
- else
- slots[nextIndex] = i
- nextIndex = nextIndex + 1
- end
- end
- --return to initial slot
- select(initialSlot, true)
- return slots
- end
- --Extension of the drop functions. drops count of items. If count is negative, all but -count items in the slot are dropped.
- function deposit(dir, slot, count, bypass)
- if not bypass then
- dir = dirForm(dir, "forward")
- slot = slotForm(slot, currentSlot)
- count = nForm(count, 64)
- assert(dir and slot and count, "sTurtle.deposit received an invalid argument.")
- end
- local initialSlot = currentSlot
- local toDrop = nil
- if count >= 0 then
- toDrop = count
- else
- toDrop = turtle.getItemCount(slot) + count
- end
- --save time if nothing is to be dropped
- if toDrop <= 0 then return true end
- turn(dir, true)
- select(slot, true)
- if dir == "up" then
- turtle.dropUp(toDrop)
- elseif dir == "down" then
- turtle.dropDown(toDrop)
- else
- turtle.drop(toDrop)
- end
- select(initialSlot, true)
- turn(dirRev(dir, true), true)
- return true
- end
- --Deposit all matching items. keepOne = true keeps a single item in the specified slot
- function depositAll(dir, slot, keepOne, bypass)
- if not bypass then
- dir = dirForm(dir, "forward")
- slot = slotForm(slot, currentSlot)
- assert(dir and slot, "sTurtle.depositAll received an invalid argument.")
- end
- local initialSlot = currentSlot
- local slotList = getMatchingSlots(slot, true)
- local dir2 = dir
- if countAll(slotList, true) == 0 then return true end
- --dir2 is the direction to pass into the deposit subFunction. The turtle will have already turned.
- if dir2 == DIRECTIONS[2] then dir2 = DIRECTIONS[1]
- elseif dir2 == DIRECTIONS[3] then dir2 = DIRECTIONS[1]
- elseif dir2 == DIRECTIONS[4] then dir2 = DIRECTIONS[1]
- end
- turn(dir, true)
- --Iterate through the list of slots with matching contents
- for i, s in ipairs(slotList) do
- select(s, true)
- if s ~= slot or not keepOne then
- deposit(dir2, s, 64, true)
- else
- --leave one item in the slot if it is the selected slot and keepOne is true
- deposit(dir2, s, -1, true)
- end
- end
- select(initialSlot, true)
- turn(dirRev(dir, true), true)
- return true
- end
- --[[ Extension functions ]]
- --If detect is given a valid slot argument, it will compare to the item in that slot.
- function detect(dir, slot, bypass)
- if not bypass then
- dir = dirForm(dir, "front")
- slot = slotForm(slot, currentSlot)
- assert(dir and slot, "sTurtle.detect received an invalid argument.")
- end
- if not slot then
- assert(turtle.getItemCount(slot), "Detect cannot compare to an empty slot.")
- end
- local initialSlot = currentSlot
- local isTrue = false
- turn(dir, true)
- detected = false
- if slot then
- select(slot, true)
- if dir == "up" then
- detected = turtle.compareUp()
- elseif dir == down then
- detected = turtle.compareDown()
- else
- detected = turtle.compare()
- end
- else
- if dir == "up" then
- detected = turtle.detectUp()
- elseif dir == down then
- detected = turtle.detectDown()
- else
- detected = turtle.detect()
- end
- end
- select(initialSlot, true)
- turn(dirRev(dir, true), true)
- return detected
- end
- --dig() will attempt to account for sand/gravel at the cost of a little extra time.
- function dig(dir, bypass)
- if not bypass then
- dir = dirForm(dir, "forward")
- assert(dir, "sTurtle.dig received an invalid argument.")
- end
- turn(dir, true)
- if dir == "down" and turtle.detectDown() then
- turtle.digDown()
- elseif dir == "up" then
- while turtle.detectUp() do
- turtle.digUp()
- sleep(0.1)
- end
- else
- while turtle.detect() do
- turtle.dig()
- sleep(0.1)
- end
- end
- turn(dirRev(dir, true), true)
- end
- --Places a block from the given slot in the direction specified. If slot is nil, the currently selected slot is used.
- --If replace is false/nil, currently present blocks will to be replaced. signText only relevant when placing to a side.
- --Will not place if the block in the position is of the same type as that being placed.
- function place(dir, slot, replace, bypass, signText)
- if not bypass then
- dir = dirForm(dir, "forward")
- slot = slotForm(slot, currentSlot)
- assert(dir and slot, "sTurtle.place received an invalid argument.")
- end
- local result = false
- local initialSlot = currentSlot
- turn(dir, true)
- select(slot, true)
- if dir == "up" then
- if turtle.detectUp() then
- if replace and not turtle.compareUp() then
- dig("up", true)
- result = turtle.placeUp()
- else
- result = true
- end
- else
- result = turtle.placeUp()
- end
- elseif dir == "down" then
- if turtle.detectDown() then
- if replace and not turtle.compareDown() then
- dig("down", true)
- result = turtle.placeDown()
- else
- result = true
- end
- else
- result = turtle.placeDown()
- end
- else
- if turtle.detect() then
- if replace and not turtle.compare() then
- dig("forward", true)
- result = turtle.place(signText)
- else
- result = true
- end
- else
- result = turtle.place()
- end
- end
- select(initialSlot, true)
- turn(dirRev(dir, true), true)
- return result
- end
- --[[ GPS interaction (requires modem) ]]
- --Function to find partial co-ordinates after a reboot by utilising GPS. Useful for high-motion systems
- --that may have been part-way through a move during reboot. Uses the saved value of f.
- function gpsLocate()
- loadPos()
- local p1 = vector.new(gps.locate(5))
- if p1.x == nil then
- print("Failed to find GPS signal. Using prior co-ordinates")
- return false
- end
- setPos(p1.x, p1.y, p1.z, nil)
- return true
- end
- --More robust function to find full co-ordinates via GPS.
- function gpsInit()
- local p1 = vector.new(gps.locate(5))
- if p1.x == nil then
- print("Failed to find GPS signal. Using prior co-ordinates")
- return false
- end
- local hasMoved = false
- for i=1, 4 do
- if forward(1, true) then
- hasMoved = true
- break
- else
- turn("right", true)
- end
- end
- if not hasMoved then
- print("Turtle must be able to move in order to get co-ordinate data")
- return false
- end
- local p2 = vector.new(gps.locate(5))
- if p2.x == nil then
- print("Failed to find GPS signal. Using prior co-ordinates")
- init()
- return false
- end
- --Find the co-ordinate difference caused by movement
- p1 = p2:sub(p1)
- local f = 0
- assert(p1.y == 0, "Critical GPS network failure: poorly defined co-ordinates.")
- if p1.x == 1 and p1.z == 0 then
- f = 3
- elseif p1.x == -1 and p1.z == 0 then
- f = 1
- elseif p1.x == 0 and p1.z == 1 then
- f = 0
- elseif p1.x == 0 and p1.z == -1 then
- f = 2
- else
- error("Critical GPS network failure: poorly defined co-ordinates.")
- end
- setPos(p2.x, p2.y, p2.z, f)
- select(1, true)
- return true
- end
- --Simple GPS host setup using internal co-ordinates. Warning: Internal co-ordinates are not necessarily synchronised other hosts.
- function gpsHost()
- shell.run("gps", "host", pos[1], pos[2], pos[3])
- end
- --[[ Other ]]
- --Initialize function. Not strictly necessary, but highly suggested. "gpsInit() select(1, true)" also works.
- function init()
- --Create or retrieve position data
- if fs.exists("position") then
- loadPos()
- else
- savePos()
- end
- select(1, true)
- end
Add Comment
Please, Sign In to add comment