mirror of
				https://github.com/kepler155c/opus
				synced 2025-10-31 15:43:00 +00:00 
			
		
		
		
	restructure
This commit is contained in:
		| @@ -58,6 +58,9 @@ local page = UI.Page { | ||||
| 		}, | ||||
| 	}, | ||||
| 	statusBar = UI.StatusBar { }, | ||||
| 	accelerators = { | ||||
| 		[ 'control-q' ] = 'quit', | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| function page:loadPackages() | ||||
|   | ||||
| @@ -47,8 +47,8 @@ local function runDir(directory) | ||||
| end | ||||
|  | ||||
| runDir('sys/autorun') | ||||
| for name in pairs(Packages:installed()) do | ||||
| 	local packageDir = 'packages/' .. name .. '/autorun' | ||||
| for _, package in pairs(Packages:installedSorted()) do | ||||
| 	local packageDir = 'packages/' .. package.name .. '/autorun' | ||||
| 	runDir(packageDir) | ||||
| end | ||||
| runDir('usr/autorun') | ||||
|   | ||||
| @@ -39,7 +39,7 @@ local function setModem(dev) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- create a psuedo-device named 'wireleess_modem' | ||||
| -- create a psuedo-device named 'wireless_modem' | ||||
| kernel.hook('device_attach', function(_, eventData) | ||||
| 	local dev = device[eventData[1]] | ||||
| 	if dev and dev.type == 'modem' then | ||||
|   | ||||
							
								
								
									
										1273
									
								
								sys/init/6.tl3.lua
									
									
									
									
									
								
							
							
						
						
									
										1273
									
								
								sys/init/6.tl3.lua
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										247
									
								
								sys/lzwfs.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								sys/lzwfs.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,247 @@ | ||||
| -- see: https://github.com/Rochet2/lualzw | ||||
| -- MIT License - Copyright (c) 2016 Rochet2 | ||||
|  | ||||
| -- Transparent file system compression for non-binary files using lzw | ||||
|  | ||||
| -- Files that are compressed will have the first bytes in file set to 'LZWC'. | ||||
| -- If a file does not benefit from compression, the contents will not be altered. | ||||
|  | ||||
| -- Allow exclusions for files that shouldn't be compressed | ||||
| -- Also allow for future types of exclusions using bit operations | ||||
| -- 1 is reserved for compression exclusion | ||||
| --    fs.addException('startup.lua', 1) | ||||
|  | ||||
| -- To renable compression for a file  | ||||
| --    fs.removeException('startup.lua', 1) | ||||
|  | ||||
| -- Restores file system | ||||
| --    fs.restore() | ||||
|  | ||||
| local char    = string.char | ||||
| local type    = type | ||||
| local sub     = string.sub | ||||
| local tconcat = table.concat | ||||
| local tinsert = table.insert | ||||
|  | ||||
| local SIGC = 'LZWC' | ||||
| local IGNORE_COMPRESSION = 1 -- support other bits as well | ||||
|  | ||||
| local basedictcompress = {} | ||||
| local basedictdecompress = {} | ||||
| for i = 0, 255 do | ||||
|     local ic, iic = char(i), char(i, 0) | ||||
|     basedictcompress[ic] = iic | ||||
|     basedictdecompress[iic] = ic | ||||
| end | ||||
|  | ||||
| local native = { open = fs.open } | ||||
| fs.exceptions = fs.exceptions or { } | ||||
|  | ||||
| local function dictAddA(str, dict, a, b) | ||||
|     if a >= 256 then | ||||
|         a, b = 0, b+1 | ||||
|         if b >= 256 then | ||||
|             dict = {} | ||||
|             b = 1 | ||||
|         end | ||||
|     end | ||||
|     dict[str] = char(a,b) | ||||
|     a = a+1 | ||||
|     return dict, a, b | ||||
| end | ||||
|  | ||||
| local function compress(input) | ||||
|     if type(input) ~= "string" then | ||||
|         error ("string expected, got "..type(input)) | ||||
|     end | ||||
|     local len = #input | ||||
|     if len <= 1 then | ||||
|         return input | ||||
|     end | ||||
|  | ||||
|     local dict = {} | ||||
|     local a, b = 0, 1 | ||||
|  | ||||
|     local result = { SIGC } | ||||
|     local resultlen = 1 | ||||
|     local n = 2 | ||||
|     local word = "" | ||||
|     for i = 1, len do | ||||
|         local c = sub(input, i, i) | ||||
|         local wc = word..c | ||||
|         if not (basedictcompress[wc] or dict[wc]) then | ||||
|             local write = basedictcompress[word] or dict[word] | ||||
|             if not write then | ||||
|                 error "algorithm error, could not fetch word" | ||||
|             end | ||||
|             result[n] = write | ||||
|             resultlen = resultlen + #write | ||||
|             n = n+1 | ||||
|             if  len <= resultlen then | ||||
|                 return input | ||||
|             end | ||||
|             dict, a, b = dictAddA(wc, dict, a, b) | ||||
|             word = c | ||||
|         else | ||||
|             word = wc | ||||
|         end | ||||
|     end | ||||
|     result[n] = basedictcompress[word] or dict[word] | ||||
|     resultlen = resultlen+#result[n] | ||||
|     if  len <= resultlen then | ||||
|         return input | ||||
|     end | ||||
|     return tconcat(result) | ||||
| end | ||||
|  | ||||
| local function dictAddB(str, dict, a, b) | ||||
|     if a >= 256 then | ||||
|         a, b = 0, b+1 | ||||
|         if b >= 256 then | ||||
|             dict = {} | ||||
|             b = 1 | ||||
|         end | ||||
|     end | ||||
|     dict[char(a,b)] = str | ||||
|     a = a+1 | ||||
|     return dict, a, b | ||||
| end | ||||
|  | ||||
| local function decompress(input) | ||||
|     if type(input) ~= "string" then | ||||
|         error( "string expected, got "..type(input)) | ||||
|     end | ||||
|  | ||||
|     if #input <= 1 then | ||||
|         return input | ||||
|     end | ||||
|  | ||||
|     local control = sub(input, 1, 4) | ||||
|     if control ~= SIGC then | ||||
|         return input | ||||
|     end | ||||
|     input = sub(input, 5) | ||||
|     local len = #input | ||||
|  | ||||
|     if len < 2 then | ||||
|         error("invalid input - not a compressed string") | ||||
|     end | ||||
|  | ||||
|     local dict = {} | ||||
|     local a, b = 0, 1 | ||||
|  | ||||
|     local result = {} | ||||
|     local n = 1 | ||||
|     local last = sub(input, 1, 2) | ||||
|     result[n] = basedictdecompress[last] or dict[last] | ||||
|     n = n+1 | ||||
|     for i = 3, len, 2 do | ||||
|         local code = sub(input, i, i+1) | ||||
|         local lastStr = basedictdecompress[last] or dict[last] | ||||
|         if not lastStr then | ||||
|             error( "could not find last from dict. Invalid input?") | ||||
|         end | ||||
|         local toAdd = basedictdecompress[code] or dict[code] | ||||
|         if toAdd then | ||||
|             result[n] = toAdd | ||||
|             n = n+1 | ||||
|             dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b) | ||||
|         else | ||||
|             local tmp = lastStr..sub(lastStr, 1, 1) | ||||
|             result[n] = tmp | ||||
|             n = n+1 | ||||
|             dict, a, b = dictAddB(tmp, dict, a, b) | ||||
|         end | ||||
|         last = code | ||||
|     end | ||||
|     return tconcat(result) | ||||
| end | ||||
|  | ||||
| function split(str, pattern) | ||||
| 	pattern = pattern or "(.-)\n" | ||||
| 	local t = {} | ||||
| 	local function helper(line) tinsert(t, line) return "" end | ||||
| 	helper((str:gsub(pattern, helper))) | ||||
| 	return t | ||||
| end | ||||
|  | ||||
| function fs.open(fname, flags) | ||||
|     if flags == 'r' then | ||||
|         local f, err = native.open(fname, 'rb') | ||||
|         if not f then | ||||
|             return f, err | ||||
|         end | ||||
|  | ||||
| 		local ctr = 0 | ||||
| 		local lines | ||||
| 		return { | ||||
| 			readLine = function() | ||||
| 				if not lines then | ||||
|                     lines = split(decompress(f.readAll())) | ||||
| 				end | ||||
| 				ctr = ctr + 1 | ||||
| 				return lines[ctr] | ||||
| 			end, | ||||
| 			readAll = function() | ||||
| 				return decompress(f.readAll()) | ||||
| 			end, | ||||
|             close = function() | ||||
|                 f.close() | ||||
| 			end, | ||||
|         } | ||||
|     elseif flags == 'w' or flags == 'a' then | ||||
|         if bit.band(fs.exceptions[fs.combine(fname, '')] or 0, IGNORE_COMPRESSION) == IGNORE_COMPRESSION then | ||||
|             return native.open(fname, flags) | ||||
|         end | ||||
|  | ||||
|         local c = { } | ||||
|  | ||||
|         if flags == 'a' then | ||||
|             local f = fs.open(fname, 'r') | ||||
|             if f then | ||||
|                 tinsert(c, f.readAll()) | ||||
|                 f.close() | ||||
|             end | ||||
|         end | ||||
|  | ||||
|         local f, err = native.open(fname, 'wb') | ||||
|         if not f then | ||||
|             return f, err | ||||
|         end | ||||
|  | ||||
|         return { | ||||
|             write = function(str) | ||||
|                 tinsert(c, str) | ||||
| 			end, | ||||
| 			writeLine = function(str) | ||||
|                 tinsert(c, str) | ||||
|                 tinsert(c, '\n') | ||||
| 			end, | ||||
|             flush = function() | ||||
|                 -- this isn't gonna work... | ||||
|                 // f.write(compress(tconcat(c))) | ||||
|                 f.flush(); | ||||
| 			end, | ||||
|             close = function() | ||||
|                 f.write(compress(tconcat(c))) | ||||
|                 f.close() | ||||
| 			end, | ||||
| 		} | ||||
|     end | ||||
|  | ||||
|     return native.open(fname, flags) | ||||
| end | ||||
|  | ||||
| function fs.addException(fname, mode) | ||||
|     fname = fs.combine(fname, '') | ||||
|     fs.exceptions[fname] = bit.bor(fs.exceptions[fname] or 0, mode) | ||||
| end | ||||
|  | ||||
| function fs.removeException(fname, mode) | ||||
|     fname = fs.combine(fname, '') | ||||
|     fs.exceptions[fname] = bit.bxor(fs.exceptions[fname] or 0, mode) | ||||
| end | ||||
|  | ||||
| function fs.restore() | ||||
|     fs.open = native.open | ||||
| end | ||||
| @@ -1,175 +0,0 @@ | ||||
| --- 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 <a href="http://www.policyalmanac.org/games/binaryHeaps.htm">binary heap</a> | ||||
| -- 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.<br/> | ||||
| 	-- This class is callable. | ||||
| 	-- _Therefore,_ <code>heap(...)</code> _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 | ||||
| @@ -1,41 +0,0 @@ | ||||
| --- 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 | ||||
| @@ -1,67 +0,0 @@ | ||||
| --- 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 | ||||
| @@ -1,57 +0,0 @@ | ||||
| -- 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 | ||||
| @@ -1,101 +0,0 @@ | ||||
| --- 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 | ||||
| @@ -1,104 +0,0 @@ | ||||
| --[[ | ||||
| 	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. | ||||
| 	-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li> | ||||
| 	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 | ||||
| @@ -1,77 +0,0 @@ | ||||
| -- 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 `<startX, startY>` to location `<endX, endY>`. | ||||
| 	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 | ||||
| @@ -20,6 +20,29 @@ function Packages:installed() | ||||
| 	return list | ||||
| end | ||||
|  | ||||
| function Packages:installedSorted() | ||||
| 	local list = { } | ||||
|  | ||||
| 	for k, v in pairs(self.installed()) do | ||||
| 		v.name = k | ||||
| 		v.deps = { } | ||||
| 		table.insert(list, v) | ||||
| 		for _, v2 in pairs(v.required or { }) do | ||||
| 			v.deps[v2] = true | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	table.sort(list, function(a, b) | ||||
| 		return not not (b.deps and b.deps[a.name]) | ||||
| 	end) | ||||
|  | ||||
| 	table.sort(list, function(a, b) | ||||
| 		return not (a.deps and a.deps[b.name]) | ||||
| 	end) | ||||
|  | ||||
| 	return list | ||||
| end | ||||
|  | ||||
| function Packages:list() | ||||
| 	if not fs.exists('usr/config/packages') then | ||||
| 		self:downloadList() | ||||
|   | ||||
| @@ -1,256 +0,0 @@ | ||||
| local Grid       = require('opus.jumper.grid') | ||||
| local Pathfinder = require('opus.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, | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 kepler155c@gmail.com
					kepler155c@gmail.com