Smithle

TurtleBuildFarm

Mar 12th, 2025 (edited)
108
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.15 KB | None | 0 0
  1. -- CONSTANTS
  2.  
  3. DIRECTION = { NORTH = 0, EAST = 1, SOUTH = 2, WEST = 3, UP = 4, DOWN = 5, first = 0, last = 5, num = 6,
  4.     POS_X = 1, NEG_X = 3, POS_Y = 4, NEG_Y = 5, POS_Z = 2, NEG_Z = 0 }
  5. VALID_FUELS = { "minecraft:coal", "minecraft:charcoal", size = 2 }
  6. ACTION_STATE = { EXECUTING = 0, FINISHED = 1, FAILED = 2 }
  7.  
  8. -- VARIABLES
  9.  
  10. StuckScore = 0
  11. WasStuck = false
  12.  
  13. -- HELPERS
  14.  
  15. function Mod( a, b )
  16.     return a - ( math.floor( a / b ) * b )
  17. end
  18.  
  19. function Ternary( condition , onTrue , onFalse )
  20.     if condition then return onTrue else return onFalse end
  21. end
  22.  
  23. function DirectionVector( direction )
  24.     if direction == DIRECTION.POS_X then
  25.         return vector.new( 1, 0, 0 )
  26.     elseif direction == DIRECTION.NEG_X then
  27.         return vector.new( -1, 0, 0 )
  28.     elseif direction == DIRECTION.POS_Y then
  29.         return vector.new( 0, 1, 0 )
  30.     elseif direction == DIRECTION.NEG_Y then
  31.         return vector.new( 0, -1, 0 )
  32.     elseif direction == DIRECTION.POS_Z then
  33.         return vector.new( 0, 0, 1 )
  34.     elseif direction == DIRECTION.NEG_Z then
  35.         return vector.new( 0, 0, -1 )
  36.     else
  37.         assert( false, "Invalid direction" )
  38.     end
  39.     return vector.new( 0, 0, 0 )
  40. end
  41.  
  42. function VecCompare( vecA, vecB )
  43.     return vecA.x == vecB.x and vecA.y == vecB.y and vecA.z == vecB.z
  44. end
  45.  
  46. function VecAssign( vecTo, vecFrom )
  47.     vecTo.x = vecFrom.x
  48.     vecTo.y = vecFrom.y
  49.     vecTo.z = vecFrom.z
  50. end
  51.  
  52. function VecAdd( vecA, vecB, result )
  53.     result.x = vecA.x + vecB.x
  54.     result.y = vecA.y + vecB.y
  55.     result.z = vecA.z + vecB.z
  56. end
  57.  
  58. -- DATA
  59.  
  60. Position = vector.new( 0, 0, 0 )
  61. Direction = 0
  62. MoveConfirmation = { flag = 0, pos = vector.new( 0, 0, 0 ), fuel = 0 }
  63. CachedPosition = vector.new( 0, 0, 0 )
  64. QuarryFinished = false
  65.  
  66. function WriteVector( handle, vector )
  67.     handle.writeLine( vector.x )
  68.     handle.writeLine( vector.y )
  69.     handle.writeLine( vector.z )
  70. end
  71.  
  72. function ReadVector( handle, vector )
  73.     vector.x = tonumber( handle.readLine() )
  74.     vector.y = tonumber( handle.readLine() )
  75.     vector.z = tonumber( handle.readLine() )
  76. end
  77.  
  78. function GetIntInput( queryText )
  79.     local inputInt = nil
  80.  
  81.     term.clear()
  82.     term.setCursorPos( 1, 1 )
  83.     print( queryText )
  84.  
  85.     while true do
  86.         inputInt = tonumber( read() )
  87.         if type( inputInt ) == "number" and inputInt == math.floor( inputInt ) then
  88.             return inputInt
  89.         end
  90.  
  91.         term.clear()
  92.         term.setCursorPos( 1, 1 )
  93.         print( queryText )
  94.         print( "Invalid entry" )
  95.     end
  96. end
  97.  
  98. function GetPositionInput( queryText, inputPos )
  99.     inputPos.x = GetIntInput( queryText .. " xzy(_, z, y)" )
  100.     inputPos.z = GetIntInput( queryText .. " xzy(" .. inputPos.x .. ", _, y)" )
  101.     inputPos.y = GetIntInput( queryText .. " xzy(" .. inputPos.x .. ", " .. inputPos.z .. ", _)" )
  102. end
  103.  
  104. function GetDirectionInput( queryText )
  105.     local inputStr = nil
  106.  
  107.     term.clear()
  108.     term.setCursorPos( 1, 1 )
  109.     print( queryText .. " ((n)orth, (s)outh, (e)east, (w)est)" )
  110.  
  111.     while true do
  112.         inputStr = string.lower( read() )
  113.         if inputStr == "n" or inputStr == "north" then
  114.             return DIRECTION.NORTH
  115.         elseif inputStr == "e" or inputStr == "east" then
  116.             return DIRECTION.EAST
  117.         elseif inputStr == "s" or inputStr == "south" then
  118.             return DIRECTION.SOUTH
  119.         elseif inputStr == "w" or inputStr == "west" then
  120.             return DIRECTION.WEST
  121.         end
  122.  
  123.         term.clear()
  124.         term.setCursorPos( 1, 1 )
  125.         print( queryText .. " ((n)orth, (s)outh, (e)east, (w)est)" )
  126.         print( "Invalid entry" )
  127.     end
  128. end
  129.  
  130. function LoadData()
  131.     if not fs.exists( "data" ) then return false end
  132.     local h = fs.open( "data", "r" )
  133.  
  134.     if h.readLine() ~= "VALID" then return false end
  135.  
  136.     ReadVector( h, Position )
  137.     Direction = tonumber( h.readLine() )
  138.     ReadVector( h, MoveConfirmation.pos )
  139.     MoveConfirmation.fuel = tonumber( h.readLine() )
  140.     MoveConfirmation.flag = tonumber( h.readLine() )
  141.     ReadVector( h, CachedPosition )
  142.     QuarryFinished = Ternary( h.readLine() == "T", true, false )
  143.  
  144.     local valid = h.readLine() == "END"
  145.     h.close()
  146.     return valid
  147. end
  148.  
  149. function SaveData()
  150.     local h = fs.open( "data", "w" )
  151.  
  152.     h.writeLine( "VALID" )
  153.     WriteVector( h, Position )
  154.     h.writeLine( Direction )
  155.     WriteVector( h, MoveConfirmation.pos )
  156.     h.writeLine( MoveConfirmation.fuel )
  157.     h.writeLine( MoveConfirmation.flag )
  158.     WriteVector( h, CachedPosition )
  159.     h.writeLine( Ternary( QuarryFinished, "T", "F" ) )
  160.     h.writeLine( "END" )
  161.  
  162.     h.close()
  163. end
  164.  
  165. function ClearData()
  166.     local h = fs.open( "data", "w" )
  167.     h.writeLine( "INVALID" )
  168.     h.close()
  169. end
  170.  
  171. function InitData()
  172.     term.clear()
  173.     term.setCursorPos( 1, 1 )
  174.  
  175.     SaveData()
  176. end
  177.  
  178. -- INSTRUCTIONS
  179.  
  180. Instructions = {
  181.     BlockIDs = { nil, nil },
  182.     PosMin = vector.new( 0, 0, 0 ),
  183.     PosMax = vector.new( 0, 0, 0 )
  184. }
  185.  
  186. function LoadInstructions()
  187.     if not fs.exists( "instructions" ) then return false end
  188.     local h = fs.open( "instructions", "r" )
  189.  
  190.     Instructions.BlockIDs[1] = h.readLine()
  191.     Instructions.BlockIDs[2] = h.readLine()
  192.     ReadVector( h, Instructions.PosMin )
  193.     ReadVector( h, Instructions.PosMax )
  194.  
  195.     local valid = h.readLine() == "END"
  196.     h.close()
  197.     return valid
  198. end
  199.  
  200. function SaveInstructions()
  201.     local h = fs.open( "instructions", "w" )
  202.  
  203.     h.writeLine( Instructions.BlockIDs[1] )
  204.     h.writeLine( Instructions.BlockIDs[2] )
  205.     WriteVector( h, Instructions.PosMin )
  206.     WriteVector( h, Instructions.PosMax )
  207.  
  208.     h.writeLine( "END" )
  209.  
  210.     h.close()
  211. end
  212.  
  213. function InitInstructions()
  214.     local PosA = vector.new( 0, 0, 0 )
  215.     local PosB = vector.new( 0, 0, 0 )
  216.  
  217.     local right = DirectionVector( Mod( Direction + 1, 4 ) )
  218.     local width = GetIntInput( "Enter width" )
  219.     PosB = PosB:add( right:mul( width - 1 ) )
  220.  
  221.     local forward = DirectionVector( Direction )
  222.     local depth = GetIntInput( "Enter depth" )
  223.     PosB = PosB:add( forward:mul( depth - 1 ) )
  224.  
  225.     local height = GetIntInput( "Enter height" )
  226.     PosB.y = height - 1
  227.  
  228.     Instructions.PosMin.x = math.min( PosA.x, PosB.x )
  229.     Instructions.PosMin.y = math.min( PosA.y, PosB.y )
  230.     Instructions.PosMin.z = math.min( PosA.z, PosB.z )
  231.     Instructions.PosMax.x = math.max( PosA.x, PosB.x )
  232.     Instructions.PosMax.y = math.max( PosA.y, PosB.y )
  233.     Instructions.PosMax.z = math.max( PosA.z, PosB.z )
  234.  
  235.     while true do
  236.         term.clear()
  237.         term.setCursorPos( 1, 1 )
  238.         print( "Press enter when you have the materials in the first slots" )
  239.         read()
  240.  
  241.         local item1 = turtle.getItemDetail( 1 )
  242.         local item2 = turtle.getItemDetail( 2 )
  243.         if item1 and item2 then
  244.             Instructions.BlockIDs[1] = item1.name
  245.             Instructions.BlockIDs[2] = item2.name
  246.             break
  247.         end
  248.     end
  249.  
  250.     -- start position
  251.     VecAssign( CachedPosition, Instructions.PosMin )
  252.     CachedPosition.y = CachedPosition.y + 1
  253.  
  254.     term.clear()
  255.     term.setCursorPos( 1, 1 )
  256.  
  257.     SaveData()
  258.     SaveInstructions()
  259. end
  260.  
  261. -- FUEL MANAGEMENT
  262.  
  263. function FuelUpdate()
  264.     if turtle.getFuelLimit() - turtle.getFuelLevel() < 80 then return true end
  265.  
  266.     for slot = 1, 16 do
  267.         local itemDetail = turtle.getItemDetail( slot )
  268.         if itemDetail then
  269.             for validFuel = 1, VALID_FUELS.size do
  270.                 if itemDetail.name == VALID_FUELS[validFuel] then
  271.                     turtle.select( slot )
  272.                     return turtle.refuel( 1 )
  273.                 end
  274.             end
  275.         end
  276.     end
  277.  
  278.     if turtle.getFuelLevel() == 0 then
  279.         print( "Ran out of fuel" )
  280.         return false
  281.     end
  282.  
  283.     turtle.select( 1 )
  284.     return true
  285. end
  286.  
  287. -- INVENTORY
  288.  
  289. function SelectMaterial( num )
  290.     for slot = 1, 16 do
  291.         local itemDetail = turtle.getItemDetail( slot )
  292.         if itemDetail and itemDetail.name == Instructions.BlockIDs[ num ] then
  293.             turtle.select( slot )
  294.             return true
  295.         end
  296.     end
  297.     return false
  298. end
  299.  
  300. -- BASIC MOVEMENT
  301.  
  302. function CreateMoveConfirmation( direction )
  303.     VecAdd( Position, DirectionVector( direction ), MoveConfirmation.pos )
  304.     MoveConfirmation.fuel = turtle.getFuelLevel()
  305.  
  306.     MoveConfirmation.flag = 1
  307. end
  308.  
  309. function ConsumeMoveConfirmation()
  310.     if MoveConfirmation.flag == 0 then return end
  311.  
  312.     if turtle.getFuelLevel() < MoveConfirmation.fuel then
  313.         VecAssign( Position, MoveConfirmation.pos )
  314.         StuckScore = 0
  315.     end
  316.  
  317.     SaveData()
  318.     ClearMoveConfirmation()
  319. end
  320.  
  321. function ClearMoveConfirmation()
  322.     MoveConfirmation.flag = 0
  323.  
  324.     MoveConfirmation.pos = vector.new( 0, 0, 0 )
  325.     MoveConfirmation.fuel = 0
  326. end
  327.  
  328. function Move( direction, mine )
  329.     if direction == DIRECTION.NORTH or direction == DIRECTION.EAST or direction == DIRECTION.SOUTH or direction == DIRECTION.WEST then
  330.         Face( Mod( direction + 2, 4 ) )
  331.  
  332.         CreateMoveConfirmation( direction )
  333.         SaveData()
  334.  
  335.         if turtle.back() then
  336.             ConsumeMoveConfirmation()
  337.             return ACTION_STATE.FINISHED
  338.         else
  339.             ConsumeMoveConfirmation()
  340.             StuckScore = StuckScore + 1
  341.  
  342.             if not mine then return ACTION_STATE.FAILED end
  343.  
  344.             Face( direction )
  345.             if turtle.detect() and not turtle.dig() then return ACTION_STATE.FAILED end
  346.             return Move( direction, false )
  347.         end
  348.  
  349.     elseif direction == DIRECTION.UP then
  350.         if turtle.detectUp() and not ( mine and turtle.digUp() ) then return ACTION_STATE.FAILED end
  351.  
  352.         CreateMoveConfirmation( direction )
  353.         SaveData()
  354.  
  355.         if turtle.up() then
  356.             ConsumeMoveConfirmation()
  357.             return ACTION_STATE.FINISHED
  358.         else
  359.             ConsumeMoveConfirmation()
  360.             StuckScore = StuckScore + 1
  361.             return Ternary( mine, ACTION_STATE.EXECUTING, ACTION_STATE.FAILED )
  362.         end
  363.  
  364.     elseif direction == DIRECTION.DOWN then
  365.         if turtle.detectDown() and not ( mine and turtle.digDown() ) then return ACTION_STATE.FAILED end
  366.  
  367.         CreateMoveConfirmation( direction )
  368.         SaveData()
  369.  
  370.         if turtle.down() then
  371.             ConsumeMoveConfirmation()
  372.             return ACTION_STATE.FINISHED
  373.         else
  374.             ConsumeMoveConfirmation()
  375.             StuckScore = StuckScore + 1
  376.             return Ternary( mine, ACTION_STATE.EXECUTING, ACTION_STATE.FAILED )
  377.         end
  378.  
  379.     else
  380.         assert( false, "Invalid direction" )
  381.         return ACTION_STATE.FAILED
  382.     end
  383. end
  384.  
  385. function TurnRight()
  386.     ClearData()
  387.  
  388.     local thread = coroutine.create( function()
  389.         turtle.turnRight()
  390.     end)
  391.     coroutine.resume( thread )
  392.  
  393.     Direction = Mod( Direction + 1, 4 )
  394.     SaveData()
  395.     sleep(0.5)
  396. end
  397.  
  398. function TurnLeft()
  399.     ClearData()
  400.  
  401.     local thread = coroutine.create( function()
  402.         turtle.turnLeft()
  403.     end)
  404.     coroutine.resume( thread )
  405.  
  406.     Direction = Mod( Direction - 1, 4 )
  407.     SaveData()
  408.     sleep(0.5)
  409. end
  410.  
  411. function Face( direction )
  412.     assert( direction < 4, "Unexpected direction" )
  413.  
  414.     local rotation = Mod( direction - Direction, 4 )
  415.     if rotation == 1 then
  416.         TurnRight()
  417.     elseif rotation == 2 then
  418.         TurnRight()
  419.         TurnRight()
  420.     elseif rotation == 3 then
  421.         TurnLeft()
  422.     end
  423. end
  424.  
  425. -- ACTIONS
  426.  
  427. function MoveToUpdate( pos, canMine )
  428.     if VecCompare( Position, pos ) then return ACTION_STATE.FINISHED end
  429.  
  430.     local toPos = pos - Position
  431.     local InitialDir = Direction
  432.  
  433.     for dirMod = DIRECTION.first, DIRECTION.last do
  434.         local dir = Mod( InitialDir + dirMod, DIRECTION.num )
  435.         if DirectionVector( dir ):dot( toPos ) > 0 and Move( dir, false ) ~= ACTION_STATE.FAILED then return ACTION_STATE.EXECUTING end
  436.     end
  437.  
  438.     if canMine then
  439.         InitialDir = Direction
  440.  
  441.         for dirMod = DIRECTION.first, DIRECTION.last do
  442.             local dir = Mod( InitialDir + dirMod, DIRECTION.num )
  443.             if DirectionVector( dir ):dot( toPos ) > 0 and Move( dir, true ) ~= ACTION_STATE.FAILED then return ACTION_STATE.EXECUTING end
  444.         end
  445.     end
  446.  
  447.     print( "Unable to move to (" .. pos.x .. ", " .. pos.y .. ", " .. pos.z .. ")" )
  448.     return ACTION_STATE.FAILED
  449. end
  450.  
  451. function IsInConstructionArea( pos )
  452.     return pos.x <= Instructions.PosMax.x + 1 and pos.y <= Instructions.PosMax.y + 1 and pos.z <= Instructions.PosMax.z and
  453.         pos.x >= Instructions.PosMin.x and pos.y >= Instructions.PosMin.y and pos.z >= Instructions.PosMin.z
  454. end
  455.  
  456. function BuildDescription( localPos )
  457.     -- Floor
  458.     if Mod(localPos.y, 9) == 0 then
  459.         return Ternary( Mod(localPos.x, 4) == 2 and Mod(localPos.z, 4) == 2, 2, 1)
  460.     end
  461.  
  462.     -- Pillars
  463.     if Mod(localPos.x, 4) == 0 and Mod(localPos.z, 4) == 0 then return 1 end
  464.  
  465.     -- Walls
  466.     if Mod(localPos.y, 9) > 2 and ( Mod(localPos.x, 4) == 0 or Mod(localPos.z, 4) == 0 ) then return 1 end
  467.  
  468.     return 0
  469. end
  470.  
  471. function ConstructBlockUpdate( direction )
  472.     local buildPos = Position:add( DirectionVector( direction ) )
  473.     if not IsInConstructionArea( buildPos ) then return ACTION_STATE.FINISHED end
  474.    
  475.     local material = BuildDescription( buildPos:sub( Instructions.PosMin ) )
  476.     local occupied = nil
  477.     local data = nil
  478.  
  479.     if direction == DIRECTION.UP then
  480.         occupied, data = turtle.inspectUp()
  481.     elseif direction == DIRECTION.DOWN then
  482.         occupied, data = turtle.inspectDown()
  483.     else
  484.         occupied, data = turtle.inspect()
  485.     end
  486.  
  487.     if occupied and ( material == 0 or data.name ~= Instructions.BlockIDs[material] ) then
  488.         if direction == DIRECTION.UP then
  489.             turtle.digUp()
  490.         elseif direction == DIRECTION.DOWN then
  491.             turtle.digDown()
  492.         else
  493.             turtle.dig()
  494.         end
  495.     end
  496.  
  497.     if material ~= 0 then
  498.         if data.name == Instructions.BlockIDs[material] then return ACTION_STATE.FINISHED end
  499.  
  500.         if not SelectMaterial( material ) then
  501.             print( "Ran out of " .. Instructions.BlockID )
  502.             return ACTION_STATE.FAILED
  503.         end
  504.  
  505.         local placed = false
  506.         if direction == DIRECTION.UP then
  507.             placed = turtle.placeUp()
  508.         elseif direction == DIRECTION.DOWN then
  509.             placed = turtle.placeDown()
  510.         else
  511.             placed = turtle.place()
  512.         end
  513.  
  514.         if not placed then
  515.             return ACTION_STATE.EXECUTING
  516.         end
  517.     end
  518.  
  519.     return ACTION_STATE.FINISHED
  520. end
  521.  
  522. function ConstructionUpdate()
  523.     if not IsInConstructionArea( Position ) then
  524.         print( "Turtle is outside the construction area" )
  525.         return ACTION_STATE.FAILED
  526.     end
  527.  
  528.     if not VecCompare( Position, CachedPosition ) then
  529.         if MoveToUpdate( CachedPosition, true ) == ACTION_STATE.FAILED then return ACTION_STATE.FAILED end
  530.         return ACTION_STATE.EXECUTING
  531.     end
  532.  
  533.     local constructBlockState = ConstructBlockUpdate( DIRECTION.DOWN )
  534.     if constructBlockState ~= ACTION_STATE.FINISHED then return constructBlockState end
  535.     constructBlockState = ConstructBlockUpdate( Direction )
  536.     if constructBlockState ~= ACTION_STATE.FINISHED then return constructBlockState end
  537.     constructBlockState = ConstructBlockUpdate( DIRECTION.UP )
  538.     if constructBlockState ~= ACTION_STATE.FINISHED then return constructBlockState end
  539.  
  540.     local localPos = Position:sub( Instructions.PosMin )
  541.  
  542.     local xDest = Ternary( Mod( math.floor( ( localPos.y - 1 ) / 3 ) + localPos.z, 2 ) == 0, Instructions.PosMax.x, Instructions.PosMin.x )
  543.     if Position.x ~= xDest then
  544.         if Move( Ternary( xDest > Position.x, DIRECTION.POS_X, DIRECTION.NEG_X ), true ) == ACTION_STATE.FAILED then return ACTION_STATE.FAILED end
  545.         VecAssign( CachedPosition, Position )
  546.         return ACTION_STATE.EXECUTING
  547.     end
  548.  
  549.     local zDest = Ternary( Mod( math.floor( ( localPos.y - 1 ) / 3 ), 2 ) == 0, Instructions.PosMax.z, Instructions.PosMin.z )
  550.     if Position.z ~= zDest then
  551.         if Move( Ternary( zDest > Position.z, DIRECTION.POS_Z, DIRECTION.NEG_Z ), true ) == ACTION_STATE.FAILED then return ACTION_STATE.FAILED end
  552.         VecAssign( CachedPosition, Position )
  553.         return ACTION_STATE.EXECUTING
  554.     end
  555.  
  556.     -- Check if finished
  557.     if Position.y > Instructions.PosMax.y then return ACTION_STATE.FINISHED end
  558.  
  559.     if Move( DIRECTION.POS_Y, true ) == ACTION_STATE.FAILED then return ACTION_STATE.FAILED end
  560.     VecAssign( CachedPosition, Position )
  561.     return ACTION_STATE.EXECUTING
  562. end
  563.  
  564. function Update()
  565.     if StuckScore > 128 then WasStuck = true end
  566.     if StuckScore > 256 then return ACTION_STATE.FAILED end
  567.  
  568.     if not FuelUpdate() then return ACTION_STATE.FAILED end
  569.  
  570.     return ConstructionUpdate()
  571. end
  572.  
  573. -- MAIN
  574.  
  575. function Main()
  576.     if not LoadData() then
  577.         InitData()
  578.     end
  579.  
  580.     if not LoadInstructions() then
  581.         InitInstructions()
  582.     end
  583.  
  584.     ConsumeMoveConfirmation()
  585.  
  586.     while true do
  587.         local updateState = Update()
  588.         if updateState ~= ACTION_STATE.EXECUTING then
  589.             print( Ternary( updateState == ACTION_STATE.FINISHED, "Job finished!", "Job failed!" ) )
  590.             break
  591.         end
  592.     end
  593. end
  594.  
  595. Main()
Add Comment
Please, Sign In to add comment