Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ------------------------------
- --- KIMITSU NO SHOUFU v0.3 ---
- ------------------------------
- --- + To do / = To test / ? To consider
- --- = Switch to minecraft coordinates naming
- --- = Refueling
- --- = Replanting
- --- = Base unloading
- --- = In this version base should take priority!
- --- = Wandering
- --- = For that, "just stay low" special case
- --- = Obstacle hovering
- --- = Use current tree type to minimize checks
- --- = GPS calibration capability
- --- = Do not use dirt filters, just see if there is a block and if we can plant or not
- --- = Prevent filling free slots before main slots are full
- --- = x, y, z bounds
- --- + Forbid movement without calibration
- --- +
- --- + Check how fast .detect() is
- --- + Test modem ranges
- --- + Improve attacking
- --- + Pick the right time to suck
- --- + Sleep
- --- ? Turtle vs turtle avoidance system
- --- ? Test large red trees
- --- ? Test if general leaves are of the same type
- --- ? Refactor code to use single convention for addressing blocks and directions
- --- ? Refactor code to wrap stack search into movement or do something about it
- ----------------
- --- Settings ---
- local base = {x = 2290, y = 67, z = -866} --- Base coordinates
- local regions = { --- Rectangular regions the turtle should try to roam
- {lt = {x = 2280, z = -887}, rb = {x = 2305, z = -865}},
- {lt = {x = 2273, z = -885}, rb = {x = 2310, z = -870}},
- {lt = {x = 2265, z = -880}, rb = {x = 2300, z = -860}},
- {lt = {x = 2265, z = -875}, rb = {x = 2290, z = -852}}
- }
- local bounds = {lt = {x = 2200, y = 50, z = -900}, rb = {x = 2400, y = 100, z = -800}} --- Rectangular volume, the turtle will halt if it leaves its bounds
- local wood = {1, 2, 3, 4} --- Wood slots
- local leaves = {5, 6, 7, 8} --- Leaves slots
- local saplings = {9, 10, 11, 12} --- Sapling slots
- local dirt = {} --- Dirt slots. If left empty, dirt filters are off (slight speed improvement and more inventory space), but the turtle might try to plant saplings in invalid places.
- local fuel = 13 --- Fuel slot
- local freeSlots = {14, 15, 16} --- Free slots. For best results, these should be the last slots, and there should be no empty slots before them
- local fuelLevel = 100 --- Minimum fuel level before refuel
- local fuelTreshold = 8 --- Minimum fuel item count before returning to base
- local saplingCount = 32 --- Number of saplings to keep in inventory
- local calibrationSleep = 5 --- Seconds to sleep before attempting to calibrate
- -----------------------
- --- State variables ---
- local self = {x = base.x, y = base.y, z = base.z} --- Coordinates
- local dx, dz = 1, 0 --- Heading
- local lastWood = false --- Last wood type checked by canChop
- local stack = {} --- List of blocks to chop down and scan for neighbors
- local replant = {} --- List of blocks to plant saplings below
- --- Not saving ---
- local target --- Target block to move to
- local hop = false --- Hopping over obstacle
- -------------------------------------
- --- Other variables and constants ---
- local comparator = {front = turtle.compare, up = turtle.compareUp, down = turtle.compareDown} --- Comparator function list for indexed access
- local region = {} --- Rectangle that includes all regions for random wandering
- local noDirt = (#dirt == 0) --- If true, try to plant saplings on anything
- --------------------------------
- --- State saving and loading ---
- function saveState()
- local h = fs.open("kns.state", "w")
- if not h then halt("saveState: Failed to open the file") end
- h.writeLine(self.x)
- h.writeLine(self.y)
- h.writeLine(self.z)
- h.writeLine(dx)
- h.writeLine(dz)
- h.writeLine(lastWood)
- h.close()
- end
- function saveStack()
- local h = fs.open("kns.stack", "w")
- if not h then halt("saveStack: Failed to open the file") end
- for item = 1, #stack do
- h.writeLine(stack[item].x)
- h.writeLine(stack[item].y)
- h.writeLine(stack[item].z)
- h.writeLine(stack[item].visited == true)
- h.writeLine(stack[item].wood or 0)
- end
- h.close()
- end
- function saveReplant()
- local h = fs.open("kns.replant", "w")
- if not h then halt("saveReplant: Failed to open the file") end
- for item = 1, #replant do
- h.writeLine(replant[item].x)
- h.writeLine(replant[item].y)
- h.writeLine(replant[item].z)
- h.writeLine(replant[item].wood)
- end
- h.close()
- end
- --- Loading ---
- function loadState()
- local h = fs.open("kns.state", "r")
- if h then
- self.x = maybe(h.readLine())
- self.y = maybe(h.readLine())
- self.z = maybe(h.readLine())
- dx = maybe(h.readLine())
- dz = maybe(h.readLine())
- lastWood = numberOrBoolean(h.readLine())
- h.close()
- loadStack()
- loadReplant()
- else
- report("State file not found")
- halt("Make sure the turtle is sitting at the base and is facing EAST")
- saveState()
- saveStack()
- saveReplant()
- end
- end
- function loadStack()
- local h = fs.open("kns.stack", "r")
- if not h then halt("loadStack: Failed to open the file") end
- local item = 0
- repeat
- item = item + 1
- stack[item] = {}
- stack[item].x = maybe(h.readLine())
- stack[item].y = maybe(h.readLine())
- stack[item].z = maybe(h.readLine())
- stack[item].visited = (h.readLine() == "true")
- stack[item].wood = maybe(h.readLine())
- until not stack[item].x
- stack[item] = nil
- h.close()
- end
- function loadReplant()
- local h = fs.open("kns.replant", "r")
- if not h then halt("loadReplant: Failed to open the file") end
- local item = 0
- repeat
- item = item + 1
- replant[item] = {}
- replant[item].x = maybe(h.readLine())
- replant[item].y = maybe(h.readLine())
- replant[item].z = maybe(h.readLine())
- replant[item].wood = maybe(h.readLine())
- until not replant[item].x
- replant[item] = nil
- h.close()
- end
- ---------------
- --- Utility ---
- function maybe(str)
- if str then return tonumber(str) else return nil end
- end
- function numberOrBoolean(str)
- if not str then
- return false
- elseif str == "false" then
- return false
- elseif str == "true" then
- return true
- else
- return tonumber(str)
- end
- end
- function report(msg)
- local stackSize = #stack
- print("{" .. self.x .. "," .. self.y .. "," .. self.z .. ":" .. dx .. "," .. dz .. ":" .. stackSize .. "}" .. msg)
- end
- function halt(msg)
- report(msg .. " (Press enter...)")
- read()
- end
- -----------------------
- --- Turtle commands ---
- function turnLeft()
- while not turtle.turnLeft() do report("turnLeft: Failed") attack() end
- dx, dz = dz, -dx
- saveState()
- end
- function turnRight()
- while not turtle.turnRight() do report("turnRight: Failed") attack() end
- dx, dz = -dz, dx
- saveState()
- end
- function stepForward()
- while turtle.detect() and turtle.select(1) and not turtle.dig() do report("stepForward: Failed to dig or select slot 1") attack() end
- while not turtle.forward() do report("stepForward: Failed to move") attack() end
- self.x, self.z = self.x + dx, self.z + dz
- saveState()
- end
- function stepUp()
- while turtle.detectUp() and turtle.select(1) and not turtle.digUp() do report("stepUp: Failed to dig or select slot 1") attackUp() end
- while not turtle.up() do report("stepUp: Failed to move") attackUp() end
- self.y = self.y + 1
- saveState()
- end
- function stepDown()
- while turtle.detectDown() and turtle.select(1) and not turtle.digDown() do report("stepDown: Failed to dig or select slot 1") attackDown() end
- while not turtle.down() do report("stepDown: Failed to move") attackDown() end
- self.y = self.y - 1
- saveState()
- end
- function faceSide(block)
- if (block.x == self.x) then
- if (block.z ~= self.z) then
- while ((block.z - self.z) * dz) <= 0 do
- if ((block.z - self.z) * dx) >= 0 then turnRight() else turnLeft() end
- end
- end
- elseif (block.z == self.z) then
- if (block.x ~= self.x) then
- while ((block.x - self.x) * dx) <= 0 do
- if ((block.x - self.x) * dz) >= 0 then turnLeft() else turnRight() end
- end
- end
- else
- halt("faceSide: Not an adjacent block {" .. block.x .. "," .. block.y .. "," .. block.z .. "}")
- end
- end
- function faceBlock(block)
- if math.abs(block.x - self.x) > math.abs(block.z - self.z) then
- while dx ~= sign(block.x - self.x) do
- if (dz * (block.x - self.x)) > 0 then turnLeft() else turnRight() end
- end
- elseif math.abs(block.z - self.z) > 0 then
- while dz ~= sign(block.z - self.z) do
- if (dx * (block.z - self.z)) > 0 then turnRight() else turnLeft() end
- end
- end
- end
- function attack()
- turtle.attack()
- end
- function attackUp()
- turtle.attackUp()
- end
- function attackDown()
- turtle.attackDown()
- end
- ------------
- --- Math ---
- function sign(value)
- return ((value > 0 and 1) or (value < 0 and -1) or 0)
- end
- -----------------------------------------
- --- Blocks and coordinates operations ---
- function compareBlocks(a, b)
- return ((a.x == b.x) and (a.y == b.y) and (a.z == b.z))
- end
- function getUp(block)
- return {x = block.x, y = block.y + 1, z = block.z}
- end
- function getDown(block)
- return {x = block.x, y = block.y - 1, z = block.z}
- end
- function getFront(block)
- return {x = block.x + dx, y = block.y, z = block.z + dz}
- end
- function getBack(block)
- return {x = block.x - dx, y = block.y, z = block.z - dz}
- end
- function getRight(block)
- return {x = block.x - dz, y = block.y, z = block.z + dx}
- end
- function getLeft(block)
- return {x = block.x + dz, y = block.y, z = block.z - dx}
- end
- ------------------------
- --- Stack operations ---
- function evalBlock(block)
- return math.abs(block.x - self.x) + math.abs(block.y - self.y) + math.abs(block.z - self.z) +
- (math.abs(sign(block.x - self.x) - dx) + math.abs(sign(block.z - self.z) - dz)) / 4
- end
- function findBestBlock()
- local best, bestEval
- local itemEval
- for item = 1, #stack do
- if (not stack[item].visited) then
- itemEval = evalBlock(stack[item])
- if (best == nil) or (bestEval > itemEval) then
- best = item
- bestEval = itemEval
- end
- end
- end
- return best
- end
- function findBlock(block)
- for item = 1, #stack do
- if compareBlocks(stack[item], block) then
- return true
- end
- end
- return false
- end
- function pushStack(block)
- block.wood = lastWood
- stack[#stack + 1] = block
- end
- --- Saplings ---
- function findBestReplant()
- local best, bestEval
- local itemEval
- for item = 1, #replant do
- itemEval = evalBlock(replant[item])
- if (best == nil) or (bestEval > itemEval) then
- best = item
- bestEval = itemEval
- end
- end
- return best
- end
- function pushReplant(block)
- replant[#replant + 1] = {x = block.x, y = block.y + 1, z = block.z, wood = block.wood}
- end
- function popReplant(block)
- local found
- repeat
- found = false
- for item = 1, #replant do
- if compareBlocks(replant[item], block) then found = item break end
- end
- if found then table.remove(replant, found) end
- until not found
- end
- --- General ---
- function clear(array)
- local size = #array
- for item = 1, size do
- array[item] = nil
- end
- end
- ----------------------------
- --- Inventory operations ---
- function compareSingle(direction, list, item)
- if not turtle.select(list[item]) then halt("compare(" .. direction .. "): Failed to select slot " .. list[item]) end
- if comparator[direction]() then return item end
- return false
- end
- function compare(direction, list)
- for item = 1, #list do
- if not turtle.select(list[item]) then halt("compare(" .. direction .. "): Failed to select slot " .. list[item]) end
- if comparator[direction]() then return item end
- end
- return false
- end
- ---------------------------
- --- Chopping operations ---
- function canChop(direction)
- if lastWood then
- return compareSingle(direction, wood, lastWood) or compareSingle(direction, leaves, lastWood)
- else
- lastWood = compare(direction, wood)
- return lastWood or compare(direction, leaves)
- end
- end
- function processStack()
- if #stack == 0 then return false end
- local item = findBestBlock()
- if item then
- if stack[item] ~= target then
- target = stack[item]
- report("Target: Block #" .. item .. " {" .. target.x .. "," .. target.y .. "," .. target.z .. "}")
- end
- if compareBlocks(target, self) then
- if turtle.detectUp() and (not findBlock(getUp(self))) and canChop("up") then pushStack(getUp(self)) end
- if turtle.detectDown() then
- if (not findBlock(getDown(self))) and canChop("down") then
- pushStack(getDown(self))
- elseif target.wood and (noDirt or compare("down", dirt)) then
- pushReplant(target)
- saveReplant()
- end
- end
- if turtle.detect() and (not findBlock(getFront(self))) and canChop("front") then pushStack(getFront(self)) end
- --- Hopefully, efficient turning
- local side = {
- block = getRight(self),
- turn = {
- block=getBack(self),
- turn = {block=getLeft(self)}
- },
- skip = {
- block = getLeft(self),
- turn = {block=getBack(self)}
- }
- }
- while side do
- if turtle.detect() and (not findBlock(side.block)) then
- faceSide(side.block)
- if canChop("front") then pushStack(side.block) end
- side = side.turn
- else
- side = side.skip or side.turn
- end
- end
- target.visited = true
- saveStack()
- target = nil
- end
- return true
- else
- lastWood = false
- clear(stack)
- saveStack()
- end
- return false
- end
- ------------------
- --- Replanting ---
- function processReplant()
- if #replant == 0 then return false end
- local item = findBestReplant()
- if replant[item] ~= target then
- target = replant[item]
- report("Target: Replant #" .. item .. " {" .. target.x .. "," .. target.y .. "," .. target.z .. "}")
- end
- if compareBlocks(target, self) then
- if not turtle.select(saplings[target.wood]) then halt("processReplant: Failed to select slot " .. saplings[target.wood]) end
- if noDirt then
- if not turtle.placeDown() then report("Failed to plant sapling here") end
- else
- while not turtle.placeDown() do report("processReplant: Failed to place down") attackDown() end
- end
- if turtle.getItemCount(saplings[target.wood]) < 2 then
- halt("Universal entropy law violation: Saplings low")
- end
- popReplant(target)
- saveReplant()
- target = nil
- end
- return true
- end
- --------------
- --- Moving ---
- function move()
- if (self.x < bounds.lt.x) or (self.y < bounds.lt.y) or (self.z < bounds.lt.z) or
- (self.x > bounds.rb.x) or (self.y > bounds.rb.y) or (self.z > bounds.rb.z) then
- halt("Emergency halt: moved out of bounds")
- end
- if not target then return false end
- faceBlock(target)
- if (self.x == target.x) and (self.z == target.z) then
- if target.y then --- Target y is set: get to the desired altitude
- if self.y > target.y then
- if turtle.detectDown() and (not findBlock(getDown(self))) and canChop("down") then pushStack(getDown(self)) saveStack() else stepDown() end
- elseif self.y < target.y then
- if turtle.detectUp() and (not findBlock(getUp(self))) and canChop("up") then pushStack(getUp(self)) saveStack() else stepUp() end
- else
- return false
- end
- else --- Target y is not set: just keep to the ground
- if turtle.detectDown() then
- if (not findBlock(getDown(self))) and canChop("down") then
- pushStack(getDown(self))
- saveStack()
- else
- return false
- end
- else
- stepDown()
- end
- end
- else
- if target.y then --- Target y is set: get as high as necessary and go
- if self.y ~= math.max(self.y, target.y) then
- if turtle.detectUp() and (not findBlock(getUp(self))) and canChop("up") then pushStack(getUp(self)) saveStack() else stepUp() end
- else
- if turtle.detect() then
- if not findBlock(getFront(self)) then
- if canChop("front") then
- pushStack(getFront(self))
- saveStack()
- elseif (not findBlock(getUp(self))) and canChop("up") then --- Unchoppable obstacle in front: try to hop over it
- pushStack(getUp(self))
- saveStack()
- else
- stepUp()
- end
- else
- stepForward()
- end
- else
- stepForward()
- end
- end
- else --- Target y is not set: keep to the ground, except when hopping over obstacles
- if turtle.detectDown() or hop then
- hop = false
- if turtle.detect() then
- if not findBlock(getFront(self)) then
- if canChop("front") then
- pushStack(getFront(self))
- saveStack()
- elseif (not findBlock(getUp(self))) and canChop("up") then --- Unchoppable obstacle in front: try to hop over it
- pushStack(getUp(self))
- saveStack()
- else
- hop = true
- stepUp()
- end
- else
- stepForward()
- end
- else
- stepForward()
- end
- else
- if turtle.detectDown() and (not findBlock(getDown(self))) and canChop("down") then pushStack(getDown(self)) saveStack() else stepDown() end
- end
- end
- end
- return true
- end
- -------------------------------
- --- Refueling and unloading ---
- function checkFuel()
- while turtle.getFuelLevel() < fuelLevel do
- report("Consuming fuel...")
- if not turtle.select(fuel) then halt("checkFuel: Failed to select slot " .. fuel)
- elseif not turtle.refuel(1) then halt("checkFuel: Failed to refuel") end
- if (target ~= base) and (turtle.getItemCount(fuel) < fuelTreshold) then
- report("Fuel low, returning to base...")
- target = base
- return true
- end
- end
- return false
- end
- function checkInventory()
- --- Should return to base if any of the slots is filled up and all of the slots are occupied
- local freeSlot = false
- local fullSlot = false
- for slot = 1, 16 do
- local count = turtle.getItemCount(slot)
- if count == 0 then freeSlot = true break end
- if count == 64 then fullSlot = true end
- end
- if fullSlot and not freeSlot then
- report("Inventory full, returning to base...")
- target = base
- return true
- end
- return false
- end
- function checkBase()
- --- Suck up, supposedly, fuel from the chest below and then drop off all of the unnecessary items
- if target ~= base then return false end
- if compareBlocks(self, base) then
- if not turtle.select(fuel) then halt("checkBase: Failed to select slot " .. fuel) end
- if not turtle.suckDown() then report("checkBase: Failed to suck fuel") end
- for item = 1, #wood do
- if not turtle.select(wood[item]) then halt("checkBase: Failed to select slot " .. wood[item]) end
- local count = turtle.getItemCount(wood[item])
- if count > 1 then
- if not turtle.dropDown(count - 1) then report("checkBase: Failed to drop wood") end
- end
- end
- for item = 1, #saplings do
- if not turtle.select(saplings[item]) then halt("checkBase: Failed to select slot " .. saplings[item]) end
- local count = turtle.getItemCount(saplings[item])
- if count > saplingCount then
- if not turtle.dropDown(count - saplingCount) then report("checkBase: Failed to drop saplings") end
- end
- end
- for item = 1, #freeSlots do
- if not turtle.select(freeSlots[item]) then halt("checkBase: Failed to select slot " .. freeSlots[item]) end
- local count = turtle.getItemCount(freeSlots[item])
- if count > 0 then
- if not turtle.dropDown(count) then report("checkBase: Failed to drop loot") end
- end
- end
- target = nil
- end
- return true
- end
- -----------------
- --- Wandering ---
- function wander()
- --- Assumption is that we are at the target (otherwise move would return true)
- --- And that target originates from this function (correct main loop order should guarantee that)
- --- So, no matter what the target is, we reset it
- local found
- repeat
- target = {x = region.lt.x + math.random(region.rb.x - region.lt.x + 1) - 1, y = false, z = region.lt.z + math.random(region.rb.z - region.lt.z + 1) - 1}
- found = false
- for item = 1, #regions do
- if (target.x >= regions[item].lt.x) and (target.x <= regions[item].rb.x) and
- (target.z >= regions[item].lt.z) and (target.z <= regions[item].rb.z) then
- found = true
- break
- end
- end
- until found
- report("Target: Random {" .. target.x .. ",---," .. target.z .. "}")
- return true
- end
- function checkTarget()
- --- Drop the random wandering target if any of the important stuff needs to be done
- if target and (not target.y) and ((#stack > 0) or (#replant > 0)) then
- target = nil
- end
- return false
- end
- ------------------------------
- --- Various initialization ---
- function init()
- for item = 1, #regions do
- local current = {
- lt = {x = math.min(regions[item].lt.x, regions[item].rb.x), z = math.min(regions[item].lt.z, regions[item].rb.z)},
- rb = {x = math.max(regions[item].lt.x, regions[item].rb.x), z = math.max(regions[item].lt.z, regions[item].rb.z)}
- }
- if not region.lt then region.lt = {x = current.lt.x, z = current.lt.z} end
- if region.lt.x > current.lt.x then region.lt.x = current.lt.x end
- if region.lt.z > current.lt.z then region.lt.z = current.lt.z end
- if not region.rb then region.rb = {x = current.rb.x, z = current.rb.z} end
- if region.rb.x < current.rb.x then region.rb.x = current.rb.x end
- if region.rb.z < current.rb.z then region.rb.z = current.rb.z end
- end
- end
- function calibrate()
- report("Attempting to calibrate...")
- sleep(calibrationSleep)
- local tx, ty, tz = gps.locate(calibrationSleep)
- if tx then
- self = {x = tx, y = ty, z = tz}
- saveState()
- report("Coordinates calibrated")
- local attempt = 0
- local nx, ny, nz
- repeat
- if turtle.detect() then
- attempt = attempt + 1
- if (attempt % 5) == 0 then
- if turtle.up() then
- self.y = self.y + 1
- else
- report("Heading calibration failed")
- return false
- end
- else
- if turtle.turnRight() then
- dx, dz = -dz, dx
- saveState()
- else
- report("Heading calibration failed")
- return false
- end
- end
- else
- if turtle.forward() then
- self.x = self.x + dx
- self.z = self.z + dz
- saveState()
- else
- report("Heading calibration failed")
- return false
- end
- end
- nx, ny, nz = gps.locate(calibrationSleep)
- if not nx then
- report("Heading calibration failed")
- return false
- end
- if attempt > 100 then
- report("Heading calibration failed")
- return false
- end
- until (nx ~= tx) or (nz ~= tz)
- self = {x = nx, y = ny, z = nz}
- dx = nx - tx
- dz = nz - tz
- saveState()
- report("Heading calibrated")
- return true
- else
- report("Calibration failed")
- return false
- end
- end
- -----------------
- --- Main loop ---
- print("------------------------------")
- print("--- Kimitsu no Shoufu v0.3 ---")
- print("------------------------------")
- init()
- loadState()
- checkFuel()
- while not calibrate() do sleep(0) end
- report("Main loop activated")
- while
- checkTarget() or
- move() or
- checkBase() or
- checkFuel() or
- checkInventory() or
- processStack() or
- processSaplings() or
- wander()
- do sleep (0) end
- report("Main loop terminated")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement