Pathfinder: Drop jumper.lua's pathfinder in favor of MarkBu's pathfinder with slight modifications, as the later one supports 3D paths.
Clean up old pathfinder code. Add path decoration to MarkBu pathfinder's generated paths. Change actions.lua and places.lua to use the new pathfinder. Properly modify README.md.
This commit is contained in:
parent
e1bf931064
commit
fb549e7f93
25
README.md
25
README.md
@ -4,25 +4,40 @@ advanced_npc
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Advanced NPC is a mod for Minetest, based on mobs_redo.
|
||||
The goal of this mod is to be able to have live villages in Minetest. These NPCs are highly inspired by the typical NPCs of Harvest Moon games. The general idea is that on almost all buildings of a village there are NPCs that are kind of intelligent: they have daily tasks they perform, can speak to players, can trade with the player, can use their own items (chests and furnaces for example), know where to go around their house and village, can be lumbers, miners or any other Minetest-suitable profession and can ultimately engage into relationships with the player. And while basically only players are mentioned here, the ultimate goal is that they can do all of this also among themselves, so that villages are alive and evolving by themselves, without player intervention.
|
||||
Advanced NPC is a mod for Minetest using mobs_redo API.
|
||||
The goal of this mod is to be able to have live villages in Minetest. These NPCs are highly inspired by the typical NPCs of _Harvest Moon_ games. The general idea is that on almost all buildings of a village there are NPCs that are kind of intelligent: they have daily tasks they perform, can speak to players, can trade with the player, can use their own items (chests and furnaces for example), know where to go around their house and village, can be lumbers, miners or any other Minetest-suitable profession and can ultimately engage into relationships with the player. And while basically only players are mentioned here, the ultimate goal is that they can do all of this also among themselves, so that villages are alive and evolving by themselves, without player intervention.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
__NOTE__: Advanced NPC is still under development. While the mod is largely stable, it lacks one of the most important pieces: spawning. Currently, NPCs will spawn on stone (default:stone) and the mg_villages' plotmarkers (mg_villages:plotmarker). The spawning is not controlled, so you will have several of them walking around. This is not how it is planned and is just for testing purposes. In the future, only a handful of NPCs should spawn at village house's plotmarker and they will know their way around the house and have specific jobs.
|
||||
__NOTE__: Advanced NPC is still under development. While the mod is largely stable, it lacks one of the most important pieces: spawning. Currently, NPCs can be spawned using eggs (found in creative inventory as 'NPC') and by themselves on villages of the [mg_villages mod](https://forum.minetest.net/viewtopic.php?t=13589). NPCs will spawn automatically on mg_villages villages and over time will populate the entire village. If something goes wrong, you can reset the village by:
|
||||
- Clearing all objects (in chat, type /clearobjects quick)
|
||||
- Restore original plotmarkers (in chat, type /restore_plotmarkers radius)
|
||||
- The radius can be any number, but it is recommended you use a not so large number. 200 is suitable. So stand in the middle of the village and then run that command.
|
||||
This will actually restore the village and will slowly make NPCs spawn again. Currently there's no way to disable NPCs spawning on village, except by going to `spawner.lua` and commenting out all of `minetest.register_abm()` code.
|
||||
|
||||
Download the mod [here](https://github.com/hkzorman/advanced_npc/archive/master.zip) (link always pointing to latest version)
|
||||
__Download__ the mod [here](https://github.com/hkzorman/advanced_npc/archive/master.zip) (link always pointing to latest version)
|
||||
|
||||
For this mod to work correctly, you also need to install the [mobs_redo](https://github.com/tenplus1/mobs_redo) mod. After installation, make sure you enable it in your world.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
__advanced_npc__ is Copyright (C) 2016-2017 Hector Franqui (zorman2000), licensed under the GPLv3 license. See `license.txt` for details.
|
||||
|
||||
The `jumper.lua` file contains code based on the [Jumper library](https://github.com/Yonaba/Jumper), which is Copyright (c) 2012-2013 Roland Yonaba, licensed under MIT license. See `actions/jumper.lua` for details.
|
||||
The `pathfinder.lua` file contains code slighlty modified from the [pathfinder mod](https://github.com/Yonaba/Jumper) by MarkBu, which is licensed as WTFPL. See `actions/pathfinder.lua` for details.
|
||||
|
||||
Current NPC textures are from mobs_redo mod.
|
||||
The following textures are by Zorman2000:
|
||||
- marriage_ring.png - CC BY-SA
|
||||
|
||||
|
||||
Documentation and API
|
||||
---------------------
|
||||
|
||||
This mod requires a good user manual, and also is planned to have an extensive API, properly documented. Unfortunately, these still aren't ready. A very very very WIP manual can be found in the [wiki](https://github.com/hkzorman/advanced_npc/wiki/Concept%3A-Dialogues)
|
||||
|
||||
|
||||
Roadmap
|
||||
|
@ -599,7 +599,7 @@ function npc.actions.use_bed(self, args)
|
||||
end
|
||||
local action = args.action
|
||||
local node = minetest.get_node(pos)
|
||||
minetest.log(dump(node))
|
||||
--minetest.log(dump(node))
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
|
||||
if action == npc.actions.const.beds.LAY then
|
||||
@ -746,20 +746,9 @@ function npc.actions.walk_to_pos(self, args)
|
||||
end
|
||||
|
||||
-- Find path
|
||||
--local path = pathfinder.find_path(start_pos, end_pos, 20, walkable_nodes)
|
||||
local path = pathfinder.find_path(start_pos, end_pos, self)
|
||||
local path = npc.pathfinder.find_path(start_pos, end_pos, self, true)
|
||||
|
||||
if path ~= nil and #path > 1 then
|
||||
-- Get details from path nodes
|
||||
-- This might get moved to proper place, pathfinder.lua code
|
||||
local path_detail = {}
|
||||
for i = 1, #path do
|
||||
local node = minetest.get_node(path[i])
|
||||
table.insert(path_detail, {pos=path[i], type=npc.pathfinder.is_good_node(node, {})})
|
||||
end
|
||||
path = path_detail
|
||||
|
||||
npc.log("DEBUG", "Detailed path: "..dump(path))
|
||||
npc.log("INFO", "walk_to_pos Found path to node: "..minetest.pos_to_string(end_pos))
|
||||
-- Store path
|
||||
self.actions.walking.path = path
|
||||
|
1349
actions/jumper.lua
1349
actions/jumper.lua
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,13 @@
|
||||
-- Pathfinding code by Zorman2000
|
||||
-- Pathfinding code by MarkBu, original can be found here:
|
||||
-- https://github.com/MarkuBu/pathfinder
|
||||
--
|
||||
-- Modifications by Zorman2000
|
||||
-- This version is slightly modified to use another "walkable" function,
|
||||
-- plus add a "decorating" path function which allows to know the type
|
||||
-- of nodes in the path.
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Pathfinding functionality
|
||||
---------------------------------------------------------------------------------------
|
||||
-- This class contains functions that allows to map the 3D map of Minetest into
|
||||
-- a 2D array (basically by ignoring the y coordinate for the moment being) in order
|
||||
-- to use the A* pathfinding algorithm to find the shortest path from one node to
|
||||
-- another. The A* algorithm implementation is in the 'jumper.lua' file which a
|
||||
-- reduced and slightly modified version of the Jumper library, by Roland Yonaba
|
||||
-- (https://github.com/Yonaba/Jumper).
|
||||
-- Mapping algorithm: transforms a Minetest map surface to a 2d grid.
|
||||
|
||||
npc.pathfinder = {}
|
||||
|
||||
@ -30,57 +29,15 @@ npc.pathfinder.nodes = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-- This function uses the mapping functions and the A* algorithm implementation
|
||||
-- of the Jumper library to find a path from start_pos to end_pos. The range is
|
||||
-- an extra amount of nodes to search in both the x and z coordinates.
|
||||
function npc.pathfinder.find_path(start_pos, end_pos, range, walkable_nodes)
|
||||
-- Check that start and end position are not the same
|
||||
if start_pos.x == end_pos.x and start_pos.z == end_pos.z then
|
||||
return nil
|
||||
end
|
||||
-- Set walkable nodes to empty if parameter wasn't used
|
||||
if walkable_nodes == nil then
|
||||
walkable_nodes = {}
|
||||
end
|
||||
-- Map the Minetest area to a 2D array
|
||||
local map = pathfinder.create_map(start_pos, end_pos, range, walkable_nodes)
|
||||
-- Find start and end positions
|
||||
local pos = pathfinder.find_start_and_end_pos(map)
|
||||
-- Normalize the map
|
||||
local normalized_map = pathfinder.normalize_map(map)
|
||||
-- Create pathfinder object
|
||||
local grid_object = Grid(normalized_map)
|
||||
-- Define what is a walkable node
|
||||
local walkable = 0
|
||||
|
||||
-- Pathfinder object using A* algorithm
|
||||
local finder = Pathfinder(grid_object, "ASTAR", walkable)
|
||||
-- Set orthogonal mode meaning it will not move in diagonal directions
|
||||
finder:setMode("ORTHOGONAL")
|
||||
|
||||
-- Calculates the path, and its length
|
||||
local path = finder:getPath(pos.start_pos.x, pos.start_pos.z, pos.end_pos.x, pos.end_pos.z)
|
||||
|
||||
--minetest.log("Found path: "..dump(path))
|
||||
-- Pretty-printing the results
|
||||
if path then
|
||||
return pathfinder.get_path(map, path:nodes())
|
||||
end
|
||||
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 npc.pathfinder.is_good_node(node, exceptions)
|
||||
function pathfinder.is_good_node(node, exceptions)
|
||||
--local function 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.
|
||||
--minetest.log("Is good node: "..dump(node))
|
||||
local is_openable = false
|
||||
for _,node_prefix in pairs(npc.pathfinder.nodes.openable_prefix) do
|
||||
--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
|
||||
@ -88,159 +45,346 @@ function npc.pathfinder.is_good_node(node, exceptions)
|
||||
end
|
||||
end
|
||||
if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
|
||||
--return pathfinder.node_types.walkable
|
||||
return npc.pathfinder.node_types.walkable
|
||||
elseif is_openable then
|
||||
return npc.pathfinder.node_types.openable
|
||||
--return pathfinder.node_types.openable
|
||||
else
|
||||
for i = 1, #exceptions do
|
||||
if node.name == exceptions[i] then
|
||||
return npc.pathfinder.node_types.walkable
|
||||
--return pathfinder.node_types.walkable
|
||||
end
|
||||
end
|
||||
return npc.pathfinder.node_types.non_walkable
|
||||
--return pathfinder.node_types.non_walkable
|
||||
end
|
||||
end
|
||||
|
||||
function pathfinder.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)}
|
||||
|
||||
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)}
|
||||
-- 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(current_row, {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(current_row, {pos=current_pos, type=pathfinder.node_types.goal})
|
||||
else
|
||||
-- Check if node is walkable
|
||||
local node = minetest.get_node(current_pos)
|
||||
-- Check node has air above it
|
||||
local node_above = minetest.get_node({x=current_pos.x, y=current_pos.y+1, z=current_pos.z})
|
||||
if node.name == "air" then
|
||||
-- Check if node above is air
|
||||
if node.name == "air" then
|
||||
-- If air do no more checks
|
||||
table.insert(current_row, {pos=current_pos, type=pathfinder.node_types.walkable})
|
||||
end
|
||||
else
|
||||
-- Check if it is of a walkable or openable type
|
||||
table.insert(current_row, {pos=current_pos, type=is_good_node(node, walkables)})
|
||||
end
|
||||
end
|
||||
function pathfinder.get_decorated_path(path)
|
||||
-- Get details from path nodes
|
||||
local path_detail = {}
|
||||
for i = 1, #path do
|
||||
local node = minetest.get_node(path[i])
|
||||
table.insert(path_detail, {pos=path[i], type=pathfinder.is_good_node(node, {})})
|
||||
end
|
||||
-- Insert the converted row into the grid
|
||||
table.insert(grid, current_row)
|
||||
|
||||
npc.log("DEBUG", "Detailed path: "..dump(path_detail))
|
||||
return path_detail
|
||||
end
|
||||
|
||||
function npc.pathfinder.find_path(start_pos, end_pos, entity, decorate_path)
|
||||
local path = pathfinder.find_path(start_pos, end_pos, entity)
|
||||
if path then
|
||||
if decorate_path then
|
||||
path = pathfinder.get_decorated_path(path)
|
||||
end
|
||||
else
|
||||
npc.log("ERROR", "Couldn't find path from "..minetest.pos_to_string(start_pos)
|
||||
.." to "..minetest.pos_to_string(end_pos))
|
||||
end
|
||||
|
||||
return grid
|
||||
return path
|
||||
end
|
||||
|
||||
-- Utility function to print the created map to the console.
|
||||
-- Used for debug.
|
||||
local function print_map(map)
|
||||
for z,row in pairs(map) do
|
||||
local row_string = "["
|
||||
for x,node in pairs(row) do
|
||||
if node.type == 2 then
|
||||
row_string = row_string.."- "
|
||||
else
|
||||
row_string = row_string..node.type.." "
|
||||
end
|
||||
-- Use the following if the coordinates are also needed
|
||||
--row_string = row_string..node.type..": {"..node.pos.x..", "..node.pos.y..", "..node.pos.z.."}, "
|
||||
end
|
||||
row_string = row_string.."]"
|
||||
print(row_string)
|
||||
end
|
||||
-- From this point onwards is MarkBu's original pathfinder code,
|
||||
-- except for the "walkable" function, which is modified by Zorman2000
|
||||
-- to include doors and other "walkable" nodes.
|
||||
-- The version here is exactly this:
|
||||
-- https://github.com/MarkuBu/pathfinder/commit/ca0b433bf5efde5da545b11b2691fa7f7e53dc30
|
||||
|
||||
--[[
|
||||
minetest.get_content_id(name)
|
||||
minetest.registered_nodes
|
||||
minetest.get_name_from_content_id(id)
|
||||
local ivm = a:index(pos.x, pos.y, pos.z)
|
||||
local ivm = a:indexp(pos)
|
||||
minetest.hash_node_position({x=,y=,z=})
|
||||
minetest.get_position_from_hash(hash)
|
||||
|
||||
start_index, target_index, current_index
|
||||
^ Hash of position
|
||||
|
||||
current_value
|
||||
^ {int:hCost, int:gCost, int:fCost, hash:parent, vect:pos}
|
||||
]]--
|
||||
|
||||
local openSet = {}
|
||||
local closedSet = {}
|
||||
|
||||
local function get_distance(start_pos, end_pos)
|
||||
local distX = math.abs(start_pos.x - end_pos.x)
|
||||
local distZ = math.abs(start_pos.z - end_pos.z)
|
||||
|
||||
if distX > distZ then
|
||||
return 14 * distZ + 10 * (distX - distZ)
|
||||
else
|
||||
return 14 * distX + 10 * (distZ - distX)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_distance_to_neighbor(start_pos, end_pos)
|
||||
local distX = math.abs(start_pos.x - end_pos.x)
|
||||
local distY = math.abs(start_pos.y - end_pos.y)
|
||||
local distZ = math.abs(start_pos.z - end_pos.z)
|
||||
|
||||
-- This function find the starting and ending points in the
|
||||
-- map representation, and returns the coordinates in the map
|
||||
-- for the pathfinding algorithm to use
|
||||
function pathfinder.find_start_and_end_pos(map)
|
||||
-- This is for debug
|
||||
--print_map(map)
|
||||
local result = {}
|
||||
for z,row in pairs(map) do
|
||||
for x,node in pairs(row) do
|
||||
if node.type == pathfinder.node_types.start then
|
||||
--minetest.log("Start node: "..dump(node))
|
||||
result["start_pos"] = {x=x, z=z}
|
||||
elseif node.type == pathfinder.node_types.goal then
|
||||
--minetest.log("End node: "..dump(node))
|
||||
result["end_pos"] = {x=x, z=z}
|
||||
end
|
||||
if distX > distZ then
|
||||
return (14 * distZ + 10 * (distX - distZ)) * (distY + 1)
|
||||
else
|
||||
return (14 * distX + 10 * (distZ - distX)) * (distY + 1)
|
||||
end
|
||||
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
|
||||
local function walkable(node, exceptions)
|
||||
local exceptions = exceptions or {}
|
||||
-- Is openable is to support doors, fence gates and other
|
||||
-- doors from other mods. Currently, default doors, gates
|
||||
-- and cottages doors are supported.
|
||||
--minetest.log("Is good node: "..dump(node))
|
||||
local is_openable = false
|
||||
for _,node_prefix in pairs(npc.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
|
||||
--minetest.log("Found start and end positions: ("..result.start_pos.)..", "..minetest.pos_to_string(result.end_pos))
|
||||
return result
|
||||
end
|
||||
|
||||
-- This function transforms the grid into binary values
|
||||
-- (0 walkable, 1 non-walkable) for the pathfinding algorithm.
|
||||
function pathfinder.normalize_map(map)
|
||||
local result = {}
|
||||
for _,row in pairs(map) do
|
||||
local result_row = {}
|
||||
for _,node in pairs(row) do
|
||||
if node.type ~= pathfinder.node_types.non_walkable then
|
||||
table.insert(result_row, 0)
|
||||
else
|
||||
table.insert(result_row, 1)
|
||||
-- Detect mg_villages ceilings usage of thin wood nodeboxes
|
||||
-- TODO: Improve
|
||||
local is_mg_villages_ceiling = false
|
||||
if node.name == "cottages:wood_flat" then
|
||||
is_mg_villages_ceiling = true
|
||||
end
|
||||
if node ~= nil and node.name ~= nil and not minetest.registered_nodes[node.name].walkable then
|
||||
return false
|
||||
elseif is_openable then
|
||||
return false
|
||||
elseif is_mg_villages_ceiling then
|
||||
return false
|
||||
else
|
||||
for i = 1, #exceptions do
|
||||
if node.name == exceptions[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
table.insert(result, result_row)
|
||||
return true
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- 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)
|
||||
local result = {}
|
||||
for node, count in path_nodes do
|
||||
table.insert(result, map[node:getY()][node:getX()])
|
||||
-- For debug
|
||||
--minetest.log("Node: "..dump(map[node:getY()][node:getX()]))
|
||||
--print(('Step: %d - x: %d - y: %d'):format(count, node:getX(), node:getY()))
|
||||
end
|
||||
return result
|
||||
local function check_clearance(cpos, x, z, height)
|
||||
for i = 1, height do
|
||||
local n_name = minetest.get_node({x = cpos.x + x, y = cpos.y + i, z = cpos.z + z}).name
|
||||
local c_name = minetest.get_node({x = cpos.x, y = cpos.y + i, z = cpos.z}).name
|
||||
--~ print(i, n_name, c_name)
|
||||
if walkable(n_name) or walkable(c_name) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function get_neighbor_ground_level(pos, jump_height, fall_height)
|
||||
local node = minetest.get_node(pos)
|
||||
local height = 0
|
||||
if walkable(node) then
|
||||
repeat
|
||||
height = height + 1
|
||||
if height > jump_height then
|
||||
return nil
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
node = minetest.get_node(pos)
|
||||
until not walkable(node)
|
||||
return pos
|
||||
else
|
||||
repeat
|
||||
height = height + 1
|
||||
if height > fall_height then
|
||||
return nil
|
||||
end
|
||||
pos.y = pos.y - 1
|
||||
node = minetest.get_node(pos)
|
||||
until walkable(node)
|
||||
return {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
end
|
||||
end
|
||||
|
||||
function pathfinder.find_path(pos, endpos, entity)
|
||||
local start_index = minetest.hash_node_position(pos)
|
||||
local target_index = minetest.hash_node_position(endpos)
|
||||
local count = 1
|
||||
|
||||
openSet = {}
|
||||
closedSet = {}
|
||||
|
||||
local h_start = get_distance(pos, endpos)
|
||||
openSet[start_index] = {hCost = h_start, gCost = 0, fCost = h_start, parent = nil, pos = pos}
|
||||
|
||||
-- Entity values
|
||||
local entity_height = math.ceil(entity.collisionbox[5] - entity.collisionbox[2])
|
||||
local entity_fear_height = entity.fear_height or 2
|
||||
local entity_jump_height = entity.jump_height or 1
|
||||
|
||||
repeat
|
||||
local current_index
|
||||
local current_values
|
||||
|
||||
-- Get one index as reference from openSet
|
||||
for i, v in pairs(openSet) do
|
||||
current_index = i
|
||||
current_values = v
|
||||
break
|
||||
end
|
||||
|
||||
-- Search for lowest fCost
|
||||
for i, v in pairs(openSet) do
|
||||
if v.fCost < openSet[current_index].fCost or v.fCost == current_values.fCost and v.hCost < current_values.hCost then
|
||||
current_index = i
|
||||
current_values = v
|
||||
end
|
||||
end
|
||||
|
||||
openSet[current_index] = nil
|
||||
closedSet[current_index] = current_values
|
||||
count = count - 1
|
||||
|
||||
if current_index == target_index then
|
||||
-- print("Success")
|
||||
local path = {}
|
||||
local reverse_path = {}
|
||||
repeat
|
||||
if not closedSet[current_index] then
|
||||
return
|
||||
end
|
||||
table.insert(path, closedSet[current_index].pos)
|
||||
current_index = closedSet[current_index].parent
|
||||
if #path > 100 then
|
||||
-- print("path to long")
|
||||
return
|
||||
end
|
||||
until start_index == current_index
|
||||
repeat
|
||||
table.insert(reverse_path, table.remove(path))
|
||||
until #path == 0
|
||||
-- print("path lenght: "..#reverse_path)
|
||||
return reverse_path
|
||||
end
|
||||
|
||||
local current_pos = current_values.pos
|
||||
|
||||
local neighbors = {}
|
||||
local neighbors_index = 1
|
||||
for z = -1, 1 do
|
||||
for x = -1, 1 do
|
||||
local neighbor_pos = {x = current_pos.x + x, y = current_pos.y, z = current_pos.z + z}
|
||||
local neighbor = minetest.get_node(neighbor_pos)
|
||||
local neighbor_ground_level = get_neighbor_ground_level(neighbor_pos, entity_jump_height, entity_fear_height)
|
||||
local neighbor_clearance = false
|
||||
if neighbor_ground_level then
|
||||
-- print(neighbor_ground_level.y - current_pos.y)
|
||||
--minetest.set_node(neighbor_ground_level, {name = "default:dry_shrub"})
|
||||
local node_above_head = minetest.get_node(
|
||||
{x = current_pos.x, y = current_pos.y + entity_height, z = current_pos.z})
|
||||
if neighbor_ground_level.y - current_pos.y > 0 and not walkable(node_above_head) then
|
||||
local height = -1
|
||||
repeat
|
||||
height = height + 1
|
||||
local node = minetest.get_node(
|
||||
{x = neighbor_ground_level.x,
|
||||
y = neighbor_ground_level.y + height,
|
||||
z = neighbor_ground_level.z})
|
||||
until walkable(node) or height > entity_height
|
||||
if height >= entity_height then
|
||||
neighbor_clearance = true
|
||||
end
|
||||
elseif neighbor_ground_level.y - current_pos.y > 0 and walkable(node_above_head) then
|
||||
neighbors[neighbors_index] = {
|
||||
hash = nil,
|
||||
pos = nil,
|
||||
clear = nil,
|
||||
walkable = nil,
|
||||
}
|
||||
else
|
||||
local height = -1
|
||||
repeat
|
||||
height = height + 1
|
||||
local node = minetest.get_node(
|
||||
{x = neighbor_ground_level.x,
|
||||
y = current_pos.y + height,
|
||||
z = neighbor_ground_level.z})
|
||||
until walkable(node) or height > entity_height
|
||||
if height >= entity_height then
|
||||
neighbor_clearance = true
|
||||
end
|
||||
end
|
||||
|
||||
neighbors[neighbors_index] = {
|
||||
hash = minetest.hash_node_position(neighbor_ground_level),
|
||||
pos = neighbor_ground_level,
|
||||
clear = neighbor_clearance,
|
||||
walkable = walkable(neighbor),
|
||||
}
|
||||
else
|
||||
neighbors[neighbors_index] = {
|
||||
hash = nil,
|
||||
pos = nil,
|
||||
clear = nil,
|
||||
walkable = nil,
|
||||
}
|
||||
end
|
||||
neighbors_index = neighbors_index + 1
|
||||
end
|
||||
end
|
||||
|
||||
for id, neighbor in pairs(neighbors) do
|
||||
-- don't cut corners
|
||||
local cut_corner = false
|
||||
if id == 1 then
|
||||
if not neighbors[id + 1].clear or not neighbors[id + 3].clear
|
||||
or neighbors[id + 1].walkable or neighbors[id + 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
elseif id == 3 then
|
||||
if not neighbors[id - 1].clear or not neighbors[id + 3].clear
|
||||
or neighbors[id - 1].walkable or neighbors[id + 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
elseif id == 7 then
|
||||
if not neighbors[id + 1].clear or not neighbors[id - 3].clear
|
||||
or neighbors[id + 1].walkable or neighbors[id - 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
elseif id == 9 then
|
||||
if not neighbors[id - 1].clear or not neighbors[id - 3].clear
|
||||
or neighbors[id - 1].walkable or neighbors[id - 3].walkable then
|
||||
cut_corner = true
|
||||
end
|
||||
end
|
||||
|
||||
if neighbor.hash ~= current_index and not closedSet[neighbor.hash] and neighbor.clear and not cut_corner then
|
||||
local move_cost_to_neighbor = current_values.gCost + get_distance_to_neighbor(current_values.pos, neighbor.pos)
|
||||
local gCost = 0
|
||||
if openSet[neighbor.hash] then
|
||||
gCost = openSet[neighbor.hash].gCost
|
||||
end
|
||||
if move_cost_to_neighbor < gCost or not openSet[neighbor.hash] then
|
||||
if not openSet[neighbor.hash] then
|
||||
count = count + 1
|
||||
end
|
||||
local hCost = get_distance(neighbor.pos, endpos)
|
||||
openSet[neighbor.hash] = {
|
||||
gCost = move_cost_to_neighbor,
|
||||
hCost = hCost,
|
||||
fCost = move_cost_to_neighbor + hCost,
|
||||
parent = current_index,
|
||||
pos = neighbor.pos
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if count > 100 then
|
||||
-- print("fail")
|
||||
return
|
||||
end
|
||||
until count < 1
|
||||
-- print("count < 1")
|
||||
return {pos}
|
||||
end
|
||||
|
@ -196,7 +196,7 @@ function npc.places.find_entrance_from_openable_nodes(all_openable_nodes, marker
|
||||
entity.collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20}
|
||||
--minetest.log("Start pos: "..minetest.pos_to_string(start_pos))
|
||||
--minetest.log("End pos: "..minetest.pos_to_string(end_pos))
|
||||
local path = pathfinder.find_path(start_pos, end_pos, entity)
|
||||
local path = npc.pathfinder.find_path(start_pos, end_pos, entity, false)
|
||||
--minetest.log("Found path: "..dump(path))
|
||||
if path ~= nil then
|
||||
--minetest.log("Path distance: "..dump(#path))
|
||||
|
1
init.lua
1
init.lua
@ -32,7 +32,6 @@ dofile(path .. "/trade/prices.lua")
|
||||
dofile(path .. "/actions/actions.lua")
|
||||
dofile(path .. "/actions/places.lua")
|
||||
dofile(path .. "/actions/pathfinder.lua")
|
||||
dofile(path .. "/actions/jumper.lua")
|
||||
dofile(path .. "/actions/node_registry.lua")
|
||||
dofile(path .. "/random_data.lua")
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 323 B |
Binary file not shown.
Before Width: | Height: | Size: 267 B |
Binary file not shown.
Before Width: | Height: | Size: 783 B |
Binary file not shown.
Before Width: | Height: | Size: 783 B |
Binary file not shown.
Before Width: | Height: | Size: 779 B |
Binary file not shown.
Before Width: | Height: | Size: 202 B |
Loading…
Reference in New Issue
Block a user