diff --git a/builder/.package b/builder/.package
index e1140b2..bfcf3b7 100644
--- a/builder/.package
+++ b/builder/.package
@@ -1,6 +1,7 @@
{
required = {
'core',
+ 'turtle',
},
title = 'Schematic Builder',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/builder',
diff --git a/gps/.package b/gps/.package
index 8e1ee40..9a31617 100644
--- a/gps/.package
+++ b/gps/.package
@@ -1,4 +1,7 @@
{
+ required = {
+ 'turtle',
+ },
title = 'GPS',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/gps',
description = [[A single turtle GPS server
diff --git a/milo/MiloLocal.lua b/milo/MiloLocal.lua
index d3a93ca..68b409f 100644
--- a/milo/MiloLocal.lua
+++ b/milo/MiloLocal.lua
@@ -180,21 +180,17 @@ Event.on('turtle_inventory', function()
end)
end)
-local cycleHandle
-cycleHandle = Event.onInterval(5, function()
+Event.onInterval(5, function()
Event.trigger('milo_cycle')
- if context.taskCounter > 0 then
- --local average = context.taskTimer / context.taskCounter
- --_syslog('Interval: ' .. math.max(5, 2 + average * 3))
- --cycleHandle.updateInterval(math.max(5, 2 + average * 3))
- end
end)
Event.on({ 'storage_offline', 'storage_online' }, function()
if context.storage:isOnline() then
Milo:resumeCrafting({ key = 'storageOnline' })
+ turtle.setStatus('Milo: online')
else
Milo:pauseCrafting({ key = 'storageOnline', msg = 'Storage offline' })
+ turtle.setStatus('Milo: offline')
end
end)
diff --git a/milo/apis/init.lua b/milo/apis/init.lua
index 5287811..41b42b2 100644
--- a/milo/apis/init.lua
+++ b/milo/apis/init.lua
@@ -211,6 +211,16 @@ function Milo:makeRequest(item, count, callback)
return request
end
+function Milo:emptyInventory()
+ for i = 1, 16 do
+ if turtle.getItemCount(i) > 0 then
+ turtle.select(i)
+ turtle.drop()
+ end
+ end
+ turtle.select(1)
+end
+
function Milo:eject(item, count)
local total = 0
while count > 0 do
@@ -224,7 +234,7 @@ function Milo:eject(item, count)
--Sound.play('ui.button.click')
Sound.play('entity.illusion_illager.death', .3)
- turtle.emptyInventory()
+ self:emptyInventory()
end
return total
end
diff --git a/milo/core/learnWizard.lua b/milo/core/learnWizard.lua
index ee5ff2f..0f8917f 100644
--- a/milo/core/learnWizard.lua
+++ b/milo/core/learnWizard.lua
@@ -77,7 +77,7 @@ end
function learnPage:eventHandler(event)
if event.type == 'cancel' then
- turtle.emptyInventory()
+ Milo:emptyInventory()
UI:setPreviousPage()
elseif event.type == 'form_invalid' or event.type == 'general_error' then
diff --git a/milo/core/machines.lua b/milo/core/machines.lua
index b29a60b..844e491 100644
--- a/milo/core/machines.lua
+++ b/milo/core/machines.lua
@@ -346,7 +346,7 @@ function nodeWizard.filter:eventHandler(event)
self:resetGrid()
self.grid:update()
self.grid:draw()
- turtle.emptyInventory()
+ Milo:emptyInventory()
elseif event.type == 'remove_entry' then
local row = self.grid:getSelected()
diff --git a/milo/plugins/machineLearn.lua b/milo/plugins/machineLearn.lua
index 76e5069..388a4f0 100644
--- a/milo/plugins/machineLearn.lua
+++ b/milo/plugins/machineLearn.lua
@@ -122,7 +122,7 @@ function pages.confirmation:validate()
end
Milo:saveMachineRecipe(recipe, result, machine.name)
- turtle.emptyInventory()
+ Milo:emptyInventory()
local displayName = itemDB:getName(result)
UI:setPage('listing', {
diff --git a/milo/plugins/turtleLearn.lua b/milo/plugins/turtleLearn.lua
index 43c6125..2c09abc 100644
--- a/milo/plugins/turtleLearn.lua
+++ b/milo/plugins/turtleLearn.lua
@@ -23,7 +23,7 @@ function pages.turtleCraft:validate()
if recipe then
local displayName = itemDB:getName(recipe)
- turtle.emptyInventory()
+ Milo:emptyInventory()
UI:setPage('listing', {
filter = displayName,
diff --git a/miloApps/.package b/miloApps/.package
new file mode 100644
index 0000000..30e04c8
--- /dev/null
+++ b/miloApps/.package
@@ -0,0 +1,9 @@
+{
+ required = {
+ 'turtle',
+ },
+ title = 'Milo extra applications',
+ repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/miloApps',
+ description = [[Programs for turtles in a Milo network]],
+ licence = 'MIT',
+}
diff --git a/milo/apps/brewArray.lua b/miloApps/apps/brewArray.lua
similarity index 100%
rename from milo/apps/brewArray.lua
rename to miloApps/apps/brewArray.lua
diff --git a/milo/apps/cobblegen.lua b/miloApps/apps/cobblegen.lua
similarity index 100%
rename from milo/apps/cobblegen.lua
rename to miloApps/apps/cobblegen.lua
diff --git a/milo/apps/enderchest.lua b/miloApps/apps/enderchest.lua
similarity index 100%
rename from milo/apps/enderchest.lua
rename to miloApps/apps/enderchest.lua
diff --git a/milo/apps/furni.lua b/miloApps/apps/furni.lua
similarity index 100%
rename from milo/apps/furni.lua
rename to miloApps/apps/furni.lua
diff --git a/milo/apps/storageGen.lua b/miloApps/apps/storageGen.lua
similarity index 100%
rename from milo/apps/storageGen.lua
rename to miloApps/apps/storageGen.lua
diff --git a/milo/apps/water.lua b/miloApps/apps/water.lua
similarity index 100%
rename from milo/apps/water.lua
rename to miloApps/apps/water.lua
diff --git a/miners/simpleMiner.lua b/miners/simpleMiner.lua
index afd7c14..4acd2b4 100644
--- a/miners/simpleMiner.lua
+++ b/miners/simpleMiner.lua
@@ -1,4 +1,4 @@
-local Pathing = require('opus.pathfind')
+local Pathing = require('turtle.pathfind')
local Point = require('opus.point')
local Util = require('opus.util')
diff --git a/packages.list b/packages.list
index 7f56846..cf5eb83 100644
--- a/packages.list
+++ b/packages.list
@@ -10,6 +10,7 @@
[ 'gps' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/gps/.package',
[ 'mbs' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/mbs/.package',
[ 'milo' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/milo/.package',
+ [ 'miloApps' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/miloApps/.package',
[ 'miners' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/miners/.package',
[ 'monitor' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/monitor/.package',
[ 'neural' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/neural/.package',
diff --git a/pickup/.package b/pickup/.package
index ea5dd64..3e1b222 100644
--- a/pickup/.package
+++ b/pickup/.package
@@ -1,6 +1,7 @@
{
required = {
'core',
+ 'turtle',
},
title = 'Move resources around with turtles',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/pickup',
diff --git a/turtle/apis/jumper/core/bheap.lua b/turtle/apis/jumper/core/bheap.lua
new file mode 100644
index 0000000..c58ce2f
--- /dev/null
+++ b/turtle/apis/jumper/core/bheap.lua
@@ -0,0 +1,175 @@
+--- A light implementation of Binary heaps data structure.
+-- While running a search, some search algorithms (Astar, Dijkstra, Jump Point Search) have to maintains
+-- a list of nodes called __open list__. Retrieve from this list the lowest cost node can be quite slow,
+-- as it normally requires to skim through the full set of nodes stored in this list. This becomes a real
+-- problem especially when dozens of nodes are being processed (on large maps).
+--
+-- The current module implements a binary heap
+-- data structure, from which the search algorithm will instantiate an open list, and cache the nodes being
+-- examined during a search. As such, retrieving the lower-cost node is faster and globally makes the search end
+-- up quickly.
+--
+-- This module is internally used by the library on purpose.
+-- It should normally not be used explicitely, yet it remains fully accessible.
+--
+
+--[[
+ Notes:
+ This lighter implementation of binary heaps, based on :
+ https://github.com/Yonaba/Binary-Heaps
+--]]
+
+if (...) then
+
+ -- Dependency
+ local Utils = require((...):gsub('%.bheap$','.utils'))
+
+ -- Local reference
+ local floor = math.floor
+
+ -- Default comparison function
+ local function f_min(a,b) return a < b end
+
+ -- Percolates up
+ local function percolate_up(heap, index)
+ if index == 1 then return end
+ local pIndex
+ if index <= 1 then return end
+ if index%2 == 0 then
+ pIndex = index/2
+ else pIndex = (index-1)/2
+ end
+ if not heap._sort(heap._heap[pIndex], heap._heap[index]) then
+ heap._heap[pIndex], heap._heap[index] =
+ heap._heap[index], heap._heap[pIndex]
+ percolate_up(heap, pIndex)
+ end
+ end
+
+ -- Percolates down
+ local function percolate_down(heap,index)
+ local lfIndex,rtIndex,minIndex
+ lfIndex = 2*index
+ rtIndex = lfIndex + 1
+ if rtIndex > heap._size then
+ if lfIndex > heap._size then return
+ else minIndex = lfIndex end
+ else
+ if heap._sort(heap._heap[lfIndex],heap._heap[rtIndex]) then
+ minIndex = lfIndex
+ else
+ minIndex = rtIndex
+ end
+ end
+ if not heap._sort(heap._heap[index],heap._heap[minIndex]) then
+ heap._heap[index],heap._heap[minIndex] = heap._heap[minIndex],heap._heap[index]
+ percolate_down(heap,minIndex)
+ end
+ end
+
+ -- Produces a new heap
+ local function newHeap(template,comp)
+ return setmetatable({_heap = {},
+ _sort = comp or f_min, _size = 0},
+ template)
+ end
+
+
+ --- The `heap` class.
+ -- This class is callable.
+ -- _Therefore,_ heap(...) _is used to instantiate new heaps_.
+ -- @type heap
+ local heap = setmetatable({},
+ {__call = function(self,...)
+ return newHeap(self,...)
+ end})
+ heap.__index = heap
+
+ --- Checks if a `heap` is empty
+ -- @class function
+ -- @treturn bool __true__ of no item is queued in the heap, __false__ otherwise
+ -- @usage
+ -- if myHeap:empty() then
+ -- print('Heap is empty!')
+ -- end
+ function heap:empty()
+ return (self._size==0)
+ end
+
+ --- Clears the `heap` (removes all items queued in the heap)
+ -- @class function
+ -- @treturn heap self (the calling `heap` itself, can be chained)
+ -- @usage myHeap:clear()
+ function heap:clear()
+ self._heap = {}
+ self._size = 0
+ self._sort = self._sort or f_min
+ return self
+ end
+
+ --- Adds a new item in the `heap`
+ -- @class function
+ -- @tparam value item a new value to be queued in the heap
+ -- @treturn heap self (the calling `heap` itself, can be chained)
+ -- @usage
+ -- myHeap:push(1)
+ -- -- or, with chaining
+ -- myHeap:push(1):push(2):push(4)
+ function heap:push(item)
+ if item then
+ self._size = self._size + 1
+ self._heap[self._size] = item
+ percolate_up(self, self._size)
+ end
+ return self
+ end
+
+ --- Pops from the `heap`.
+ -- Removes and returns the lowest cost item (with respect to the comparison function being used) from the `heap`.
+ -- @class function
+ -- @treturn value a value previously pushed into the heap
+ -- @usage
+ -- while not myHeap:empty() do
+ -- local lowestValue = myHeap:pop()
+ -- ...
+ -- end
+ function heap:pop()
+ local root
+ if self._size > 0 then
+ root = self._heap[1]
+ self._heap[1] = self._heap[self._size]
+ self._heap[self._size] = nil
+ self._size = self._size-1
+ if self._size>1 then
+ percolate_down(self, 1)
+ end
+ end
+ return root
+ end
+
+ --- Restores the `heap` property.
+ -- Reorders the `heap` with respect to the comparison function being used.
+ -- When given argument __item__ (a value existing in the `heap`), will sort from that very item in the `heap`.
+ -- Otherwise, the whole `heap` will be cheacked.
+ -- @class function
+ -- @tparam[opt] value item the modified value
+ -- @treturn heap self (the calling `heap` itself, can be chained)
+ -- @usage myHeap:heapify()
+ function heap:heapify(item)
+ if self._size == 0 then return end
+ if item then
+ local i = Utils.indexOf(self._heap,item)
+ if i then
+ percolate_down(self, i)
+ percolate_up(self, i)
+ end
+ return
+ end
+ for i = floor(self._size/2),1,-1 do
+ percolate_down(self,i)
+ end
+ return self
+ end
+
+ return heap
+end
\ No newline at end of file
diff --git a/turtle/apis/jumper/core/node.lua b/turtle/apis/jumper/core/node.lua
new file mode 100644
index 0000000..09d1db4
--- /dev/null
+++ b/turtle/apis/jumper/core/node.lua
@@ -0,0 +1,41 @@
+--- The Node class.
+-- The `node` represents a cell (or a tile) on a collision map. Basically, for each single cell (tile)
+-- in the collision map passed-in upon initialization, a `node` object will be generated
+-- and then cached within the `grid`.
+--
+-- In the following implementation, nodes can be compared using the `<` operator. The comparison is
+-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
+-- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details.
+--
+if (...) then
+
+ local Node = {}
+ Node.__index = Node
+
+ function Node:new(x,y,z)
+ return setmetatable({x = x, y = y, z = z }, Node)
+ end
+
+ -- Enables the use of operator '<' to compare nodes.
+ -- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost
+ function Node.__lt(A,B) return (A._f < B._f) end
+
+ function Node:getX() return self.x end
+ function Node:getY() return self.y end
+ function Node:getZ() return self.z end
+
+ --- Clears temporary cached attributes of a `node`.
+ -- Deletes the attributes cached within a given node after a pathfinding call.
+ -- This function is internally used by the search algorithms, so you should not use it explicitely.
+ function Node:reset()
+ self._g, self._h, self._f = nil, nil, nil
+ self._opened, self._closed, self._parent = nil, nil, nil
+ return self
+ end
+
+ return setmetatable(Node,
+ {__call = function(_,...)
+ return Node:new(...)
+ end}
+ )
+end
\ No newline at end of file
diff --git a/turtle/apis/jumper/core/path.lua b/turtle/apis/jumper/core/path.lua
new file mode 100644
index 0000000..f02c41b
--- /dev/null
+++ b/turtle/apis/jumper/core/path.lua
@@ -0,0 +1,67 @@
+--- The Path class.
+-- The `path` class is a structure which represents a path (ordered set of nodes) from a start location to a goal.
+-- An instance from this class would be a result of a request addressed to `Pathfinder:getPath`.
+--
+-- This module is internally used by the library on purpose.
+-- It should normally not be used explicitely, yet it remains fully accessible.
+--
+
+if (...) then
+
+ local t_remove = table.remove
+
+ local Path = {}
+ Path.__index = Path
+
+ function Path:new()
+ return setmetatable({_nodes = {}}, Path)
+ end
+
+ --- Iterates on each single `node` along a `path`. At each step of iteration,
+ -- returns the `node` plus a count value. Aliased as @{Path:nodes}
+ -- @usage
+ -- for node, count in p:iter() do
+ -- ...
+ -- end
+ function Path:nodes()
+ local i = 1
+ return function()
+ if self._nodes[i] then
+ i = i+1
+ return self._nodes[i-1],i-1
+ end
+ end
+ end
+
+ --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path`
+ -- consisting of straight moves. Does the opposite of @{Path:fill}
+ -- @class function
+ -- @treturn path self (the calling `path` itself, can be chained)
+ -- @see Path:fill
+ -- @usage p:filter()
+ function Path:filter()
+ local i = 2
+ local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz
+ xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z
+ dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z
+ while true do
+ olddx, olddy, olddz = dx, dy, dz
+ if self._nodes[i+1] then
+ i = i+1
+ xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z
+ dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z
+ if olddx == dx and olddy == dy and olddz == dz then
+ t_remove(self._nodes, i-1)
+ i = i - 1
+ end
+ else break end
+ end
+ return self
+ end
+
+ return setmetatable(Path,
+ {__call = function(_,...)
+ return Path:new(...)
+ end
+ })
+end
\ No newline at end of file
diff --git a/turtle/apis/jumper/core/utils.lua b/turtle/apis/jumper/core/utils.lua
new file mode 100644
index 0000000..ab5f764
--- /dev/null
+++ b/turtle/apis/jumper/core/utils.lua
@@ -0,0 +1,57 @@
+-- Various utilities for Jumper top-level modules
+
+if (...) then
+
+ -- Dependencies
+ local _PATH = (...):gsub('%.utils$','')
+ local Path = require (_PATH .. '.path')
+
+ -- Local references
+ local pairs = pairs
+ local t_insert = table.insert
+
+ -- Raw array items count
+ local function arraySize(t)
+ local count = 0
+ for _ in pairs(t) do
+ count = count+1
+ end
+ return count
+ end
+
+ -- Extract a path from a given start/end position
+ local function traceBackPath(finder, node, startNode)
+ local path = Path:new()
+ path._grid = finder._grid
+ while true do
+ if node._parent then
+ t_insert(path._nodes,1,node)
+ node = node._parent
+ else
+ t_insert(path._nodes,1,startNode)
+ return path
+ end
+ end
+ end
+
+ -- Lookup for value in a table
+ local indexOf = function(t,v)
+ for i = 1,#t do
+ if t[i] == v then return i end
+ end
+ return nil
+ end
+
+ -- Is i out of range
+ local function outOfRange(i,low,up)
+ return (i< low or i > up)
+ end
+
+ return {
+ arraySize = arraySize,
+ indexOf = indexOf,
+ outOfRange = outOfRange,
+ traceBackPath = traceBackPath
+ }
+
+end
diff --git a/turtle/apis/jumper/grid.lua b/turtle/apis/jumper/grid.lua
new file mode 100644
index 0000000..6cfc53a
--- /dev/null
+++ b/turtle/apis/jumper/grid.lua
@@ -0,0 +1,101 @@
+--- The Grid class.
+-- Implementation of the `grid` class.
+-- The `grid` is a implicit graph which represents the 2D
+-- world map layout on which the `pathfinder` object will run.
+-- During a search, the `pathfinder` object needs to save some critical values.
+-- These values are cached within each `node`
+-- object, and the whole set of nodes are tight inside the `grid` object itself.
+
+if (...) then
+
+ -- Dependencies
+ local _PATH = (...):gsub('%.grid$','')
+
+ -- Local references
+ local Utils = require (_PATH .. '.core.utils')
+ local Node = require (_PATH .. '.core.node')
+
+ -- Local references
+ local setmetatable = setmetatable
+
+ -- Offsets for straights moves
+ local straightOffsets = {
+ {x = 1, y = 0, z = 0} --[[W]], {x = -1, y = 0, z = 0}, --[[E]]
+ {x = 0, y = 1, z = 0} --[[S]], {x = 0, y = -1, z = 0}, --[[N]]
+ {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
+ }
+
+ local Grid = {}
+ Grid.__index = Grid
+
+ function Grid:new(dim)
+ local newGrid = { }
+ newGrid._min_x, newGrid._max_x = dim.x, dim.ex
+ newGrid._min_y, newGrid._max_y = dim.y, dim.ey
+ newGrid._min_z, newGrid._max_z = dim.z, dim.ez
+ newGrid._nodes = { }
+ newGrid._width = (newGrid._max_x-newGrid._min_x)+1
+ newGrid._height = (newGrid._max_y-newGrid._min_y)+1
+ newGrid._length = (newGrid._max_z-newGrid._min_z)+1
+ return setmetatable(newGrid,Grid)
+ end
+
+ function Grid:isWalkableAt(x, y, z)
+ local node = self:getNodeAt(x,y,z)
+ return node and node.walkable ~= 1
+ end
+
+ function Grid:getWidth()
+ return self._width
+ end
+
+ function Grid:getHeight()
+ return self._height
+ end
+
+ function Grid:getNodes()
+ return self._nodes
+ end
+
+ function Grid:getBounds()
+ return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z
+ end
+
+ --- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`.
+ -- @treturn {node,...} an array of nodes neighbouring a given node
+ function Grid:getNeighbours(node)
+ local neighbours = {}
+ for i = 1,#straightOffsets do
+ local n = self:getNodeAt(
+ node.x + straightOffsets[i].x,
+ node.y + straightOffsets[i].y,
+ node.z + straightOffsets[i].z
+ )
+ if n and self:isWalkableAt(n.x, n.y, n.z) then
+ neighbours[#neighbours+1] = n
+ end
+ end
+
+ return neighbours
+ end
+
+ function Grid:getNodeAt(x,y,z)
+ if not x or not y or not z then return end
+ if Utils.outOfRange(x,self._min_x,self._max_x) then return end
+ if Utils.outOfRange(y,self._min_y,self._max_y) then return end
+ if Utils.outOfRange(z,self._min_z,self._max_z) then return end
+
+ -- inefficient
+ if not self._nodes[y] then self._nodes[y] = {} end
+ if not self._nodes[y][x] then self._nodes[y][x] = {} end
+ if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end
+ return self._nodes[y][x][z]
+ end
+
+ return setmetatable(Grid,{
+ __call = function(self,...)
+ return self:new(...)
+ end
+ })
+
+end
diff --git a/turtle/apis/jumper/pathfinder.lua b/turtle/apis/jumper/pathfinder.lua
new file mode 100644
index 0000000..0ff2844
--- /dev/null
+++ b/turtle/apis/jumper/pathfinder.lua
@@ -0,0 +1,104 @@
+--[[
+ The following License applies to all files within the jumper directory.
+
+ Note that this is only a partial copy of the full jumper code base. Also,
+ the code was modified to support 3D maps.
+--]]
+
+--[[
+This work is under MIT-LICENSE
+Copyright (c) 2012-2013 Roland Yonaba.
+
+-- https://opensource.org/licenses/MIT
+
+--]]
+
+local _VERSION = ""
+local _RELEASEDATE = ""
+
+if (...) then
+
+ -- Dependencies
+ local _PATH = (...):gsub('%.pathfinder$','')
+ local Utils = require (_PATH .. '.core.utils')
+
+ -- Internalization
+ local pairs = pairs
+ local assert = assert
+ local setmetatable = setmetatable
+
+ --- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
+ --
[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)
+ local Finders = {
+ ['ASTAR'] = require (_PATH .. '.search.astar'),
+ }
+
+ -- Will keep track of all nodes expanded during the search
+ -- to easily reset their properties for the next pathfinding call
+ local toClear = {}
+
+ -- Performs a traceback from the goal node to the start node
+ -- Only happens when the path was found
+
+ local Pathfinder = {}
+ Pathfinder.__index = Pathfinder
+
+ function Pathfinder:new(heuristic)
+ local newPathfinder = {}
+ setmetatable(newPathfinder, Pathfinder)
+ self._finder = Finders.ASTAR
+ self._heuristic = heuristic
+ return newPathfinder
+ end
+
+ function Pathfinder:setGrid(grid)
+ self._grid = grid
+ return self
+ end
+
+ --- Calculates a `path`. Returns the `path` from start to end location
+ -- Both locations must exist on the collision map. The starting location can be unwalkable.
+ -- @treturn path a path (array of nodes) when found, otherwise nil
+ -- @usage local path = myFinder:getPath(1,1,5,5)
+ function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)
+ self:reset()
+ local startNode = self._grid:getNodeAt(startX, startY, startZ)
+ local endNode = self._grid:getNodeAt(endX, endY, endZ)
+ if not startNode or not endNode then
+ return nil
+ end
+
+ startNode.heading = ih
+ endNode.heading = oh
+
+ assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
+ assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
+ ('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
+ local _endNode = self._finder(self, startNode, endNode, toClear)
+ if _endNode then
+ return Utils.traceBackPath(self, _endNode, startNode)
+ end
+ return nil
+ end
+
+ --- Resets the `pathfinder`. This function is called internally between
+ -- successive pathfinding calls, so you should not
+ -- use it explicitely, unless under specific circumstances.
+ -- @class function
+ -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
+ -- @usage local path, len = myFinder:getPath(1,1,5,5)
+ function Pathfinder:reset()
+ for node in pairs(toClear) do node:reset() end
+ toClear = {}
+ return self
+ end
+
+ -- Returns Pathfinder class
+ Pathfinder._VERSION = _VERSION
+ Pathfinder._RELEASEDATE = _RELEASEDATE
+ return setmetatable(Pathfinder,{
+ __call = function(self,...)
+ return self:new(...)
+ end
+ })
+end
diff --git a/turtle/apis/jumper/search/astar.lua b/turtle/apis/jumper/search/astar.lua
new file mode 100644
index 0000000..c92dabc
--- /dev/null
+++ b/turtle/apis/jumper/search/astar.lua
@@ -0,0 +1,77 @@
+-- Astar algorithm
+-- This actual implementation of A-star is based on
+-- [Nash A. & al. pseudocode](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/)
+
+if (...) then
+
+ -- Internalization
+ local huge = math.huge
+
+ -- Dependancies
+ local _PATH = (...):match('(.+)%.search.astar$')
+ local Heap = require (_PATH.. '.core.bheap')
+
+ -- Updates G-cost
+ local function computeCost(node, neighbour, heuristic)
+ local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)
+
+ if node._g + mCost < neighbour._g then
+ neighbour._parent = node
+ neighbour._g = node._g + mCost
+ neighbour.heading = heading
+ end
+ end
+
+ -- Updates vertex node-neighbour
+ local function updateVertex(openList, node, neighbour, endNode, heuristic)
+ local oldG = neighbour._g
+ computeCost(node, neighbour, heuristic)
+ if neighbour._g < oldG then
+ if neighbour._opened then neighbour._opened = false end
+ neighbour._h = heuristic(endNode, neighbour)
+ neighbour._f = neighbour._g + neighbour._h
+ openList:push(neighbour)
+ neighbour._opened = true
+ end
+ end
+
+ -- Calculates a path.
+ -- Returns the path from location `` to location ``.
+ return function (finder, startNode, endNode, toClear)
+ local openList = Heap()
+ startNode._g = 0
+ startNode._h = finder._heuristic(endNode, startNode)
+ startNode._f = startNode._g + startNode._h
+ openList:push(startNode)
+ toClear[startNode] = true
+ startNode._opened = true
+
+ while not openList:empty() do
+ local node = openList:pop()
+ node._closed = true
+ if node == endNode then return node end
+ local neighbours = finder._grid:getNeighbours(node)
+ for i = 1,#neighbours do
+ local neighbour = neighbours[i]
+ if not neighbour._closed then
+ toClear[neighbour] = true
+ if not neighbour._opened then
+ neighbour._g = huge
+ neighbour._parent = nil
+ end
+ updateVertex(openList, node, neighbour, endNode, finder._heuristic)
+ end
+ end
+
+ --[[
+ printf('x:%d y:%d z:%d g:%d', node.x, node.y, node.z, node._g)
+ for i = 1,#neighbours do
+ local n = neighbours[i]
+ printf('x:%d y:%d z:%d f:%f g:%f h:%d', n.x, n.y, n.z, n._f, n._g, n.heading or -1)
+ end
+ --]]
+
+ end
+ return nil
+ end
+end
diff --git a/turtle/apis/pathfind.lua b/turtle/apis/pathfind.lua
new file mode 100644
index 0000000..f475418
--- /dev/null
+++ b/turtle/apis/pathfind.lua
@@ -0,0 +1,256 @@
+local Grid = require('turtle.jumper.grid')
+local Pathfinder = require('turtle.jumper.pathfinder')
+local Point = require('opus.point')
+local Util = require('opus.util')
+
+local turtle = _G.turtle
+
+local function addBlock(grid, b, dim)
+ if Point.inBox(b, dim) then
+ local node = grid:getNodeAt(b.x, b.y, b.z)
+ if node then
+ node.walkable = 1
+ end
+ end
+end
+
+-- map shrinks/grows depending upon blocks encountered
+-- the map will encompass any blocks encountered, the turtle position, and the destination
+local function mapDimensions(dest, blocks, boundingBox, dests)
+ local box = Point.makeBox(turtle.point, turtle.point)
+
+ Point.expandBox(box, dest)
+
+ for _,d in pairs(dests) do
+ Point.expandBox(box, d)
+ end
+
+ for _,b in pairs(blocks) do
+ Point.expandBox(box, b)
+ end
+
+ -- expand one block out in all directions
+ if boundingBox then
+ box.x = math.max(box.x - 1, boundingBox.x)
+ box.z = math.max(box.z - 1, boundingBox.z)
+ box.y = math.max(box.y - 1, boundingBox.y)
+ box.ex = math.min(box.ex + 1, boundingBox.ex)
+ box.ez = math.min(box.ez + 1, boundingBox.ez)
+ box.ey = math.min(box.ey + 1, boundingBox.ey)
+ else
+ box.x = box.x - 1
+ box.z = box.z - 1
+ box.y = box.y - 1
+ box.ex = box.ex + 1
+ box.ez = box.ez + 1
+ box.ey = box.ey + 1
+ end
+
+ return box
+end
+
+local function nodeToPoint(node)
+ return { x = node.x, y = node.y, z = node.z, heading = node.heading }
+end
+
+local function heuristic(n, node)
+ return Point.calculateMoves(node, n)
+-- { x = node.x, y = node.y, z = node.z, heading = node.heading },
+-- { x = n.x, y = n.y, z = n.z, heading = n.heading })
+end
+
+local function dimsAreEqual(d1, d2)
+ return d1.ex == d2.ex and
+ d1.ey == d2.ey and
+ d1.ez == d2.ez and
+ d1.x == d2.x and
+ d1.y == d2.y and
+ d1.z == d2.z
+end
+
+-- turtle sensor returns blocks in relation to the world - not turtle orientation
+-- so cannot figure out block location unless we know our orientation in the world
+-- really kinda dumb since it returns the coordinates as offsets of our location
+-- instead of true coordinates
+local function addSensorBlocks(blocks, sblocks)
+ for _,b in pairs(sblocks) do
+ if b.type ~= 'AIR' then
+ local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }
+ pt.x = pt.x - b.x
+ pt.z = pt.z - b.z -- this will only work if we were originally facing west
+ local found = false
+ for _,ob in pairs(blocks) do
+ if pt.x == ob.x and pt.y == ob.y and pt.z == ob.z then
+ found = true
+ break
+ end
+ end
+ if not found then
+ table.insert(blocks, pt)
+ end
+ end
+ end
+end
+
+local function selectDestination(pts, box, grid)
+ while #pts > 0 do
+ local pt = Point.closest(turtle.point, pts)
+ if box and not Point.inBox(pt, box) then
+ Util.removeByValue(pts, pt)
+ else
+ if grid:isWalkableAt(pt.x, pt.y, pt.z) then
+ return pt
+ end
+ Util.removeByValue(pts, pt)
+ end
+ end
+end
+
+local function updateCanvas(path)
+ local t = { }
+ for node in path:nodes() do
+ table.insert(t, { x = node.x, y = node.y, z = node.z })
+ end
+ os.queueEvent('canvas', {
+ type = 'canvas_path',
+ data = t,
+ })
+end
+
+local function pathTo(dest, options)
+ local blocks = options.blocks or turtle.getState().blocks or { }
+ local dests = options.dest or { dest } -- support alternative destinations
+ local box = options.box or turtle.getState().box
+ local lastDim
+ local grid
+
+ if box then
+ box = Point.normalizeBox(box)
+ end
+
+ -- Creates a pathfinder object
+ local finder = Pathfinder(heuristic)
+
+ while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
+
+ -- map expands as we encounter obstacles
+ local dim = mapDimensions(dest, blocks, box, dests)
+
+ -- reuse map if possible
+ if not lastDim or not dimsAreEqual(dim, lastDim) then
+ -- Creates a grid object
+ grid = Grid(dim)
+ finder:setGrid(grid)
+
+ lastDim = dim
+ end
+ for _,b in pairs(blocks) do
+ addBlock(grid, b, dim)
+ end
+
+ dest = selectDestination(dests, box, grid)
+ if not dest then
+ return false, 'failed to reach destination'
+ end
+ if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then
+ break
+ end
+
+ -- Define start and goal locations coordinates
+ local startPt = turtle.point
+
+ -- Calculates the path, and its length
+ local path = finder:getPath(
+ startPt.x, startPt.y, startPt.z, turtle.point.heading,
+ dest.x, dest.y, dest.z, dest.heading)
+
+ if not path then
+ Util.removeByValue(dests, dest)
+ else
+ updateCanvas(path)
+
+ path:filter()
+
+ for node in path:nodes() do
+ local pt = nodeToPoint(node)
+
+ if turtle.isAborted() then
+ return false, 'aborted'
+ end
+
+--if this is the next to last node
+--and we are traveling up or down, then the
+--heading for this node should be the heading of the last node
+--or, maybe..
+--if last node is up or down (or either?)
+
+ -- use single turn method so the turtle doesn't turn around
+ -- when encountering obstacles
+ --if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
+ pt.heading = nil
+ if not turtle.go(pt) then
+ local bpt = Point.nearestTo(turtle.point, pt)
+
+ if turtle.getFuelLevel() == 0 then
+ return false, 'Out of fuel'
+ end
+ table.insert(blocks, bpt)
+ os.queueEvent('canvas', {
+ type = 'canvas_barrier',
+ data = { bpt },
+ })
+ -- really need to check if the block we ran into was a turtle.
+ -- if so, this block should be temporary (1-2 secs)
+
+ --local side = turtle.getSide(turtle.point, pt)
+ --if turtle.isTurtleAtSide(side) then
+ -- pt.timestamp = os.clock() + ?
+ --end
+ -- if dim has not changed, then need to update grid with
+ -- walkable = nil (after time has elapsed)
+
+ --if device.turtlesensorenvironment then
+ -- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan())
+ --end
+ break
+ end
+ end
+ end
+ end
+
+ if dest.heading then
+ turtle.setHeading(dest.heading)
+ end
+ return dest
+end
+
+return {
+ pathfind = function(dest, options)
+ options = options or { }
+ --if not options.blocks and turtle.gotoPoint(dest) then
+ -- return dest
+ --end
+ return pathTo(dest, options)
+ end,
+
+ -- set a global bounding box
+ -- box can be overridden by passing box in pathfind options
+ setBox = function(box)
+ turtle.getState().box = box
+ end,
+
+ setBlocks = function(blocks)
+ turtle.getState().blocks = blocks
+ end,
+
+ addBlock = function(block)
+ if turtle.getState().blocks then
+ table.insert(turtle.getState().blocks, block)
+ end
+ end,
+
+ reset = function()
+ turtle.getState().box = nil
+ turtle.getState().blocks = nil
+ end,
+}
diff --git a/turtle/autorun/6.tl3.lua b/turtle/autorun/6.tl3.lua
new file mode 100644
index 0000000..51db8ec
--- /dev/null
+++ b/turtle/autorun/6.tl3.lua
@@ -0,0 +1,1273 @@
+if not _G.turtle then
+ return
+end
+
+local Pathing = require('turtle.pathfind')
+local Point = require('opus.point')
+local synchronized = require('opus.sync').sync
+local Util = require('opus.util')
+
+local os = _G.os
+local peripheral = _G.peripheral
+local turtle = _G.turtle
+
+local function noop() end
+local headings = Point.headings
+local state = { }
+
+turtle.pathfind = Pathing.pathfind
+turtle.point = { x = 0, y = 0, z = 0, heading = 0 }
+
+function turtle.getState() return state end
+function turtle.isAborted() return state.abort end
+function turtle.getStatus() return state.status end
+function turtle.setStatus(s) state.status = s end
+
+local function _defaultMove(action)
+ while not action.move() do
+ if not state.digPolicy(action) and not state.attackPolicy(action) then
+ return false
+ end
+ end
+ return true
+end
+
+function turtle.getPoint() return turtle.point end
+function turtle.setPoint(pt, isGPS)
+ turtle.point.x = pt.x
+ turtle.point.y = pt.y
+ turtle.point.z = pt.z
+ if pt.heading then
+ turtle.point.heading = pt.heading
+ end
+ turtle.point.gps = isGPS
+ return true
+end
+
+function turtle.resetState()
+ state.abort = false
+ state.status = 'idle'
+ state.attackPolicy = noop
+ state.digPolicy = noop
+ state.movePolicy = _defaultMove
+ state.moveCallback = noop
+ state.blacklist = nil
+ state.reference = nil -- gps reference when converting to relative coords
+ Pathing.reset()
+ return true
+end
+
+function turtle.reset()
+ turtle.point.x = 0
+ turtle.point.y = 0
+ turtle.point.z = 0
+ turtle.point.heading = 0 -- should be facing
+ turtle.point.gps = false
+
+ turtle.resetState()
+ return true
+end
+
+local function _dig(name, inspect, dig)
+ if name then
+ local s, b = inspect()
+ if not s or b.name ~= name then
+ return false
+ end
+ end
+ return dig()
+end
+
+function turtle.dig(s)
+ return _dig(s, turtle.inspect, turtle.native.dig)
+end
+
+function turtle.digUp(s)
+ return _dig(s, turtle.inspectUp, turtle.native.digUp)
+end
+
+function turtle.digDown(s)
+ return _dig(s, turtle.inspectDown, turtle.native.digDown)
+end
+
+local actions = {
+ up = {
+ detect = turtle.native.detectUp,
+ dig = turtle.digUp,
+ move = turtle.native.up,
+ attack = turtle.native.attackUp,
+ place = turtle.native.placeUp,
+ drop = turtle.native.dropUp,
+ suck = turtle.native.suckUp,
+ compare = turtle.native.compareUp,
+ inspect = turtle.native.inspectUp,
+ side = 'top'
+ },
+ down = {
+ detect = turtle.native.detectDown,
+ dig = turtle.digDown,
+ move = turtle.native.down,
+ attack = turtle.native.attackDown,
+ place = turtle.native.placeDown,
+ drop = turtle.native.dropDown,
+ suck = turtle.native.suckDown,
+ compare = turtle.native.compareDown,
+ inspect = turtle.native.inspectDown,
+ side = 'bottom'
+ },
+ forward = {
+ detect = turtle.native.detect,
+ dig = turtle.dig,
+ move = turtle.native.forward,
+ attack = turtle.native.attack,
+ place = turtle.native.place,
+ drop = turtle.native.drop,
+ suck = turtle.native.suck,
+ compare = turtle.native.compare,
+ inspect = turtle.native.inspect,
+ side = 'front'
+ },
+ back = {
+ detect = noop,
+ dig = noop,
+ move = turtle.native.back,
+ attack = noop,
+ place = noop,
+ suck = noop,
+ compare = noop,
+ side = 'back'
+ },
+}
+
+function turtle.getAction(direction)
+ return actions[direction]
+end
+
+function turtle.getHeadingInfo(heading)
+ heading = heading or turtle.point.heading
+ return headings[heading]
+end
+
+function turtle.isTurtleAtSide(side)
+ local sideType = peripheral.getType(side)
+ return sideType and sideType == 'turtle'
+end
+
+-- [[ Policies ]] --
+turtle.policies = { }
+
+function turtle.addPolicy(name, policy)
+ turtle.policies[name] = policy
+end
+
+function turtle.getPolicy(policy)
+ if type(policy) == 'function' then
+ return policy
+ end
+ local p = turtle.policies[policy]
+ if not p then
+ error('Invalid policy: ' .. tostring(policy))
+ end
+ return p
+end
+
+-- [[ Basic turtle actions ]] --
+local function inventoryAction(fn, name, qty)
+ local slots = turtle.getFilledSlots()
+ local s
+ for _,slot in pairs(slots) do
+ if slot.key == name or slot.name == name then
+ turtle.native.select(slot.index)
+ if not qty then
+ s = fn()
+ else
+ s = fn(math.min(qty, slot.count))
+ qty = qty - slot.count
+ if qty < 0 then
+ break
+ end
+ end
+ end
+ end
+ if not s then
+ return false, 'No items found'
+ end
+ return s
+end
+
+-- [[ Attack ]] --
+local function _attack(action)
+ if action.attack() then
+ repeat until not action.attack()
+ return true
+ end
+ return false
+end
+
+function turtle.attack() return _attack(actions.forward) end
+function turtle.attackUp() return _attack(actions.up) end
+function turtle.attackDown() return _attack(actions.down) end
+
+turtle.addPolicy('attackNone', noop)
+turtle.addPolicy('attack', function(action)
+ return _attack(action)
+end)
+
+function turtle.setAttackPolicy(policy) state.attackPolicy = policy end
+
+-- [[ Place ]] --
+local function _place(action, indexOrId)
+ local slot
+
+ if indexOrId then
+ slot = turtle.getSlot(indexOrId)
+ if not slot then
+ return false, 'No items to place'
+ end
+ end
+
+ if slot and slot.count == 0 then
+ return false, 'No items to place'
+ end
+
+ return Util.tryTimes(3, function()
+ if slot then
+ turtle.select(slot.index)
+ end
+ local result = { action.place() }
+ if result[1] then
+ return true
+ end
+ if not state.digPolicy(action) then
+ state.attackPolicy(action)
+ end
+ return table.unpack(result)
+ end)
+end
+
+function turtle.place(slot) return _place(actions.forward, slot) end
+function turtle.placeUp(slot) return _place(actions.up, slot) end
+function turtle.placeDown(slot) return _place(actions.down, slot) end
+
+-- [[ Drop ]] --
+local function _drop(action, qtyOrName, qty)
+ if not qtyOrName or type(qtyOrName) == 'number' then
+ return action.drop(qtyOrName or 64)
+ end
+ return inventoryAction(action.drop, qtyOrName, qty)
+end
+
+function turtle.drop(count, slot) return _drop(actions.forward, count, slot) end
+function turtle.dropUp(count, slot) return _drop(actions.up, count, slot) end
+function turtle.dropDown(count, slot) return _drop(actions.down, count, slot) end
+
+-- [[ Dig ]] --
+turtle.addPolicy('digNone', noop)
+
+turtle.addPolicy('dig', function(action)
+ return action.dig()
+end)
+
+turtle.addPolicy('turtleSafe', function(action)
+ if action.side == 'back' then
+ return false
+ end
+ if not turtle.isTurtleAtSide(action.side) then
+ return action.dig()
+ end
+ return Util.tryTimes(6, function()
+ os.sleep(.25)
+ if not action.detect() then
+ return true
+ end
+ end)
+end)
+
+local function isBlacklisted(b)
+ if b and state.blacklist then
+ for _, v in pairs(state.blacklist) do
+ if b.name:find(v) then
+ return true
+ end
+ end
+ end
+end
+
+turtle.addPolicy('blacklist', function(action)
+ if action.side == 'back' then
+ return false
+ end
+ local s, m = action.inspect()
+ if not isBlacklisted(s and m) then
+ return action.dig()
+ end
+ if s and m and m.name:find('turtle') then
+ return Util.tryTimes(math.random(3, 6), function()
+ os.sleep(.25)
+ if not action.detect() then
+ return true
+ end
+ end)
+ end
+end)
+
+turtle.addPolicy('digAndDrop', function(action)
+ if action.detect() then
+ local slots = turtle.getInventory()
+ if action.dig() then
+ turtle.reconcileInventory(slots)
+ return true
+ end
+ end
+ return false
+end)
+
+function turtle.setDigPolicy(policy) state.digPolicy = policy end
+
+-- [[ Move ]] --
+turtle.addPolicy('moveNone', noop)
+turtle.addPolicy('moveDefault', _defaultMove)
+turtle.addPolicy('moveAssured', function(action)
+ if not _defaultMove(action) then
+ if action.side == 'back' then
+ return false
+ end
+ local oldStatus = state.status
+ print('assured move: stuck')
+ state.status = 'stuck'
+ repeat
+ os.sleep(1)
+ until _defaultMove(action)
+ state.status = oldStatus
+ end
+ return true
+end)
+
+function turtle.setMoveCallback(cb) state.moveCallback = cb end
+function turtle.clearMoveCallback() state.moveCallback = noop end
+function turtle.getMoveCallback() return state.moveCallback end
+
+-- convenience method for setting multiple values
+function turtle.set(args)
+ for k,v in pairs(args) do
+
+ if k == 'attackPolicy' then
+ turtle.setAttackPolicy(turtle.getPolicy(v))
+
+ elseif k == 'digPolicy' then
+ turtle.setDigPolicy(turtle.getPolicy(v))
+
+ elseif k == 'movePolicy' then
+ state.movePolicy = turtle.getPolicy(v)
+
+ elseif k == 'movementStrategy' then
+ turtle.setMovementStrategy(v)
+
+ elseif k == 'pathingBox' then
+ turtle.setPathingBox(v)
+
+ elseif k == 'point' then
+ turtle.setPoint(v)
+
+ elseif k == 'moveCallback' then
+ turtle.setMoveCallback(v)
+
+ elseif k == 'status' then
+ turtle.setStatus(v)
+
+ elseif k == 'blacklist' then
+ state.blacklist = v
+
+ elseif k == 'reference' then
+ state.reference = v
+
+ else
+ error('Invalid turle.set: ' .. tostring(k))
+ end
+ end
+end
+
+-- [[ Fuel ]] --
+if type(turtle.getFuelLevel()) ~= 'number' then
+ -- Support unlimited fuel
+ function turtle.getFuelLevel()
+ return 100000
+ end
+end
+
+-- override to optionally specify a fuel
+function turtle.refuel(qtyOrName, qty)
+ if not qtyOrName or type(qtyOrName) == 'number' then
+ return turtle.native.refuel(qtyOrName or 64)
+ end
+ return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64)
+end
+
+-- [[ Heading ]] --
+function turtle.getHeading()
+ return turtle.point.heading
+end
+
+function turtle.turnRight()
+ turtle.setHeading((turtle.point.heading + 1) % 4)
+ return turtle.point
+end
+
+function turtle.turnLeft()
+ turtle.setHeading((turtle.point.heading - 1) % 4)
+ return turtle.point
+end
+
+function turtle.turnAround()
+ turtle.setHeading((turtle.point.heading + 2) % 4)
+ return turtle.point
+end
+
+function turtle.setHeading(heading)
+ if not heading then
+ return false, 'Invalid heading'
+ end
+
+ if heading == turtle.point.heading then
+ return turtle.point
+ end
+
+ local fi = Point.facings[heading]
+ if not fi then
+ return false, 'Invalid heading'
+ end
+
+ heading = fi.heading % 4
+ if heading ~= turtle.point.heading then
+ while heading < turtle.point.heading do
+ heading = heading + 4
+ end
+ if heading - turtle.point.heading == 3 then
+ turtle.native.turnLeft()
+ turtle.point.heading = (turtle.point.heading - 1) % 4
+ state.moveCallback('turn', turtle.point)
+ else
+ local turns = heading - turtle.point.heading
+ while turns > 0 do
+ turns = turns - 1
+ turtle.native.turnRight()
+ turtle.point.heading = (turtle.point.heading + 1) % 4
+ state.moveCallback('turn', turtle.point)
+ end
+ end
+ end
+
+ return turtle.point
+end
+
+function turtle.headTowardsX(dx)
+ if turtle.point.x ~= dx then
+ if turtle.point.x > dx then
+ turtle.setHeading(2)
+ else
+ turtle.setHeading(0)
+ end
+ end
+end
+
+function turtle.headTowardsZ(dz)
+ if turtle.point.z ~= dz then
+ if turtle.point.z > dz then
+ turtle.setHeading(3)
+ else
+ turtle.setHeading(1)
+ end
+ end
+end
+
+function turtle.headTowards(pt)
+ local xd = math.abs(turtle.point.x - pt.x)
+ local zd = math.abs(turtle.point.z - pt.z)
+ if xd > zd then
+ turtle.headTowardsX(pt.x)
+ else
+ turtle.headTowardsZ(pt.z)
+ end
+end
+
+-- [[ move ]] --
+function turtle.up()
+ if state.movePolicy(actions.up) then
+ turtle.point.y = turtle.point.y + 1
+ state.moveCallback('up', turtle.point)
+ return true, turtle.point
+ end
+end
+
+function turtle.down()
+ if state.movePolicy(actions.down) then
+ turtle.point.y = turtle.point.y - 1
+ state.moveCallback('down', turtle.point)
+ return true, turtle.point
+ end
+end
+
+function turtle.forward()
+ if state.movePolicy(actions.forward) then
+ turtle.point.x = turtle.point.x + headings[turtle.point.heading].xd
+ turtle.point.z = turtle.point.z + headings[turtle.point.heading].zd
+ state.moveCallback('forward', turtle.point)
+ return true, turtle.point
+ end
+end
+
+function turtle.back()
+ if state.movePolicy(actions.back) then
+ turtle.point.x = turtle.point.x - headings[turtle.point.heading].xd
+ turtle.point.z = turtle.point.z - headings[turtle.point.heading].zd
+ state.moveCallback('back', turtle.point)
+ return true, turtle.point
+ end
+end
+
+local function moveTowardsX(dx)
+ if not tonumber(dx) then error('moveTowardsX: Invalid arguments') end
+ local direction = dx - turtle.point.x
+ local move
+
+ if direction == 0 then
+ return true
+ end
+
+ if direction > 0 and turtle.point.heading == 0 or
+ direction < 0 and turtle.point.heading == 2 then
+ move = turtle.forward
+ else
+ move = turtle.back
+ end
+
+ repeat
+ if not move() then
+ return false
+ end
+ until turtle.point.x == dx
+ return true
+end
+
+local function moveTowardsZ(dz)
+ local direction = dz - turtle.point.z
+ local move
+
+ if direction == 0 then
+ return true
+ end
+
+ if direction > 0 and turtle.point.heading == 1 or
+ direction < 0 and turtle.point.heading == 3 then
+ move = turtle.forward
+ else
+ move = turtle.back
+ end
+
+ repeat
+ if not move() then
+ return false
+ end
+ until turtle.point.z == dz
+ return true
+end
+
+-- [[ go ]] --
+-- 1 turn goto (going backwards if possible)
+function turtle.gotoSingleTurn(dx, dy, dz, dh)
+ dx = dx or turtle.point.x
+ dy = dy or turtle.point.y
+ dz = dz or turtle.point.z
+
+ local function gx()
+ if turtle.point.x ~= dx then
+ moveTowardsX(dx)
+ end
+ if turtle.point.z ~= dz then
+ if dh and dh % 2 == 1 then
+ turtle.setHeading(dh)
+ else
+ turtle.headTowardsZ(dz)
+ end
+ end
+ end
+
+ local function gz()
+ if turtle.point.z ~= dz then
+ moveTowardsZ(dz)
+ end
+ if turtle.point.x ~= dx then
+ if dh and dh % 2 == 0 then
+ turtle.setHeading(dh)
+ else
+ turtle.headTowardsX(dx)
+ end
+ end
+ end
+
+ repeat
+ local x, z
+ local y = turtle.point.y
+
+ repeat
+ x, z = turtle.point.x, turtle.point.z
+
+ if turtle.point.heading % 2 == 0 then
+ gx()
+ gz()
+ else
+ gz()
+ gx()
+ end
+ until x == turtle.point.x and z == turtle.point.z
+
+ if turtle.point.y ~= dy then
+ turtle.gotoY(dy)
+ end
+
+ if turtle.point.x == dx and turtle.point.z == dz and turtle.point.y == dy then
+ return true
+ end
+
+ until x == turtle.point.x and z == turtle.point.z and y == turtle.point.y
+
+ return false
+end
+
+local function gotoEx(dx, dy, dz)
+ -- determine the heading to ensure the least amount of turns
+ -- first check is 1 turn needed - remaining require 2 turns
+ if turtle.point.heading == 0 and turtle.point.x <= dx or
+ turtle.point.heading == 2 and turtle.point.x >= dx or
+ turtle.point.heading == 1 and turtle.point.z <= dz or
+ turtle.point.heading == 3 and turtle.point.z >= dz then
+ -- maintain current heading
+ -- nop
+ elseif dz > turtle.point.z and turtle.point.heading == 0 or
+ dz < turtle.point.z and turtle.point.heading == 2 or
+ dx < turtle.point.x and turtle.point.heading == 1 or
+ dx > turtle.point.x and turtle.point.heading == 3 then
+ turtle.turnRight()
+ else
+ turtle.turnLeft()
+ end
+
+ if (turtle.point.heading % 2) == 1 then
+ if not turtle.gotoZ(dz) then return false end
+ if not turtle.gotoX(dx) then return false end
+ else
+ if not turtle.gotoX(dx) then return false end
+ if not turtle.gotoZ(dz) then return false end
+ end
+
+ if dy then
+ if not turtle.gotoY(dy) then return false end
+ end
+
+ return true
+end
+
+-- fallback goto - will turn around if was previously moving backwards
+local function gotoMultiTurn(dx, dy, dz)
+ if gotoEx(dx, dy, dz) then
+ return true
+ end
+
+ local moved
+ repeat
+ local x, y, z = turtle.point.x, turtle.point.y, turtle.point.z
+
+ -- try going the other way
+ if (turtle.point.heading % 2) == 1 then
+ turtle.headTowardsX(dx)
+ else
+ turtle.headTowardsZ(dz)
+ end
+
+ if gotoEx(dx, dy, dz) then
+ return true
+ end
+
+ if dy then
+ turtle.gotoY(dy)
+ end
+
+ moved = x ~= turtle.point.x or y ~= turtle.point.y or z ~= turtle.point.z
+ until not moved
+
+ return false
+end
+
+-- go backwards - turning around if necessary to fight mobs / break blocks
+function turtle.goback()
+ local hi = headings[turtle.point.heading]
+ return turtle.go({
+ x = turtle.point.x - hi.xd,
+ y = turtle.point.y,
+ z = turtle.point.z - hi.zd,
+ heading = turtle.point.heading,
+ })
+end
+
+function turtle.gotoYfirst(pt)
+ if turtle.gotoY(pt.y) then
+ if turtle.go(pt) then
+ turtle.setHeading(pt.heading)
+ return true
+ end
+ end
+end
+
+function turtle.go(pt)
+ if not pt.x and not pt.z and pt.y then
+ if turtle.gotoY(pt.y) then
+ turtle.setHeading(pt.heading)
+ return true
+ end
+ return false, 'Failed to reach location'
+ end
+
+ local dx = pt.x or turtle.point.x
+ local dz = pt.z or turtle.point.z
+ local dy, dh = pt.y, pt.heading
+ if not turtle.gotoSingleTurn(dx, dy, dz, dh) then
+ if not gotoMultiTurn(dx, dy, dz) then
+ return false, 'Failed to reach location'
+ end
+ end
+ turtle.setHeading(dh)
+ return pt
+end
+
+-- avoid lint errors
+-- deprecated
+turtle['goto'] = turtle.go
+turtle['_goto'] = turtle.go
+
+-- TODO: localize these goto functions
+function turtle.gotoX(dx)
+ turtle.headTowardsX(dx)
+
+ while turtle.point.x ~= dx do
+ if not turtle.forward() then
+ return false
+ end
+ end
+ return true
+end
+
+function turtle.gotoZ(dz)
+ turtle.headTowardsZ(dz)
+
+ while turtle.point.z ~= dz do
+ if not turtle.forward() then
+ return false
+ end
+ end
+ return true
+end
+
+function turtle.gotoY(dy)
+ while turtle.point.y > dy do
+ if not turtle.down() then
+ return false
+ end
+ end
+
+ while turtle.point.y < dy do
+ if not turtle.up() then
+ return false
+ end
+ end
+ return true
+end
+
+-- [[ Inventory ]] --
+function turtle.getSlot(indexOrId, slots)
+ if type(indexOrId) == 'string' then
+ slots = slots or turtle.getInventory()
+ local _,c = string.gsub(indexOrId, ':', '')
+ if c == 2 then -- combined id and dmg .. ie. minecraft:coal:0
+ return Util.find(slots, 'key', indexOrId)
+ end
+ return Util.find(slots, 'name', indexOrId)
+ end
+
+ local detail = turtle.getItemDetail(indexOrId)
+ if detail then
+ return {
+ name = detail.name,
+ damage = detail.damage,
+ count = detail.count,
+ key = detail.name .. ':' .. detail.damage,
+
+ index = indexOrId,
+
+ -- deprecate
+ qty = detail.count,
+ dmg = detail.damage,
+ id = detail.name,
+ }
+ end
+
+ -- inconsistent return value
+ -- null is returned if indexOrId is a string and no item is present
+ return {
+ qty = 0, -- deprecate
+ count = 0,
+ index = indexOrId,
+ }
+end
+
+function turtle.select(indexOrId)
+ if type(indexOrId) == 'number' then
+ return turtle.native.select(indexOrId)
+ end
+
+ local s = turtle.getSlot(indexOrId)
+ if s then
+ turtle.native.select(s.index)
+ return s
+ end
+
+ return false, 'Inventory does not contain item'
+end
+
+function turtle.getInventory(slots)
+ slots = slots or { }
+ for i = 1, 16 do
+ slots[i] = turtle.getSlot(i)
+ end
+ return slots
+end
+
+function turtle.getSummedInventory()
+ local slots = turtle.getFilledSlots()
+ local t = { }
+ for _,slot in pairs(slots) do
+ local entry = t[slot.key]
+ if not entry then
+ entry = {
+ count = 0,
+ damage = slot.damage,
+ name = slot.name,
+ key = slot.key,
+
+ -- deprecate
+ qty = 0,
+ dmg = slot.dmg,
+ id = slot.id,
+ }
+ t[slot.key] = entry
+ end
+ entry.qty = entry.qty + slot.qty
+ entry.count = entry.qty
+ end
+ return t
+end
+
+function turtle.has(item, count)
+ if item:match('.*:%d') then
+ local slot = turtle.getSummedInventory()[item]
+ return slot and slot.count >= (count or 1)
+ end
+ local slot = turtle.getSlot(item)
+ return slot and slot.count >= (count or 1)
+end
+
+function turtle.getFilledSlots(startSlot)
+ startSlot = startSlot or 1
+
+ local slots = { }
+ for i = startSlot, 16 do
+ local count = turtle.getItemCount(i)
+ if count > 0 then
+ slots[i] = turtle.getSlot(i)
+ end
+ end
+ return slots
+end
+
+function turtle.eachFilledSlot(fn)
+ local slots = turtle.getFilledSlots()
+ for _,slot in pairs(slots) do
+ fn(slot)
+ end
+end
+
+function turtle.emptyInventory(dropAction)
+ dropAction = dropAction or turtle.native.drop
+ turtle.eachFilledSlot(function(slot)
+ turtle.select(slot.index)
+ dropAction()
+ end)
+ turtle.select(1)
+end
+
+function turtle.reconcileInventory(slots, dropAction)
+ dropAction = dropAction or turtle.native.drop
+ for _,s in pairs(slots) do
+ local qty = turtle.getItemCount(s.index)
+ if qty > s.qty then
+ turtle.select(s.index)
+ dropAction(qty-s.qty, s)
+ end
+ end
+end
+
+function turtle.selectSlotWithItems(startSlot)
+ startSlot = startSlot or 1
+ for i = startSlot, 16 do
+ if turtle.getItemCount(i) > 0 then
+ turtle.select(i)
+ return i
+ end
+ end
+end
+
+function turtle.selectSlotWithQuantity(qty, startSlot)
+ startSlot = startSlot or 1
+
+ for i = startSlot, 16 do
+ if turtle.getItemCount(i) == qty then
+ turtle.select(i)
+ return i
+ end
+ end
+end
+
+function turtle.selectOpenSlot(startSlot)
+ return turtle.selectSlotWithQuantity(0, startSlot)
+end
+
+function turtle.condense()
+ local slots = turtle.getInventory()
+
+ for i = 1, 16 do
+ if slots[i].count < 64 then
+ for j = 16, i + 1, -1 do
+ if slots[j].count > 0 and (slots[i].count == 0 or slots[i].key == slots[j].key) then
+ turtle.select(j)
+ if turtle.transferTo(i) then
+ local transferred = turtle.getItemCount(i) - slots[i].count
+ slots[j].count = slots[j].count - transferred
+ slots[i].count = slots[i].count + transferred
+ slots[i].key = slots[j].key
+ if slots[j].count == 0 then
+ slots[j].key = nil
+ end
+ if slots[i].count == 64 then
+ break
+ end
+ else
+ break
+ end
+ end
+ end
+ end
+ end
+ turtle.select(1)
+ return true
+end
+
+function turtle.getItemCount(idOrName)
+ if type(idOrName) == 'number' then
+ return turtle.native.getItemCount(idOrName)
+ end
+ local slots = turtle.getFilledSlots()
+ local count = 0
+ for _,slot in pairs(slots) do
+ if slot.key == idOrName or slot.name == idOrName then
+ count = count + slot.count
+ end
+ end
+ return count
+end
+
+-- [[ Equipment ]] --
+function turtle.equip(side, item)
+ if item then
+ if not turtle.select(item) then
+ return false, 'Unable to equip ' .. item
+ end
+ end
+
+ if side == 'left' then
+ return turtle.equipLeft()
+ end
+ return turtle.equipRight()
+end
+
+function turtle.isEquipped(item)
+ if peripheral.getType('left') == item then
+ return 'left'
+ elseif peripheral.getType('right') == item then
+ return 'right'
+ end
+end
+
+function turtle.unequip(side)
+ if not turtle.selectSlotWithQuantity(0) then
+ return false, 'No slots available'
+ end
+ return turtle.equip(side)
+end
+
+-- deprecate
+function turtle.run(fn, ...)
+ local args = { ... }
+ local s, m
+
+ if type(fn) == 'string' then
+ fn = turtle[fn]
+ end
+
+ synchronized(turtle, function()
+ turtle.resetState()
+ s, m = pcall(function() fn(table.unpack(args)) end)
+ turtle.resetState()
+ if not s and m then
+ _G.printError(m)
+ end
+ end)
+
+ return s, m
+end
+
+function turtle.abort(abort)
+ state.abort = abort
+ if abort then
+ os.queueEvent('turtle_abort')
+ end
+end
+
+-- [[ Pathing ]] --
+function turtle.setPersistent(isPersistent)
+ if isPersistent then
+ Pathing.setBlocks({ })
+ else
+ Pathing.setBlocks()
+ end
+end
+
+function turtle.setPathingBox(box)
+ Pathing.setBox(box)
+end
+
+function turtle.addWorldBlock(pt)
+ Pathing.addBlock(pt)
+end
+
+function turtle.addWorldBlocks(pts)
+ Util.each(pts, function(pt)
+ Pathing.addBlock(pt)
+ end)
+end
+
+local movementStrategy = turtle.pathfind
+
+function turtle.setMovementStrategy(strategy)
+ if strategy == 'pathing' then
+ movementStrategy = turtle.pathfind
+ elseif strategy == 'goto' then
+ movementStrategy = turtle.go
+ else
+ error('Invalid movement strategy')
+ end
+end
+
+function turtle.faceAgainst(pt, options) -- 4 sided
+ options = options or { }
+ options.dest = { }
+
+ for i = 0, 3 do
+ local hi = Point.facings[i]
+ table.insert(options.dest, {
+ x = pt.x + hi.xd,
+ z = pt.z + hi.zd,
+ y = pt.y + hi.yd,
+ heading = (hi.heading + 2) % 4,
+ })
+ end
+
+ return movementStrategy(Point.closest(turtle.point, options.dest), options)
+end
+
+-- move against this point
+-- if the point does not contain a heading, then the turtle
+-- will face the block (if on same plane)
+-- if above or below, the heading is undetermined unless specified
+function turtle.moveAgainst(pt, options) -- 6 sided
+ options = options or { }
+ options.dest = { }
+
+ for i = 0, 5 do
+ local hi = turtle.getHeadingInfo(i)
+ local heading, direction
+ if i < 4 then
+ heading = (hi.heading + 2) % 4
+ direction = 'forward'
+ elseif i == 4 then
+ direction = 'down'
+ elseif i == 5 then
+ direction = 'up'
+ end
+
+ table.insert(options.dest, {
+ x = pt.x + hi.xd,
+ z = pt.z + hi.zd,
+ y = pt.y + hi.yd,
+ direction = direction,
+ heading = pt.heading or heading,
+ })
+ end
+
+ return movementStrategy(Point.closest(turtle.point, options.dest), options)
+end
+
+local actionsAt = {
+ detect = {
+ up = turtle.detectUp,
+ down = turtle.detectDown,
+ forward = turtle.detect,
+ },
+ dig = {
+ up = turtle.digUp,
+ down = turtle.digDown,
+ forward = turtle.dig,
+ },
+ move = {
+ up = turtle.moveUp,
+ down = turtle.moveDown,
+ forward = turtle.move,
+ },
+ attack = {
+ up = turtle.attackUp,
+ down = turtle.attackDown,
+ forward = turtle.attack,
+ },
+ place = {
+ up = turtle.placeUp,
+ down = turtle.placeDown,
+ forward = turtle.place,
+ },
+ drop = {
+ up = turtle.dropUp,
+ down = turtle.dropDown,
+ forward = turtle.drop,
+ },
+ suck = {
+ up = turtle.suckUp,
+ down = turtle.suckDown,
+ forward = turtle.suck,
+ },
+ compare = {
+ up = turtle.compareUp,
+ down = turtle.compareDown,
+ forward = turtle.compare,
+ },
+ inspect = {
+ up = turtle.inspectUp,
+ down = turtle.inspectDown,
+ forward = turtle.inspect,
+ },
+}
+
+-- pt = { x,y,z,heading,direction }
+-- direction should only be up or down if provided
+-- heading can be provided to tell which way to face during action
+-- ex: place a block at the point from above facing east
+local function _actionAt(action, pt, ...)
+ if not pt.heading and not pt.direction then
+ local msg
+ pt, msg = turtle.moveAgainst(pt)
+ if pt then
+ return action[pt.direction](...)
+ end
+ return pt, msg
+ end
+
+ local reversed =
+ { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, }
+ local dir = reversed[headings[pt.direction or pt.heading].heading]
+ local apt = { x = pt.x + headings[dir].xd,
+ y = pt.y + headings[dir].yd,
+ z = pt.z + headings[dir].zd, }
+ local direction
+
+ -- ex: place a block at this point, from above, facing east
+ if dir < 4 then
+ apt.heading = (dir + 2) % 4
+ direction = 'forward'
+ elseif dir == 4 then
+ apt.heading = pt.heading
+ direction = 'down'
+ elseif dir == 5 then
+ apt.heading = pt.heading
+ direction = 'up'
+ end
+
+ if movementStrategy(apt) then
+ return action[direction](...)
+ end
+end
+
+local function _actionDownAt(action, pt, ...)
+ pt = Util.shallowCopy(pt)
+ pt.direction = Point.DOWN
+ return _actionAt(action, pt, ...)
+end
+
+local function _actionUpAt(action, pt, ...)
+ pt = Util.shallowCopy(pt)
+ pt.direction = Point.UP
+ return _actionAt(action, pt, ...)
+end
+
+local function _actionForwardAt(action, pt, ...)
+ if turtle.faceAgainst(pt) then
+ return action.forward(...)
+ end
+end
+
+function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end
+function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end
+function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end
+function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end
+
+function turtle.digAt(pt, ...) return _actionAt(actionsAt.dig, pt, ...) end
+function turtle.digDownAt(pt, ...) return _actionDownAt(actionsAt.dig, pt, ...) end
+function turtle.digForwardAt(pt, ...) return _actionForwardAt(actionsAt.dig, pt, ...) end
+function turtle.digUpAt(pt, ...) return _actionUpAt(actionsAt.dig, pt, ...) end
+
+function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end
+function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end
+function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end
+function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end
+
+function turtle.placeAt(pt, arg, dir) return _actionAt(actionsAt.place, pt, arg, dir) end
+function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end
+function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end
+function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end
+
+function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end
+function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end
+function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end
+function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end
+
+function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end
+function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end
+function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end
+function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end
+
+function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end
+function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end
+function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end
+function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end
+
+function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end
+function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end
+function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end
+function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end
+
+turtle.reset()
diff --git a/common/canvasClient.lua b/turtle/canvasClient.lua
similarity index 100%
rename from common/canvasClient.lua
rename to turtle/canvasClient.lua