Last active
October 24, 2021 20:49
-
-
Save Pyeroh/81e88be13c819d612530f59b7d173c20 to your computer and use it in GitHub Desktop.
cTurtle
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
assert(turtle,"This API may only be used on turtles!") | |
local ver = 2.5 | |
local turtleOS = turtle.inspect and 1.64 or fs.getDir and 1.63 or turtle.equipRight and 1.6 or peripheral.getNames and 1.51 | |
local tFile = { | |
--file path table | |
["cTurtle"] = "cTurtle" | |
} | |
tFile.directory = "/"..tFile.cTurtle.."Files" | |
tFile.data = tFile.directory.."/data" | |
tFile.restarted = tFile.directory.."/restarted" | |
tFile.settings = tFile.directory.."/settings" | |
local unlimitedFuel = turtle.getFuelLevel() == "unlimited" | |
tSettings = {} | |
function saveSettings(path,tTable) | |
path = path or tFile.settings | |
tTable = tTable or tSettings | |
local file = fs.open(path,"w") | |
for k,v in pairs(tTable) do | |
if type(v) == "table" then | |
v = textutils.serialize(v):gsub("\n%s-","") | |
elseif v == true then | |
v = "true" | |
elseif v == false then | |
v = "false" | |
end | |
file.writeLine(k.." = "..v) | |
end | |
file.close() | |
end | |
local function loadSettings(path,tTable) | |
path = path or tFile.settings | |
tTable = tTable or tSettings | |
local file = fs.open(path,"r") | |
local line = file.readLine() | |
while line do | |
local k = line:match"^%S+" | |
local v = line:sub(#k+4,#line) | |
if v:match"^%d+$" then --number | |
v = tonumber(v) | |
elseif v == "true" then --true | |
v = true | |
elseif v == "false" then --false | |
v = false | |
elseif v:match"^{.-}$" then --table | |
v = textutils.unserialize(v) | |
end | |
tTable[k] = v | |
line = file.readLine() | |
end | |
file.close() | |
end | |
--settings, configure these from external programs using cTurtle.tSettings.setting = setting then cTurtle.saveSettings() | |
if fs.exists(tFile.settings) then | |
loadSettings() | |
else | |
tSettings.renderMove = true --wether to render info page on movement or not | |
tSettings.enderFuel = false --slot of enderchest containing fuel | |
tSettings.autoRefuel = false --wether to check inventory for fuel before stopping when out of fuel | |
tSettings.controlChannel = 9280 --channel for external modem control, modemControl(true) must be called prior to usage | |
tSettings.controlResponseChannel = 9281 --channel for modem responses | |
tSettings.swarmChannel = 9290 --channel for swarm controls | |
tSettings.swarmResponseChannel = 9291 --channel for swarm responses | |
tSettings.swarm = false --swarm number. Swarm turtles will not break other turtles. | |
tSettings.formation = false --swarm formation | |
tSettings.tEnderFuel = {} -- slots to check enderchest for fuel | |
for i=17,54 do | |
tSettings.tEnderFuel[#tSettings.tEnderFuel+1] = i | |
end | |
end | |
tTimer = { | |
--timer table | |
["inputCoords"] = 20, --time to cancel coordinate input | |
["dig"] = 0.1, --time to wait after digging a block, incase of sand or gravel | |
["moveFail"] = 0.5, --time to wait if not digging and path is blocked | |
["enderFuel"] = 60 --time between each enderchest fuel check. | |
} | |
--init tables | |
local tColors | |
--color table | |
if term.isColor() then | |
tColors = { | |
["screen"] = colors.lightBlue, | |
["text"] = colors.white, | |
["textBackground"] = colors.black, | |
["topBar"] = colors.blue, | |
["bottomBar"] = colors.blue, | |
["topBarText"] = colors.yellow, | |
["bottomBarText"] = colors.yellow | |
} | |
else | |
tColors = { | |
--black & white color table | |
["screen"] = colors.white, | |
["text"] = colors.white, | |
["textBackground"] = colors.black, | |
["topBar"] = colors.black, | |
["bottomBar"] = colors.black, | |
["topBarText"] = colors.white, | |
["bottomBarText"] = colors.white | |
} | |
end | |
local tText = { | |
--string table | |
["topBar"] = "cTurtle ver: "..ver, | |
["x"] = "X: ", | |
["y"] = "Y: ", | |
["z"] = "Z: ", | |
["dir"] = "Dir: ", | |
["fuel"] = "Fuel: ", | |
["failedGPS"] = "No GPS, please input co-ordinates" | |
} | |
local tTerm = {} | |
--terminal size table | |
tTerm.x,tTerm.y = term.getSize() | |
tTerm.xMid = tTerm.x/2 | |
tTerm.yMid = tTerm.y/2 | |
local tCurs = { | |
--cursor table | |
["x"] = 1, | |
["y"] = 1 | |
} | |
tDir = { | |
--direction conversion table | |
["X+"] = { | |
"x+","-x-","1","+1","-3","east","-west","e","-w" | |
}, | |
["X-"] = { | |
"x-","-x+","3","+3","-1","west","-east","w","-e" | |
}, | |
["Z+"] = { | |
"z+","-z-","2","+2","-4","south","-north","s","-n" | |
}, | |
["Z-"] = { | |
"z-","-z+","4","+4","-2","north","-south","n","-s" | |
}, | |
["Y+"] = { | |
"y+","-y-","up","-down","top","-bottom","5","+5","-6" | |
}, | |
["Y-"] = { | |
"y-","-y+","down","-up","bottom","-top","6","+6","-5" | |
} | |
} | |
tDirText = { | |
--text to direction conversion table | |
["forward"] = { | |
"forward","front","ahead","-back","-backward","-backwards","-behind" | |
}, | |
["backward"] = { | |
"back","backward","backwards","behind","around","-front","-forward","-ahead" | |
}, | |
["left"] = { | |
"left","-right" | |
}, | |
["right"] = { | |
"right","-left" | |
} | |
} | |
tNumDir = { | |
--number to symbol conversion table | |
"X+", | |
"Z+", | |
"X-", | |
"Z-", | |
"Y+", | |
"Y-" | |
} | |
tNumHeading = { | |
--number to heading conversion table | |
"east", | |
"south", | |
"west", | |
"north", | |
"up", | |
"down" | |
} | |
tDirNum = { | |
--symbol to number conversion table | |
["X+"] = 1, | |
["X-"] = 3, | |
["Z+"] = 2, | |
["Z-"] = 4, | |
["Y+"] = 5, | |
["Y-"] = 6 | |
} | |
tNumSide = { | |
--number to turtle side conversion table | |
[1] = "front", | |
[2] = "front", | |
[3] = "front", | |
[4] = "front", | |
[5] = "top", | |
[6] = "bottom" | |
} | |
tPos = {} --location table | |
tDest = {} --destination table | |
--custom native turtle functions | |
tPos.selectedSlot = 1 | |
turtle.select(1) | |
local turtleSelect = _G.turtle.select | |
_G.turtle.select = function(slot) | |
turtleSelect(slot) | |
tPos.selectedSlot = slot | |
end | |
native = turtle.native or turtle | |
eventHandler = {} --stores custom event handler functions | |
function clearEventHandler(skipcTurtle) --idfk why i can't just redef it.... | |
local tKey = {} | |
for k in pairs(eventHandler) do | |
if not skipcTurtle | |
or (k ~= "refuel" and k ~= "moveFail") then | |
tKey[#tKey+1] = k | |
end | |
end | |
for i=1,#tKey do --fuck you next | |
eventHandler[tKey[i]] = nil | |
end | |
end | |
local function waitForResponse(id) --modified to not throw away non-turtle events and handle cTurtle events | |
local resBool,resString | |
while true do | |
local tEvent = {os.pullEvent()} | |
if tEvent[1] == "turtle_response" then | |
if tEvent[2] == id then | |
if tEvent[3] then | |
resBool = true | |
else | |
resBool,resString = false, tEvent[4] | |
end | |
return resBool,resString | |
end | |
elseif tEvent[1] == "cTurtle" then | |
return tEvent[2] | |
elseif eventHandler[tEvent[1]] then | |
eventHandler[tEvent[1]](tEvent) | |
end | |
end | |
end | |
local function wrap(sCommand) --get native function | |
return turtleOS >= 1.6 and native[sCommand] | |
or function(...) | |
local id = native[sCommand](...) | |
if id == -1 then | |
return false | |
end | |
return waitForResponse(id) | |
end | |
end | |
-- Wrap standard commands | |
local turtle = {} | |
turtle["getItemCount"] = native.getItemCount | |
turtle["getItemSpace"] = native.getItemSpace | |
turtle["getFuelLevel"] = native.getFuelLevel | |
for k,v in pairs( native ) do | |
if type( k ) == "string" and type( v ) == "function" and turtle[k] == nil then | |
turtle[k] = wrap( k ) | |
end | |
end | |
-- Wrap peripheral commands | |
if peripheral.getType("left") == "workbench" then | |
turtle["craft"] = function( ... ) | |
local id = peripheral.call( "left", "craft", ... ) | |
return waitForResponse( id ) | |
end | |
elseif peripheral.getType("right") == "workbench" then | |
turtle["craft"] = function( ... ) | |
local id = peripheral.call( "right", "craft", ... ) | |
return waitForResponse( id ) | |
end | |
end | |
--internal API functions | |
tTextDir = {} | |
local function updateDirs() | |
--updates table containing current directions | |
tTextDir.forward = tPos.dir | |
tTextDir.right = dirAdd(tPos.dir) | |
tTextDir.left = dirSub(tPos.dir) | |
tTextDir.backward = dirAdd(tPos.dir,2) | |
return tTextDir | |
end | |
local function getTurtleFunc(FUNCTION,DIRECTION,oneTurn) | |
--simplified function for acessing all turtle functions with cTurtle's direction capabilities | |
assert(DIRECTION,"No direction given for function "..FUNCTION) | |
DIRECTION = dirStandardize(DIRECTION) | |
if DIRECTION < 5 then -- not Y+ or Y- | |
if FUNCTION == "move" then | |
FUNCTION = "forward" | |
end | |
if oneTurn == true then | |
turn(DIRECTION) | |
return turtle[FUNCTION] | |
elseif oneTurn == false then | |
return turtle[FUNCTION] | |
else | |
return function() | |
turn(DIRECTION) | |
return turtle[FUNCTION]() | |
end | |
end | |
elseif DIRECTION == 5 then --Y+ | |
if FUNCTION == "move" then | |
return turtle.up | |
end | |
return turtle[FUNCTION.."Up"] | |
elseif DIRECTION == 6 then -- Y- | |
if FUNCTION == "move" then | |
return turtle.down | |
end | |
return turtle[FUNCTION.."Down"] | |
end | |
end | |
local function turnRight(AMOUNT) | |
--proper right turn | |
AMOUNT = AMOUNT or 1 | |
if AMOUNT < 1 then | |
AMOUNT = -AMOUNT | |
end | |
for i=1,AMOUNT do | |
tPos.dir = dirAdd(tPos.dir,1) | |
saveData() | |
--updates data prior to turn, to compensate for crash during turn | |
turtle.turnRight() | |
if renderMove then | |
refreshLines() | |
end | |
end | |
updateDirs() | |
end | |
local function turnLeft(AMOUNT) | |
--proper left turn | |
AMOUNT = AMOUNT or 1 | |
if AMOUNT < 1 then | |
AMOUNT = -AMOUNT | |
end | |
for i=1,AMOUNT do | |
tPos.dir = dirSub(tPos.dir,1) | |
saveData() | |
--updates data prior to turn, to compensate for crash during turn | |
turtle.turnLeft() | |
if renderMove then | |
refreshLines() | |
end | |
end | |
updateDirs() | |
end | |
local function sleep(time) | |
-- modified to not throw away events and cancel cTurtle events if c is pressed | |
local timerId = os.startTimer(time or 0) | |
while true do | |
local tEvent = {os.pullEvent()} | |
if tEvent[1] == "timer" and tEvent[2] == timerId then | |
return time | |
elseif tEvent[1] == "key" and tEvent[2] == keys.c then --c | |
os.queueEvent("cTurtle","cancel") | |
else | |
os.queueEvent(unpack(tEvent)) | |
end | |
end | |
end | |
--API functions | |
function update(swarm) | |
if swarm then --update swarm turtles through rednet | |
local cTurtleFile = { | |
id = "all", | |
[1] = "cTurtle update" | |
} | |
for line in fs.open(tFile.cTurtle,"r").readLine do | |
cTurtleFile[#cTurtleFile+1] = line | |
end | |
_G.modem.transmit(tSettings.swarmChannel,tSettings.swarmResponseChannel,cTurtleFile) | |
else --update self | |
local response,paste = http.get("https://gist.github.com/Pyeroh/81e88be13c819d612530f59b7d173c20/raw/cTurtle.lua") | |
if response then | |
paste = response.readAll() | |
response.close() | |
--save to file | |
local file = fs.open(tFile.cTurtle,"w") | |
file.writeLine(paste) | |
file.close() | |
os.reboot() | |
else | |
return false | |
end | |
end | |
end | |
tInput = {} | |
function input() | |
--enables co-ordinate input | |
local tFieldOrder = {"x","y","z","dir"} | |
for i=1,#tFieldOrder do | |
--creates input field table | |
local key,value = tFieldOrder[i],tPos[tFieldOrder[i]] | |
if key == "dir" and string.match(string.format(value),"%d") then | |
value = tNumDir[value] | |
end | |
tInput[i] = {} | |
tInput[i]["value"] = string.format(value) | |
tInput[i]["field"] = tText[key] | |
tInput[i]["startX"] = #tInput[i]["field"]+2 | |
end | |
term.setBackgroundColor(tColors.screen) | |
term.clear() | |
refreshLines() | |
renderBars(tText.failedGPS) | |
tCurs.x,tCurs.Y = 0,1 | |
term.setCursorPos(tInput[tCurs.y]["startX"],tCurs.y*2+1) | |
term.setCursorBlink(true) | |
local timeOut = os.startTimer(tTimer.inputCoords) | |
while true do | |
--user input begins | |
local tEvent = {os.pullEvent()} | |
if timeOut | |
and (tEvent[1] == "key" or tEvent[1] == "mouse_click" or tEvent[1] == "char") then | |
--cancel timeout | |
timeOut = nil | |
end | |
if tEvent[1] == "mouse_click" then | |
if tEvent[2] == 1 then | |
local clickY = (tEvent[4]-1)/2 | |
if tFieldOrder[clickY] and tEvent[3] < tTerm.xMid-1 then | |
local clickX = tEvent[3]-#tInput[clickY]["field"]-1 | |
if clickX > 0 then | |
tCurs.y = clickY | |
tCurs.x = math.min(#tInput[clickY]["value"],clickX-1) | |
end | |
end | |
end | |
elseif tEvent[1] == "key" then | |
--special keyes | |
if tEvent[2] == keys.up then --up arrow | |
tCurs.x = tCurs.x+#tInput[tCurs.y]["field"] | |
tCurs.y = tCurs.y-1 | |
tCurs.x = tCurs.x-#tInput[math.max(1,tCurs.y)]["field"] | |
elseif tEvent[2] == keys.down then --down arrow | |
tCurs.x = tCurs.x+#tInput[tCurs.y]["field"] | |
tCurs.y = tCurs.y+1 | |
tCurs.x= tCurs.x-#tInput[math.min(#tInput,tCurs.y)]["field"] | |
elseif tEvent[2] == keys.left then --left arrow | |
tCurs.x = tCurs.x-1 | |
elseif tEvent[2] == keys.right then --right arrow | |
tCurs.x = tCurs.x+1 | |
elseif tEvent[2] == keys.enter then --enter | |
if tCurs.y >= #tInput then | |
--input complete | |
if dirStandardize(tInput[4]["value"]) then | |
break | |
else | |
renderBars"Faulty direction." | |
end | |
else | |
tCurs.x = tCurs.x+#tInput[tCurs.y]["field"]-#tInput[tCurs.y+1]["field"] | |
tCurs.y = tCurs.y+1 | |
end | |
elseif tEvent[2] == keys.backspace and tCurs.x > 0 then --backspace | |
tInput[tCurs.y]["value"] = string.sub(tInput[tCurs.y]["value"],1,tCurs.x-1)..string.sub(tInput[tCurs.y]["value"],tCurs.x+1,#tInput[tCurs.y]["value"]) | |
tCurs.x = tCurs.x-1 | |
elseif tEvent[2] == keys["end"] then --end | |
tCurs.x = #tInput[tCurs.y]["value"] | |
elseif tEvent[2] == keys.home then --home | |
tCurs.x = 1 | |
elseif tEvent[2] == keys.delete then --delete | |
if #tInput[tCurs.y]["value"] == 1 and tCurs.x <= 0 then | |
tInput[tCurs.y]["value"] = "" | |
else | |
tInput[tCurs.y]["value"] = string.sub(tInput[tCurs.y]["value"],1,tCurs.x)..string.sub(tInput[tCurs.y]["value"],tCurs.x+2,#tInput[tCurs.y]["value"]) | |
end | |
end | |
--cursor check | |
tCurs.y = math.min(#tInput,tCurs.y) | |
tCurs.y = math.max(1,tCurs.y) | |
tCurs.x = math.min(#tInput[tCurs.y]["value"],tCurs.x) | |
tCurs.x = math.max(0,tCurs.x) | |
elseif tEvent[1] == "char" then | |
local input | |
if #tInput[tCurs.y]["value"]+#tInput[tCurs.y]["field"] >= tTerm.xMid-3 then | |
--limits amount of characters to bar size | |
elseif tEvent[2]:match"[%+%-]" and tInput[tCurs.y]["value"]:match"[%+%-]" then | |
--only one of either + or - | |
elseif tInput[tCurs.y]["field"]:match"Dir:" and tEvent[2]:match"[XZxznorthNORTHsuSUweWEaA%+%-]" then | |
--input to direction field, accepts +- and letters related to directions | |
if tEvent[2]:match"[%+%-]" and tCurs.x ~= 1 | |
or tEvent[2]:match"[%+%-]" and #tInput[tCurs.y]["value"] > 1 then | |
-- + and - can only be at the end of single char directions | |
else | |
input = true | |
end | |
elseif tEvent[2]:match"[0-9%+%-]" then | |
--other fields, only accept numbers and +- | |
if tEvent[2]:match"[%+%-]" and tCurs.x ~= 0 | |
or tInput[tCurs.y]["value"]:match"[%+%-]" and tCurs.x == 0 then | |
-- + and - can only be at start | |
else | |
input = true | |
end | |
end | |
if input then | |
--input accepted | |
tInput[tCurs.y]["value"] = string.sub(tInput[tCurs.y]["value"],1,tCurs.x)..tEvent[2]..string.sub(tInput[tCurs.y]["value"],tCurs.x+1,#tInput[tCurs.y]["value"]) | |
tCurs.x = tCurs.x+1 | |
end | |
elseif tEvent[1] == "timer" then | |
--timers | |
if tEvent[2] == timeOut then | |
break | |
end | |
end | |
--updates current field on user input | |
renderLine(tCurs.y) | |
term.setCursorPos(tInput[tCurs.y]["startX"]+tCurs.x,tCurs.y*2+1) | |
term.setCursorBlink(true) | |
end | |
--input complete, storing data | |
tPos.x = tonumber(tInput[1]["value"]) | |
tPos.y = tonumber(tInput[2]["value"]) | |
tPos.z = tonumber(tInput[3]["value"]) | |
tPos.lastMove = dirStandardize(tInput[4]["value"]) | |
tPos.dir = tPos.lastMove | |
term.setCursorBlink(false) | |
term.setTextColor(colors.white) | |
term.setBackgroundColor(colors.black) | |
term.clear() | |
term.setCursorPos(1,1) | |
end | |
function locate(FORCE) | |
--gets turtle coordinates | |
local x,y,z,dir,x2,y2,z2 | |
if _G.restarted or FORCE then | |
--re-gets coordinates | |
--via GPS | |
x,y,z = gps.locate(3) | |
if x and (not unlimitedFuel and turtle.getFuelLevel() > 0 or unlimitedFuel) then | |
--GPS sucess, getting direction by moving | |
local moved,down = false,false | |
while not moved do | |
while not unlimitedFuel and turtle.getFuelLevel() <= 0 do | |
if tSettings.enderFuel then | |
enderRestock(tSettings.enderFuel,true,tSettings.tEnderFuel) | |
os.startTimer(tTimer.enderFuel) | |
end | |
if tSettings.autoRefuel and turtle.getFuelLevel() <= 0 then | |
for i=1,16 do | |
turtle.refuel(64) | |
end | |
end | |
if turtle.getFuelLevel() <= 0 then | |
if eventHandler.refuel then | |
eventHandler.refuel() | |
else | |
renderBars("Fuel required! Press any key.") | |
while true do | |
local e = os.pullEvent() | |
if e == "key" then | |
turtle.refuel(64) | |
break | |
elseif e == "timer" then | |
break | |
end | |
end | |
end | |
end | |
end | |
for i=1,4 do | |
if turtle.forward() then | |
moved = true | |
break | |
else | |
turtle.turnLeft() | |
end | |
end | |
if (turtle.detectUp() or down) | |
and not moved then | |
turtle.down() | |
down = true | |
elseif not moved then | |
turtle.up() | |
end | |
end | |
-- get new posistion | |
x2,y2,z2 = gps.locate(3) | |
-- calculate direction | |
if x2 and (x ~= x2 or z ~= z2) then --incase second GPS fails | |
if x < x2 then | |
dir = "X+" | |
elseif x > x2 then | |
dir = "X-" | |
elseif z < z2 then | |
dir = "Z+" | |
elseif z > z2 then | |
dir = "Z-" | |
end | |
tPos.x = x2 | |
tPos.z = z2 | |
tPos.y = y2 | |
tPos.dir = tDirNum[dir] | |
tPos.fuel = unlimitedFuel and 9999 or turtle.getFuelLevel() | |
end | |
end | |
if not x | |
or not x2 | |
or x == x2 and z == z2 | |
or not dir then | |
--GPS failed, attempting player input | |
if fs.exists(tFile.data) then | |
loadData() | |
else | |
tPos.x = (x or 0) | |
tPos.y = (y or 0) | |
tPos.z = (z or 0) | |
tPos.dir = 1 | |
tPos.fuel = unlimitedFuel and 9999 or turtle.getFuelLevel() | |
end | |
tDest.x = tPos.x | |
tDest.y = tPos.y | |
tDest.z = tPos.z | |
input() | |
end | |
saveData() | |
_G.restarted = false | |
local file = fs.open(tFile.restarted,"w") | |
file.writeLine"false" | |
file.close() | |
else | |
--non-restarted call to locate | |
loadData() | |
end | |
tDest.x = tPos.x | |
tDest.y = tPos.y | |
tDest.z = tPos.z | |
return tPos | |
end | |
function renderBars(BOTTOMTEXT) | |
--render top and bottom bar | |
BOTTOMTEXT = BOTTOMTEXT or " " | |
paintutils.drawLine(1,1,tTerm.x,1,tColors.topBar) | |
paintutils.drawLine(1,tTerm.y,tTerm.x,tTerm.y,tColors.bottomBar) | |
term.setCursorPos(tTerm.xMid-(#tText.topBar/2),1) | |
term.setTextColor(tColors.topBarText) | |
term.setBackgroundColor(tColors.topBar) | |
term.write(tText.topBar) | |
term.setCursorPos(tTerm.xMid-(#BOTTOMTEXT/2),tTerm.y) | |
term.setTextColor(tColors.bottomBarText) | |
term.setBackgroundColor(tColors.bottomBar) | |
term.write(BOTTOMTEXT) | |
term.setTextColor(tColors.text) | |
term.setBackgroundColor(tColors.textBackground) | |
end | |
function refreshLines() | |
term.setTextColor(tColors.text) | |
term.setBackgroundColor(tColors.textBackground) | |
local tFieldOrder = {"x","y","z","dir","fuel","x","y","z"} | |
for i,k in pairs(tFieldOrder) do | |
if i <= 5 then | |
--pos, dir and fuel bars | |
local value = tPos[k] | |
if k == "dir" and string.match(string.format(value),"%d") then | |
value = tNumDir[value] | |
end | |
paintutils.drawLine(2,i*2+1,tTerm.xMid-1,i*2+1,tColors.textBackground) | |
term.setCursorPos(2,i*2+1) | |
term.write(string.sub(k:upper(),1,1)..k:sub(2,#k)..": "..value) | |
else | |
--dest bars | |
local value = tDest[k] | |
paintutils.drawLine(tTerm.xMid+1,(i-5)*2+1,tTerm.x-1,(i-5)*2+1,tColors.textBackground) | |
term.setCursorPos(tTerm.xMid+1,(i-5)*2+1) | |
term.write("Dest"..k:upper()..": "..value) | |
end | |
end | |
end | |
function renderLine(LINE) | |
--renders input lines | |
paintutils.drawLine(2,LINE*2+1,tTerm.xMid-1,LINE*2+1,tColors.textBackground) | |
term.setCursorPos(2,LINE*2+1) | |
term.write(tInput[LINE]["field"]..tInput[LINE]["value"]) | |
end | |
function loadData() | |
--loads position data from file | |
local file = fs.open(tFile.data,"r") | |
file.readLine() --skip first line | |
tPos.x = tonumber(string.sub(file.readLine(),4,10)) | |
tPos.y = tonumber(string.sub(file.readLine(),4,10)) | |
tPos.z = tonumber(string.sub(file.readLine(),4,10)) | |
tPos.dir = tDirNum[string.sub(file.readLine(),6,7)] | |
tPos.fuel = tonumber(string.sub(file.readLine(),7,13)) | |
tPos.lastMove = string.sub(file.readLine(),12,13) | |
if not unlimitedFuel and turtle.getFuelLevel() < tPos.fuel and _G.restarted then | |
--fuel missing, correcting co-ordinates accordingly | |
local lastMove = string.lower(tPos.lastMove) | |
tPos[lastMove:sub(1,1)] = tPos[lastMove:sub(1,1)] + tonumber(lastMove:sub(2,2)..1) | |
tPos.fuel = turtle.getFuelLevel() | |
tPos.lastMove = tDirNum[tPos.lastMove] | |
saveData() | |
else | |
tPos.fuel = unlimitedFuel and 9999 or turtle.getFuelLevel() | |
tPos.lastMove = tDirNum[tPos.lastMove] | |
end | |
return tPos | |
end | |
function saveData(x,y,z,dir,fuel,path) | |
x = x or tPos.x | |
y = y or tPos.y | |
z = z or tPos.z | |
dir = dir or tPos.dir | |
fuel = fuel or tPos.fuel | |
path = path or tFile.data | |
--saves position data to file | |
local file = fs.open(path,"w") | |
file.writeLine"cTurtle data file." | |
file.writeLine("X: "..x) | |
file.writeLine("Y: "..y) | |
file.writeLine("Z: "..z) | |
file.writeLine("Dir: "..tNumDir[dir]) | |
file.writeLine("Fuel: "..(tPos.fuel or unlimitedFuel and 9999 or turtle.getFuelLevel())) | |
file.writeLine("Last move: "..tNumDir[(tPos.lastMove or dir)]) | |
file.close() | |
end | |
function dirStandardize(DIRECTION) | |
--formats the given direction for usage | |
assert(DIRECTION,"No direction given!") | |
DIRECTION = string.format(string.lower(DIRECTION)) | |
if tPos.dir then | |
updateDirs() | |
for k,v in pairs(tDirText) do | |
for i=1,#tDirText[k] do | |
if DIRECTION == tDirText[k][i] then | |
--checks for forward, left, right, etc. | |
return tonumber(tTextDir[k]) | |
end | |
end | |
end | |
end | |
for k,v in pairs(tDir) do | |
--checks for x+,x-,z+,north,south etc. | |
for i=1,#tDir[k] do | |
if DIRECTION == tDir[k][i] then | |
return tonumber(tDirNum[k]) | |
end | |
end | |
end | |
--failed to format direction | |
error("Failed to format the given direction: "..DIRECTION,2) | |
end | |
function dirAdd(NUM,ADDNUM) | |
--direction num right turn | |
NUM = NUM+(ADDNUM or 1) | |
if NUM > 4 then | |
NUM = NUM-4 | |
end | |
return NUM | |
end | |
function dirSub(NUM,SUBNUM) | |
--direction num left turn | |
NUM = NUM-(SUBNUM or 1) | |
if NUM < 1 then | |
NUM = 4+NUM | |
end | |
return NUM | |
end | |
function turn(DIRECTION) | |
DIRECTION = dirStandardize(DIRECTION) | |
if DIRECTION > 4 then | |
return false --unable to turn Y+, Y- | |
end | |
if tSettings.renderMove then | |
renderBars("Turning "..tNumDir[DIRECTION]) | |
end | |
if DIRECTION == dirSub(tPos.dir,1) then | |
--left turn when turning one left | |
turnLeft(1) | |
else | |
local turns = 0 | |
while dirAdd(tPos.dir,turns) ~= DIRECTION do | |
turns = turns+1 | |
end | |
--otherwise turn right | |
turnRight(turns) | |
end | |
if tSettings.renderMove then | |
renderBars("Turn complete") | |
end | |
return true | |
end | |
function detect(DIRECTION) | |
return getTurtleFunc("detect",DIRECTION,true)() | |
end | |
function dig(DIRECTION,SLOT,LOOP) | |
if SLOT then | |
turtle.select(SLOT) | |
end | |
local digFunc = getTurtleFunc("dig",DIRECTION,true) | |
local detectFunc = getTurtleFunc("detect",DIRECTION,false) | |
if LOOP then | |
digFunc() | |
while detectFunc() do | |
digFunc() | |
end | |
return true | |
end | |
return digFunc() | |
end | |
function compare(DIRECTION,SLOT) | |
if SLOT then | |
turtle.select(SLOT) | |
end | |
return getTurtleFunc("compare",DIRECTION,true)() | |
end | |
function place(DIRECTION,SLOT,LOOP) | |
if SLOT then | |
turtle.select(SLOT) | |
end | |
local func = getTurtleFunc("place",DIRECTION,true) | |
if loop then | |
while not func() do | |
end | |
return true | |
else | |
return getTurtleFunc("place",DIRECTION,true)() | |
end | |
end | |
function suck(DIRECTION,SLOT) | |
if SLOT then | |
turtle.select(SLOT) | |
end | |
return getTurtleFunc("suck",DIRECTION,true)() | |
end | |
function drop(DIRECTION,SLOT,AMOUNT) | |
if SLOT then | |
turtle.select(SLOT) | |
end | |
return getTurtleFunc("drop",DIRECTION,true)(AMOUNT or 64) | |
end | |
function select(SLOT) | |
return turtle.select(SLOT) | |
end | |
function replace(DIRECTION,SLOT,DIGSLOT) | |
if not compare(DIRECTION,SLOT) then | |
dig(DIRECTION,DIGSLOT,true) | |
turtle.select(SLOT or tPos.selectedSlot) | |
end | |
place(DIRECTION,SLOT) | |
end | |
function findSpace(place) | |
--finds an open space and returns direction, places slected block if place | |
if not turtle.detectUp() then | |
while place and not turtle.placeUp() do end | |
return 5,"top" | |
elseif not turtle.detectDown() then | |
while place and not turtle.placeDown() do end | |
return 6,"bottom" | |
elseif not turtle.detect() then | |
while place and not turtle.place() do end | |
return tPos.dir,"front" | |
else | |
for i=1,3 do | |
turn"right" | |
if not turtle.detect() then | |
while place and not turtle.place() do end | |
return tPos.dir,"front" | |
end | |
end | |
end | |
return false | |
end | |
function enderInteract(enderSlot,tItemsOut,tItemsIn) | |
--[[puts down an ender chest from the slot (enderSlot), iteracts with it according to the tItems table | |
using the following structure | |
tItemsOut = { --push items into chest | |
"Coal","Charcoal", --you can either store the name of the item directly in an index | |
1,2,3,4,5 --or the number of specific slots | |
{ --or use a table for more advanced options | |
name = "Stone", --display name of the desired item, slot numbers work aswell | |
amount = 128, --optional, amount of items to attempt to deposit | |
slot = 1, --optional, slot to deposit the items to | |
slot = {1,2,3,4,5} --or a table may be used for multiple slots | |
} | |
}, | |
tItemsIn = { --pull in specific items | |
"Coal","Charcoal", --you can either store the name of the item directly in an index | |
1,2,3,4,5 --or the number of specific slots to pull from | |
{ --or use a table for more advanced options | |
name = "Stone", --display name of the desired item, slot numbers work aswell | |
amount = 128, --optional,amount of items to attempt to retrieve | |
slot = 1, --optional,slot to store the items in | |
slot = {1,2,3,4,5} --or a table may be used for multiple slots | |
} | |
} | |
]] | |
turtle.select(enderSlot) | |
local _dir,chestDir = findSpace(true) | |
sleep(0.05) | |
--locate open space for ender chest | |
if chestDir then | |
local pushDir = tNumHeading[dirStandardize("-"..chestDir)] | |
local selected | |
local amount = 0 -- amout of items pushed from chest | |
if peripheral.getType(chestDir) == "ender_chest" then -- confirms ender chest peripheral | |
tItemsOut = tItemsOut or {} | |
tItemsIn = tItemsIn or {} | |
for k,v in pairs(tItemsOut) do | |
local class = type(v) | |
if class == "number" then | |
elseif class == "string" then | |
else --class == "table" | |
end | |
end | |
for k,v in pairs(tItemsIn) do | |
if class == "number" then | |
elseif class == "string" then | |
else --class == "table" | |
end | |
end | |
end | |
end | |
end | |
function enderRestock(enderSlot,tTurtleSlots,tEnderSlots) | |
--puts down an ender chest from the slot (enderSlot), then attempts to restock the | |
--turtle slots in the table tTurtleSlots with the chest slots in tEnderSlots | |
turtle.select(enderSlot) | |
local _dir,chestDir = findSpace(true) | |
sleep(0.05) | |
--locate open space for ender chest | |
if chestDir then | |
local pushDir = tNumHeading[dirStandardize("-"..chestDir)] | |
local tTurtleSlotsCopy | |
if type(tTurtleSlots) == "table" then | |
--create local copy of turtle slots table | |
tTurtleSlotsCopy = {} | |
for i=1,#tTurtleSlots do | |
tTurtleSlotsCopy[i] = tTurtleSlots[i] | |
end | |
else | |
tTurtleSlotsCopy = {enderSlot} | |
end | |
local tEnderSlotsCopy = {} | |
--create local copy of ender chest slots table | |
for i=1,#tEnderSlots do | |
tEnderSlotsCopy[i] = tEnderSlots[i] | |
end | |
local selected | |
local amount = 0 -- amout of items pushed from chest | |
if peripheral.getType(chestDir) == "ender_chest" then -- confirms ender chest peripheral | |
local chest = peripheral.wrap(chestDir) | |
local chestFunc = chest.pushItemIntoSlot or chest.pushIntoSlot | |
for iTurtle=1,#tTurtleSlotsCopy do | |
local turtleCount = turtle.getItemCount(tTurtleSlotsCopy[iTurtle]) | |
if turtleCount < 64 then -- only restocks the slot if there is space | |
local iEnder = 1 | |
while iEnder <= #tEnderSlotsCopy do --dynamic for loop | |
local stack = peripheral.call(chestDir,"getStackInSlot",tEnderSlotsCopy[iEnder]) or {["qty"] = 0} | |
local removed = false | |
if stack.qty > 0 then -- items in ender chest slot | |
local pushed = chestFunc(pushDir,tEnderSlotsCopy[iEnder],tTurtleSlots == true and stack.qty-1 or stack.qty,tTurtleSlotsCopy[iTurtle]) | |
turtleCount = turtleCount+pushed | |
amount = amount+pushed | |
if pushed == (tTurtleSlots == true and stack.qty-1 or stack.qty) then | |
remove = true | |
end | |
if turtleCount >= 64 then --ends loop if turtle slot is full | |
break | |
end | |
else -- no items in ender chest slot, slot is removed | |
remove = true | |
end | |
if remove then | |
table.remove(tEnderSlotsCopy,iEnder) | |
else | |
iEnder = iEnder+1 | |
end | |
end | |
end | |
end | |
end | |
if tTurtleSlots == true then | |
turtle.select(enderSlot) | |
turtle.refuel(64) | |
end | |
dig(chestDir,enderSlot,true) -- retrieves the ender chest | |
if amount > 0 then | |
return amount | |
else | |
return false --no items pushed | |
end | |
end | |
return false --chest placement failed | |
end | |
function enderDropoff(enderSlot,tTurtleSlots) | |
--puts down an ender chest from the slot (enderSlot), then empties the specified | |
--turtle slots in the table tTurtleSlots into the chest slots in tEnderSlots | |
turtle.select(enderSlot) | |
local _dir,chestDir = findSpace(true) | |
sleep(0.1) | |
--locate open space for ender chest | |
if chestDir then | |
local pullDir = tNumHeading[dirStandardize("-"..chestDir)] | |
local tTurtleSlotsCopy = {} | |
--create local copy of turtle slots table | |
for i=1,#tTurtleSlots do | |
tTurtleSlotsCopy[i] = tTurtleSlots[i] | |
end | |
local selected | |
local amount = 0 -- amout of items stored in chest | |
if peripheral.getType(chestDir) == "ender_chest" then -- confirms ender chest peripheral | |
local chest = peripheral.wrap(chestDir) | |
local chestFunc = chest.pullItemIntoSlot or chest.pullIntoSlot | |
for iTurtle=1,#tTurtleSlotsCopy do | |
local turtleCount = turtle.getItemCount(tTurtleSlotsCopy[iTurtle]) | |
if turtleCount > 0 then -- only stores occupied slots | |
for iChest=17,chest.getInventorySize() do | |
local stack = chest.getStackInSlot(iChest) | |
stack = stack and stack.qty and stack.maxSize or {["qty"] = 0,["maxSize"] = 64} | |
if stack.qty < stack.maxSize then --space in enderSlot | |
local pulled = chestFunc(pullDir,tTurtleSlotsCopy[iTurtle],64,iChest) | |
turtleCount = turtleCount-pulled | |
amount = amount+pulled | |
if turtleCount < 1 then -- Ends loop if turtle slot is empty | |
break | |
end | |
end | |
end | |
end | |
end | |
else | |
for iTurtle=1,#tTurtleSlotsCopy do | |
local turtleCount = turtle.getItemCount(tTurtleSlotsCopy[iTurtle]) | |
if turtleCount > 0 then -- only stores occupied slots | |
drop(chestDir, tTurtleSlotsCopy[iTurtle], turtleCount) | |
end | |
end | |
end | |
dig(chestDir,enderSlot,true) -- retrieves the ender chest | |
if amount > 0 then | |
return amount | |
else | |
return false --no items pulled | |
end | |
end | |
return false --chest placement failed | |
end | |
function move(DIRECTION,AMOUNT,DIG) | |
--moves turtle in DIRECTION, AMOUNT times, digs if DIG optional slot | |
if AMOUNT == 0 then | |
return | |
end | |
DIRECTION = dirStandardize(DIRECTION) | |
AMOUNT = AMOUNT or 1 | |
if AMOUNT < 0 then | |
DIRECTION = dirStandardize("-"..DIRECTION) | |
AMOUNT = -AMOUNT | |
end | |
--function definitions | |
local moveFunc | |
if DIRECTION == tTextDir.backward and not DIG then | |
moveFunc = turtle.back | |
else | |
moveFunc = getTurtleFunc("move",DIRECTION,true) | |
end | |
local detectFunc = getTurtleFunc("detect",DIRECTION) | |
local internalDigFunc = getTurtleFunc("dig",DIRECTION) | |
local digFunc = type(DIG) == "number" and function() | |
turtle.select(DIG) | |
internalDigFunc() | |
end or internalDigFunc | |
local attackFunc = getTurtleFunc("attack",DIRECTION) | |
saveData() | |
local symDir = tNumDir[DIRECTION] | |
local posEntry = string.lower(symDir:sub(1,1)) | |
local mathOp = symDir:sub(2,2) | |
--movement calc function | |
local calcFunc = function() | |
tPos[posEntry] = tPos[posEntry] + tonumber(mathOp.."1") | |
tPos.fuel = not unlimitedFuel and tPos.fuel-1 or 9999 | |
end | |
tDest[posEntry] = tPos[posEntry] + tonumber(mathOp..AMOUNT) | |
for i=1,AMOUNT do | |
while DIG and detectFunc() do | |
while tSettings.swarm and peripheral.getType(tNumSide[DIRECTION]) == "turtle" do -- swarm turtles check for other turtles | |
sleep(0.5) | |
end | |
digFunc() | |
if DIRECTION == 5 then | |
sleep(tTimer.dig) | |
end | |
end | |
local res = moveFunc() | |
while not res or res == "cancel" do | |
if res == "cancel" then | |
return | |
elseif not unlimitedFuel and tPos.fuel <= 1 then | |
if tSettings.enderFuel then | |
enderRestock(tSettings.enderFuel,true,tSettings.tEnderFuel) | |
tPos.fuel = turtle.getFuelLevel() | |
os.startTimer(tTimer.enderFuel) | |
end | |
if tSettings.autoRefuel and tPos.fuel <= 1 then | |
for i=1,16 do | |
turtle.refuel(64) | |
end | |
tPos.fuel = turtle.getFuelLevel() | |
end | |
if tPos.fuel <= 0 then | |
if eventHandler.refuel then | |
eventHandler.refuel() | |
tPos.fuel = turtle.getFuelLevel() | |
else | |
renderBars("Fuel required! Press any key.") | |
while true do | |
local e = os.pullEvent() | |
if e == "key" then | |
turtle.refuel(64) | |
tPos.fuel = turtle.getFuelLevel() | |
break | |
elseif e == "timer" then | |
break | |
end | |
end | |
end | |
end | |
else | |
turtle.attack() | |
if DIG then | |
digFunc() | |
attackFunc() | |
end | |
if eventHandler.moveFail then | |
eventHandler.moveFail(tNumDir[DIRECTION]) | |
elseif tSettings.renderMove then | |
renderBars("Move "..tNumDir[DIRECTION].." failed. Retry in "..tTimer.moveFail) | |
end | |
sleep(tTimer.moveFail) | |
end | |
res = moveFunc() | |
end | |
calcFunc() | |
tPos.lastMove = DIRECTION | |
saveData() | |
if tSettings.renderMove then | |
refreshLines() | |
renderBars("Moving "..symDir) | |
end | |
end | |
if renderMove then | |
renderBars("Moved "..symDir) | |
end | |
end | |
function moveTo(COORDS,SYM,DIG) | |
SYM = SYM:lower() | |
tDest[SYM] = COORDS | |
local mathOp | |
local amount = tPos[SYM]-COORDS | |
if amount > 0 then | |
mathOp = "-" | |
elseif amount == 0 then | |
return | |
else | |
mathOp = "+" | |
amount = -amount | |
end | |
move(SYM..mathOp,amount,DIG) | |
end | |
function moveToXYZ(x,y,z,faceDir,DIG) | |
tDest.x = x | |
tDest.y = y | |
tDest.z = z | |
if faceDir == true then | |
DIG = faceDir | |
faceDir = false | |
end | |
moveTo(x,"x",DIG) | |
moveTo(z,"z",DIG) | |
moveTo(y,"y",DIG) | |
if faceDir then | |
turn(faceDir) | |
end | |
end | |
function deployGPS(tSlots,x,y,z) | |
--builds a GPS tower at the given coordinates or the current coordinates | |
--[[tSlots = { | |
diskDrive = 1, | |
disk = 2, | |
computer = 3, | |
modem = 4 | |
}]] | |
assert(tSlots,"Slots table missing!") | |
assert(tSlots.diskDrive,"diskDrive slot missing!") | |
assert(turtle.getItemCount(tSlots.diskDrive) > 0,"No disk drive in slot "..tSlots.diskDrive.."!") | |
assert(tSlots.disk,"disk slot missing!") | |
assert(turtle.getItemCount(tSlots.disk) > 0,"No disk in slot "..tSlots.disk.."!") | |
assert(tSlots.computer,"computer slot missing!") | |
assert(turtle.getItemCount(tSlots.computer) > 3,"Missing "..(4-turtle.getItemCount(tSlots.computer).." computers in slot "..tSlots.computer.."!")) | |
assert(tSlots.modem,"modem slot missing!") | |
assert(turtle.getItemCount(tSlots.modem) > 3,"Missing "..(4-turtle.getItemCount(tSlots.modem).." modems in slot "..tSlots.modem.."!")) | |
x = x or tPos.x | |
y = math.min((y or tPos.y),252) | |
z = z or tPos.z | |
local function setupHost(hostX,hostZ) | |
place("forward",tSlots.computer) | |
move"up" | |
place("forward",tSlots.modem) | |
place("down",tSlots.diskDrive) | |
assert(peripheral.getType"bottom" == "drive","The block on slot "..tSlots.diskDrive.." was not a disk drive!") | |
drop("down",tSlots.disk) | |
assert(fs.exists"/disk","Missing disk drive in slot "..tSlots.disk) | |
if not fs.exists"/disk/startup" then | |
local diskFile = fs.open("/disk/startup", "w") | |
diskFile.writeLine[[ | |
if not os.getComputerLabel() then | |
os.setComputerLabel"GPS Host" | |
end | |
fs.copy("/disk/hostFile", "/startup") | |
shell.run"startup"]] | |
diskFile.close() | |
end | |
local hostFile = fs.open("/disk/hostFile", "w") | |
hostFile.writeLine('shell.run"gps host '..hostX..' '..(tPos.y-1)..' '..hostZ..'"') | |
hostFile.close() | |
move"left" | |
move"right" | |
move"down" | |
turn"right" | |
assert(peripheral.getType"front" == "computer","The block in slot "..tSlots.computer.." was not a computer!") | |
peripheral.call("front", "turnOn") | |
move"right" | |
suck("left",tSlots.disk) | |
turtle.select(tSlots.diskDrive) | |
dig("forward") | |
end | |
moveTo(y,"y",true) | |
moveToXYZ(x+4,y,z,"x+",true) | |
setupHost(tPos.x+1,tPos.z) | |
moveToXYZ(x-4,y,z,"x-",true) | |
setupHost(tPos.x-1,tPos.z) | |
moveToXYZ(x,y,z+4,"z+",true) | |
setupHost(tPos.x,tPos.z+1) | |
moveToXYZ(x,y+1,z-4,"z-",true) | |
setupHost(tPos.x,tPos.z-1) | |
move("down",2) | |
end | |
function deployTurtle(tSlots,tData) | |
--[[tSlots = { | |
diskDrive = 1, | |
disk = 2, | |
turtle = 3, | |
enderChest = 4 -- optional, enables enderRefuel slot 16 | |
} | |
tData = { | |
label = "the turtle label derpface", | |
type = "remote" --enables remote modem control on startup | |
type = "swarm" --assigns swarm ID and enables remote control, enderRefuel slot 16 and disables renderMove. | |
swarm = 1 --swarm ID number | |
formation = "wall" or "floor" --swarm formation | |
]]-- | |
tData = tData or {} | |
assert(tSlots,"Slots table missing!") | |
assert(tSlots.diskDrive,"diskDrive slot missing!") | |
assert(tSlots.disk,"disk slot missing!") | |
assert(tSlots.turtle,"turtle slot missing!") | |
place("down",tSlots.diskDrive,true) | |
assert(peripheral.getType"bottom" == "drive", "The block in slot "..tSlots.diskDrive.." was not a diskDrive!") | |
drop("down",tSlots.disk) | |
assert(fs.exists"/disk", "The block in slot "..tSlots.disk.." was not a disk!") | |
--setup cTurtle | |
fs.delete("/disk/"..tFile.cTurtle) | |
fs.copy(tFile.cTurtle,"disk/cTurtle") | |
fs.delete("/disk"..tFile.directory) | |
fs.makeDir("/disk"..tFile.directory) | |
--setup disk bootup file | |
local file = fs.open("/disk/startup","w") | |
file.writeLine([[ | |
os.setComputerLabel("]]..(tData.label or tData.type or "cTurtle")..[[") | |
fs.copy("disk/]]..tFile.cTurtle..[[", "/]]..tFile.cTurtle..[[") | |
fs.copy("disk/startupFile","/startup") | |
fs.copy("disk]]..tFile.directory..[[","]]..tFile.directory..[[")]] | |
) | |
file.close() | |
if tSlots.enderChest then | |
local file = fs.open("/disk/startup","a") | |
file.writeLine([[ | |
turtle.select(1) | |
turtle.transferTo(16)]] | |
) | |
file.close() | |
end | |
local file = fs.open("/disk/startup","a") --one time actions goes here | |
file.writeLine([[ | |
sleep(5) | |
_G.restarted = false | |
shell.run"/startup" | |
cTurtle.move"forward" | |
cTurtle.move"back"]] | |
) | |
file.close() | |
--setup settings | |
local tNewSettings = {} | |
for k,v in pairs(tSettings) do | |
tNewSettings[k] = v | |
end | |
if tSlots.enderChest then | |
tNewSettings.enderFuel = 16 | |
end | |
if tData.type == "swarm" then | |
tNewSettings.renderMove = false | |
end | |
for k,v in pairs(tData) do | |
if tNewSettings[k] ~= nil then | |
tNewSettings[k] = v | |
end | |
end | |
saveSettings("/disk"..tFile.settings,tNewSettings) | |
--setup startup file | |
local file = fs.open("/disk/startupFile","w") | |
file.writeLine([[ | |
local file = fs.open("]]..tFile.restarted..[[", "w") | |
file.writeLine"true" | |
file.close() | |
os.loadAPI"]]..tFile.cTurtle..[["]] | |
) | |
file.close() | |
--setup remote turtles | |
if tData.type == "remote" then | |
local file = fs.open("/disk/startupFile","a") | |
file.writeLine([[ | |
cTurtle.modemControl(true)]] | |
) | |
file.close() | |
--setup swarm turtles | |
elseif tData.type == "swarm" then | |
local file = fs.open("/disk/startupFile","a") | |
file.writeLine([[ | |
cTurtle.modemControl(true)]] | |
) | |
file.close() | |
end | |
--setup location info | |
--locaiton offset correction | |
local tOffs = { | |
x = 0, | |
y = -1, | |
z = 0 | |
} | |
local dir = string.lower(tNumDir[tPos.dir]) | |
tOffs[dir:sub(1,1)] = tOffs[dir:sub(1,1)] + tonumber(dir:sub(2,2)..1) | |
--save new location | |
saveData(tPos.x+tOffs.x,tPos.y+tOffs.y,tPos.z+tOffs.z,tPos.dir,0,"/disk"..tFile.data) | |
--place turtle | |
move"forward" | |
place("down",tSlots.turtle,true) | |
assert(peripheral.getType"bottom" == "turtle","The block in slot "..tSlots.turtle.." was not a turtle!") | |
if tSlots.enderChest then | |
drop("down",tSlots.enderChest,1) | |
end | |
peripheral.call("bottom","turnOn") | |
--retrieve disk and drive | |
move"back" | |
suck("down",tSlots.disk) | |
turtle.select(tSlots.diskDrive) | |
dig"down" | |
end | |
function deploySwarm (tSlots,height,width,formation) | |
--[[tSlots = { | |
diskDrive = 1, | |
disk = 2, | |
turtle = 3, | |
enderChest = 4 | |
}]]-- | |
formation = formation or "wall" | |
local frontDir = dirStandardize"forward" | |
local sideDir = dirStandardize"right" | |
local id = 1 | |
for h=1,height do | |
for w=1,width do | |
deployTurtle(tSlots,{ | |
label = "swarm cTurtle "..id, | |
type = "swarm", --assigns swarm ID and enables remote control, enderRefuel slot 16 and disables renderMove. | |
swarm = id, --swarm ID number | |
formation = formation --swarm formation | |
}) | |
id = id+1 | |
if w < width then | |
if formation == "wall" then | |
move(sideDir) | |
turn(frontDir) | |
elseif formation == "floor" then | |
move(sideDir) | |
turn(frontDir) | |
end | |
end | |
end | |
sideDir = cTurtle.dirStandardize("-"..sideDir) | |
if formation == "wall" then | |
move"up" | |
elseif formation == "floor" then | |
move"back" | |
end | |
end | |
end | |
tCommandQueue = {} -- table of queued commands | |
function modemControl(enable) | |
--enables a very advanced remote control of the turtle | |
if enable then | |
if modem then | |
if cTurtle.tSettings.swarm then | |
_G.modem.open(tSettings.swarmChannel) | |
else | |
_G.modem.open(tSettings.controlChannel) | |
end | |
else | |
return | |
end | |
oldPull = _G.os.pullEvent | |
_G.os.pullEvent = function(filter) -- override original pullEvent | |
while true do | |
local tEvent = {oldPull()} | |
if tEvent[1] == "modem_message" | |
and tEvent[3] == tSettings.controlChannel | |
or tEvent[3] == tSettings.swarmChannel | |
and type(tEvent[5]) == "table" | |
and (tEvent[5].id == "all" or tEvent[5].id == tSettings.swarm) then | |
tCommandQueue[#tCommandQueue+1] = tEvent | |
if not tCommandQueue[2] | |
or tEvent[5] == "cancel" then | |
while tCommandQueue[1] do | |
local oldRenderMove = tSettings.renderMove | |
tSettings.renderMove = false | |
local tArg = {} | |
if tEvent[5] == "cancel" then --remote cancel | |
os.queueEvent("cTurtle","cancel") | |
elseif type(tEvent[5]) == "table" then --table commands | |
if tEvent[5][1] == "cTurtle update" then --update cTurtle through modem message | |
table.remove(tEvent[5],1) | |
fs.open(tFile.cTurtle,"w").writeLine(table.concat(tEvent[5],"\n")) | |
os.reboot() | |
else -- function command | |
tArg[1] = tEvent[5][1] | |
for i=2,#tEvent[5] do | |
if type(tEvent[5][i]) == "table" then | |
tArg[i] = "{" | |
for k,v in pairs(tEvent[5][i]) do | |
tArg[i] = tArg[i].." "..k.." = "..v.."," | |
end | |
tArg[i] = tArg[i].."}" | |
elseif type(tEvent[5][i]) == "string" then | |
tArg[i] = '"'..tEvent[5][i]..'"' --add quotation marks to strings | |
elseif tEvent[5][i] == true then | |
tArg[i] = "true" --change boolean to string | |
elseif tEvent[5][i] == false then | |
tArg[i] = "false" --change boolean to string | |
else | |
tArg[i] = tEvent[5][i] --numbers | |
end | |
end | |
end | |
else --string function command | |
tArg[1] = tEvent[5]:match"^%S+" | |
tEvent[5] = tEvent[5]:sub(#tArg[1]+2,#tEvent[5]) | |
for word in tEvent[5]:gmatch"%S+" do --split message into words | |
if word:match"^%d+$" then --convert numbers to number format | |
word = tonumber(word) | |
elseif word:match"true" then --convert booleans to string | |
word = "true" | |
elseif word:match"false" then | |
word = "false" | |
else | |
word = '"'..word..'"' --add quotation marks to strings | |
end | |
table.insert(tArg,word) | |
end | |
end | |
if tArg[1] then --command | |
local funcString = "" | |
funcString = "cTurtle."..table.remove(tArg,1).."(" --write function | |
for i=1,#tArg do | |
local arg = tArg[i] | |
if i > 1 then | |
funcString = funcString.."," --commas if multipe args | |
end | |
funcString = funcString..arg | |
end | |
funcString = funcString..")" --end function | |
local func = loadstring("setfenv(1,_G) "..funcString) -- create function | |
local tRes = {pcall(func)} | |
local str = funcString | |
if tRes[1] then -- no errors | |
table.remove(tRes,1) --remove error index | |
for i=1,#tRes do --convert return values to string | |
if tRes[i] == true then | |
str = str.."|True " | |
elseif tRes[i] == false then | |
str = str.."|False " | |
elseif type(tRes[i]) == "table" then | |
for k,v in pairs(tRes[i]) do | |
str = str.."|"..k.."="..v.." " | |
end | |
elseif tRes[i] then | |
str = str.."|"..tRes[i].." " | |
end | |
end | |
else --function failed | |
str = str.."|"..tRes[2] | |
end | |
if tEvent[3] == tSettings.controlChannel then | |
modem.transmit(tSettings.controlResponseChannel,tSettings.controlChannel,{ | |
response = str, | |
tPos = tPos, | |
turtleId = os.getComputerID() | |
}) --send return values | |
end | |
renderMove = oldRenderMove | |
end | |
table.remove(tCommandQueue,1) | |
if tCommandQueue[1] then -- command in queue | |
tEvent = tCommandQueue[1] | |
end | |
end | |
end | |
elseif not filter or filter == tEvent[1] then | |
return unpack(tEvent) -- return event params | |
end | |
end | |
end | |
if tSettings.swarm then | |
_G.modem.transmit(tSettings.controlResponseChannel,tSettings.swarmChannel,"Swarm turtle #"..tSettings.swarm.." online.") | |
else | |
_G.modem.transmit(tSettings.controlResponseChannel,tSettings.controlChannel,"Turtle #"..os.getComputerID().." online.") | |
end | |
else | |
_G.modem.close(tSettings.swarmChannel) | |
_G.modem.close(tSettings.controlChannel) | |
_G.os.pullEvent = oldPullEvent --restore original pullEvent | |
end | |
end | |
--API init | |
if not fs.exists(tFile.directory) then | |
fs.makeDir(tFile.directory) | |
end | |
if not fs.exists(tFile.settings) then | |
saveSettings() | |
end | |
if not fs.exists"/startup" then | |
local file = fs.open("/startup","w") | |
file.writeLine([[ | |
local file = fs.open("]]..tFile.restarted..[[", "w") | |
file.writeLine"true" | |
file.close() | |
_G.restarted = true | |
]]) | |
file.close() | |
_G.restarted = true | |
else | |
local file = fs.open("/startup","r") | |
local sFile = file.readAll() | |
file.close() | |
if not sFile:match([[fs%.open%("]]..tFile.restarted..[[", "w"%)]]) then | |
local file = fs.open("/startup","a") | |
file.writeLine([[ | |
local file = fs.open("]]..tFile.restarted..[[", "w") | |
file.writeLine"true" | |
file.close() | |
_G.restarted = true | |
]]) | |
file.close() | |
_G.restarted = true | |
end | |
local file = fs.open(tFile.restarted, "w") | |
file.writeLine"true" | |
file.close() | |
end | |
if not _G.restarted then | |
local file = fs.open(tFile.restarted,"r") | |
_G.restarted = file.readLine() == "true" | |
file.close() | |
end | |
if not _G.modem then --auto wrap turtle modem, for ease of use | |
for k,side in pairs(peripheral.getNames()) do | |
if peripheral.getType(side) == "modem" then | |
_G.modem = peripheral.wrap(side) | |
_G.modem.open(gps.CHANNEL_GPS) --open GPS channel | |
break | |
end | |
end | |
end | |
locate() | |
local tArg = {...} | |
if tArg[1] then | |
--shell usage | |
for i=2,#tArg do | |
if tArg[i]:match"^%d+$" then | |
tArg[i] = tonumber(tArg[i]) | |
else | |
tArg[i] = '"'..tArg[i]..'"' | |
end | |
end | |
local func = loadstring(table.remove(tArg,1).."("..table.concat(tArg,",")..")") | |
setfenv(func,getfenv()) | |
term.setBackgroundColor(colors.black) | |
term.clear() | |
func() | |
term.clear() | |
term.setCursorPos(1,1) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment