Advertisement
asweigart

hare

Mar 11th, 2016
283
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 54.58 KB | None | 0 0
  1. local DEBUG = false
  2. print('hare ver 4')
  3. -- A utility module for ComputerCraft's turtles that adds several functions
  4.  
  5. -- TODO switch from return values to exceptions for errors
  6. -- TODO call hasFuelFor for various events
  7.  
  8. -- TODO come up with a better name than "direction"
  9.  
  10.  
  11. --[[
  12. Directions:
  13.  
  14. -z UP +y
  15. N
  16. -x W E +x
  17. S
  18. +z DOWN -y
  19.  
  20. ]]
  21.  
  22.  
  23. function split(str)
  24. -- splits a string up into an array of strings
  25. str = string.lower(str)
  26. local actions = {}
  27. local word
  28. for word in str:gmatch("%w+") do table.insert(actions, word) end
  29. return actions
  30. end
  31.  
  32. local VALID_ACTIONS = {'f', 'forward', 'b', 'back', 'l', 'left', 'r', 'right', 'up', 'dn', 'down', 'fn', 'fs', 'fe', 'fw', 'facenorth', 'facesouth', 'faceeast', 'facewest', 'no', 'north', 'so', 'south', 'ea', 'east', 'we', 'west', 'd', 'dig', 'du', 'digup', 'dd', 'digdown', 'i', 'inspect', 'iu', 'inspectup', 'id', 'inspectdown', 'sel', 'select', 's', 'suck', 'su', 'suckup', 'sd', 'suckdown', 'p', 'place', 'pu', 'placeup', 'pd', 'placedown', 'dr', 'drop', 'dru', 'dropup', 'drd', 'dropdown', 'c', 'craft'}
  33.  
  34. local direction = 'north'
  35. --local x, y, z = gps.locate()
  36. local x = 0
  37. local y = 0
  38. local z = 0
  39. local positionStack = {}
  40.  
  41. local recordingNow = false
  42. local recordedMoves = {}
  43.  
  44. local recipes = {}
  45.  
  46. local MAX_STACK_SIZE = 64
  47.  
  48. -- TODO add fuel checks!!!! Also, if not enough fuel for, say, forward(9999), should we stop at first?
  49.  
  50.  
  51. function forward(steps)
  52. -- TODO record new position to a file for reloading
  53. local success, errMsg, i
  54. if steps == nil then steps = 1 end
  55.  
  56. if steps < 0 then
  57. return back(math.abs(steps)) -- edge case for negative steps
  58. end
  59.  
  60. for i=1,steps do
  61. success, errMsg = turtle.forward()
  62. if not success then return false, errMsg end
  63.  
  64. if recordingNow then recordMove('f') end
  65. -- track location
  66. if direction == 'north' then
  67. z = z - 1
  68. elseif direction == 'south' then
  69. z = z + 1
  70. elseif direction == 'west' then
  71. x = x - 1
  72. elseif direction == 'east' then
  73. x = x + 1
  74. end
  75. end
  76. if DEBUG then print('Moved forward ' .. steps .. ' steps') end
  77.  
  78. gpsx, gpsy, gpsz = gps.locate()
  79. if DEBUG and gpsx ~= nil and (gpsx ~= x or gpsy ~= y or gpsz ~= z) then
  80. print('Pos out of sync with GPS!')
  81. print('!!! Pos: ' .. x .. ' ' .. y .. ' ' .. z)
  82. print('!!! GPS: ' .. gpsx .. ' ' .. gpsy .. ' ' .. gpsz)
  83. end
  84. return true
  85. end
  86.  
  87.  
  88. function back(steps)
  89. -- TODO record new position to a file for reloading
  90. local success, errMsg, i
  91. if steps == nil then steps = 1 end
  92.  
  93. if steps < 0 then
  94. return forward(math.abs(steps)) -- edge case for negative steps
  95. end
  96.  
  97. for i=1,steps do
  98. success, errMsg = turtle.back()
  99. if not success then return false, errMsg end
  100.  
  101. if recordingNow then recordMove('b') end
  102.  
  103. -- track location
  104. if direction == 'north' then
  105. z = z + 1
  106. elseif direction == 'south' then
  107. z = z - 1
  108. elseif direction == 'west' then
  109. x = x + 1
  110. elseif direction == 'east' then
  111. x = x - 1
  112. end
  113. end
  114. if DEBUG then print('Moved back ' .. steps .. ' steps') end
  115. gpsx, gpsy, gpsz = gps.locate()
  116. if DEBUG and gpsx ~= nil and (gpsx ~= x or gpsy ~= y or gpsz ~= z) then
  117. print('Pos out of sync with GPS!')
  118. print('!!! Pos: ' .. x .. ' ' .. y .. ' ' .. z)
  119. print('!!! GPS: ' .. gpsx .. ' ' .. gpsy .. ' ' .. gpsz)
  120. end
  121. return true
  122. end
  123.  
  124.  
  125. function turnLeft(turns)
  126. local success, i
  127. if turns == nil then turns = 1 end
  128.  
  129. for i=1,turns do
  130. success = turtle.turnLeft()
  131. if not success then return false end
  132.  
  133. if recordingNow then recordMove('l') end
  134.  
  135. -- track location
  136. if direction == 'north' then
  137. direction = 'west'
  138. elseif direction == 'south' then
  139. direction = 'east'
  140. elseif direction == 'west' then
  141. direction = 'south'
  142. elseif direction == 'east' then
  143. direction = 'north'
  144. end
  145. end
  146. if DEBUG then print('Turned left ' .. turns .. ' turns') end
  147. return true
  148. end
  149.  
  150.  
  151. function turnRight(turns)
  152. local success, i
  153. if turns == nil then turns = 1 end
  154.  
  155. for i=1,turns do
  156. success = turtle.turnRight()
  157. if not success then return false end
  158.  
  159. if recordingNow then recordMove('r') end
  160.  
  161. -- track location
  162. if direction == 'north' then
  163. direction = 'east'
  164. elseif direction == 'east' then
  165. direction = 'south'
  166. elseif direction == 'south' then
  167. direction = 'west'
  168. elseif direction == 'west' then
  169. direction = 'north'
  170. end
  171. end
  172. if DEBUG then print('Turned right ' .. turns .. ' turns') end
  173. return true
  174. end
  175.  
  176.  
  177. function up(steps)
  178. local success, errMsg, i
  179. if steps == nil then steps = 1 end
  180.  
  181. for i=1,steps do
  182. success, errMsg = turtle.up()
  183. if not success then return false, errMsg end
  184. if recordingNow then recordMove('up') end
  185. y = y + 1 -- track position
  186. end
  187. if DEBUG then print('Moved up ' .. steps .. ' steps') end
  188. gpsx, gpsy, gpsz = gps.locate()
  189. if DEBUG and gpsx ~= nil and (gpsx ~= x or gpsy ~= y or gpsz ~= z) then
  190. print('Pos out of sync with GPS!')
  191. print('!!! Pos: ' .. x .. ' ' .. y .. ' ' .. z)
  192. print('!!! GPS: ' .. gpsx .. ' ' .. gpsy .. ' ' .. gpsz)
  193. end
  194. return true
  195. end
  196.  
  197.  
  198. function down(steps)
  199. local success, errMsg, i
  200. if steps == nil then steps = 1 end
  201.  
  202. for i=1,steps do
  203. success, errMsg = turtle.down()
  204. if not success then return false, errMsg end
  205. if recordingNow then recordMove('dn') end
  206. y = y - 1 -- track position
  207. end
  208. if DEBUG then print('Moved down ' .. steps .. ' steps') end
  209. gpsx, gpsy, gpsz = gps.locate()
  210. if DEBUG and gpsx ~= nil and (gpsx ~= x or gpsy ~= y or gpsz ~= z) then
  211. print('Pos out of sync with GPS!')
  212. print('!!! Pos: ' .. x .. ' ' .. y .. ' ' .. z)
  213. print('!!! GPS: ' .. gpsx .. ' ' .. gpsy .. ' ' .. gpsz)
  214. end
  215. return true
  216. end
  217.  
  218.  
  219. function face(d)
  220. d = string.lower(d)
  221. if d == 'north' or d == 'no' then
  222. return faceNorth()
  223. elseif d == 'south' or d == 'so' then
  224. return faceSouth()
  225. elseif d == 'west' or d == 'we' then
  226. return faceWest()
  227. elseif d == 'east' or d == 'ea' then
  228. return faceEast()
  229. end
  230.  
  231. return false, '"' .. d .. '" is not a valid direction'
  232. end
  233.  
  234.  
  235. function faceNorth()
  236. local success
  237. if direction == 'south' then
  238. success = turnRight(2)
  239. if not success then return false end
  240. elseif direction == 'east' then
  241. success = turnLeft()
  242. if not success then return false end
  243. elseif direction == 'west' then
  244. success = turnRight()
  245. if not success then return false end
  246. end
  247. return true
  248. end
  249.  
  250.  
  251. function faceSouth()
  252. local success
  253. if direction == 'north' then
  254. success = turnRight(2)
  255. if not success then return false end
  256. elseif direction == 'west' then
  257. success = turnLeft()
  258. if not success then return false end
  259. elseif direction == 'east' then
  260. success = turnRight()
  261. if not success then return false end
  262. end
  263. return true
  264. end
  265.  
  266.  
  267. function faceEast()
  268. local success
  269. if direction == 'west' then
  270. success = turnRight(2)
  271. if not success then return false end
  272. elseif direction == 'south' then
  273. success = turnLeft()
  274. if not success then return false end
  275. elseif direction == 'north' then
  276. success = turnRight()
  277. if not success then return false end
  278. end
  279. return true
  280. end
  281.  
  282.  
  283. function faceWest()
  284. local success
  285. if direction == 'east' then
  286. success = turnRight(2)
  287. if not success then return false end
  288. elseif direction == 'north' then
  289. success = turnLeft()
  290. if not success then return false end
  291. elseif direction == 'south' then
  292. success = turnRight()
  293. if not success then return false end
  294. end
  295. return true
  296. end
  297.  
  298.  
  299. function north(steps)
  300. if not faceNorth() then return false end
  301. if steps > 0 then
  302. return forward(steps)
  303. else
  304. return back(-steps)
  305. end
  306. end
  307.  
  308.  
  309. function south(steps)
  310. if not faceSouth() then return false end
  311. if steps > 0 then
  312. return forward(steps)
  313. else
  314. return back(-steps)
  315. end
  316. end
  317.  
  318.  
  319. function west(steps)
  320. if not faceWest() then return false end
  321. if steps > 0 then
  322. return forward(steps)
  323. else
  324. return back(-steps)
  325. end
  326. end
  327.  
  328.  
  329. function east(steps)
  330. if not faceEast() then return false end
  331. if steps > 0 then
  332. return forward(steps)
  333. else
  334. return back(-steps)
  335. end
  336. end
  337.  
  338.  
  339. function pushPositionSetting()
  340. table.insert(positionStack, {x=x, y=y, z=z}) -- TODO test
  341. end
  342.  
  343.  
  344. function popPositionSetting()
  345. -- TODO test
  346. local pos = table.remove(positionStack)
  347. if pos == nil then return nil end
  348. x = pos['x']
  349. y = pos['y']
  350. z = pos['z']
  351. return pos
  352. end
  353.  
  354.  
  355. function line(x0, y0, z0, x1, y1, z1)
  356. -- returns an array of tables with x & y coordinates of the line between the two given points
  357. local points = {}
  358. local dx, dy, dz, xptr, yptr, zptr, sx, sy, sz, errx, erry, errz
  359.  
  360. dx = math.abs(x1 - x0)
  361. dy = math.abs(y1 - y0)
  362. dz = math.abs(z1 - z0)
  363.  
  364. xptr, yptr, zptr = x0, y0, z0
  365.  
  366. if x0 > x1 then
  367. sx = -1
  368. else
  369. sx = 1
  370. end
  371.  
  372. if y0 > y1 then
  373. sy = -1
  374. else
  375. sy = 1
  376. end
  377.  
  378. if z0 > z1 then
  379. sz = -1
  380. else
  381. sz = 1
  382. end
  383.  
  384. if dx >= dy and dx >= dz then
  385. erry = dx / 2
  386. errz = dx / 2
  387. while xptr ~= x1 do
  388. table.insert(points, {x=xptr, y=yptr, z=zptr})
  389. erry = erry - dy
  390. if erry < 0 then
  391. yptr = yptr + sy
  392. erry = erry + dx
  393. end
  394. errz = errz - dz
  395. if errz < 0 then
  396. zptr = zptr + sz
  397. errz = errz + dx
  398. end
  399. xptr = xptr + sx
  400. end
  401. elseif dy >= dx and dy >= dz then
  402. errx = dy / 2
  403. errz = dy / 2
  404. while yptr ~= y1 do
  405. table.insert(points, {x=xptr, y=yptr, z=zptr})
  406. errx = errx - dx
  407. if errx < 0 then
  408. xptr = xptr + sx
  409. errx = errx + dy
  410. end
  411. errz = errz - dz
  412. if errz < 0 then
  413. zptr = zptr + sz
  414. errz = errz + dy
  415. end
  416. yptr = yptr + sy
  417. end
  418. else
  419. errx = dz / 2
  420. erry = dz / 2
  421. while zptr ~= z1 do
  422. table.insert(points, {x=xptr, y=yptr, z=zptr})
  423. errx = errx - dx
  424. if errx < 0 then
  425. xptr = xptr + sx
  426. errx = errx + dz
  427. end
  428. erry = erry - dy
  429. if erry < 0 then
  430. yptr = yptr + sy
  431. erry = erry + dz
  432. end
  433. zptr = zptr + sz
  434. end
  435. end
  436.  
  437. table.insert(points, {x=xptr, y=yptr, z=zptr})
  438.  
  439. -- TODO seems to be a bug where if you move by 1 on x&y or by 1 on y&z then the last point isn't on the final destination. This is a cheap fix.
  440. if xptr ~= x1 or yptr ~= y1 or zptr ~= z1 then
  441. table.insert(points, {x=x1, y=y1, z=z1})
  442. end
  443.  
  444. return points
  445. end
  446.  
  447.  
  448. function goto(destx, desty, destz, facing)
  449. -- returns false if at some point the turtle can't move along the path
  450. local startx, starty, startz = getx(), gety(), getz()
  451. local originalDirection = direction
  452.  
  453. if type(destx) == 'table' then
  454. desty = destx[2]
  455. destz = destx[3]
  456. facing = destx[4]
  457. destx = destx[1]
  458. end
  459.  
  460. if facing == nil then facing = getDirection() end
  461.  
  462. if destx == nil then destx = getx() end
  463. if desty == nil then desty = gety() end
  464. if destz == nil then destz = getz() end
  465.  
  466. if startx == destx and starty == desty and startz == destz then
  467. if facing ~= false then face(facing) end
  468. return true -- edge case; we are already at the destination
  469. end
  470.  
  471. --[[
  472. -- THE LINE FUNCTION IS FLAWED AND OFTEN FAILS. I THINK IT MAY HAVE BEEN THE CHANGES FOR 3D
  473. linePoints = line(startx, starty, startz, destx, desty, destz)
  474. if DEBUG then
  475. print('linePoints=')
  476. for i=1,#linePoints do
  477. print(' ' .. linePoints[i]['x'] .. ' ' .. linePoints[i]['y'] .. ' ' .. linePoints[i]['z'])
  478. end
  479. end
  480.  
  481. -- go through all the points and make them relative to the one before it
  482. for i=#linePoints,2,-1 do
  483. linePoints[i]['x'] = linePoints[i]['x'] - linePoints[i-1]['x']
  484. linePoints[i]['y'] = linePoints[i]['y'] - linePoints[i-1]['y']
  485. linePoints[i]['z'] = linePoints[i]['z'] - linePoints[i-1]['z']
  486. end
  487. table.remove(linePoints, 1) -- get rid of the first point; we won't need it
  488. ]]
  489.  
  490. -- came up with this random alg to mostly move to the destination in a straight line
  491. linePoints = {}
  492. tempx, tempy, tempz = math.abs(destx-startx), math.abs(desty-starty), math.abs(destz-startz)
  493. while tempx ~= 0 or tempy ~= 0 or tempz ~= 0 do
  494. r = math.random(1, tempx+tempy+tempz)
  495. if r <= tempx then
  496. if destx > startx then
  497. table.insert(linePoints, {x=1, y=0, z=0})
  498. elseif destx < startx then
  499. table.insert(linePoints, {x=-1, y=0, z=0})
  500. else
  501. assert(false, 'tempx should have always been 0, and reaching this should be impossible')
  502. end
  503. tempx = tempx - 1
  504. elseif r <= tempx+tempy then
  505. if desty > starty then
  506. table.insert(linePoints, {x=0, y=1, z=0})
  507. elseif desty < starty then
  508. table.insert(linePoints, {x=0, y=-1, z=0})
  509. else
  510. assert(false, 'tempy should have always been 0, and reaching this should be impossible')
  511. end
  512. tempy = tempy - 1
  513. else
  514. if destz > startz then
  515. table.insert(linePoints, {x=0, y=0, z=1})
  516. elseif destz < startz then
  517. table.insert(linePoints, {x=0, y=0, z=-1})
  518. else
  519. assert(false, 'tempz should have always been 0, and reaching this should be impossible')
  520. end
  521. tempz = tempz - 1
  522. end
  523. end
  524.  
  525.  
  526. for i=1,#linePoints do
  527. if DEBUG then print('next step: ' .. linePoints[i]['x'] .. ' ' .. linePoints[i]['y'] .. ' ' .. linePoints[i]['z']) end
  528. if linePoints[i]['x'] == 1 then
  529. faceEast()
  530. if DEBUG then print('goto: moving east') end
  531. success, errMsg = forward()
  532. elseif linePoints[i]['x'] == -1 then
  533. faceWest()
  534. if DEBUG then print('goto: moving west') end
  535. success, errMsg = forward()
  536. end
  537.  
  538. if linePoints[i]['y'] == 1 then
  539. if DEBUG then print('goto: moving up') end
  540. success, errMsg = up()
  541. elseif linePoints[i]['y'] == -1 then
  542. if DEBUG then print('goto: moving down') end
  543. success, errMsg = down()
  544. end
  545.  
  546. if linePoints[i]['z'] == 1 then
  547. faceSouth()
  548. if DEBUG then print('goto: moving south') end
  549. success, errMsg = forward()
  550. elseif linePoints[i]['z'] == -1 then
  551. faceNorth()
  552. if DEBUG then print('goto: moving north') end
  553. success, errMsg = forward()
  554. end
  555.  
  556. if not success then return false, errMsg end
  557. end
  558.  
  559. if facing ~= false then face(facing) end
  560. return true
  561. end
  562.  
  563.  
  564. function doActions(actionsStr, safeMode)
  565. --[[
  566. Complete list of commands:
  567. f - move forward
  568. b - move backward
  569. TODO
  570.  
  571. ]]
  572. actionsStr = string.lower(actionsStr)
  573. local actions = split(actionsStr)
  574.  
  575. if safeMode == nil then safeMode = false end
  576.  
  577. local i, j, k, v, success, errMsg, reps
  578.  
  579. -- check that there are no invalid commands
  580. success, errMsg = isValidActionString(actionsStr)
  581. if not success then return false, errMsg end
  582.  
  583. for i = 1,#actions do
  584. cmd = actions[i] -- get the command
  585. if #actions < i + 1 then
  586. reps = 1 -- end of actions, so set this to 1
  587. else
  588. if tonumber(actions[i+1]) ~= nil then -- check if next arg is numeric
  589. reps = tonumber(actions[i+1]) -- set
  590. else
  591. -- "reps" is actually the next command, so set it to 1
  592. reps = 1
  593. end
  594. end
  595. if actions[i] == 'f' or actions[i] == 'forward' then
  596. success, errMsg = forward(reps)
  597. if safeMode and not success then return success, errMsg end
  598. elseif actions[i] == 'b' or actions[i] == 'back' then
  599. success, errMsg = back(reps)
  600. if safeMode and not success then return success, errMsg end
  601. elseif actions[i] == 'l' or actions[i] == 'left' then
  602. turnLeft(reps)
  603. elseif actions[i] == 'r' or actions[i] == 'right' then
  604. print('type(turnRight) = ' .. type(turnRight))
  605. print('type(reps) = ' .. type(reps))
  606. turnRight(reps)
  607. elseif actions[i] == 'up' then
  608. success, errMsg = up(reps)
  609. if safeMode and not success then return success, errMsg end
  610. elseif actions[i] == 'dn' or actions[i] == 'down' then
  611. success, errMsg = down(reps)
  612. if safeMode and not success then return success, errMsg end
  613. elseif actions[i] == 'fn' or actions[i] == 'facenorth' then
  614. success = faceNorth()
  615. if safeMode and not success then return success end -- TODO figure out if there are error messages for turning, I'm assuming there are not.
  616. elseif actions[i] == 'fs' or actions[i] == 'facesouth' then
  617. success = faceSouth()
  618. if safeMode and not success then return success end -- TODO figure out if there are error messages for turning
  619. elseif actions[i] == 'fe' or actions[i] == 'faceeast' then
  620. success = faceEast()
  621. if safeMode and not success then return success end -- TODO figure out if there are error messages for turning
  622. elseif actions[i] == 'fw' or actions[i] == 'facewest' then
  623. success = faceWest()
  624. if safeMode and not success then return success end -- TODO figure out if there are error messages for turning
  625. elseif actions[i] == 'no' or actions[i] == 'north' then
  626. success = faceNorth()
  627. if safeMode and not success then return success end
  628. success, errMsg = forward()
  629. if safeMode and not success then return success, errMsg end
  630. elseif actions[i] == 'so' or actions[i] == 'south' then
  631. success = faceSouth()
  632. if safeMode and not success then return success end
  633. success, errMsg = forward()
  634. if safeMode and not success then return success, errMsg end
  635. elseif actions[i] == 'we' or actions[i] == 'west' then
  636. success = faceWest()
  637. if safeMode and not success then return success end
  638. success, errMsg = forward()
  639. if safeMode and not success then return success, errMsg end
  640. elseif actions[i] == 'ea' or actions[i] == 'east' then
  641. success = faceEast()
  642. if safeMode and not success then return success end
  643. success, errMsg = forward()
  644. if safeMode and not success then return success, errMsg end
  645. elseif actions[i] == 'd' or actions[i] == 'dig' then
  646. for j = 1,reps do
  647. success, errMsg = turtle.dig()
  648. --print('dig: ' .. tostring(success) .. ' ' .. errMsg)
  649. if safeMode and not success then return success, errMsg end
  650. end
  651. elseif actions[i] == 'du' or actions[i] == 'digup' then
  652. for j = 1,reps do
  653. success, errMsg = turtle.digUp()
  654. --print('digUp: ' .. tostring(success) .. ' ' .. errMsg)
  655. if safeMode and not success then return success, errMsg end
  656. end
  657. elseif actions[i] == 'dd' or actions[i] == 'digdown' then
  658. for j = 1,reps do
  659. success, errMsg = turtle.digDown()
  660. --print('digDown: ' .. tostring(success) .. ' ' .. errMsg)
  661. if safeMode and not success then return success, errMsg end
  662. end
  663. elseif actions[i] == 'i' or actions[i] == 'inspect' then
  664. for j = 1,reps do
  665. success, inspectResults = turtle.inspect()
  666. if safeMode and not success then return success, errMsg end
  667. end
  668. elseif actions[i] == 'iu' or actions[i] == 'inspectup' then
  669. for j = 1,reps do
  670. success, inspectResults = turtle.inspectUp()
  671. if safeMode and not success then return success, errMsg end
  672. end
  673. elseif actions[i] == 'id' or actions[i] == 'inspectdown' then
  674. for j = 1,reps do
  675. success, inspectResults = turtle.inspectDown()
  676. if safeMode and not success then return success, errMsg end
  677. end
  678. elseif actions[i] == 'sel' or actions[i] == 'select' then
  679. -- in this case, reps is the inventory number
  680. success, errMsg = turtle.select(reps)
  681. if safeMode and not success then return success, errMsg end
  682. elseif actions[i] == 's' or actions[i] == 'suck' then
  683. success, errMsg = turtle.suck(reps)
  684. if safeMode and not success then return success, errMsg end
  685. elseif actions[i] == 'su' or actions[i] == 'suckup' then
  686. success, errMsg = turtle.suckUp(reps)
  687. if safeMode and not success then return success, errMsg end
  688. elseif actions[i] == 'sd' or actions[i] == 'suckdown' then
  689. success, errMsg = turtle.suckDown(reps)
  690. if safeMode and not success then return success, errMsg end
  691. elseif actions[i] == 'p' or actions[i] == 'place' then
  692. for j = 1,reps do
  693. success, errMsg = turtle.place()
  694. if safeMode and not success then return success, errMsg end
  695. end
  696. elseif actions[i] == 'pu' or actions[i] == 'placeup' then
  697. for j = 1,reps do
  698. success, errMsg = turtle.placeUp()
  699. if safeMode and not success then return success, errMsg end
  700. end
  701. elseif actions[i] == 'pd' or actions[i] == 'placedown' then
  702. for j = 1,reps do
  703. success, errMsg = turtle.placeDown()
  704. if safeMode and not success then return success, errMsg end
  705. end
  706. elseif actions[i] == 'dr' or actions[i] == 'drop' then
  707. success, errMsg = turtle.drop(reps)
  708. if safeMode and not success then return success, errMsg end
  709. elseif actions[i] == 'dru' or actions[i] == 'dropup' then
  710. success, errMsg = turtle.dropUp(reps)
  711. if safeMode and not success then return success, errMsg end
  712. elseif actions[i] == 'drd' or actions[i] == 'dropdown' then
  713. success, errMsg = turtle.dropDown(reps)
  714. if safeMode and not success then return success, errMsg end
  715. elseif actions[i] == 'c' or actions[i] == 'craft' then
  716. success, errMsg = turtle.craft(reps)
  717. if safeMode and not success then return success, errMsg end
  718. end
  719. end
  720. return true
  721. end
  722.  
  723.  
  724. function doReverseMovement(actionsStr, safeMode)
  725. -- TODO check for fn and other commands that can't be reversed
  726. actionsStr = string.lower(actionsStr)
  727. if string.find(actionsStr, 'fn') or string.find(actionsStr, 'fs') or string.find(actionsStr, 'fw') or string.find(actionsStr, 'fe') or
  728. string.find(actionsStr, 'north') or string.find(actionsStr, 'south') or string.find(actionsStr, 'west') or string.find(actionsStr, 'east') then
  729. return false, 'Cannot reverse actions that include facing compass directions.'
  730. end
  731.  
  732. local actions = split(actionsStr)
  733.  
  734. if safeMode == nil then safeMode = false end
  735. local i, j, k, v
  736.  
  737. for i = 1,#actions do
  738. cmd = actions[i] -- get the command
  739. if #actions < i + 1 then
  740. reps = 1 -- end of actions, so set this to 1
  741. else
  742. if tonumber(actions[i+1]) ~= nil then -- check if next arg is numeric
  743. reps = tonumber(actions[i+1]) -- set
  744. else
  745. -- "reps" is actually the next command, so set it to 1
  746. reps = 1
  747. end
  748. end
  749. if actions[i] == 'f' or actions[i] == 'forward' then
  750. success, errMsg = back(reps)
  751. if safeMode and not success then return success, errMsg end
  752. elseif actions[i] == 'b' or actions[i] == 'back' then
  753. success, errMsg = forward(reps)
  754. if safeMode and not success then return success, errMsg end
  755. elseif actions[i] == 'l' or actions[i] == 'left' then
  756. turnRight(reps)
  757. elseif actions[i] == 'r' or actions[i] == 'right' then
  758. turnLeft(reps)
  759. elseif actions[i] == 'up' then
  760. success, errMsg = down(reps)
  761. if safeMode and not success then return success, errMsg end
  762. elseif actions[i] == 'dn' or actions[i] == 'down' then
  763. success, errMsg = up(reps)
  764. if safeMode and not success then return success, errMsg end
  765. end
  766. end
  767. end
  768.  
  769.  
  770. function doOneAction(actionsStr)
  771. -- TODO figure this out, does this return (success, errMsg) or (actionsStr)? Should we be using return values for errors?
  772. local action, reps
  773. local actions = split(actionsStr)
  774.  
  775. -- retrieve the first action and its reps (if given)
  776. action = actions[1]
  777. reps = tonumber(actions[2]) -- if not a number then this will be nil
  778. if reps == nil then
  779. reps = 1
  780. else
  781. table.remove(actions, 2)
  782. end
  783. table.remove(actions, 1)
  784.  
  785. if DEBUG then print('doing one action: ' .. action .. ' ' .. tostring(reps)) end
  786. success, errMsg = doActions(action .. ' ' .. tostring(reps)) -- do the first action
  787. if success == false then return success, errMsg end
  788.  
  789. if DEBUG then print('doOneAction returns: ' .. table.concat(actions, ' ')) end
  790. return table.concat(actions, ' ')
  791. end
  792.  
  793.  
  794. function hasFuelFor(actionsStr)
  795. -- TODO returns true if it can do all these steps with the fuel it has
  796. local actions = split(actionsStr)
  797. local totalFuelConsumption = 0
  798.  
  799. for i = 1,#actions do
  800. if actions[i] == 'f' or actions[i] == 'forward' or actions[i] == 'b' or actions[i] == 'back' or actions[i] == 'up' or actions[i] == 'dn' or actions[i] == 'down' or
  801. actions[i] == 'no' or actions[i] == 'north' or actions[i] == 'so' or actions[i] == 'south' or actions[i] == 'we' or actions[i] == 'west' or actions[i] == 'ea' or actions[i] == 'east' then
  802. reps = tonumber(actions[i+1])
  803. if reps == nil then reps = 1 end
  804. totalFuelConsumption = totalFuelConsumption + reps
  805. end
  806. end
  807.  
  808. return totalFuelConsumption <= turtle.getFuelLevel()
  809. end
  810.  
  811.  
  812. function doActionsSafely(actions)
  813. -- TODO (essentially the do program but in a function)
  814. -- will immediately stop the first time a call fails or if there is not
  815. -- enough fuel to do all the steps.
  816. return doActions(actions, true)
  817. end
  818.  
  819.  
  820. function getAreaCoverActions(forward, right, goHome)
  821. -- returns a string that can be passed to doActions() and doOneAction()
  822. -- these actions are for moving the turtle to cover an area
  823. local r, actions, r_str, l_str, turnRight
  824. if forward <= 0 or right == 0 then return '' end -- edge case: no movement
  825.  
  826. if goHome == nil then goHome = false end
  827.  
  828. -- if 'right' was negative, then flip the r_str and l_str actions
  829. if right < 0 then
  830. right = math.abs(right)
  831. r_str = 'l '
  832. l_str = 'r '
  833. else
  834. r_str = 'r '
  835. l_str = 'l '
  836. end
  837.  
  838. actions = ''
  839. turnRight = true
  840. for r=1,(right-1) do
  841. actions = actions .. string.rep('f ', forward-1) -- go to end of column
  842. if turnRight then
  843. actions = actions .. r_str .. 'f ' .. r_str
  844. else
  845. actions = actions .. l_str .. 'f ' .. l_str
  846. end
  847. turnRight = not turnRight
  848. end
  849.  
  850. -- last column movements
  851. actions = actions .. string.rep('f ', forward-1)
  852.  
  853. -- add "go home" actions if asked for
  854. if goHome then
  855. if right % 2 == 1 then
  856. -- turtle ends up in the far right corner
  857. actions = actions .. r_str .. r_str .. string.rep('f ', forward-1) .. l_str .. string.rep('f ', right-1) .. r_str
  858. else
  859. -- turtle ends up in the near right corner
  860. actions = actions .. r_str .. string.rep('f ', right-1) .. r_str
  861. end
  862. end
  863. return actions
  864. end
  865.  
  866.  
  867. function inventoryMatches(recipe)
  868. -- special case: check if everything is already in the correct spot
  869. local matches = true
  870. for i=1,16 do
  871. itemData = turtle.getItemDetail(i)
  872. if (itemData == nil and not (recipe[i] == nil or recipe[i] == '')) or (itemData ~= nil and string.find(string.lower(itemData['name']), recipe[i])) then
  873. matches = false
  874. break
  875. end
  876. end
  877. return matches
  878. end
  879.  
  880.  
  881. function arrange(recipe, discardDirection)
  882. -- TODO (arranges the turtle's inventory, mostly for making. Can split/combine stacks if needed)
  883. -- will try to get it as close as possible. '' for blank, nil for "don't care"
  884. -- turtle will drop unwanted items in direction of dropDir
  885.  
  886. -- NOTE: Due to the nature of having to swap, we can't arrange items in the case where all the recipe's items have filled all 16 spaces of the inventory but are in the wrong order.
  887.  
  888. local origx = getx() -- save original position and direction so it can be reset to this at the end
  889. local origy = gety()
  890. local origz = getz()
  891. local origFace = getDirection()
  892.  
  893. local i, j
  894.  
  895. -- set item names to lowercase for easier matching
  896. for i=1,16 do
  897. recipe[i] = string.lower(recipe[i])
  898. end
  899.  
  900. -- special case: check if everything is already in the correct spot
  901. if inventoryMatches(recipe) then return true end
  902.  
  903. -- there must be at least one empty slot (after getting rid of all non-recipe items)
  904. emptySlot = selectEmptySlot()
  905. if emptySlot == false then return false, 'No empty slots' end
  906.  
  907. -- loop through inventory and determine if there are items that need to be dropped because they aren't a part of the recipe
  908. local discardItemsInSlots = {}
  909. for i=1,16 do
  910. -- loop through all the inventory slots, note any slots with non-recipe items
  911. local notInRecipe = true
  912. if DEBUG then print('#recipe=' .. #recipe) end
  913. for j=1,#recipe do
  914. itemData = turtle.getItemDetail(j)
  915. if (itemData ~= nil and string.find(string.lower(itemData['name']), recipe[i])) or (itemData ~= nil and (recipe[i] == '' or recipe[i] == nil)) then
  916. notInRecipe = false
  917. break
  918. end
  919. end
  920. if notInRecipe then table.insert(discardItemsInSlots, i) end
  921. end
  922.  
  923. if #discardItemsInSlots > 0 then
  924. -- travel to discard area and drop off the to-be-discard items, then return
  925. dropAt(discardDirection, discardItemsInSlots)
  926. end
  927.  
  928. -- TODO any remaining items that are in the inventory should be swapped to their proper place
  929. for i=1,15 do
  930. itemData = turtle.getItemDetail(i)
  931. if recipe[i] ~= nil and recipe[i] ~= '' and itemData ~= nil and not string.find(string.lower(itemData['name']), recipe[i]) then
  932. -- the item in slot i is not the item the recipe dictates. Figure out if the recipe's item is in another slot
  933. for j=i,16 do
  934. itemData2 = turtle.getItemDetail(j)
  935. if string.find(string.lower(itemData2['name'], recipe[i])) then
  936. -- we've found the item for slot i; it is in slot j. We should move it to slot i
  937. turtle.select(i) -- move items in slot i to a free spot
  938. if DEBUG then
  939. print('Moving ' .. turtle.getItemDetail(i)['name'] .. ' from slot ' .. tostring(i) .. ' to empty slot ')
  940. end
  941. turtle.transferTo(selectEmptySlot(), MAX_STACK_SIZE)
  942. turtle.select(j) -- move items in slot i to slot j where they belong
  943. if DEBUG then
  944. print('Moving ' .. turtle.getItemDetail(j)['name'] .. ' from slot ' .. tostring(j) .. ' to slot ' .. tostring(i))
  945. end
  946. turtle.transferTo(i, MAX_STACK_SIZE)
  947. break
  948. end
  949. end
  950. end
  951. end
  952.  
  953. -- special case: check if everything is already in the correct spot
  954. if inventoryMatches(recipe) then
  955. goto(origx, origy, origz)
  956. face(origFace)
  957. return true
  958. else
  959. return false, 'Not all recipe items in inventory'
  960. end
  961. end
  962.  
  963.  
  964. function isReversibleActionString(actionsStr)
  965. -- the movements in a string of commands is not reversible if it contains compass direction commands
  966. local actions = split(actionsStr)
  967. for i=1,#actions do
  968. if actions[i] == 'fn' or actions[i] == 'fs' or actions[i] == 'fe' or actions[i] == 'fw' or
  969. actions[i] == 'facenorth' or actions[i] == 'facesouth' or actions[i] == 'faceeast' or actions[i] == 'facewest' or
  970. actions[i] == 'no' or actions[i] == 'so' or actions[i] == 'ea' or actions[i] == 'we' or
  971. actions[i] == 'north' or actions[i] == 'south' or actions[i] == 'east' or actions[i] == 'west' then
  972. return false, 'Action "' .. actions[i] .. '" is not a reversible movement'
  973. end
  974. end
  975. return true
  976. end
  977.  
  978.  
  979. function isValidActionString(actionStr)
  980. local actions = split(actionStr)
  981. local expectCmd = true -- commands are expected after other commands or rep numbers or at the start
  982. for i=1,#actions do
  983. if expectCmd and not table.contains(VALID_ACTIONS, actions[i]) then
  984. return false, 'Expected a command but found "' .. tostring(actions[i]) .. '"'
  985. end
  986.  
  987. if not expectCmd and (not table.contains(VALID_ACTIONS, actions[i]) and tonumber(actions[i]) == nil) then
  988. return false, 'Expected a command or reps number but found "' .. tostring(actions[i]) .. '"'
  989. end
  990.  
  991. if expectCmd then
  992. expectCmd = false -- next action can be a command OR reps number, it doesn't HAVE to be a command
  993. end
  994.  
  995. if tonumber(actions[i]) ~= nil then
  996. -- the command was a reps number, so the next action MUST be a command
  997. expectCmd = true
  998. end
  999. end
  1000. return true
  1001. end
  1002.  
  1003. --[[
  1004. function suckFrom(suckDirection, amount, faceOriginalDirection) -- TODO add amount param?
  1005. local success, errMsg
  1006. local preSuckDirection = getDirection()
  1007.  
  1008. if faceOriginalDirection == nil then faceOriginalDirection = true end
  1009.  
  1010. if amount == nil then amount = MAX_STACK_SIZE end
  1011.  
  1012. -- move and point turtle to the item source
  1013. if type(suckDirection) == 'string' then
  1014. success, errMsg = doActionsSafely(suckDirection)
  1015. if success == false then return false, errMsg end
  1016. elseif type(suckDirection) == 'table' then
  1017. if goto(suckDirection[1], suckDirection[2], suckDirection[3], faceOriginalDirection) == false then return false, 'Movement obstructed' end
  1018. face(suckDirection[4])
  1019. end
  1020.  
  1021. -- suck items from the item source
  1022. if suckDirection[4] == 'up' then
  1023. success, errMsg = turtle.suckUp()
  1024. elseif suckDirection[4] == 'down' or suckDirection[4] == 'dn' then
  1025. success, errMsg = turtle.suckDown()
  1026. else
  1027. success, errMsg = turtle.suck()
  1028. end
  1029. if DEBUG then print('sucked item') end
  1030. face(preSuckDirection)
  1031. return success, errMsg
  1032. end
  1033. ]]
  1034.  
  1035. function isValidDirectionValue(direction, requiredCmdType)
  1036. -- a valid "direction value" is either a {x, y, z, compass direction} table or a reversible action string
  1037. -- requiredCmdType can be either 'suck' or 'drop', and checks the action string for this type of command
  1038.  
  1039.  
  1040. if type(direction) == 'string' then
  1041. success, errMsg = isReversibleActionString(direction)
  1042. if not success then return false, errMsg end
  1043.  
  1044. -- check for required command type
  1045. dirAsTable = split(direction)
  1046. if (requiredCmdType == 'suck') and (not (table.contains(dirAsTable, 's') or table.contains(dirAsTable, 'sd') or table.contains(dirAsTable, 'su') or
  1047. table.contains(dirAsTable, 'suck') or table.contains(dirAsTable, 'suckdown') or table.contains(dirAsTable, 'suckup'))) then
  1048. return false, 'No suck command in action string'
  1049. elseif (requiredCmdType == 'drop') and (not (table.contains(dirAsTable, 'dr') or table.contains(dirAsTable, 'drd') or table.contains(dirAsTable, 'dru') or
  1050. table.contains(dirAsTable, 'drop') or table.contains(dirAsTable, 'dropdown') or table.contains(dirAsTable, 'dropup'))) then
  1051. return false, 'No drop command in action string'
  1052. end
  1053.  
  1054. elseif type(direction) == 'table' then
  1055. if type(direction[1]) ~= 'number' or type(direction[2]) ~= 'number' or type(direction[3]) ~= 'number' then
  1056. return false, 'Only numbers can be passed for XYZ coordinates'
  1057. end
  1058.  
  1059. if direction[4] ~= 'no' and direction[4] ~= 'north' and direction[4] ~= 'so' and direction[4] ~= 'south' and
  1060. direction[4] ~= 'ea' and direction[4] ~= 'east' and direction[4] ~= 'we' and direction[4] ~= 'west' and
  1061. direction[4] ~= 'dn' and direction[4] ~= 'down' and direction[4] ~= 'up' then
  1062. return false, '"' .. direction[4] .. '" is not a valid N-S-E-W-UP-DN direction'
  1063. end
  1064. else
  1065. return false, '"' .. type(direction) .. '" is an invalid type for the `direction` parameter'
  1066. end
  1067.  
  1068. return true
  1069. end
  1070.  
  1071.  
  1072. function suckAt(direction, slots, amount)
  1073. local success, errMsg
  1074. local origx = getx()
  1075. local origy = gety()
  1076. local origz = getz()
  1077. local origDirection = getDirection()
  1078.  
  1079. if slots == nil then slots = {turtle.getSelectedSlot()} end
  1080. if slots == 'craft' then slots = {1, 2, 3, 5, 6, 7, 9, 10, 11} end
  1081. if type(slots) == 'number' then slots = {slots} end
  1082.  
  1083. if amount == nil then amount = MAX_STACK_SIZE end
  1084.  
  1085. --if DEBUG then print('Call: suckAt(' .. table.tostring(direction) .. ', ' .. table.tostring(slots) .. ', ' .. amount .. ')') end
  1086.  
  1087. success, errMsg = isValidDirectionValue(direction, 'suck')
  1088. if success == false then return false, errMsg end
  1089.  
  1090. if type(direction) == 'string' then
  1091. originalDirectionArg = direction
  1092. while direction ~= '' do
  1093. -- check if this is a suck command
  1094. firstAction = split(direction)[1]
  1095. local isSuckCmd = firstAction == 's' or firstAction == 'suck' or firstAction == 'sd' or firstAction == 'suckdown' or firstAction == 'suckup' or firstAction == 'su'
  1096.  
  1097. if isSuckCmd then
  1098. -- run the suck command for every slot in slots
  1099. for i=1,#slots do
  1100. if DEBUG then print('Sucking to slot #' .. tostring(slots[i])) end
  1101. turtle.select(slots[i])
  1102. success, errMsg = doOneAction(firstAction .. ' ' .. amount)
  1103. if success == false then return false, errMsg end
  1104. direction = success -- assign the remainder of the action string to direction
  1105. end
  1106. else
  1107. -- this is not a suck command, so just do it
  1108. success, errMsg = doOneAction(direction)
  1109. if success == false then return false, errMsg end
  1110. direction = success -- assign the remainder of the action string to direction
  1111. end
  1112. end
  1113.  
  1114. -- return to original place
  1115. doReverseMovement(originalDirectionArg)
  1116.  
  1117. elseif type(direction) == 'table' then
  1118. -- direction is an xyz coordinate with direction
  1119. if goto(direction[1], direction[2], direction[3], false) == false then return false, 'Movement obstructed' end
  1120. face(direction[4])
  1121.  
  1122. -- suck items at the suck destination
  1123. for i=1,#slots do
  1124. if DEBUG then print('Sucking slot #' .. tostring(slots[i])) end
  1125. turtle.select(slots[i])
  1126. if direction[4] == 'up' then
  1127. success, errMsg = turtle.suckUp(amount)
  1128. elseif direction[4] == 'down' or direction[4] == 'dn' then
  1129. success, errMsg = turtle.suckDown(amount)
  1130. else
  1131. success, errMsg = turtle.suck(amount)
  1132. end
  1133. end
  1134.  
  1135. if goto(origx, origy, origz) == false then return false, 'Movement obstructed' end
  1136. face(origDirection)
  1137. end
  1138.  
  1139. if DEBUG then print('done sucking items at suck site') end
  1140. end
  1141.  
  1142.  
  1143. function dropAt(direction, slots, amount)
  1144. local success, errMsg
  1145. local origx = getx()
  1146. local origy = gety()
  1147. local origz = getz()
  1148. local origDirection = getDirection()
  1149.  
  1150. if slots == nil then slots = {turtle.getSelectedSlot()} end
  1151. if slots == 'craft' then slots = {1, 2, 3, 5, 6, 7, 9, 10, 11} end
  1152. if type(slots) == 'number' then slots = {slots} end
  1153.  
  1154. if amount == nil then amount = MAX_STACK_SIZE end
  1155.  
  1156. if DEBUG then print('Call: dropAt(' .. table.tostring(direction) .. ', ' .. table.tostring(slots) .. ', ' .. amount .. ')') end
  1157.  
  1158. success, errMsg = isValidDirectionValue(direction, 'drop')
  1159. if success == false then return false, errMsg end
  1160.  
  1161. if type(direction) == 'string' then
  1162. originalDirectionArg = direction
  1163. while direction ~= '' do
  1164. -- check if this is a drop command
  1165. firstAction = split(direction)[1]
  1166. local isDropCmd = firstAction == 'dr' or firstAction == 'drop' or firstAction == 'drd' or firstAction == 'dropdown' or firstAction == 'dropup' or firstAction == 'dru'
  1167.  
  1168. if isDropCmd then
  1169. -- run the drop command for every slot in slots
  1170. for i=1,#slots do
  1171. if DEBUG then print('Dropping slot #' .. tostring(slots[i])) end
  1172. turtle.select(slots[i])
  1173. success, errMsg = doOneAction(firstAction .. ' ' .. amount)
  1174. if success == false then return false, errMsg end
  1175. direction = success -- assign the remainder of the action string to direction
  1176. end
  1177. else
  1178. -- this is not a drop command, so just do it
  1179. success, errMsg = doOneAction(direction)
  1180. if success == false then return false, errMsg end
  1181. direction = success -- assign the remainder of the action string to direction
  1182. end
  1183. end
  1184.  
  1185. -- return to original place
  1186. doReverseMovement(originalDirectionArg)
  1187.  
  1188. elseif type(direction) == 'table' then
  1189. -- direction is an xyz coordinate with direction
  1190. if goto(direction[1], direction[2], direction[3], false) == false then return false, 'Movement obstructed' end
  1191. face(direction[4])
  1192.  
  1193. -- drop items at the drop destination
  1194. for i=1,#slots do
  1195. if DEBUG then print('Dropping slot #' .. tostring(slots[i])) end
  1196. turtle.select(slots[i])
  1197. if direction[4] == 'up' then
  1198. success, errMsg = turtle.dropUp(amount)
  1199. elseif direction[4] == 'down' or direction[4] == 'dn' then
  1200. success, errMsg = turtle.dropDown(amount)
  1201. else
  1202. success, errMsg = turtle.drop(amount)
  1203. end
  1204. end
  1205.  
  1206. if goto(origx, origy, origz) == false then return false, 'Movement obstructed' end
  1207. face(origDirection)
  1208. end
  1209.  
  1210. if DEBUG then print('done dropping items at direction') end
  1211. end
  1212.  
  1213.  
  1214. function pickOut(itemName, amount, suckDirection, dropDirection, slot, timeout)
  1215. -- suck/drop directions can be: {x,y,z,nsew} or an action string with suck/drop commands
  1216. -- TODO implement timeout
  1217. while true do
  1218. if selectEmptySlot() == false then return false, 'No empty slots' end
  1219. if DEBUG then print('selected slot #' .. tostring(turtle.getSelectedSlot())) end
  1220.  
  1221. success, errMsg = suckAt(suckDirection, MAX_STACK_SIZE, false)
  1222.  
  1223. if success == false then return false, errMsg end -- nothing left to get
  1224.  
  1225. -- figure out what item it is
  1226. itemData = turtle.getItemDetail()
  1227. if itemData ~= nil and string.find(string.lower(itemData['name']), string.lower(itemName)) then
  1228. if DEBUG then print('sucked ' .. tostring(itemData['count']) .. ' ' .. itemData['name']) end
  1229. -- put back excess amount of items
  1230. if itemData['count'] > amount then
  1231. if suckDirection[4] == 'up' or suckDirection[4] == 'u' then
  1232. success, errMsg = turtle.dropUp(amount - itemData['count'])
  1233. elseif suckDirection[4] == 'down' or suckDirection[4] == 'd' then
  1234. success, errMsg = turtle.dropDown(amount - itemData['count'])
  1235. else
  1236. success, errMsg = turtle.drop(amount - itemData['count'])
  1237. end
  1238. end
  1239.  
  1240. return true -- NOTE: like the turtle API's suck functions, this might not have collected the full amount requested.
  1241. end
  1242.  
  1243. success, errMsg = dropAt(dropDirection, MAX_STACK_SIZE, false)
  1244. if success == false then return false, errMsg end -- could not drop in that direction
  1245. end
  1246. end
  1247.  
  1248.  
  1249. function moveAllBetween(suckDirection, dropDirection)
  1250. -- TODO finish, moves all items from one source chest to another
  1251. -- TODO currently this only moves 1 stack at a time
  1252. local success, tempSlots
  1253. local origx = getx() -- save original position and direction so it can be reset to this at the end
  1254. local origy = gety()
  1255. local origz = getz()
  1256. local origFace = getDirection()
  1257.  
  1258. local noMoreItems = false
  1259. local cantDropItems = false
  1260. local finishLastDrop = false
  1261.  
  1262. tempSlots = {}
  1263. for i=1,16 do
  1264. if turtle.getItemDetail(i) == nil then
  1265. table.insert(tempSlots, i)
  1266. end
  1267. end
  1268.  
  1269. if DEBUG then print('temp slots: ' .. table.concat(tempSlots, ',')) end
  1270.  
  1271. if selectEmptySlot() == false then return false, 'No empty slots' end
  1272.  
  1273. while true do
  1274. -- grab items from source
  1275. for i=1,#tempSlots do
  1276. turtle.select(tempSlots[i])
  1277. success = suckFrom(suckDirection, MAX_STACK_SIZE, false)
  1278. if success == false then
  1279. if i == 1 then
  1280. noMoreItems = true
  1281. break
  1282. else
  1283. finishLastDrop = true
  1284. end
  1285. end
  1286. end
  1287. if noMoreItems then break end
  1288.  
  1289. -- drop items at drop site
  1290. for i=1,#tempSlots do
  1291. turtle.select(tempSlots[i])
  1292. success = dropAt(dropDirection, MAX_STACK_SIZE, false)
  1293. if success == false then
  1294. cantDropItems = true
  1295. break
  1296. end
  1297. end
  1298.  
  1299. if finishLastDrop then break end
  1300. end
  1301.  
  1302. goto(origx, origy, origz)
  1303. face(origFace)
  1304. end
  1305.  
  1306.  
  1307. function getRecipe(itemName)
  1308. -- TODO find recipe by name
  1309. -- TODO where to store the recipes? In a file? How can the user add new recipes?
  1310. -- TODO should recipes be arrays of 9 or 16 items?
  1311. end
  1312.  
  1313. function craft(recipe, amount, suckDirection, dropDirection, slot, timeout)
  1314. -- return value is the slot number where crafted result is
  1315. -- how do we craft multiple items?
  1316. -- what if I want to feed this function a list of "you can find this ingredient in this chest", and let it figure out how to make everything?
  1317. -- when it can't create, it should return an error
  1318. --arrange()
  1319. -- TODO arranges and then crafts item
  1320. -- need to figure out how to handle getting stuff from chests
  1321.  
  1322. -- TODO pickout items still needed for the recipe, and when found put them in the proper slot
  1323.  
  1324. -- TODO craft the item, then place it in the result direction
  1325.  
  1326. end
  1327.  
  1328.  
  1329. function dropItem(itemName, amount)
  1330. if not selectItem(itemName) then return false end
  1331. return turtle.drop(amount)
  1332. end
  1333.  
  1334.  
  1335. function dropItemDown(itemName, amount)
  1336. if not selectItem(itemName) then return false end
  1337. return turtle.dropDown(amount)
  1338. end
  1339.  
  1340.  
  1341. function dropItemUp(itemName, amount)
  1342. if not selectItem(itemName) then return false end
  1343. return turtle.dropUp(amount)
  1344. end
  1345.  
  1346.  
  1347. function dropAllItem(itemName)
  1348. while dropItem(itemName) do end
  1349. end
  1350.  
  1351.  
  1352. function dropAllItemDown(itemName)
  1353. while dropItemDown(itemName) do end
  1354. end
  1355.  
  1356.  
  1357. function dropAllItemUp(itemName)
  1358. while dropItemUp(itemName) do end
  1359. end
  1360.  
  1361.  
  1362. function suckAll()
  1363. while turtle.suck(MAX_STACK_SIZE) do end
  1364. end
  1365.  
  1366.  
  1367. function suckAllDown()
  1368. while turtle.suckDown(MAX_STACK_SIZE) do end
  1369. end
  1370.  
  1371.  
  1372. function suckAllUp()
  1373. while turtle.suckUp(MAX_STACK_SIZE) do end
  1374. end
  1375.  
  1376.  
  1377. function getFuelPercent()
  1378. return 100 * turtle.getFuelLevel() / turtle.getFuelLimit()
  1379. end
  1380.  
  1381.  
  1382. function getFuelSpace()
  1383. -- returns amount of fuel free space there is
  1384. return turtle.getFuelLimit() - turtle.getFuelLevel()
  1385. end
  1386.  
  1387.  
  1388. function getItemNames()
  1389. local i, itemData, names = {}
  1390. for i=1,16 do
  1391. itemData = turtle.getItemDetail(i)
  1392. table.insert(names, itemData)
  1393. end
  1394. return names
  1395. end
  1396.  
  1397.  
  1398. function selectEmptySlot()
  1399. local i, itemData
  1400. for i=1,16 do
  1401. itemData = turtle.getItemDetail(i)
  1402. if itemData == nil then
  1403. return turtle.select(i) -- TODO is this correct? What does select return?
  1404. end
  1405. end
  1406. return false, 'No empty slots'
  1407. end
  1408.  
  1409.  
  1410. function selectItem(itemName)
  1411. -- selects an item exactly, or the closest match
  1412. local i, itemData
  1413.  
  1414. -- try to find an exact match:
  1415. for i=1,16 do
  1416. itemData = turtle.getItemDetail(i)
  1417. if itemData ~= nil and itemData['name'] == itemName then
  1418. return turtle.select(i)
  1419. end
  1420. end
  1421.  
  1422. -- try to find an exact match after the : part of the name:
  1423. for i=1,16 do
  1424. itemData = turtle.getItemDetail(i)
  1425. if itemData ~= nil then
  1426. local colonPos = string.find(itemData['name'], ':')
  1427. if colonPos ~= nil then
  1428. if itemData ~= nil and string.sub(itemData['name'], colonPos + 1, -1) == itemName then
  1429. return turtle.select(i)
  1430. end
  1431. end
  1432. end
  1433. end
  1434.  
  1435. -- try to find a "like" match:
  1436. for i=1,16 do
  1437. itemData = turtle.getItemDetail(i)
  1438. if itemData ~= nil and string.find(itemData['name'], itemName) then
  1439. return turtle.select(i)
  1440. end
  1441. end
  1442.  
  1443. return false -- could not find item
  1444. end
  1445.  
  1446.  
  1447.  
  1448.  
  1449.  
  1450. function table.contains(table, element)
  1451. for _, value in pairs(table) do
  1452. if value == element then
  1453. return true
  1454. end
  1455. end
  1456. return false
  1457. end
  1458.  
  1459.  
  1460. function setDirection(d) -- TODO, shoudl we replace no/so/ea/we with n/s/e/w and change 's' to 'su' for the commands?
  1461. if not table.contains({'north', 'no', 'south', 'so', 'east', 'ea', 'west', 'we'}, d) then
  1462. return false, '"' .. d .. '" is not a valid direction'
  1463. end
  1464. if d == 'no' or d == 'north' then direction = 'north' end
  1465. if d == 'so' or d == 'south' then direction = 'south' end
  1466. if d == 'we' or d == 'west' then direction = 'west' end
  1467. if d == 'ea' or d == 'east' then direction = 'east' end
  1468. return true
  1469. end
  1470.  
  1471.  
  1472. function getDirection()
  1473. return direction
  1474. end
  1475.  
  1476.  
  1477. function setPos(xpos, ypos, zpos)
  1478. if xpos ~= nil then x = xpos end
  1479. if ypos ~= nil then y = ypos end
  1480. if zpos ~= nil then z = zpos end
  1481. return true
  1482. end
  1483.  
  1484.  
  1485. function getPos()
  1486. return {x=x, y=y, z=z}
  1487. end
  1488.  
  1489.  
  1490. function getx()
  1491. return x
  1492. end
  1493.  
  1494.  
  1495. function setx(xpos)
  1496. if tonumber(xpos) == nil then
  1497. return false, 'Non-number passed to setx()'
  1498. end
  1499. return x
  1500. end
  1501.  
  1502.  
  1503. function gety()
  1504. return y
  1505. end
  1506.  
  1507.  
  1508. function sety(ypos)
  1509. if tonumber(ypos) == nil then
  1510. return false, 'Non-number passed to sety()'
  1511. end
  1512. return y
  1513. end
  1514.  
  1515.  
  1516. function getz()
  1517. return z
  1518. end
  1519.  
  1520.  
  1521. function setz(zpos)
  1522. if tonumber(zpos) == nil then
  1523. return false, 'Non-number passed to setz()'
  1524. end
  1525. return z
  1526. end
  1527.  
  1528.  
  1529. function discoverDirection()
  1530. -- requires gps to figure out what direction they're facing
  1531. success, errMsg = useGPS()
  1532. if success == false then return false, errMsg end
  1533.  
  1534. origx, origy, origz = gps.locate()
  1535.  
  1536. if forward() == true then
  1537. newx, newy, newz = gps.locate()
  1538. if newx == origx + 1 then setDirection('east') end
  1539. if newx == origx - 1 then setDirection('west') end
  1540. if newz == origz + 1 then setDirection('south') end
  1541. if newz == origz - 1 then setDirection('north') end
  1542. back()
  1543. useGPS()
  1544. return true
  1545. elseif back() == true then
  1546. newx, newy, newz = gps.locate()
  1547. if newx == origx + 1 then setDirection('west') end
  1548. if newx == origx - 1 then setDirection('east') end
  1549. if newz == origz + 1 then setDirection('north') end
  1550. if newz == origz - 1 then setDirection('south') end
  1551. forward()
  1552. useGPS()
  1553. return true
  1554. else
  1555. -- try turning to the left to see if you can move in that direction
  1556. turnLeft()
  1557. if forward() == true then
  1558. newx, newy, newz = gps.locate()
  1559. if newx == origx + 1 then setDirection('east') end
  1560. if newx == origx - 1 then setDirection('west') end
  1561. if newz == origz + 1 then setDirection('south') end
  1562. if newz == origz - 1 then setDirection('north') end
  1563. back()
  1564. turnRight()
  1565. useGPS()
  1566. return true
  1567. elseif back() == true then
  1568. newx, newy, newz = gps.locate()
  1569. if newx == origx + 1 then setDirection('west') end
  1570. if newx == origx - 1 then setDirection('east') end
  1571. if newz == origz + 1 then setDirection('north') end
  1572. if newz == origz - 1 then setDirection('south') end
  1573. forward()
  1574. turnRight()
  1575. useGPS()
  1576. return true
  1577. end
  1578. turnRight()
  1579. return false -- turtle is completely boxed in (or out of fuel) and can't determine compass direction from gps
  1580. end
  1581. end
  1582.  
  1583.  
  1584. function useGPS()
  1585. local gpsx, gpsy, gpsz = gps.locate()
  1586. if gpsx == nil then
  1587. return false, 'GPS not available'
  1588. else
  1589. x, y, z = gpsx, gpsy, gpsz
  1590. return true, x, y, z
  1591. end
  1592. end
  1593.  
  1594.  
  1595. function matchesGPS()
  1596. local gpsx, gpsy, gpsz = gps.locate()
  1597. if x == nil then
  1598. return false, 'GPS not available'
  1599. else
  1600. if x == gpsx and y == gpsy and z == gpsz then
  1601. return true
  1602. else
  1603. return false, 'Position does not match GPS'
  1604. end
  1605. end
  1606. end
  1607.  
  1608.  
  1609. function recordMove(move)
  1610. if (recordedMoves[#recordedMoves] == move) then
  1611. table.insert(recordedMoves, 2) -- adding a second move of the same type
  1612. elseif (type(recordedMoves[#recordedMoves]) == 'number') and (recordedMoves[#recordedMoves - 1] == move) then
  1613. prevCount = table.remove(recordedMoves, #recordedMoves)
  1614. table.insert(recordedMoves, prevCount + 1) -- increment the existing count
  1615. else
  1616. table.insert(recordedMoves, move) -- this is a new, non-repeated move, so just append it
  1617. end
  1618. end
  1619.  
  1620.  
  1621. function record()
  1622. recordedMoves = {}
  1623. recordingNow = true
  1624. end
  1625.  
  1626.  
  1627. function stopRecording()
  1628. recordingNow = false
  1629. return table.concat(recordedMoves, ' ')
  1630. end
  1631.  
  1632.  
  1633. function getRecording()
  1634. return table.concat(recordedMoves, ' ')
  1635. end
  1636.  
  1637.  
  1638. function table.tostring(tab) -- TODO this doesn't seem to work very well. test with various data types passed for tab
  1639. if type(tab) ~= 'table' then return tostring(tab) end
  1640.  
  1641. result = '{'
  1642. for k, v in pairs(tab) do
  1643. result = result .. tostring(k) .. ': ' .. tostring(v) .. ', '
  1644. end
  1645. return result .. '}'
  1646. end
  1647.  
  1648.  
  1649. function nineSpaceToSixteenSpace(slot)
  1650. if slot <= 3 then return slot end
  1651. if slot <= 6 then return slot + 1 end
  1652. if slot <= 9 then return slot + 2 end
  1653. return false, 'Not a valid nine-space slot'
  1654. end
  1655.  
  1656.  
  1657. function sixteenSpaceToNineSpace(slot)
  1658. if slot <= 3 then return slot end
  1659. if slot >= 5 and slot <= 7 then return slot - 1 end
  1660. if slot >= 9 and slot <= 11 then return slot - 2 end
  1661. return false, 'Cannot convert to valid nine-space slot'
  1662. end
  1663.  
  1664.  
  1665.  
  1666. function manufacture(recipe, amount, sourceDirection, resultDirection, discardDirection)
  1667. local success, errMsg
  1668. if amount == nil then amount = MAX_STACK_SIZE end
  1669.  
  1670. -- recipe is a 9-item array for a 3x3 recipe, it must be converted to a full 4x4 sized recipe
  1671. fullSizeRecipe = {recipe[1], recipe[2], recipe[3], '', recipe[4], recipe[5], recipe[6], '', recipe[7], recipe[8], recipe[9], '', '', '', '', ''}
  1672.  
  1673. -- call arrange to see if the turtle already carries parts for the recipe (discarding any non-recipe items)
  1674. success, errMsg = arrange(fullSizeRecipe, discardDirection)
  1675. if success == false then
  1676. -- if the turtle can't craft the item, then pick out craft items from the source (discarding them if they are non-recipe items)
  1677. for i=1,9 do
  1678. -- (because we ran arrange(), we can assume that if a slot isn't blank then it has the correct recipe item)
  1679. if turtle.getItemDetail(nineSpaceToSixteenSpace(i)) == nil then
  1680. success, errMsg = pickOut(recipe[i], amount, sourceDirection, discardDirection, nineSpaceToSixteenSpace(i))
  1681. if success == false then return false, errMsg end -- TODO test this
  1682. end
  1683. end
  1684. end
  1685.  
  1686. -- (if there isn't enough recipe items, then just return false)
  1687.  
  1688. -- put the resulting crafted item in the result Direction
  1689.  
  1690. end
  1691.  
  1692.  
  1693. function testGoto()
  1694. origx, origy, origz = gps.locate()
  1695. if origx == nil then return false, 'Test requires gps' end
  1696.  
  1697. for changex = -2,2 do
  1698. for changey = -2,2 do
  1699. for changez = -2,2 do
  1700. if goto(origx+changex, origy+changey, origz+changez) == false then return false, 'Could not finish test (1)' end
  1701. actx, acty, actz = gps.locate()
  1702. if actx ~= origx+changex or acty ~= origy+changey or actz ~= origz+changez then
  1703. print('out of sync for test ' .. changex .. ' ' .. changey .. ' ' .. changez)
  1704. print('Expected: ' .. origx+changex .. ' ' .. origy+changey .. ' ' .. origz+changez)
  1705. print('Actual: ' .. actx .. ' ' .. acty .. ' ' .. actz)
  1706. end
  1707.  
  1708. -- go back to original place
  1709. if goto(origx, origy, origz) == false then return false, 'Could not finish test (2)' end
  1710. actx, acty, actz = gps.locate()
  1711. if actx ~= origx or acty ~= origy or actz ~= origz then return false, 'Could not go back to original spot' end
  1712. end
  1713. end
  1714. end
  1715. return true -- "true" here means that the test finished
  1716. end
  1717.  
  1718.  
  1719.  
  1720. -- startup code to run:
  1721. discoverDirection()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement