diff --git a/actions/pathfinder.lua b/actions/pathfinder.lua
index 874481b..80bac23 100644
--- a/actions/pathfinder.lua
+++ b/actions/pathfinder.lua
@@ -9,29 +9,6 @@
-- by Roland Yonaba (https://github.com/Yonaba/Jumper).
-- Mapping algorithm: transforms a Minetest map surface to a 2d grid.
-local path = minetest.get_modpath("advanced_npc")
-
--- Below code for require is taken and slightly modified
--- from irc mod by Diego Martinez (kaeza)
--- https://github.com/minetest-mods/irc
--- Handle mod security if needed
-local ie, req_ie = _G, minetest.request_insecure_environment
-if req_ie then ie = req_ie() end
-if not ie then
- error("The Advances NPC mod requires access to insecure functions in "..
- "order to work. Please add the Advanced NPC mod to the "..
- "secure.trusted_mods setting or disable the mod.")
-end
-
--- Modify package path so that it can find the Jumper library files
-ie.package.path =
- path .. "/Jumper/?.lua;"..
- ie.package.path
-
--- Require the main files from Jumper
-local Grid = ie.require("jumper.grid")
-local Pathfinder = ie.require("jumper.pathfinder")
-
pathfinder = {}
pathfinder.node_types = {
@@ -238,7 +215,7 @@ function pathfinder.normalize_map(map)
return result
end
--- This function returns an array of tables with to parameters: type and pos.
+-- This function returns an array of tables with two parameters: type and pos.
-- The position parameter is the actual coordinate on the Minetest map. The
-- type is the type of the node at the coordinate defined as pathfinder.node_types.
function pathfinder.get_path(map, path_nodes)
diff --git a/actions/pathfinder2.lua b/actions/pathfinder2.lua
new file mode 100644
index 0000000..593ab57
--- /dev/null
+++ b/actions/pathfinder2.lua
@@ -0,0 +1,274 @@
+-- Pathfinder by Zorman2000
+-- Pathfinding code with included A* implementation, customized
+-- for Minetest. At the moment, paths can only be found in flat
+-- terrain (only 2D pathfinding)
+
+-- Public namespace
+pathfinder = {}
+-- Private namespace for pathfinder functions
+local finder = {}
+
+pathfinder.node_types = {
+ start = 0,
+ goal = 1,
+ walkable = 2,
+ openable = 3,
+ non_walkable = 4
+}
+
+pathfinder.nodes = {
+ openable_prefix = {
+ "doors:",
+ "cottages:gate",
+ "cottages:half_door"
+ }
+}
+
+function pathfinder.find_path(start_pos, end_pos, extra_range, walkable_nodes)
+ -- Create map
+ local map = finder.create_map(start_pos, end_pos, extra_range, walkable_nodes)
+ minetest.log("Number of nodes in map: "..dump(#map))
+ -- Use A* algorithm
+ local path = minetest.find_path(start_pos, end_pos, 30, 1, 1, "Dijkstra")
+ minetest.log("Path: "..dump(path))
+ return path
+ --return finder.astar({name="air", pos=start_pos}, {name="air", pos=end_pos}, map)
+end
+
+-- This function is used to determine if a node is walkable
+-- or openable, in which case is good to use when finding a path
+function finder.is_good_node(node, exceptions)
+ -- Is openable is to support doors, fence gates and other
+ -- doors from other mods. Currently, default doors, gates
+ -- and cottages doors are supported.
+ local is_openable = false
+ for _,node_prefix in pairs(pathfinder.nodes.openable_prefix) do
+ local start_i,end_i = string.find(node.name, node_prefix)
+ if start_i ~= nil then
+ is_openable = true
+ break
+ end
+ end
+ if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
+ return pathfinder.node_types.walkable
+ elseif is_openable then
+ return pathfinder.node_types.openable
+ else
+ for i = 1, #exceptions do
+ if node.name == exceptions[i] then
+ return pathfinder.node_types.walkable
+ end
+ end
+ return pathfinder.node_types.non_walkable
+ end
+end
+
+-- Maps a 2D slice of Minetest terrain into an array of nodes
+-- Extra range is a number that will be added in the x and the z coordinates
+-- to allow more room to find paths.
+-- Walkables is an array of node names which are considered walkable,
+-- even if they are not.
+function finder.create_map(start_pos, end_pos, extra_range, walkables)
+
+ minetest.log("Start pos: "..minetest.pos_to_string(start_pos))
+ minetest.log("End pos: "..minetest.pos_to_string(end_pos))
+
+ -- Calculate all signs to ensure:
+ -- 1. Correct area calculation
+ -- 2. Iterate in the correct direction
+ local start_x_sign = (start_pos.x - end_pos.x) / math.abs(start_pos.x - end_pos.x)
+ local start_z_sign = (start_pos.z - end_pos.z) / math.abs(start_pos.z - end_pos.z)
+ local end_x_sign = (end_pos.x - start_pos.x) / math.abs(end_pos.x - start_pos.x)
+ local end_z_sign = (end_pos.z - start_pos.z) / math.abs(end_pos.z - start_pos.z)
+
+ -- Correct the signs if they are nan
+ if math.abs(start_pos.x - end_pos.x) == 0 then
+ start_x_sign = -1
+ end_x_sign = 1
+ end
+ if math.abs(start_pos.z - end_pos.z) == 0 then
+ start_z_sign = -1
+ end_z_sign = 1
+ end
+
+ -- Get starting and ending positions, adding the extra nodes to the area
+ local pos1 = {x=start_pos.x + (extra_range * start_x_sign), y = start_pos.y - 1, z=start_pos.z + (extra_range * start_z_sign)}
+ local pos2 = {x=end_pos.x + (extra_range * end_x_sign), y = end_pos.y, z=end_pos.z + (extra_range * end_z_sign)}
+
+ minetest.log("Recalculated pos1: "..minetest.pos_to_string(pos1))
+ minetest.log("Recalculated pos2: "..minetest.pos_to_string(pos2))
+
+ local grid = {}
+
+ -- Loop through the area and classify nodes
+ for z = 1, math.abs(pos1.z - pos2.z) do
+ --local current_row = {}
+ for x = 1, math.abs(pos1.x - pos2.x) do
+ -- Calculate current position
+ local current_pos = {x=pos1.x + (x*end_x_sign), y=pos1.y, z=pos1.z + (z*end_z_sign)}
+ -- Get node info
+ local node = minetest.get_node(current_pos)
+ -- Check if this is the starting position
+ if current_pos.x == start_pos.x and current_pos.z == start_pos.z then
+ -- Is start position
+ table.insert(grid, {name=node.name, pos=current_pos, type=pathfinder.node_types.start})
+ elseif current_pos.x == end_pos.x and current_pos.z == end_pos.z then
+ -- Is ending position or goal position
+ table.insert(grid, {name=node.name, pos=current_pos, type=pathfinder.node_types.goal})
+ else
+ -- Check if node is walkable
+ if node.name == "air" then
+ -- If air do no more checks
+ table.insert(grid, {name=node.name, pos=current_pos, type=pathfinder.node_types.walkable})
+ else
+ -- Check if it is of a walkable or openable type
+ table.insert(grid, {name=node.name, pos=current_pos, type=finder.is_good_node(node, walkables)})
+ end
+ end
+ end
+ -- Insert the converted row into the grid
+ --table.insert(grid, current_row)
+ end
+
+ return grid
+end
+
+--------------------------------------------------------------------------
+-- A* algorithm implementation
+--------------------------------------------------------------------------
+-- Utility functions
+function finder.distance(node1, node2)
+ return math.sqrt(math.pow(node2.pos.x - node1.pos.x, 2) + math.pow(node2.pos.z - node1.pos.z, 2))
+end
+
+function finder.is_valid_neighbor(node1, node2)
+ -- Consider only orthogonal nodes
+ if (node1.pos.x == node2.pos.x and node1.pos.z ~= node2.pos.z)
+ or (node1.pos.z == node2.pos.z and node1.pos.x ~= node2.pos.z) then
+ if (finder.distance(node1, node2) < 2) then
+ return finder.is_good_node(node2, {})
+ end
+ end
+ return false
+end
+
+function finder.cost_estimate(node1, node2)
+ return finder.distance(node1, node2)
+end
+
+function finder.get_lowest_fscore(nodes, f_scores)
+ local lowest = 1/0
+ local best_node = nil
+ for _, node in pairs(nodes) do
+ local score = f_scores[node]
+ if score < lowest then
+ lowest = score
+ best_node = node
+ end
+ end
+ return best_node
+end
+
+function finder.get_neighbor(node, nodes)
+ local neighbors = {}
+ for _, current_node in pairs(nodes) do
+ if current_node ~= node then
+ if finder.is_valid_neighbor(node, current_node) then
+ table.insert(neighbors, current_node)
+ end
+ end
+ end
+ return neighbors
+end
+
+function finder.contains_node(node, all_nodes)
+ for _,current_node in pairs(all_nodes) do
+ if current_node == node then
+ return true
+ end
+ end
+ return false
+end
+
+function finder.remove_node(node, all_nodes)
+ --minetest.log("On remove_node: "..dump(all_nodes))
+ for key, current_node in pairs(all_nodes) do
+ if current_node == node then
+ --minetest.log("Table before: "..dump(all_nodes))
+ table.remove(all_nodes, key)
+ --minetest.log("Table after: "..dump(all_nodes))
+ return
+ end
+ end
+end
+
+function finder.create_path(path, grid, node)
+ if grid[node] ~= nil then
+ table.insert(path, 1, grid[node])
+ return create_path(path, grid, grid[node])
+ else
+ return path
+ end
+end
+
+function finder.astar(start_pos, end_pos, nodes)
+
+ local closed_set = {}
+ local open_set = {
+ start_pos
+ }
+ local came_from = {}
+
+ local g_score = {}
+ local f_score = {}
+ g_score[start_pos] = 0
+ f_score[start_pos] = g_score[start_pos] + finder.cost_estimate(start_pos, end_pos)
+
+ minetest.log("Open set: "..dump(#open_set))
+
+ while #open_set > 0 do
+ minetest.log("Nodes size: "..dump(#nodes))
+
+ local current = finder.get_lowest_fscore(open_set, f_score)
+ minetest.log("Node with best fscore: "..dump(current))
+ if current == end_pos then
+ minetest.log("Creating path: "..dump(came_from))
+ local path = create_path({}, came_from, end_pos)
+ table.insert(path, end_pos)
+ return path
+ end
+
+ minetest.log("Removing node from openset..."..dump(#open_set))
+ finder.remove_node(current, open_set)
+ minetest.log("Removed. Open set size: "..dump(#open_set))
+ minetest.log("Adding to closed set..."..dump(#closed_set))
+ table.insert(closed_set, current)
+ minetest.log("Added. New closed set size: "..dump(#closed_set))
+
+ local neighbors = finder.get_neighbor(current, nodes)
+ minetest.log("Found "..dump(#neighbors).." neighbors for current node")--dump(neighbors))
+ for _, neighbor in pairs(neighbors) do
+ minetest.log("Currently looking at neighbor: "..minetest.pos_to_string(neighbor.pos))
+ if finder.contains_node(neighbor, closed_set) == false then
+ minetest.log("Node is not in closed set")
+
+ local tentative_g_score = g_score[current] + finder.distance(current, neighbor)
+ minetest.log("Tentative g score is: "..dump(tentative_g_score))
+ minetest.log("Logic: "..dump(finder.contains_node(neighbor, open_set) == false or tentative_g_score < g_score[neighbor]))
+ if finder.contains_node(neighbor, open_set) == false or tentative_g_score < g_score[neighbor] then
+ came_from[neighbor] = current
+ minetest.log("Added node to came_from set: "..dump(table.getn(came_from)))
+ g_score[neighbor] = tentative_g_score
+ f_score[neighbor] = g_score[neighbor] + finder.cost_estimate(neighbor, end_pos)
+ if finder.contains_node(neighbor, open_set) == false then
+ minetest.log("Adding neighbor node to open_set: "..dump(#open_set))
+ table.insert(open_set, neighbor)
+ minetest.log("Added. New open set size: "..dump(#open_set))
+ end
+ end
+ end
+ end
+ end
+ -- Path not found
+ return nil
+end
diff --git a/actions/pathfinding.lua b/actions/pathfinding.lua
new file mode 100644
index 0000000..3fa56e5
--- /dev/null
+++ b/actions/pathfinding.lua
@@ -0,0 +1,1311 @@
+-- Code based on Jumper library
+
+--- The Pathfinder class
+
+-- Modification by Zorman2000:
+-- This file has been modified to be usable with Minetest.
+-- Do to Minetest's mod security, the global "require()" call
+-- is disabled, and needs to be replaced by one using Minetest's
+-- environment.
+-- - Get Minetest's insecure environment
+-- - Change all "require" calls to "ie.require()"
+--
+-- The rest of the code is left intact and is (c) 2012-2013 Roland Yonaba
+
+local abs = math.abs
+local sqrt = math.sqrt
+
+local Heuristics = {
+ ['MANHATTAN'] = function(nodeA, nodeB)
+ local dx = abs(nodeA._x - nodeB._x)
+ local dy = abs(nodeA._y - nodeB._y)
+ return (dx + dy)
+ end,
+ ['EUCLIDIAN'] = function(nodeA, nodeB)
+ local dx = nodeA._x - nodeB._x
+ local dy = nodeA._y - nodeB._y
+ return sqrt(dx*dx+dy*dy)
+ end
+}
+
+local Node = setmetatable({},
+ {__call = function(self,...)
+ return Node:new(...)
+ end}
+ )
+ Node.__index = Node
+
+ --- Inits a new `node`
+ -- @class function
+ -- @tparam int x the x-coordinate of the node on the collision map
+ -- @tparam int y the y-coordinate of the node on the collision map
+ -- @treturn node a new `node`
+ -- @usage local node = Node(3,4)
+ function Node:new(x,y)
+ return setmetatable({_x = x, _y = y, _clearance = {}}, 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
+
+ --- Returns x-coordinate of a `node`
+ -- @class function
+ -- @treturn number the x-coordinate of the `node`
+ -- @usage local x = node:getX()
+ function Node:getX() return self._x end
+
+ --- Returns y-coordinate of a `node`
+ -- @class function
+ -- @treturn number the y-coordinate of the `node`
+ -- @usage local y = node:getY()
+ function Node:getY() return self._y end
+
+ --- Returns x and y coordinates of a `node`
+ -- @class function
+ -- @treturn number the x-coordinate of the `node`
+ -- @treturn number the y-coordinate of the `node`
+ -- @usage local x, y = node:getPos()
+ function Node:getPos() return self._x, self._y end
+
+ --- Returns the amount of true [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
+ -- for a given `node`
+ -- @class function
+ -- @tparam string|int|func walkable the value for walkable locations in the collision map array.
+ -- @treturn int the clearance of the `node`
+ -- @usage
+ -- -- Assuming walkable was 0
+ -- local clearance = node:getClearance(0)
+ function Node:getClearance(walkable)
+ return self._clearance[walkable]
+ end
+
+ --- Removes the clearance value for a given walkable.
+ -- @class function
+ -- @tparam string|int|func walkable the value for walkable locations in the collision map array.
+ -- @treturn node self (the calling `node` itself, can be chained)
+ -- @usage
+ -- -- Assuming walkable is defined
+ -- node:removeClearance(walkable)
+ function Node:removeClearance(walkable)
+ self._clearance[walkable] = nil
+ return self
+ 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.
+ -- @class function
+ -- @treturn node self (the calling `node` itself, can be chained)
+ -- @usage
+ -- local thisNode = Node(1,2)
+ -- thisNode:reset()
+ function Node:reset()
+ self._g, self._h, self._f = nil, nil, nil
+ self._opened, self._closed, self._parent = nil, nil, nil
+ return self
+ end
+
+---------------------------------------------------------------
+-- Path class implementation by Ronald Yonaba
+-- Original code here:
+---------------------------------------------------------------
+-- Local references
+local abs, max = math.abs, math.max
+local t_insert, t_remove = table.insert, table.remove
+
+--- The `Path` class.
+-- This class is callable.
+-- Therefore, Path(...)
acts as a shortcut to Path:new(...)
.
+-- @type Path
+local Path = setmetatable({},
+ {__call = function(self,...)
+ return Path:new(...)
+ end
+ })
+
+Path.__index = Path
+
+--- Inits a new `path`.
+-- @class function
+-- @treturn path a `path`
+-- @usage local p = 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}
+-- @class function
+-- @treturn node a `node`
+-- @treturn int the count for the number of nodes
+-- @see Path:nodes
+-- @usage
+-- for node, count in p:iter() do
+-- ...
+-- end
+function Path:iter()
+ local i,pathLen = 1,#self._nodes
+ return function()
+ if self._nodes[i] then
+ i = i+1
+ return self._nodes[i-1],i-1
+ end
+ end
+end
+
+--- Iterates on each single `node` along a `path`. At each step of iteration,
+-- returns a `node` plus a count value. Alias for @{Path:iter}
+-- @class function
+-- @name Path:nodes
+-- @treturn node a `node`
+-- @treturn int the count for the number of nodes
+-- @see Path:iter
+-- @usage
+-- for node, count in p:nodes() do
+-- ...
+-- end
+Path.nodes = Path.iter
+
+--- Evaluates the `path` length
+-- @class function
+-- @treturn number the `path` length
+-- @usage local len = p:getLength()
+function Path:getLength()
+ local len = 0
+ for i = 2,#self._nodes do
+ len = len + Heuristic.EUCLIDIAN(self._nodes[i], self._nodes[i-1])
+ end
+ return len
+end
+
+--- Counts the number of steps.
+-- Returns the number of waypoints (nodes) in the current path.
+-- @class function
+-- @tparam node node a node to be added to the path
+-- @tparam[opt] int index the index at which the node will be inserted. If omitted, the node will be appended after the last node in the path.
+-- @treturn path self (the calling `path` itself, can be chained)
+-- @usage local nSteps = p:countSteps()
+function Path:addNode(node, index)
+ index = index or #self._nodes+1
+ t_insert(self._nodes, index, node)
+ return self
+end
+
+
+--- `Path` filling modifier. Interpolates between non contiguous nodes along a `path`
+-- to build a fully continuous `path`. This maybe useful when using search algorithms such as Jump Point Search.
+-- Does the opposite of @{Path:filter}
+-- @class function
+-- @treturn path self (the calling `path` itself, can be chained)
+-- @see Path:filter
+-- @usage p:fill()
+function Path:fill()
+ local i = 2
+ local xi,yi,dx,dy
+ local N = #self._nodes
+ local incrX, incrY
+ while true do
+ xi,yi = self._nodes[i]._x,self._nodes[i]._y
+ dx,dy = xi-self._nodes[i-1]._x,yi-self._nodes[i-1]._y
+ if (abs(dx) > 1 or abs(dy) > 1) then
+ incrX = dx/max(abs(dx),1)
+ incrY = dy/max(abs(dy),1)
+ t_insert(self._nodes, i, self._grid:getNodeAt(self._nodes[i-1]._x + incrX, self._nodes[i-1]._y +incrY))
+ N = N+1
+ else i=i+1
+ end
+ if i>N then break end
+ end
+ return self
+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,dx,dy, olddx, olddy
+ xi,yi = self._nodes[i]._x, self._nodes[i]._y
+ dx, dy = xi - self._nodes[i-1]._x, yi-self._nodes[i-1]._y
+ while true do
+ olddx, olddy = dx, dy
+ if self._nodes[i+1] then
+ i = i+1
+ xi, yi = self._nodes[i]._x, self._nodes[i]._y
+ dx, dy = xi - self._nodes[i-1]._x, yi - self._nodes[i-1]._y
+ if olddx == dx and olddy == dy then
+ t_remove(self._nodes, i-1)
+ i = i - 1
+ end
+ else break end
+ end
+ return self
+end
+
+--- Clones a `path`.
+-- @class function
+-- @treturn path a `path`
+-- @usage local p = path:clone()
+function Path:clone()
+ local p = Path:new()
+ for node in self:nodes() do p:addNode(node) end
+ return p
+end
+
+--- Checks if a `path` is equal to another. It also supports *filtered paths* (see @{Path:filter}).
+-- @class function
+-- @tparam path p2 a path
+-- @treturn boolean a boolean
+-- @usage print(myPath:isEqualTo(anotherPath))
+function Path:isEqualTo(p2)
+ local p1 = self:clone():filter()
+ local p2 = p2:clone():filter()
+ for node, count in p1:nodes() do
+ if not p2._nodes[count] then return false end
+ local n = p2._nodes[count]
+ if n._x~=node._x or n._y~=node._y then return false end
+ end
+ return true
+end
+
+--- Reverses a `path`.
+-- @class function
+-- @treturn path self (the calling `path` itself, can be chained)
+-- @usage myPath:reverse()
+function Path:reverse()
+ local _nodes = {}
+ for i = #self._nodes,1,-1 do
+ _nodes[#_nodes+1] = self._nodes[i]
+ end
+ self._nodes = _nodes
+ return self
+end
+
+--- Appends a given `path` to self.
+-- @class function
+-- @tparam path p a path
+-- @treturn path self (the calling `path` itself, can be chained)
+-- @usage myPath:append(anotherPath)
+function Path:append(p)
+ for node in p:nodes() do self:addNode(node) end
+ return self
+end
+
+
+
+ -- Local reference
+local floor = math.floor
+
+local Utils = {
+ traceBackPath = function(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
+ indexOf = function(t,v)
+ for i = 1,#t do
+ if t[i] == v then return i end
+ end
+ return nil
+ end,
+
+ getArrayBounds = function(map)
+ local min_x, max_x
+ local min_y, max_y
+ for y in pairs(map) do
+ min_y = not min_y and y or (ymax_y and y or max_y)
+ for x in pairs(map[y]) do
+ min_x = not min_x and x or (xmax_x and x or max_x)
+ end
+ end
+ return min_x,max_x,min_y,max_y
+ end,
+
+ -- Converts an array to a set of nodes
+ arrayToNodes = function(map)
+ local min_x, max_x
+ local min_y, max_y
+ local nodes = {}
+ for y in pairs(map) do
+ min_y = not min_y and y or (ymax_y and y or max_y)
+ nodes[y] = {}
+ for x in pairs(map[y]) do
+ min_x = not min_x and x or (xmax_x and x or max_x)
+ nodes[y][x] = Node:new(x,y)
+ end
+ end
+ return nodes,
+ (min_x or 0), (max_x or 0),
+ (min_y or 0), (max_y or 0)
+ end
+}
+
+---------------------------------------------------------------
+-- B-Heap implementation by Ronald Yonaba
+-- Original code here:
+---------------------------------------------------------------
+
+-- 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
+
+---------------------------------------------------------------
+-- Grid implementation by Ronald Yonaba
+-- Original code here:
+---------------------------------------------------------------
+local pairs = pairs
+local assert = assert
+local next = next
+local setmetatable = setmetatable
+local floor = math.floor
+local coroutine = coroutine
+
+-- Offsets for straights moves
+local straightOffsets = {
+ {x = 1, y = 0} --[[W]], {x = -1, y = 0}, --[[E]]
+ {x = 0, y = 1} --[[S]], {x = 0, y = -1}, --[[N]]
+}
+
+-- Offsets for diagonal moves
+local diagonalOffsets = {
+ {x = -1, y = -1} --[[NW]], {x = 1, y = -1}, --[[NE]]
+ {x = -1, y = 1} --[[SW]], {x = 1, y = 1}, --[[SE]]
+}
+
+--- The `Grid` class.
+-- This class is callable.
+-- Therefore,_ Grid(...)
_acts as a shortcut to_ Grid:new(...)
.
+-- @type Grid
+Grid = setmetatable({},{
+ __call = function(self,...)
+ return self:new(...)
+ end
+})
+Grid.__index = Grid
+
+-- Specialized grids
+local PreProcessGrid = setmetatable({},Grid)
+local PostProcessGrid = setmetatable({},Grid)
+PreProcessGrid.__index = PreProcessGrid
+PostProcessGrid.__index = PostProcessGrid
+PreProcessGrid.__call = function (self,x,y)
+ return self:getNodeAt(x,y)
+end
+PostProcessGrid.__call = function (self,x,y,create)
+ if create then return self:getNodeAt(x,y) end
+ return self._nodes[y] and self._nodes[y][x]
+end
+
+--- Inits a new `grid`
+-- @class function
+-- @tparam table|string map A collision map - (2D array) with consecutive indices (starting at 0 or 1)
+-- or a `string` with line-break chars (\n
or \r
) as row delimiters.
+-- @tparam[opt] bool cacheNodeAtRuntime When __true__, returns an empty `grid` instance, so that
+-- later on, indexing a non-cached `node` will cause it to be created and cache within the `grid` on purpose (i.e, when needed).
+-- This is a __memory-safe__ option, in case your dealing with some tight memory constraints.
+-- Defaults to __false__ when omitted.
+-- @treturn grid a new `grid` instance
+-- @usage
+-- -- A simple 3x3 grid
+-- local myGrid = Grid:new({{0,0,0},{0,0,0},{0,0,0}})
+--
+-- -- A memory-safe 3x3 grid
+-- myGrid = Grid('000\n000\n000', true)
+function Grid:new(map, cacheNodeAtRuntime)
+ if type(map) == 'string' then
+ map = Utils.strToMap(map)
+ end
+ if cacheNodeAtRuntime then
+ return PostProcessGrid:new(map,walkable)
+ end
+ return PreProcessGrid:new(map,walkable)
+end
+
+--- Checks if `node` at [x,y] is __walkable__.
+-- Will check if `node` at location [x,y] both *exists* on the collision map and *is walkable*
+-- @class function
+-- @tparam int x the x-location of the node
+-- @tparam int y the y-location of the node
+-- @tparam[opt] string|int|func walkable the value for walkable locations in the collision map array (see @{Grid:new}).
+-- Defaults to __false__ when omitted.
+-- If this parameter is a function, it should be prototyped as __f(value)__ and return a `boolean`:
+-- __true__ when value matches a __walkable__ `node`, __false__ otherwise. If this parameter is not given
+-- while location [x,y] __is valid__, this actual function returns __true__.
+-- @tparam[optchain] int clearance the amount of clearance needed. Defaults to 1 (normal clearance) when not given.
+-- @treturn bool __true__ if `node` exists and is __walkable__, __false__ otherwise
+-- @usage
+-- -- Always true
+-- print(myGrid:isWalkableAt(2,3))
+--
+-- -- True if node at [2,3] collision map value is 0
+-- print(myGrid:isWalkableAt(2,3,0))
+--
+-- -- True if node at [2,3] collision map value is 0 and has a clearance higher or equal to 2
+-- print(myGrid:isWalkableAt(2,3,0,2))
+--
+function Grid:isWalkableAt(x, y, walkable, clearance)
+ local nodeValue = self._map[y] and self._map[y][x]
+ if nodeValue then
+ if not walkable then return true end
+ else
+ return false
+ end
+ local hasEnoughClearance = not clearance and true or false
+ if not hasEnoughClearance then
+ if not self._isAnnotated[walkable] then return false end
+ local node = self:getNodeAt(x,y)
+ local nodeClearance = node:getClearance(walkable)
+ hasEnoughClearance = (nodeClearance >= clearance)
+ end
+ if self._eval then
+ return walkable(nodeValue) and hasEnoughClearance
+ end
+ return ((nodeValue == walkable) and hasEnoughClearance)
+end
+
+--- Returns the `grid` width.
+-- @class function
+-- @treturn int the `grid` width
+-- @usage print(myGrid:getWidth())
+function Grid:getWidth()
+ return self._width
+end
+
+--- Returns the `grid` height.
+-- @class function
+-- @treturn int the `grid` height
+-- @usage print(myGrid:getHeight())
+function Grid:getHeight()
+ return self._height
+end
+
+--- Returns the collision map.
+-- @class function
+-- @treturn map the collision map (see @{Grid:new})
+-- @usage local map = myGrid:getMap()
+function Grid:getMap()
+ return self._map
+end
+
+--- Returns the set of nodes.
+-- @class function
+-- @treturn {{node,...},...} an array of nodes
+-- @usage local nodes = myGrid:getNodes()
+function Grid:getNodes()
+ return self._nodes
+end
+
+--- Returns the `grid` bounds. Returned values corresponds to the upper-left
+-- and lower-right coordinates (in tile units) of the actual `grid` instance.
+-- @class function
+-- @treturn int the upper-left corner x-coordinate
+-- @treturn int the upper-left corner y-coordinate
+-- @treturn int the lower-right corner x-coordinate
+-- @treturn int the lower-right corner y-coordinate
+-- @usage local left_x, left_y, right_x, right_y = myGrid:getBounds()
+function Grid:getBounds()
+ return self._min_x, self._min_y,self._max_x, self._max_y
+end
+
+--- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`.
+-- @class function
+-- @tparam node node a given `node`
+-- @tparam[opt] string|int|func walkable the value for walkable locations in the collision map array (see @{Grid:new}).
+-- Defaults to __false__ when omitted.
+-- @tparam[optchain] bool allowDiagonal when __true__, allows adjacent nodes are included (8-neighbours).
+-- Defaults to __false__ when omitted.
+-- @tparam[optchain] bool tunnel When __true__, allows the `pathfinder` to tunnel through walls when heading diagonally.
+-- @tparam[optchain] int clearance When given, will prune for the neighbours set all nodes having a clearance value lower than the passed-in value
+-- Defaults to __false__ when omitted.
+-- @treturn {node,...} an array of nodes neighbouring a given node
+-- @usage
+-- local aNode = myGrid:getNodeAt(5,6)
+-- local neighbours = myGrid:getNeighbours(aNode, 0, true)
+function Grid:getNeighbours(node, walkable, allowDiagonal, tunnel, clearance)
+ local neighbours = {}
+ for i = 1,#straightOffsets do
+ local n = self:getNodeAt(
+ node._x + straightOffsets[i].x,
+ node._y + straightOffsets[i].y
+ )
+ if n and self:isWalkableAt(n._x, n._y, walkable, clearance) then
+ neighbours[#neighbours+1] = n
+ end
+ end
+
+ if not allowDiagonal then return neighbours end
+
+ tunnel = not not tunnel
+ for i = 1,#diagonalOffsets do
+ local n = self:getNodeAt(
+ node._x + diagonalOffsets[i].x,
+ node._y + diagonalOffsets[i].y
+ )
+ if n and self:isWalkableAt(n._x, n._y, walkable, clearance) then
+ if tunnel then
+ neighbours[#neighbours+1] = n
+ else
+ local skipThisNode = false
+ local n1 = self:getNodeAt(node._x+diagonalOffsets[i].x, node._y)
+ local n2 = self:getNodeAt(node._x, node._y+diagonalOffsets[i].y)
+ if ((n1 and n2) and not self:isWalkableAt(n1._x, n1._y, walkable, clearance) and not self:isWalkableAt(n2._x, n2._y, walkable, clearance)) then
+ skipThisNode = true
+ end
+ if not skipThisNode then neighbours[#neighbours+1] = n end
+ end
+ end
+ end
+
+ return neighbours
+end
+
+--- Grid iterator. Iterates on every single node
+-- in the `grid`. Passing __lx, ly, ex, ey__ arguments will iterate
+-- only on nodes inside the bounding-rectangle delimited by those given coordinates.
+-- @class function
+-- @tparam[opt] int lx the leftmost x-coordinate of the rectangle. Default to the `grid` leftmost x-coordinate (see @{Grid:getBounds}).
+-- @tparam[optchain] int ly the topmost y-coordinate of the rectangle. Default to the `grid` topmost y-coordinate (see @{Grid:getBounds}).
+-- @tparam[optchain] int ex the rightmost x-coordinate of the rectangle. Default to the `grid` rightmost x-coordinate (see @{Grid:getBounds}).
+-- @tparam[optchain] int ey the bottom-most y-coordinate of the rectangle. Default to the `grid` bottom-most y-coordinate (see @{Grid:getBounds}).
+-- @treturn node a `node` on the collision map, upon each iteration step
+-- @treturn int the iteration count
+-- @usage
+-- for node, count in myGrid:iter() do
+-- print(node:getX(), node:getY(), count)
+-- end
+function Grid:iter(lx,ly,ex,ey)
+ local min_x = lx or self._min_x
+ local min_y = ly or self._min_y
+ local max_x = ex or self._max_x
+ local max_y = ey or self._max_y
+
+ local x, y
+ y = min_y
+ return function()
+ x = not x and min_x or x+1
+ if x > max_x then
+ x = min_x
+ y = y+1
+ end
+ if y > max_y then
+ y = nil
+ end
+ return self._nodes[y] and self._nodes[y][x] or self:getNodeAt(x,y)
+ end
+end
+
+--- Grid iterator. Iterates on each node along the outline (border) of a squared area
+-- centered on the given node.
+-- @tparam node node a given `node`
+-- @tparam[opt] int radius the area radius (half-length). Defaults to __1__ when not given.
+-- @treturn node a `node` at each iteration step
+-- @usage
+-- for node in myGrid:around(node, 2) do
+-- ...
+-- end
+function Grid:around(node, radius)
+ local x, y = node._x, node._y
+ radius = radius or 1
+ local _around = Utils.around()
+ local _nodes = {}
+ repeat
+ local state, x, y = coroutine.resume(_around,x,y,radius)
+ local nodeAt = state and self:getNodeAt(x, y)
+ if nodeAt then _nodes[#_nodes+1] = nodeAt end
+ until (not state)
+ local _i = 0
+ return function()
+ _i = _i+1
+ return _nodes[_i]
+ end
+end
+
+--- Each transformation. Calls the given function on each `node` in the `grid`,
+-- passing the `node` as the first argument to function __f__.
+-- @class function
+-- @tparam func f a function prototyped as __f(node,...)__
+-- @tparam[opt] vararg ... args to be passed to function __f__
+-- @treturn grid self (the calling `grid` itself, can be chained)
+-- @usage
+-- local function printNode(node)
+-- print(node:getX(), node:getY())
+-- end
+-- myGrid:each(printNode)
+function Grid:each(f,...)
+ for node in self:iter() do f(node,...) end
+ return self
+end
+
+--- Each (in range) transformation. Calls a function on each `node` in the range of a rectangle of cells,
+-- passing the `node` as the first argument to function __f__.
+-- @class function
+-- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
+-- @tparam int ly the topmost y-coordinate of the rectangle
+-- @tparam int ex the rightmost x-coordinate of the rectangle
+-- @tparam int ey the bottom-most y-coordinate of the rectangle
+-- @tparam func f a function prototyped as __f(node,...)__
+-- @tparam[opt] vararg ... args to be passed to function __f__
+-- @treturn grid self (the calling `grid` itself, can be chained)
+-- @usage
+-- local function printNode(node)
+-- print(node:getX(), node:getY())
+-- end
+-- myGrid:eachRange(1,1,8,8,printNode)
+function Grid:eachRange(lx,ly,ex,ey,f,...)
+ for node in self:iter(lx,ly,ex,ey) do f(node,...) end
+ return self
+end
+
+--- Map transformation.
+-- Calls function __f(node,...)__ on each `node` in a given range, passing the `node` as the first arg to function __f__ and replaces
+-- it with the returned value. Therefore, the function should return a `node`.
+-- @class function
+-- @tparam func f a function prototyped as __f(node,...)__
+-- @tparam[opt] vararg ... args to be passed to function __f__
+-- @treturn grid self (the calling `grid` itself, can be chained)
+-- @usage
+-- local function nothing(node)
+-- return node
+-- end
+-- myGrid:imap(nothing)
+function Grid:imap(f,...)
+ for node in self:iter() do
+ node = f(node,...)
+ end
+ return self
+end
+
+--- Map in range transformation.
+-- Calls function __f(node,...)__ on each `node` in a rectangle range, passing the `node` as the first argument to the function and replaces
+-- it with the returned value. Therefore, the function should return a `node`.
+-- @class function
+-- @tparam int lx the leftmost x-coordinate coordinate of the rectangle
+-- @tparam int ly the topmost y-coordinate of the rectangle
+-- @tparam int ex the rightmost x-coordinate of the rectangle
+-- @tparam int ey the bottom-most y-coordinate of the rectangle
+-- @tparam func f a function prototyped as __f(node,...)__
+-- @tparam[opt] vararg ... args to be passed to function __f__
+-- @treturn grid self (the calling `grid` itself, can be chained)
+-- @usage
+-- local function nothing(node)
+-- return node
+-- end
+-- myGrid:imap(1,1,6,6,nothing)
+function Grid:imapRange(lx,ly,ex,ey,f,...)
+ for node in self:iter(lx,ly,ex,ey) do
+ node = f(node,...)
+ end
+ return self
+end
+
+-- Specialized grids
+-- Inits a preprocessed grid
+function PreProcessGrid:new(map)
+ local newGrid = {}
+ newGrid._map = map
+ newGrid._nodes, newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y = Utils.arrayToNodes(newGrid._map)
+ newGrid._width = (newGrid._max_x-newGrid._min_x)+1
+ newGrid._height = (newGrid._max_y-newGrid._min_y)+1
+ newGrid._isAnnotated = {}
+ return setmetatable(newGrid,PreProcessGrid)
+end
+
+-- Inits a postprocessed grid
+function PostProcessGrid:new(map)
+ local newGrid = {}
+ newGrid._map = map
+ newGrid._nodes = {}
+ newGrid._min_x, newGrid._max_x, newGrid._min_y, newGrid._max_y = Utils.getArrayBounds(newGrid._map)
+ newGrid._width = (newGrid._max_x-newGrid._min_x)+1
+ newGrid._height = (newGrid._max_y-newGrid._min_y)+1
+ newGrid._isAnnotated = {}
+ return setmetatable(newGrid,PostProcessGrid)
+end
+
+--- Returns the `node` at location [x,y].
+-- @class function
+-- @name Grid:getNodeAt
+-- @tparam int x the x-coordinate coordinate
+-- @tparam int y the y-coordinate coordinate
+-- @treturn node a `node`
+-- @usage local aNode = myGrid:getNodeAt(2,2)
+
+-- Gets the node at location on a preprocessed grid
+function PreProcessGrid:getNodeAt(x,y)
+ return self._nodes[y] and self._nodes[y][x] or nil
+end
+
+-- Gets the node at location on a postprocessed grid
+function PostProcessGrid:getNodeAt(x,y)
+ if not x or not y 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 not self._nodes[y] then self._nodes[y] = {} end
+ if not self._nodes[y][x] then self._nodes[y][x] = Node:new(x,y) end
+ return self._nodes[y][x]
+end
+
+
+---------------------------------------------------------------
+-- A* algorithm implementation by Ronald Yonaba
+-- Original code here:
+---------------------------------------------------------------
+-- Internalization
+local ipairs = ipairs
+local huge = math.huge
+
+-- Updates G-cost
+local function computeCost(node, neighbour, finder, clearance)
+ local mCost = Heuristics.EUCLIDIAN(neighbour, node)
+ if node._g + mCost < neighbour._g then
+ neighbour._parent = node
+ neighbour._g = node._g + mCost
+ end
+end
+
+-- Updates vertex node-neighbour
+local function updateVertex(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
+ local oldG = neighbour._g
+ local cmpCost = overrideCostEval or computeCost
+ cmpCost(node, neighbour, finder, clearance)
+ if neighbour._g < oldG then
+ local nClearance = neighbour._clearance[finder._walkable]
+ local pushThisNode = clearance and nClearance and (nClearance >= clearance)
+ if (clearance and pushThisNode) or (not clearance) 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
+end
+
+-- Calculates a path.
+-- Returns the path from location `` to location ``.
+local function ASTAR(finder, startNode, endNode, clearance, toClear, overrideHeuristic, overrideCostEval)
+
+ local heuristic = overrideHeuristic or finder._heuristic
+ local openList = heap()
+ startNode._g = 0
+ startNode._h = 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, finder._walkable, finder._allowDiagonal, finder._tunnel)
+ 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(finder, openList, node, neighbour, endNode, clearance, heuristic, overrideCostEval)
+ end
+ end
+ end
+
+ return nil
+end
+
+local Finders = {
+ ['ASTAR'] = ASTAR
+}
+
+-- Internalization
+local pairs = pairs
+local assert = assert
+local type = type
+
+local setmetatable, getmetatable = setmetatable, getmetatable
+
+-- Will keep track of all nodes expanded during the search
+-- to easily reset their properties for the next pathfinding call
+local toClear = {}
+
+--- Search modes. Refers to the search modes. In ORTHOGONAL mode, 4-directions are only possible when moving,
+-- including North, East, West, South. In DIAGONAL mode, 8-directions are possible when moving,
+-- including North, East, West, South and adjacent directions.
+--
+-- ORTHOGONAL
+-- DIAGONAL
+-- @mode Modes
+-- @see Pathfinder:getModes
+local searchModes = {['DIAGONAL'] = true, ['ORTHOGONAL'] = true}
+
+-- Performs a traceback from the goal node to the start node
+-- Only happens when the path was found
+
+--- The `Pathfinder` class.
+-- This class is callable.
+-- Therefore,_ Pathfinder(...)
_acts as a shortcut to_ Pathfinder:new(...)
.
+-- @type Pathfinder
+Pathfinder = setmetatable({},{
+ __call = function(self,...)
+ return self:new(...)
+ end
+})
+Pathfinder.__index = Pathfinder
+
+--- Inits a new `pathfinder`
+-- @class function
+-- @tparam grid grid a `grid`
+-- @tparam[opt] string finderName the name of the `Finder` (search algorithm) to be used for search.
+-- Defaults to `ASTAR` when not given (see @{Pathfinder:getFinders}).
+-- @tparam[optchain] string|int|func walkable the value for __walkable__ nodes.
+-- If this parameter is a function, it should be prototyped as __f(value)__, returning a boolean:
+-- __true__ when value matches a __walkable__ `node`, __false__ otherwise.
+-- @treturn pathfinder a new `pathfinder` instance
+-- @usage
+-- -- Example one
+-- local finder = Pathfinder:new(myGrid, 'ASTAR', 0)
+--
+-- -- Example two
+-- local function walkable(value)
+-- return value > 0
+-- end
+-- local finder = Pathfinder(myGrid, 'JPS', walkable)
+function Pathfinder:new(grid, finderName, walkable)
+ local newPathfinder = {}
+ setmetatable(newPathfinder, Pathfinder)
+ newPathfinder:setGrid(grid)
+ newPathfinder:setFinder(finderName)
+ newPathfinder:setWalkable(walkable)
+ newPathfinder:setMode('DIAGONAL')
+ newPathfinder:setHeuristic('MANHATTAN')
+ newPathfinder:setTunnelling(false)
+ return newPathfinder
+end
+
+--- Evaluates [clearance](http://aigamedev.com/open/tutorial/clearance-based-pathfinding/#TheTrueClearanceMetric)
+-- for the whole `grid`. It should be called only once, unless the collision map or the
+-- __walkable__ attribute changes. The clearance values are calculated and cached within the grid nodes.
+-- @class function
+-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
+-- @usage myFinder:annotateGrid()
+function Pathfinder:annotateGrid()
+ --assert(self._walkable, 'Finder must implement a walkable value')
+ for x=self._grid._max_x,self._grid._min_x,-1 do
+ for y=self._grid._max_y,self._grid._min_y,-1 do
+ local node = self._grid:getNodeAt(x,y)
+ if self._grid:isWalkableAt(x,y,self._walkable) then
+ local nr = self._grid:getNodeAt(node._x+1, node._y)
+ local nrd = self._grid:getNodeAt(node._x+1, node._y+1)
+ local nd = self._grid:getNodeAt(node._x, node._y+1)
+ if nr and nrd and nd then
+ local m = nrd._clearance[self._walkable] or 0
+ m = (nd._clearance[self._walkable] or 0)0
+-- end
+function Pathfinder:setWalkable(walkable)
+ --assert(Assert.matchType(walkable,'stringintfunctionnil'),
+ -- ('Wrong argument #1. Expected \'string\', \'number\' or \'function\', got %s.'):format(type(walkable)))
+ self._walkable = walkable
+ self._grid._eval = type(self._walkable) == 'function'
+ return self
+end
+
+--- Gets the __walkable__ value or function.
+-- @class function
+-- @treturn string|int|func the `walkable` value or function
+-- @usage local walkable = myFinder:getWalkable()
+function Pathfinder:getWalkable()
+ return self._walkable
+end
+
+--- Defines the `finder`. It refers to the search algorithm used by the `pathfinder`.
+-- Default finder is `ASTAR`. Use @{Pathfinder:getFinders} to get the list of available finders.
+-- @class function
+-- @tparam string finderName the name of the `finder` to be used for further searches.
+-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
+-- @usage
+-- --To use Breadth-First-Search
+-- myFinder:setFinder('BFS')
+-- @see Pathfinder:getFinders
+function Pathfinder:setFinder(finderName)
+ if not finderName then
+ if not self._finder then
+ finderName = 'ASTAR'
+ else return
+ end
+ end
+ --assert(Finders[finderName],'Not a valid finder name!')
+ self._finder = finderName
+ return self
+end
+
+--- Returns the name of the `finder` being used.
+-- @class function
+-- @treturn string the name of the `finder` to be used for further searches.
+-- @usage local finderName = myFinder:getFinder()
+function Pathfinder:getFinder()
+ return self._finder
+end
+
+--- Returns the list of all available finders names.
+-- @class function
+-- @treturn {string,...} array of built-in finders names.
+-- @usage
+-- local finders = myFinder:getFinders()
+-- for i, finderName in ipairs(finders) do
+-- print(i, finderName)
+-- end
+function Pathfinder:getFinders()
+ return Utils.getKeys(Finders)
+end
+
+--- Sets a heuristic. This is a function internally used by the `pathfinder` to find the optimal path during a search.
+-- Use @{Pathfinder:getHeuristics} to get the list of all available `heuristics`. One can also define
+-- his own `heuristic` function.
+-- @class function
+-- @tparam func|string heuristic `heuristic` function, prototyped as __f(dx,dy)__ or as a `string`.
+-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
+-- @see Pathfinder:getHeuristics
+-- @see core.heuristics
+-- @usage myFinder:setHeuristic('MANHATTAN')
+function Pathfinder:setHeuristic(heuristic)
+ --assert(Heuristic[heuristic] or (type(heuristic) == 'function'),'Not a valid heuristic!')
+ self._heuristic = Heuristics[heuristic] or heuristic
+ return self
+end
+
+--- Returns the `heuristic` used. Returns the function itself.
+-- @class function
+-- @treturn func the `heuristic` function being used by the `pathfinder`
+-- @see core.heuristics
+-- @usage local h = myFinder:getHeuristic()
+function Pathfinder:getHeuristic()
+ return self._heuristic
+end
+
+--- Gets the list of all available `heuristics`.
+-- @class function
+-- @treturn {string,...} array of heuristic names.
+-- @see core.heuristics
+-- @usage
+-- local heur = myFinder:getHeuristic()
+-- for i, heuristicName in ipairs(heur) do
+-- ...
+-- end
+function Pathfinder:getHeuristics()
+ return Utils.getKeys(Heuristic)
+end
+
+--- Defines the search `mode`.
+-- The default search mode is the `DIAGONAL` mode, which implies 8-possible directions when moving (north, south, east, west and diagonals).
+-- In `ORTHOGONAL` mode, only 4-directions are allowed (north, south, east and west).
+-- Use @{Pathfinder:getModes} to get the list of all available search modes.
+-- @class function
+-- @tparam string mode the new search `mode`.
+-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
+-- @see Pathfinder:getModes
+-- @see Modes
+-- @usage myFinder:setMode('ORTHOGONAL')
+function Pathfinder:setMode(mode)
+ --assert(searchModes[mode],'Invalid mode')
+ self._allowDiagonal = (mode == 'DIAGONAL')
+ return self
+end
+
+--- Returns the search mode.
+-- @class function
+-- @treturn string the current search mode
+-- @see Modes
+-- @usage local mode = myFinder:getMode()
+function Pathfinder:getMode()
+ return (self._allowDiagonal and 'DIAGONAL' or 'ORTHOGONAL')
+end
+
+--- Gets the list of all available search modes.
+-- @class function
+-- @treturn {string,...} array of search modes.
+-- @see Modes
+-- @usage local modes = myFinder:getModes()
+-- for modeName in ipairs(modes) do
+-- ...
+-- end
+function Pathfinder:getModes()
+ return Utils.getKeys(searchModes)
+end
+
+--- Enables tunnelling. Defines the ability for the `pathfinder` to tunnel through walls when heading diagonally.
+-- This feature __is not compatible__ with Jump Point Search algorithm (i.e. enabling it will not affect Jump Point Search)
+-- @class function
+-- @tparam bool bool a boolean
+-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
+-- @usage myFinder:setTunnelling(true)
+function Pathfinder:setTunnelling(bool)
+ --assert(Assert.isBool(bool), ('Wrong argument #1. Expected boolean, got %s'):format(type(bool)))
+ self._tunnel = bool
+ return self
+end
+
+--- Returns tunnelling feature state.
+-- @class function
+-- @treturn bool tunnelling feature actual state
+-- @usage local isTunnellingEnabled = myFinder:getTunnelling()
+function Pathfinder:getTunnelling()
+ return self._tunnel
+end
+
+--- Calculates a `path`. Returns the `path` from location __[startX, startY]__ to location __[endX, endY]__.
+-- Both locations must exist on the collision map. The starting location can be unwalkable.
+-- @class function
+-- @tparam int startX the x-coordinate for the starting location
+-- @tparam int startY the y-coordinate for the starting location
+-- @tparam int endX the x-coordinate for the goal location
+-- @tparam int endY the y-coordinate for the goal location
+-- @tparam int clearance the amount of clearance (i.e the pathing agent size) to consider
+-- @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, endX, endY, clearance)
+ self:reset()
+ local startNode = self._grid:getNodeAt(startX, startY)
+ local endNode = self._grid:getNodeAt(endX, endY)
+ --assert(startNode, ('Invalid location [%d, %d]'):format(startX, startY))
+ --assert(endNode and self._grid:isWalkableAt(endX, endY),
+ -- ('Invalid or unreachable location [%d, %d]'):format(endX, endY))
+ local _endNode = Finders[self._finder](self, startNode, endNode, clearance, 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
\ No newline at end of file
diff --git a/actions/places.lua b/actions/places.lua
index eaae95c..d7a121c 100644
--- a/actions/places.lua
+++ b/actions/places.lua
@@ -146,6 +146,7 @@ function npc.places.openable_node_is_entrance(pos)
local node = minetest.get_node(pos)
local x_adj = 0
local z_adj = 0
+ minetest.log(node.param2)
if node.param2 then
if node.param2 == 0 then
@@ -157,7 +158,8 @@ function npc.places.openable_node_is_entrance(pos)
end
end
- --local first_check_pos = {x=, y=, z=}
+ local y_adj = 2
+ local first_check_pos = {x=pos.x + x_adj, y=pos.y + y_adj, z=pos.z + z_adj}
end
diff --git a/init.lua b/init.lua
index eb1ad9f..3b44a38 100755
--- a/init.lua
+++ b/init.lua
@@ -1,6 +1,30 @@
-- Advanced NPC mod by Zorman2000
local path = minetest.get_modpath("advanced_npc")
+-- Below code for require is taken and slightly modified
+-- from irc mod by Diego Martinez (kaeza)
+-- https://github.com/minetest-mods/irc
+-- Handle mod security if needed
+-- local ie = minetest.request_insecure_environment()
+-- -- local req_ie = minetest.request_insecure_environment()
+-- -- if req_ie then
+-- -- ie = req_ie
+-- -- end
+-- if not ie then
+-- error("The Advanced NPC mod requires access to insecure functions in "..
+-- "order to work. Please add the Advanced NPC mod to the "..
+-- "secure.trusted_mods setting or disable the mod.")
+-- end
+
+-- -- Modify package path so that it can find the Jumper library files
+-- ie.package.path =
+-- path .. "/Jumper/?.lua;"..
+-- ie.package.path
+
+-- -- Require the main files from Jumper
+-- Grid = ie.require("jumper.grid")
+-- Pathfinder = ie.require("jumper.pathfinder")
+
-- Intllib
local S
if minetest.get_modpath("intllib") then
@@ -33,6 +57,8 @@ dofile(path .. "/trade/prices.lua")
dofile(path .. "/actions/actions.lua")
dofile(path .. "/actions/places.lua")
dofile(path .. "/actions/pathfinder.lua")
+dofile(path .. "/actions/pathfinding.lua")
+--dofile(path .. "/actions/pathfinder2.lua")
dofile(path .. "/actions/node_registry.lua")
dofile(path .. "/random_data.lua")
diff --git a/npc.lua b/npc.lua
index bf5afe4..e80a728 100755
--- a/npc.lua
+++ b/npc.lua
@@ -269,9 +269,11 @@ function npc.initialize(entity, pos, is_lua_entity)
-- Temporary initialization of actions for testing
local nodes = npc.places.find_node_nearby(ent.object:getpos(), {"cottages:bench"}, 20)
- --minetest.log("Found nodes: "..dump(nodes))
- --local path = pathfinder.find_path(ent.object:getpos(), nodes[1], 20)
+
+ minetest.log("Self destination: "..minetest.pos_to_string(nodes[1]))
+
+ local path = pathfinder.find_path(ent.object:getpos(), nodes[1], 20, {})
--minetest.log("Path to node: "..dump(path))
--npc.add_action(ent, npc.actions.use_door, {self = ent, pos = nodes[1], action = npc.actions.door_action.OPEN})
--npc.add_action(ent, npc.actions.stand, {self = ent})
@@ -861,6 +863,8 @@ mobs:register_mob("advanced_npc:npc", {
minetest.log("[advanced_npc] WARNING: Initializing NPC from entity step. This message should only be appearing if an NPC is being spawned from inventory with egg!")
npc.initialize(self, self.object:getpos(), true)
else
+ self.tamed = false
+ self.owner = nil
-- NPC is initialized, check other variables
-- Timer function for casual traders to reset their trade offers
self.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime
@@ -971,6 +975,81 @@ mobs:register_mob("advanced_npc:npc", {
end
end
end
+
+ -- if self.follow_path then
+ -- self.home_timer = (self.home_timer or 0) + dtime
+ -- if self.home_timer < 1 then return end -- every 1 second
+ -- self.home_timer = 0
+
+ -- -- if self.time_of_day > 0.2 and self.time_of_day < 0.8 then
+ -- -- return -- return if not night time
+ -- -- end
+
+ -- local h = self.destination
+ -- --local h = {x = 1, y = 8, z = 2} -- destination coords
+ -- local p = self.object:getpos() -- mob position
+
+ -- -- lets try find a path, first take care of positions
+ -- -- since pathfinder is very sensitive
+ -- local pheight = self.collisionbox[5] - self.collisionbox[2]
+
+ -- -- round position to center of node to avoid stuck in walls
+ -- -- also adjust height for player models!
+ -- p.x = math.floor(p.x + 0.5)
+ -- p.y = math.floor(p.y + 0.5) - pheight
+ -- p.z = math.floor(p.z + 0.5)
+
+ -- local ssight, sground = minetest.line_of_sight(p, {
+ -- x = p.x, y = p.y - 4, z = p.z}, 1)
+
+ -- -- determine node above ground
+ -- if not ssight then
+ -- p.y = sground.y + 1
+ -- end
+
+ -- h.x = math.floor(h.x + 0.5)
+ -- h.y = math.floor(h.y + 0.5)
+ -- h.z = math.floor(h.z + 0.5)
+
+
+ -- local x, y, z = p.x - h.x, p.y - h.y, p.z - h.z
+ -- local dist = math.floor(math.sqrt(x * x + y * y + z * z))
+
+ -- minetest.log("Self pos : "..minetest.pos_to_string(p))
+ -- minetest.log("Self dest: "..minetest.pos_to_string(h))
+
+ -- if dist <= 1 then
+ -- print ("--- home!")
+ -- self.homepath = nil
+ -- self.state = "stand"
+ -- return
+ -- end
+
+ -- if self.homepath == nil then
+ -- self.homepath = minetest.find_path(p, h, 50, 3, 6, "A*")
+ -- print ("--- finding route", self.homepath, dist)
+ -- end
+
+ -- if self.homepath then
+ -- print ("--- following path", dist, #self.homepath)
+
+ -- local np = self.homepath[1] ; if not np then return end
+
+ -- if math.abs(np.x - p.x) + math.abs(np.z - p.z) < 0.6 then
+ -- table.remove(self.homepath, 1) ; print ("-- removed entry")
+ -- end
+
+ -- np = {x = np.x, y = np.y, z = np.z}
+
+ -- local vec = {x = np.x - p.x, z = np.z - p.z}
+ -- local yaw = (math.atan(vec.z / vec.x) + math.pi / 2) - self.rotate
+
+ -- if np.x > p.x then yaw = yaw + math.pi end
+
+ -- self.object:setyaw(yaw)
+ -- set_velocity(self, self.walk_velocity)
+ -- end
+ -- end
return self.freeze
end
diff --git a/random_data.lua b/random_data.lua
index c31fc36..ceb3564 100644
--- a/random_data.lua
+++ b/random_data.lua
@@ -327,12 +327,55 @@ npc.data.FIRST_NAMES = {
female = {
"Kimy",
"Lili",
- "Cora",
- "Caroline",
+ "Coraline",
"Gloria",
"Mary",
"Mayra",
- "Arlene"
+ "Arlene",
+ "Tita",
+ "Lola",
+ "Olivia",
+ "Katherine",
+ "Cataline",
+ "Pinky",
+ "Kathleen",
+ "Marilyn",
+ "Sunshine",
+ "April",
+ "Rainy",
+ "Lulu",
+ "Sandra",
+ "Marlene",
+ "Lany",
+ "Zoe",
+ "Jolie",
+ "Vicky",
+ "Natalia",
+ "Evelyn",
+ "Elizabeth",
+ "Giselle",
+ "Jasmine",
+ "Karla",
+ "Leslie",
+ "Karen",
+ "Dana",
+ "Merry",
+ "Helena",
+ "Rose",
+ "Thalia",
+ "Luna",
+ "Valery",
+ "Carol",
+ "Paulette",
+ "Rosie",
+ "Leti",
+ "Sophie",
+ "Miranda",
+ "Arianne",
+ "Lizzy",
+ "Amy",
+ "Chole",
+ "Alisson"
},
male = {
"Jote",
@@ -341,7 +384,9 @@ npc.data.FIRST_NAMES = {
"Joseph",
"Gerald",
"Kiko",
- "Michael"
+ "Michael",
+ "Alexis",
+ "Rafa"
}
}