--[[ 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 )