diff --git a/actions/pathfinding.lua b/actions/jumper.lua similarity index 90% rename from actions/pathfinding.lua rename to actions/jumper.lua index 3fa56e5..90c1087 100644 --- a/actions/pathfinding.lua +++ b/actions/jumper.lua @@ -1,20 +1,51 @@ --- Code based on Jumper library +--------------------------------------------------------------------------------------- +-- This code is entirely based on the Jumper library by Roland Yonaba. +-- The modifications are only to make it work under Minetest's secure +-- environment. Therefore, the code in this file is under the MIT license +-- as the original Jumper library. The original library code can be found +-- here: https://github.com/Yonaba/Jumper ---- The Pathfinder class +-- Modifications are by Hector Franqui (Zorman2000) --- 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 +--------------------------------------------------------------------------------------- +-- Copyright (c) 2012-2013 Roland Yonaba +-- Permission is hereby granted, free of charge, to any person obtaining a +-- copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: + +-- The above copyright notice and this permission notice shall be included +-- in all copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-- +--------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------- + +-- Local variables declarations local abs = math.abs local sqrt = math.sqrt +local max = math.max +local floor = math.floor +local t_insert, t_remove = table.insert, table.remove +local huge = math.huge + +--------------------------------------------------------------------------------------- +-- Heuristics based on implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/core/heuristics.lua +--------------------------------------------------------------------------------------- local Heuristics = { ['MANHATTAN'] = function(nodeA, nodeB) local dx = abs(nodeA._x - nodeB._x) @@ -28,6 +59,11 @@ local Heuristics = { end } + +--------------------------------------------------------------------------------------- +-- Node class implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/core/node.lua +--------------------------------------------------------------------------------------- local Node = setmetatable({}, {__call = function(self,...) return Node:new(...) @@ -106,14 +142,11 @@ local Node = setmetatable({}, 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 +--------------------------------------------------------------------------------------- +-- Path class implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/core/path.lua +--------------------------------------------------------------------------------------- --- The `Path` class.
-- This class is callable. -- Therefore, Path(...) acts as a shortcut to Path:new(...). @@ -294,12 +327,12 @@ function Path:append(p) for node in p:nodes() do self:addNode(node) end return self end - - -- Local reference -local floor = math.floor - +--------------------------------------------------------------------------------------- +-- Utils class based on implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/core/utils.lua +--------------------------------------------------------------------------------------- local Utils = { traceBackPath = function(finder, node, startNode) local path = Path:new() @@ -358,11 +391,11 @@ local Utils = { end } ---------------------------------------------------------------- --- B-Heap implementation by Ronald Yonaba --- Original code here: ---------------------------------------------------------------- +--------------------------------------------------------------------------------------- +-- Bheap class implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/core/bheap.lua +--------------------------------------------------------------------------------------- -- Default comparison function local function f_min(a,b) return a < b end @@ -507,15 +540,15 @@ function heap:heapify(item) return self end ---------------------------------------------------------------- --- Grid implementation by Ronald Yonaba --- Original code here: ---------------------------------------------------------------- + +--------------------------------------------------------------------------------------- +-- Grid class implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/grid.lua +--------------------------------------------------------------------------------------- local pairs = pairs local assert = assert local next = next local setmetatable = setmetatable -local floor = math.floor local coroutine = coroutine -- Offsets for straights moves @@ -904,13 +937,12 @@ function PostProcessGrid:getNodeAt(x,y) end ---------------------------------------------------------------- --- A* algorithm implementation by Ronald Yonaba --- Original code here: ---------------------------------------------------------------- +--------------------------------------------------------------------------------------- +-- A* algorithm based on implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/search/astar.lua +--------------------------------------------------------------------------------------- -- Internalization local ipairs = ipairs -local huge = math.huge -- Updates G-cost local function computeCost(node, neighbour, finder, clearance) @@ -973,6 +1005,11 @@ local function ASTAR(finder, startNode, endNode, clearance, toClear, overrideHeu return nil end + +--------------------------------------------------------------------------------------- +-- Pathfinder class based on implementation by Ronald Yonaba +-- Original code here: https://github.com/Yonaba/Jumper/jumper/pathfinder.lua +--------------------------------------------------------------------------------------- local Finders = { ['ASTAR'] = ASTAR } diff --git a/actions/pathfinder2.lua b/actions/pathfinder2.lua deleted file mode 100644 index 593ab57..0000000 --- a/actions/pathfinder2.lua +++ /dev/null @@ -1,274 +0,0 @@ --- 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/npc.lua b/npc.lua index e80a728..36270a5 100755 --- a/npc.lua +++ b/npc.lua @@ -273,7 +273,7 @@ function npc.initialize(entity, pos, is_lua_entity) minetest.log("Self destination: "..minetest.pos_to_string(nodes[1])) - local path = pathfinder.find_path(ent.object:getpos(), nodes[1], 20, {}) + --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})