Adds pathfinding library Jumper by Ronald Yonaba. This includes an implementation of the A* pathfinding algorithm which makes NPC now always get to their goal node.
Pathfinding: Adds functions that allows to map the Minetest 3D map to a 2D array to use by the pathfinding algorithm. Actions: Use new code for find_path function. Improves door opening while walking on paths, and also now close them. Cottages fence gates and doors are also now supported in addition to the default doors and gates. Changes to the Readme and the License.
This commit is contained in:
@ -1,469 +0,0 @@
|
||||
-- Actions code for Advanced NPC by Zorman2000
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Action functionality
|
||||
---------------------------------------------------------------------------------------
|
||||
-- The NPCs will be able to perform five fundamental actions that will allow
|
||||
-- for them to perform any other kind of interaction in the world. These
|
||||
-- fundamental actions are: place a node, dig a node, put items on an inventory,
|
||||
-- take items from an inventory, find a node closeby (radius 3) and
|
||||
-- walk a step on specific direction. These actions will be set on an action queue.
|
||||
-- The queue will have the specific steps, in order, for the NPC to be able to do
|
||||
-- something (example, go to a specific place and put a chest there). The
|
||||
-- fundamental actions are added to the action queue to make a complete task for the NPC.
|
||||
|
||||
npc.actions = {}
|
||||
|
||||
function npc.actions.rotate(args)
|
||||
local self = args.self
|
||||
local dir = args.dir
|
||||
local yaw = 0
|
||||
self.rotate = 0
|
||||
if dir == npc.direction.north then
|
||||
yaw = 0
|
||||
elseif dir == npc.direction.east then
|
||||
yaw = (3 * math.pi) / 2
|
||||
elseif dir == npc.direction.south then
|
||||
yaw = math.pi
|
||||
elseif dir == npc.direction.west then
|
||||
yaw = math.pi / 2
|
||||
end
|
||||
self.object:setyaw(yaw)
|
||||
end
|
||||
|
||||
-- This function will make the NPC walk one step on a
|
||||
-- specifc direction. One step means one node. It returns
|
||||
-- true if it can move on that direction, and false if there is an obstacle
|
||||
function npc.actions.walk_step(args)
|
||||
local self = args.self
|
||||
local dir = args.dir
|
||||
local vel = {}
|
||||
if dir == npc.direction.north then
|
||||
vel = {x=0, y=0, z=1}
|
||||
elseif dir == npc.direction.east then
|
||||
vel = {x=1, y=0, z=0}
|
||||
elseif dir == npc.direction.south then
|
||||
vel = {x=0, y=0, z=-1}
|
||||
elseif dir == npc.direction.west then
|
||||
vel = {x=-1, y=0, z=0}
|
||||
end
|
||||
set_animation(self, "walk")
|
||||
npc.actions.rotate({self=self, dir=dir})
|
||||
self.object:setvelocity(vel)
|
||||
end
|
||||
|
||||
-- This action makes the NPC stand and remain like that
|
||||
function npc.actions.stand(args)
|
||||
local self = args.self
|
||||
-- Stop NPC
|
||||
self.object:setvelocity({x=0, y=0, z=0})
|
||||
-- Set stand animation
|
||||
set_animation(self, "stand")
|
||||
end
|
||||
|
||||
-- This action makes the NPC sit on the node where it is
|
||||
function npc.actions.sit(args)
|
||||
local self = args.self
|
||||
-- Stop NPC
|
||||
self.object:setvelocity({x=0, y=0, z=0})
|
||||
-- Set sit animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_SIT_START,
|
||||
y = npc.ANIMATION_SIT_END},
|
||||
self.animation.speed_normal, 0)
|
||||
end
|
||||
|
||||
-- This action makes the NPC lay on the node where it is
|
||||
function npc.actions.lay(args)
|
||||
local self = args.self
|
||||
-- Stop NPC
|
||||
self.object:setvelocity({x=0, y=0, z=0})
|
||||
-- Set sit animation
|
||||
self.object:set_animation({
|
||||
x = npc.ANIMATION_LAY_START,
|
||||
y = npc.ANIMATION_LAY_END},
|
||||
self.animation.speed_normal, 0)
|
||||
end
|
||||
|
||||
-- Inventory functions for players and for nodes
|
||||
-- This function is a convenience function to make it easy to put
|
||||
-- and get items from another inventory (be it a player inv or
|
||||
-- a node inv)
|
||||
function npc.actions.put_item_on_external_inventory(args)
|
||||
local self = args.self
|
||||
local player = args.player
|
||||
local pos = args.pos
|
||||
local inv_list = args.inv_list
|
||||
local item_name = args.item_name
|
||||
local count = args.count
|
||||
local is_furnace = args.is_furnace
|
||||
local inv
|
||||
if player ~= nil then
|
||||
inv = minetest.get_inventory({type="player", name=player})
|
||||
else
|
||||
inv = minetest.get_inventory({type="node", pos=pos})
|
||||
end
|
||||
|
||||
-- Create ItemStack to put on external inventory
|
||||
local item = ItemStack(item_name.." "..count)
|
||||
-- Check if there is enough room to add the item on external invenotry
|
||||
if inv:room_for_item(inv_list, item) then
|
||||
-- Take item from NPC's inventory
|
||||
if npc.take_item_from_inventory_itemstring(self, item) then
|
||||
-- NPC doesn't have item and/or specified quantity
|
||||
return false
|
||||
end
|
||||
-- Add items to external inventory
|
||||
inv:add_item(inv_list, item)
|
||||
|
||||
-- If this is a furnace, start furnace timer
|
||||
if is_furnace == true then
|
||||
minetest.get_node_timer(pos):start(1.0)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
-- Not able to put on external inventory
|
||||
return false
|
||||
end
|
||||
|
||||
function npc.actions.take_item_from_external_inventory(args)
|
||||
local self = args.self
|
||||
local player = args.player
|
||||
local pos = args.pos
|
||||
local inv_list = args.inv_list
|
||||
local item_name = args.item_name
|
||||
local count = args.count
|
||||
local inv
|
||||
if player ~= nil then
|
||||
inv = minetest.get_inventory({type="player", name=player})
|
||||
else
|
||||
inv = minetest.get_inventory({type="node", pos})
|
||||
end
|
||||
-- Create ItemSTack to take from external inventory
|
||||
local item = ItemStack(item_name.." "..count)
|
||||
-- Check if there is enough of the item to take
|
||||
if inv:contains_item(inv_list, item) then
|
||||
-- Add item to NPC's inventory
|
||||
npc.add_item_to_inventory_itemstring(self, item)
|
||||
-- Add items to external inventory
|
||||
inv:remove_item(inv_list, item)
|
||||
return true
|
||||
end
|
||||
-- Not able to put on external inventory
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Tasks functionality
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Tasks are operations that require many actions to perform. Basic tasks, like
|
||||
-- walking from one place to another, operating a furnace, storing or taking
|
||||
-- items from a chest, opening/closing doors, etc. are provided here.
|
||||
|
||||
-- This function allows a NPC to use a furnace using only items from
|
||||
-- its own inventory. Fuel is not provided. Once the furnace is finished
|
||||
-- with the fuel items the NPC will take whatever was cooked and whatever
|
||||
-- remained to cook. The function received the position of the furnace
|
||||
-- to use, and the item to cook in furnace. Item is an itemstring
|
||||
function npc.actions.use_furnace(self, pos, item)
|
||||
-- Check if any item in the NPC inventory serve as fuel
|
||||
-- For now, just use some specific items as fuel
|
||||
local fuels = {"default:leaves", "default:tree"}
|
||||
-- Check if NPC has a fuel item
|
||||
for i = 1,2 do
|
||||
local fuel_item = npc.inventory_contains(self, fuels[i])
|
||||
local src_item = npc.inventory_contains(self, item)
|
||||
|
||||
if fuel_item ~= nil and src_item ~= nil then
|
||||
-- Put this item on the fuel inventory list of the furnace
|
||||
local args = {
|
||||
self = self,
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "fuel",
|
||||
item_name = npc.get_item_name(fuel_item.item_string),
|
||||
count = npc.get_item_count(fuel_item.item_string)
|
||||
}
|
||||
minetest.log("Adding fuel action")
|
||||
npc.add_action(self, npc.actions.put_item_on_external_inventory, args)
|
||||
-- Put the item that we want to cook on the furnace
|
||||
args = {
|
||||
self = self,
|
||||
player = nil,
|
||||
pos = pos,
|
||||
inv_list = "src",
|
||||
item_name = npc.get_item_name(src_item.item_string),
|
||||
count = npc.get_item_count(src_item.item_string),
|
||||
is_furnace = true
|
||||
}
|
||||
minetest.log("Adding src action")
|
||||
npc.add_action(self, npc.actions.put_item_on_external_inventory, args)
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- Couldn't use the furnace due to lack of items
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function npc.actions.walk_to_pos(self, end_pos)
|
||||
|
||||
local start_pos = self.object:getpos()
|
||||
|
||||
minetest.log("Starting pos: "..dump(start_pos))
|
||||
|
||||
-- Use Minetest built-in pathfinding algorithm, A*
|
||||
local path = npc.actions.find_path({x=start_pos.x, y=start_pos.y-1, z=start_pos.z}, end_pos)
|
||||
|
||||
if path ~= nil then
|
||||
minetest.log("Found path to node: "..dump(end_pos))
|
||||
for i = 1, #path do
|
||||
minetest.log("Path: (i) "..dump(path[i])..": Path i+1 "..dump(path[i+1]))
|
||||
local dir = npc.actions.get_direction(path[i].pos, path[i+1].pos)
|
||||
-- Add walk action to action queue
|
||||
npc.add_action(self, npc.actions.walk_step, {self = self, dir = dir})
|
||||
if i+1 == #path then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Add stand animation at end
|
||||
npc.add_action(self, npc.actions.stand, {self = self})
|
||||
|
||||
end
|
||||
|
||||
local function vector_add(p1, p2)
|
||||
return {x=p1.x+p2.x, y=p1.y+p2.y, z=p1.z+p2.z}
|
||||
end
|
||||
|
||||
local function vector_diff(p1, p2)
|
||||
return {x=p1.x-p2.x, y=p1.y-p2.y, z=p1.z-p2.z}
|
||||
end
|
||||
|
||||
local function vector_opposite(v)
|
||||
return vector.multiply(v, -1)
|
||||
end
|
||||
|
||||
local function get_unit_dir_vector_based_on_diff(v)
|
||||
if math.abs(v.x) > math.abs(v.z) then
|
||||
return {x=(v.x/math.abs(v.x)) * -1, y=0, z=0}
|
||||
elseif math.abs(v.z) > math.abs(v.x) then
|
||||
return {x=0, y=0, z=(v.z/math.abs(v.z)) * -1}
|
||||
elseif math.abs(v.x) == math.abs(v.z) then
|
||||
return {x=(v.x/math.abs(v.x)) * -1, y=0, z=0}
|
||||
end
|
||||
end
|
||||
|
||||
function npc.actions.get_direction(v1, v2)
|
||||
local dir = vector.subtract(v2, v1)
|
||||
if dir.x ~= 0 then
|
||||
if dir.x > 0 then
|
||||
return npc.direction.east
|
||||
else
|
||||
return npc.direction.west
|
||||
end
|
||||
elseif dir.z ~= 0 then
|
||||
if dir.z > 0 then
|
||||
return npc.direction.north
|
||||
else
|
||||
return npc.direction.south
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DIFF_LIMIT = 125
|
||||
|
||||
-- Finds paths ignoring vertical obstacles
|
||||
-- This function is recursive and attempts to move all the time on
|
||||
-- the direction that will definetely lead to the end position.
|
||||
local function find_path_recursive(start_pos, end_pos, path_nodes, last_dir, last_good_dir)
|
||||
minetest.log("Start pos: "..dump(start_pos))
|
||||
-- Find difference. The purpose of this is to weigh movement, attempting
|
||||
-- the largest difference first, or both if equal.
|
||||
|
||||
local diff = vector_diff(start_pos, end_pos)
|
||||
minetest.log("Difference: "..dump(diff))
|
||||
-- End if difference is larger than max difference possible (limit)
|
||||
if math.abs(diff.x) > DIFF_LIMIT or math.abs(diff.z) > DIFF_LIMIT then
|
||||
-- Cannot find feasable path
|
||||
return nil
|
||||
end
|
||||
-- Determine direction to move
|
||||
local dir_vector = get_unit_dir_vector_based_on_diff(diff)
|
||||
minetest.log("Direction vector: "..dump(dir_vector))
|
||||
|
||||
if last_good_dir ~= nil then
|
||||
dir_vector = last_good_dir
|
||||
end
|
||||
|
||||
-- Get next position based on direction
|
||||
local next_pos = vector_add(start_pos, dir_vector)
|
||||
|
||||
minetest.log("Next pos: "..dump(next_pos))
|
||||
-- Check if next_pos is actually within one block from the
|
||||
-- expected position. If so, finish
|
||||
local diff_to_end = vector_diff(next_pos, end_pos)
|
||||
if math.abs(diff_to_end.x) < 1 and math.abs(diff_to_end.y) < 1 and math.abs(diff_to_end.z) < 1 then
|
||||
minetest.log("Diff to end: "..dump(diff_to_end))
|
||||
table.insert(path_nodes, {pos=next_pos, type="E"})
|
||||
minetest.log("Found path to end.")
|
||||
return path_nodes
|
||||
end
|
||||
-- Check if movement is possible on the calculated direction
|
||||
local next_node = minetest.get_node(next_pos)
|
||||
-- If direction vector is opposite to the last dir, then do not attempt to walk into it
|
||||
minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
local attempted_to_go_opposite = false
|
||||
if last_dir ~= nil and vector.equals(dir_vector, vector_opposite(last_dir)) then
|
||||
attempted_to_go_opposite = true
|
||||
minetest.log("Last dir: "..dump(last_dir))
|
||||
minetest.log("Calculated dir vector is the opposite of last dir: "..dump(vector.equals(dir_vector, vector_opposite(last_dir))))
|
||||
end
|
||||
if minetest.registered_nodes[next_node.name].walkable == false
|
||||
and (not attempted_to_go_opposite) then
|
||||
table.insert(path_nodes, {pos=next_pos, type="W"})
|
||||
return find_path_recursive(next_pos, end_pos, path_nodes, nil, nil)
|
||||
else
|
||||
minetest.log("------------ Second attempt ------------")
|
||||
-- If not walkable, attempt turn into the other coordinate
|
||||
-- Store last good direction to retry at all times
|
||||
minetest.log("Last known good dir: "..dump(last_good_dir))
|
||||
local step = 0
|
||||
if last_good_dir == nil then
|
||||
last_good_dir = dir_vector
|
||||
if dir_vector.x == 0 then
|
||||
minetest.log("Choosing x direction")
|
||||
step = diff.x/math.abs(diff.x) * -1
|
||||
if diff.x == 0 then
|
||||
if last_dir ~= nil then
|
||||
step = last_dir.x
|
||||
else
|
||||
-- Set a default step to avoid locks
|
||||
step = 1
|
||||
end
|
||||
end
|
||||
dir_vector = {x = step, y = 0, z = 0}
|
||||
elseif dir_vector.z == 0 then
|
||||
minetest.log("Choosing z direction")
|
||||
step = diff.z/math.abs(diff.z) * -1
|
||||
minetest.log("Step: "..dump(step)..". Diff: "..dump(diff))
|
||||
minetest.log("Last dir: ".. dump(last_dir))
|
||||
if diff.z == 0 then
|
||||
if last_dir ~= nil then
|
||||
step = last_dir.z
|
||||
else
|
||||
-- Set a default step to avoid locks
|
||||
step = 1
|
||||
end
|
||||
end
|
||||
dir_vector = {x = 0, y = 0, z = step}
|
||||
end
|
||||
minetest.log("Re-calculated dir vector: "..dump(dir_vector))
|
||||
next_pos = vector.add(start_pos, dir_vector)
|
||||
else
|
||||
dir_vector = last_good_dir
|
||||
if dir_vector.x == 0 then
|
||||
minetest.log("Moving into x direction")
|
||||
step = last_dir.x
|
||||
elseif dir_vector.z == 0 then
|
||||
minetest.log("Moving into z direction")
|
||||
step = last_dir.z
|
||||
end
|
||||
dir_vector = last_dir
|
||||
next_pos = vector.add(start_pos, dir_vector)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- if dir_vector.x == 0 then
|
||||
-- minetest.log("Moving into x direction")
|
||||
-- local step = diff.x/math.abs(diff.x) * -1
|
||||
-- if diff.x == 0 then
|
||||
-- -- If the difference for x with end position is zero, then try
|
||||
-- -- to move in the last known direction
|
||||
-- if last_dir ~= nil then
|
||||
-- step = last_dir.x
|
||||
-- end
|
||||
-- end
|
||||
-- next_pos = {x = start_pos.x + step, y = start_pos.y, z = start_pos.z}
|
||||
-- dir_vector = {x = step, y = 0, z = 0}
|
||||
-- elseif dir_vector.z == 0 then
|
||||
-- minetest.log("Moving into z direction")
|
||||
-- local step = diff.z/math.abs(diff.z) * -1
|
||||
-- if diff.z == 0 then
|
||||
-- -- If the difference for z with end position is zero, then try
|
||||
-- -- to move in the last known direction
|
||||
-- if last_dir ~= nil then
|
||||
-- step = last_dir.z
|
||||
-- end
|
||||
-- end
|
||||
-- next_pos = {x = start_pos.x, y = start_pos.y, z = start_pos.z + step}
|
||||
-- dir_vector = {x = 0, y = 0, z = step}
|
||||
-- end
|
||||
minetest.log("Next calculated position: "..dump(next_pos))
|
||||
|
||||
-- Check if new node is walkable
|
||||
next_node = minetest.get_node(next_pos)
|
||||
minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
if last_dir ~= nil and vector.equals(dir_vector, vector_opposite(last_dir)) then
|
||||
attempted_to_go_opposite = true
|
||||
minetest.log("Last dir: "..dump(last_dir))
|
||||
minetest.log("Calculated dir vector is the opposite of last dir: "..dump(vector.equals(dir_vector, vector_opposite(last_dir))))
|
||||
end
|
||||
if minetest.registered_nodes[next_node.name].walkable == false then
|
||||
table.insert(path_nodes, {pos=next_pos, type="W"})
|
||||
return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, last_good_dir)
|
||||
else
|
||||
last_good_dir = dir_vector
|
||||
minetest.log("------------ Third attempt ------------")
|
||||
-- If not walkable, then try the next node
|
||||
if dir_vector.x ~= 0 then
|
||||
minetest.log("Move into opposite z dir")
|
||||
dir_vector = get_unit_dir_vector_based_on_diff(start_pos, diff)
|
||||
vector.multiply(dir_vector, -1)
|
||||
elseif dir_vector.z ~= 0 then
|
||||
minetest.log("Move into opposite x dir")
|
||||
dir_vector = get_unit_dir_vector_based_on_diff(start_pos, diff)
|
||||
vector.multiply(dir_vector, -1)
|
||||
end
|
||||
minetest.log("New direction: "..dump(dir_vector))
|
||||
|
||||
next_pos = vector_add(start_pos, dir_vector)
|
||||
minetest.log("New next_pos: "..dump(next_pos))
|
||||
next_node = minetest.get_node(next_pos)
|
||||
minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
-- if last_dir ~= nil and vector.equals(dir_vector, vector_opposite(last_dir)) then
|
||||
-- attempted_to_go_opposite = true
|
||||
-- minetest.log("Last dir: "..dump(last_dir))
|
||||
-- minetest.log("Calculated dir vector is the opposite of last dir: "..dump(vector.equals(dir_vector, vector_opposite(last_dir))))
|
||||
-- end
|
||||
if minetest.registered_nodes[next_node.name].walkable == false then
|
||||
--and (not attempted_to_go_opposite) then
|
||||
table.insert(path_nodes, {pos=next_pos, type="W"})
|
||||
return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, last_good_dir)
|
||||
else
|
||||
--return back, opposite of last dir. For now return nil as this code is not
|
||||
-- good
|
||||
return nil
|
||||
-- minetest.log("Have to go back")
|
||||
-- local return_dir = vector_opposite(last_dir)
|
||||
-- -- If it is returning back already, continue on that direction
|
||||
-- if attempted_to_go_opposite then
|
||||
-- return_dir = last_dir
|
||||
-- end
|
||||
-- minetest.log("Opposite dir: "..dump(return_dir))
|
||||
-- next_pos = vector_add(start_pos, return_dir)
|
||||
-- minetest.log("Calculated pos: "..dump(next_pos))
|
||||
-- return find_path(next_pos, end_pos, return_dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function npc.actions.find_path(start_pos, end_pos)
|
||||
return find_path_recursive(start_pos, end_pos, {}, nil, nil)
|
||||
end
|
@ -14,15 +14,25 @@
|
||||
npc.actions = {}
|
||||
|
||||
-- Describes actions with doors or openable nodes
|
||||
npc.actions.door_action = {
|
||||
OPEN = 1,
|
||||
CLOSE = 2
|
||||
}
|
||||
|
||||
-- Describe the state of doors or openable nodes
|
||||
npc.actions.door_state = {
|
||||
OPEN = 1,
|
||||
CLOSED = 2
|
||||
npc.actions.const = {
|
||||
doors = {
|
||||
action = {
|
||||
OPEN = 1,
|
||||
CLOSE = 2
|
||||
},
|
||||
state = {
|
||||
OPEN = 1,
|
||||
CLOSED = 2
|
||||
}
|
||||
},
|
||||
beds = {
|
||||
LAY = 1,
|
||||
GET_UP = 2
|
||||
},
|
||||
sittable = {
|
||||
SIT = 1,
|
||||
GET_UP = 2
|
||||
}
|
||||
}
|
||||
|
||||
function npc.actions.rotate(args)
|
||||
@ -32,12 +42,20 @@ function npc.actions.rotate(args)
|
||||
self.rotate = 0
|
||||
if dir == npc.direction.north then
|
||||
yaw = 0
|
||||
elseif dir == npc.direction.north_east then
|
||||
yaw = (7 * math.pi) / 4
|
||||
elseif dir == npc.direction.east then
|
||||
yaw = (3 * math.pi) / 2
|
||||
elseif dir == npc.direction.south_east then
|
||||
yaw = (5 * math.pi) / 4
|
||||
elseif dir == npc.direction.south then
|
||||
yaw = math.pi
|
||||
elseif dir == npc.direction.south_west then
|
||||
yaw = (3 * math.pi) / 4
|
||||
elseif dir == npc.direction.west then
|
||||
yaw = math.pi / 2
|
||||
elseif dir == npc.direction.north_west then
|
||||
yaw = math.pi / 4
|
||||
end
|
||||
self.object:setyaw(yaw)
|
||||
end
|
||||
@ -175,12 +193,35 @@ function npc.actions.take_item_from_external_inventory(args)
|
||||
return false
|
||||
end
|
||||
|
||||
function npc.actions.get_openable_node_state(node)
|
||||
local state = npc.actions.door_state.CLOSED
|
||||
local a_i1, a_i2 = string.find(node.name, "_a")
|
||||
if a_i1 == nil then
|
||||
state = npc.actions.door_state.OPEN
|
||||
function npc.actions.check_external_inventory_contains_item(args)
|
||||
local self = args.self
|
||||
local player = args.player
|
||||
local pos = args.pos
|
||||
local inv_list = args.inv_list
|
||||
local item_name = args.item_name
|
||||
local count = args.count
|
||||
local inv
|
||||
if player ~= nil then
|
||||
inv = minetest.get_inventory({type="player", name=player})
|
||||
else
|
||||
inv = minetest.get_inventory({type="node", pos})
|
||||
end
|
||||
|
||||
-- Create ItemStack for checking the external inventory
|
||||
local item = ItemStack(item_name.." "..count)
|
||||
-- Check if inventory contains item
|
||||
return inv:contains_item(inv_list, item)
|
||||
end
|
||||
|
||||
function npc.actions.get_openable_node_state(node)
|
||||
minetest.log("Node name: "..dump(node.name))
|
||||
local state = npc.actions.const.doors.state.CLOSED
|
||||
local a_i1, a_i2 = string.find(node.name, "_a")
|
||||
local open_i1, open_i2 = string.find(node.name, "_close")
|
||||
if a_i1 == nil and open_i1 == nil then
|
||||
state = npc.actions.const.doors.state.OPEN
|
||||
end
|
||||
minetest.log("Door state: "..dump(state))
|
||||
return state
|
||||
end
|
||||
|
||||
@ -231,7 +272,6 @@ function npc.actions.use_furnace(self, pos, item)
|
||||
item_name = npc.get_item_name(fuel_item.item_string),
|
||||
count = npc.get_item_count(fuel_item.item_string)
|
||||
}
|
||||
minetest.log("Adding fuel action")
|
||||
npc.add_action(self, npc.actions.put_item_on_external_inventory, args)
|
||||
-- Put the item that we want to cook on the furnace
|
||||
args = {
|
||||
@ -243,9 +283,14 @@ function npc.actions.use_furnace(self, pos, item)
|
||||
count = npc.get_item_count(src_item.item_string),
|
||||
is_furnace = true
|
||||
}
|
||||
minetest.log("Adding src action")
|
||||
npc.add_action(self, npc.actions.put_item_on_external_inventory, args)
|
||||
|
||||
-- TODO: Need to add a way to calculate how many seconds will pass
|
||||
-- until the furnace is done, or at least the items that we expect
|
||||
-- to get (assume all items to be cooked are the ones ewe expect back)
|
||||
-- Then, add that many stand actions, then an action to take the items.
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
@ -253,11 +298,6 @@ function npc.actions.use_furnace(self, pos, item)
|
||||
return false
|
||||
end
|
||||
|
||||
npc.actions.bed_action = {
|
||||
LAY = 1,
|
||||
GET_UP = 2
|
||||
}
|
||||
|
||||
-- This function makes the NPC lay or stand up from a bed. The
|
||||
-- pos is the location of the bed, action can be lay or get up
|
||||
function npc.actions.use_bed(self, pos, action)
|
||||
@ -265,7 +305,7 @@ function npc.actions.use_bed(self, pos, action)
|
||||
minetest.log(dump(param2))
|
||||
local dir = minetest.facedir_to_dir(param2.param2)
|
||||
|
||||
if action == npc.actions.bed_action.LAY then
|
||||
if action == npc.actions.const.beds.LAY then
|
||||
-- Calculate position (from beds mod)
|
||||
local bed_pos = {x = pos.x + dir.x / 2, y = pos.y + 1, z = pos.z + dir.z / 2}
|
||||
-- Sit down on bed
|
||||
@ -286,77 +326,44 @@ function npc.actions.use_bed(self, pos, action)
|
||||
end
|
||||
end
|
||||
|
||||
-- This function makes the NPC lay or stand up from a bed. The
|
||||
-- pos is the location of the bed, action can be lay or get up
|
||||
function npc.actions.use_sittable(self, pos, action)
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
-- This function can be used to make the NPC walk from one
|
||||
-- position to another.
|
||||
function npc.actions.walk_to_pos(self, end_pos)
|
||||
|
||||
local start_pos = self.object:getpos()
|
||||
|
||||
-- Find path
|
||||
local path = npc.actions.find_path({x=start_pos.x, y=start_pos.y-1, z=start_pos.z}, end_pos)
|
||||
|
||||
if path ~= nil then
|
||||
minetest.log("Found path to node: "..dump(end_pos))
|
||||
|
||||
-- Add a first step
|
||||
local dir = npc.actions.get_direction(start_pos, path[1].pos)
|
||||
npc.add_action(self, npc.actions.walk_step, {self = self, dir = dir})
|
||||
|
||||
-- Add subsequent steps
|
||||
for i = 1, #path do
|
||||
--minetest.log("Path: (i) "..dump(path[i])..": Path i+1 "..dump(path[i+1]))
|
||||
-- Do not add an extra step
|
||||
if i == #path then
|
||||
-- Add direction to last node
|
||||
local dir = npc.actions.get_direction(path[i].pos, end_pos)
|
||||
-- Add stand animation at end
|
||||
npc.add_action(self, npc.actions.stand, {self = self})
|
||||
-- Rotate to face the end node
|
||||
npc.actions.rotate({self = self, dir = dir})
|
||||
break
|
||||
end
|
||||
-- Get direction to move from path[i] to path[i+1]
|
||||
local dir = npc.actions.get_direction(path[i].pos, path[i+1].pos)
|
||||
-- Check if next node is a door, if it is, open it, then walk
|
||||
if path[i+1].type == "O" then
|
||||
-- Check if door is already open
|
||||
local node = minetest.get_node(path[i+1].pos)
|
||||
if npc.actions.get_openable_node_state(node) == npc.actions.door_state.CLOSED then
|
||||
-- Stop to open door, this avoids misplaced movements later on
|
||||
npc.add_action(self, npc.actions.stand, {self = self})
|
||||
-- Open door
|
||||
npc.add_action(self, npc.actions.use_door, {self=self, pos=path[i+1].pos, action=npc.actions.door_action.OPEN})
|
||||
end
|
||||
end
|
||||
-- Add walk action to action queue
|
||||
npc.add_action(self, npc.actions.walk_step, {self = self, dir = dir})
|
||||
if action == npc.actions.const.sittable.SIT then
|
||||
-- Calculate position depending on bench
|
||||
-- For cottages bench (code taken from Sokomine's cottages mod):
|
||||
local p2 = {x=pos.x, y=pos.y, z=pos.z};
|
||||
if not( node ) or node.param2 == 0 then
|
||||
p2.z = p2.z+0.3;
|
||||
elseif node.param2 == 1 then
|
||||
p2.x = p2.x+0.3;
|
||||
elseif node.param2 == 2 then
|
||||
p2.z = p2.z-0.3;
|
||||
elseif node.param2 == 3 then
|
||||
p2.x = p2.x-0.3;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Path-finding code
|
||||
---------------------------------------------------------------------------------------
|
||||
-- This is the limit to search for a path based on the goal node.
|
||||
-- If the path finder code goes beyond this limit in nodes away
|
||||
-- on the x or z plane, it will stop looking for a path
|
||||
npc.actions.PATH_DIFF_LIMIT = 125
|
||||
|
||||
-- Returns the opposite of a vector (scalar multiplication by -1)
|
||||
local function vector_opposite(v)
|
||||
return vector.multiply(v, -1)
|
||||
end
|
||||
|
||||
-- Returns a unit direction vector based on the largest coordinate
|
||||
local function get_unit_dir_vector_based_on_diff(v)
|
||||
if math.abs(v.x) > math.abs(v.z) then
|
||||
return {x=(v.x/math.abs(v.x)) * -1, y=0, z=0}
|
||||
elseif math.abs(v.z) > math.abs(v.x) then
|
||||
return {x=0, y=0, z=(v.z/math.abs(v.z)) * -1}
|
||||
elseif math.abs(v.x) == math.abs(v.z) then
|
||||
return {x=(v.x/math.abs(v.x)) * -1, y=0, z=0}
|
||||
-- For stairs (based on the above code):
|
||||
local p2 = {x=pos.x, y=pos.y, z=pos.z};
|
||||
if not( node ) or node.param2 == 0 then
|
||||
p2.z = p2.z-0.2;
|
||||
elseif node.param2 == 1 then
|
||||
p2.x = p2.x-0.2;
|
||||
elseif node.param2 == 2 then
|
||||
p2.z = p2.z+0.2;
|
||||
elseif node.param2 == 3 then
|
||||
p2.x = p2.x+0.2;
|
||||
end
|
||||
-- Sit down on bench/chair/stairs
|
||||
npc.add_action(self, npc.actions.sit, {self=self, pos=p2})
|
||||
-- Rotate to the correct position
|
||||
npc.add_action(self, npc.actions.rotate, {self=self, dir=node.param2 + 2 % 4})
|
||||
else
|
||||
-- Walk up from bed
|
||||
npc.add_action(self, npc.actions.walk_step, {self=self, dir=param2.param2 + 2 % 4})
|
||||
-- Stand
|
||||
npc.add_action(self, npc.actions.stand, {self=self})
|
||||
end
|
||||
end
|
||||
|
||||
@ -379,184 +386,344 @@ function npc.actions.get_direction(v1, v2)
|
||||
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 is_good_node(node)
|
||||
-- Is openable is to support doors, fence gates and other
|
||||
-- doors from other mods. Currently, default doors and gates
|
||||
-- will be supported. Cottages doors should also be supported.
|
||||
--minetest.log("Node name: "..dump(node.name))
|
||||
local is_openable = false
|
||||
local start_i,end_i = string.find(node.name, "doors:")
|
||||
is_openable = start_i ~= nil
|
||||
--minetest.log("Is node openable: "..dump(is_openable))
|
||||
--minetest.log("Is node walkable: "..dump(not minetest.registered_nodes[node.name].walkable))
|
||||
if not minetest.registered_nodes[node.name].walkable then
|
||||
return "W"
|
||||
elseif is_openable then
|
||||
return "O"
|
||||
-- This function can be used to make the NPC walk from one
|
||||
-- position to another.
|
||||
function npc.actions.walk_to_pos(self, end_pos)
|
||||
|
||||
local start_pos = self.object:getpos()
|
||||
|
||||
-- Find path
|
||||
local path = pathfinder.find_path(start_pos, end_pos, 20)
|
||||
|
||||
if path ~= nil then
|
||||
minetest.log("Found path to node: "..dump(end_pos))
|
||||
|
||||
-- Add a first step
|
||||
--local dir = npc.actions.get_direction(start_pos, path[1].pos)
|
||||
--minetest.log("Start_pos: "..dump(start_pos)..", First path step: "..dump(path[1].pos))
|
||||
--minetest.log("Direction: "..dump(dir))
|
||||
--npc.add_action(self, npc.actions.walk_step, {self = self, dir = dir})
|
||||
|
||||
-- Add subsequent steps
|
||||
local door_opened = false
|
||||
|
||||
for i = 1, #path do
|
||||
--minetest.log("Path: (i) "..dump(path[i])..": Path i+1 "..dump(path[i+1]))
|
||||
-- Do not add an extra step
|
||||
if (i+1) == #path then
|
||||
-- Add direction to last node
|
||||
local dir = npc.actions.get_direction(path[i].pos, end_pos)
|
||||
-- Add stand animation at end
|
||||
npc.add_action(self, npc.actions.stand, {self = self})
|
||||
-- Rotate to face the end node
|
||||
npc.actions.rotate({self = self, dir = dir})
|
||||
break
|
||||
end
|
||||
-- Get direction to move from path[i] to path[i+1]
|
||||
local dir = npc.actions.get_direction(path[i].pos, path[i+1].pos)
|
||||
-- Check if next node is a door, if it is, open it, then walk
|
||||
if path[i+1].type == pathfinder.node_types.openable then
|
||||
-- Check if door is already open
|
||||
local node = minetest.get_node(path[i+1].pos)
|
||||
if npc.actions.get_openable_node_state(node) == npc.actions.const.doors.state.CLOSED then
|
||||
minetest.log("Opening action to open door")
|
||||
-- Stop to open door, this avoids misplaced movements later on
|
||||
npc.add_action(self, npc.actions.stand, {self = self})
|
||||
-- Open door
|
||||
npc.add_action(self, npc.actions.use_door, {self=self, pos=path[i+1].pos, action=npc.actions.const.doors.action.OPEN})
|
||||
|
||||
door_opened = true
|
||||
end
|
||||
end
|
||||
-- Add walk action to action queue
|
||||
npc.add_action(self, npc.actions.walk_step, {self = self, dir = dir})
|
||||
|
||||
if door_opened then
|
||||
-- Stop to close door, this avoids misplaced movements later on
|
||||
npc.add_action(self, npc.actions.stand, {self = self})
|
||||
-- Close door
|
||||
npc.add_action(self, npc.actions.use_door, {self=self, pos=path[i+1].pos, action=npc.actions.const.doors.action.CLOSE})
|
||||
|
||||
door_opened = false
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
return "N"
|
||||
minetest.log("Unable to find path.")
|
||||
end
|
||||
end
|
||||
|
||||
-- Finds paths ignoring vertical obstacles
|
||||
-- This function is recursive and attempts to move all the time on
|
||||
-- the direction that will definetely lead to the end position.
|
||||
local function find_path_recursive(start_pos, end_pos, path_nodes, last_dir, last_good_dir)
|
||||
--minetest.log("Start pos: "..dump(start_pos))
|
||||
|
||||
-- Find difference. The purpose of this is to weigh movement, attempting
|
||||
-- the largest difference first, or both if equal.
|
||||
local diff = vector.subtract(start_pos, end_pos)
|
||||
-- ATTENTION:
|
||||
-- Old, deprecated, non-functional code below:
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Path-finding code
|
||||
---------------------------------------------------------------------------------------
|
||||
-- This is the limit to search for a path based on the goal node.
|
||||
-- If the path finder code goes beyond this limit in nodes away
|
||||
-- on the x or z plane, it will stop looking for a path
|
||||
-- npc.actions.PATH_DIFF_LIMIT = 125
|
||||
|
||||
--minetest.log("Difference: "..dump(diff))
|
||||
-- -- Returns the opposite of a vector (scalar multiplication by -1)
|
||||
-- local function vector_opposite(v)
|
||||
-- return vector.multiply(v, -1)
|
||||
-- end
|
||||
|
||||
-- End if difference is larger than max difference possible (limit)
|
||||
if math.abs(diff.x) > npc.actions.PATH_DIFF_LIMIT
|
||||
or math.abs(diff.z) > npc.actions.PATH_DIFF_LIMIT then
|
||||
-- Cannot find feasable path
|
||||
return nil
|
||||
end
|
||||
-- Determine direction to move
|
||||
local dir_vector = get_unit_dir_vector_based_on_diff(diff)
|
||||
-- -- Returns a unit direction vector based on the largest coordinate
|
||||
-- local function get_unit_dir_vector_based_on_diff(v)
|
||||
-- if math.abs(v.x) > math.abs(v.z) then
|
||||
-- return {x=(v.x/math.abs(v.x)) * -1, y=0, z=0}
|
||||
-- elseif math.abs(v.z) > math.abs(v.x) then
|
||||
-- return {x=0, y=0, z=(v.z/math.abs(v.z)) * -1}
|
||||
-- elseif math.abs(v.x) == math.abs(v.z) then
|
||||
-- return {x=(v.x/math.abs(v.x)) * -1, y=0, z=(v.z/math.abs(v.z)) * -1}
|
||||
-- end
|
||||
-- end
|
||||
|
||||
--minetest.log("Direction vector: "..dump(dir_vector))
|
||||
-- -- 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 is_good_node(node)
|
||||
-- -- Is openable is to support doors, fence gates and other
|
||||
-- -- doors from other mods. Currently, default doors and gates
|
||||
-- -- will be supported. Cottages doors should also be supported.
|
||||
-- --minetest.log("Node name: "..dump(node.name))
|
||||
-- local is_openable = false
|
||||
-- local start_i,end_i = string.find(node.name, "doors:")
|
||||
-- is_openable = start_i ~= nil
|
||||
-- --minetest.log("Is node openable: "..dump(is_openable))
|
||||
-- --minetest.log("Is node walkable: "..dump(not minetest.registered_nodes[node.name].walkable))
|
||||
-- if not minetest.registered_nodes[node.name].walkable then
|
||||
-- return "W"
|
||||
-- elseif is_openable then
|
||||
-- return "O"
|
||||
-- else
|
||||
-- return "N"
|
||||
-- end
|
||||
-- end
|
||||
|
||||
if last_good_dir ~= nil then
|
||||
dir_vector = last_good_dir
|
||||
end
|
||||
-- -- Finds paths ignoring vertical obstacles
|
||||
-- -- This function is recursive and attempts to move all the time on
|
||||
-- -- the direction that will definetely lead to the end position.
|
||||
-- local function find_path_recursive(start_pos, end_pos, path_nodes, last_dir, last_good_dir, last_good_try)
|
||||
-- minetest.log("Start pos: "..dump(start_pos))
|
||||
|
||||
-- Get next position based on direction
|
||||
local next_pos = vector.add(start_pos, dir_vector)
|
||||
-- -- Find difference. The purpose of this is to weigh movement, attempting
|
||||
-- -- the largest difference first, or both if equal.
|
||||
-- local diff = vector.subtract(start_pos, end_pos)
|
||||
|
||||
--minetest.log("Next pos: "..dump(next_pos))
|
||||
-- minetest.log("Difference: "..dump(diff))
|
||||
|
||||
-- Check if next_pos is actually within one block from the
|
||||
-- expected position. If so, finish
|
||||
local diff_to_end = vector.subtract(next_pos, end_pos)
|
||||
if math.abs(diff_to_end.x) < 1 and math.abs(diff_to_end.y) < 1 and math.abs(diff_to_end.z) < 1 then
|
||||
--minetest.log("Diff to end: "..dump(diff_to_end))
|
||||
table.insert(path_nodes, {pos=next_pos, type="E"})
|
||||
minetest.log("Found path to end.")
|
||||
return path_nodes
|
||||
end
|
||||
-- Check if movement is possible on the calculated direction
|
||||
local next_node = minetest.get_node(next_pos)
|
||||
-- If direction vector is opposite to the last dir, then do not attempt to walk into it
|
||||
--minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
local attempted_to_go_opposite = false
|
||||
if last_dir ~= nil and vector.equals(dir_vector, vector_opposite(last_dir)) then
|
||||
attempted_to_go_opposite = true
|
||||
--minetest.log("Last dir: "..dump(last_dir))
|
||||
--minetest.log("Calculated dir vector is the opposite of last dir: "..dump(vector.equals(dir_vector, vector_opposite(last_dir))))
|
||||
end
|
||||
-- -- End if difference is larger than max difference possible (limit)
|
||||
-- if math.abs(diff.x) > npc.actions.PATH_DIFF_LIMIT
|
||||
-- or math.abs(diff.z) > npc.actions.PATH_DIFF_LIMIT then
|
||||
-- minetest.log("Can't find feasable path.")
|
||||
-- -- Cannot find feasable path
|
||||
-- return nil
|
||||
-- end
|
||||
-- -- Determine direction to move
|
||||
-- local dir_vector = get_unit_dir_vector_based_on_diff(diff)
|
||||
|
||||
local node_type = is_good_node(next_node)
|
||||
if node_type ~= "N" and (not attempted_to_go_opposite) then
|
||||
table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
return find_path_recursive(next_pos, end_pos, path_nodes, nil, nil)
|
||||
else
|
||||
--minetest.log("------------ Second attempt ------------")
|
||||
-- minetest.log("Direction vector: "..dump(dir_vector))
|
||||
|
||||
-- if last_dir ~= nil then
|
||||
-- if last_good_try == 4
|
||||
-- or (dir_vector.x ~= 0 and dir_vector.z ~=0)
|
||||
-- -- Attention: Hacks below! The magic number 3 could be just extremely wrong.
|
||||
-- -- This is a terrible hack based on experimentations :(
|
||||
-- or (dir_vector.x ~= 0 and last_dir.x == 0 and math.abs(diff.x) > math.abs(diff.z) and math.abs(diff.z) < 3)
|
||||
-- or (dir_vector.z ~= 0 and last_dir.z == 0 and math.abs(diff.z) > math.abs(diff.x) and math.abs(diff.x) < 3) then
|
||||
-- if last_dir.x ~= 0 and diff.x ~= 0
|
||||
-- or last_dir.z ~= 0 and diff.z ~= 0 then
|
||||
-- minetest.log("Using last dir as direction vector: "..dump(last_dir))
|
||||
-- dir_vector = last_dir
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- if last_good_dir ~= nil then
|
||||
-- minetest.log("Using last good dir as direction vector: "..dump(last_good_dir))
|
||||
-- dir_vector = last_good_dir
|
||||
-- end
|
||||
|
||||
-- -- Get next position based on direction
|
||||
-- local next_pos = vector.add(start_pos, dir_vector)
|
||||
|
||||
-- minetest.log("Next pos: "..dump(next_pos))
|
||||
|
||||
-- -- Check if next_pos is actually within one block from the
|
||||
-- -- expected position. If so, finish
|
||||
-- local diff_to_end = vector.subtract(next_pos, end_pos)
|
||||
-- if math.abs(diff_to_end.x) < 1 and math.abs(diff_to_end.y) < 1 and math.abs(diff_to_end.z) < 1 then
|
||||
-- minetest.log("Diff to end: "..dump(diff_to_end))
|
||||
-- table.insert(path_nodes, {pos=next_pos, type="E"})
|
||||
-- minetest.log("Found path to end.")
|
||||
-- return path_nodes
|
||||
-- end
|
||||
-- -- Check if movement is possible on the calculated direction
|
||||
-- local next_node = minetest.get_node(next_pos)
|
||||
-- -- If direction vector is opposite to the last dir, then do not attempt to walk into it
|
||||
-- minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
-- local attempted_to_go_opposite = false
|
||||
-- if last_dir ~= nil and vector.equals(dir_vector, vector_opposite(last_dir)) then
|
||||
-- attempted_to_go_opposite = true
|
||||
-- minetest.log("Last dir: "..dump(last_dir))
|
||||
-- minetest.log("Calculated dir vector is the opposite of last dir: "..dump(vector.equals(dir_vector, vector_opposite(last_dir))))
|
||||
-- end
|
||||
|
||||
-- local node_type = is_good_node(next_node)
|
||||
-- if node_type ~= "N" and (not attempted_to_go_opposite) then
|
||||
-- table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
-- return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, nil, 1)
|
||||
-- else
|
||||
-- minetest.log("------------ Second attempt ------------")
|
||||
|
||||
-- If not walkable, attempt turn into the other coordinate
|
||||
-- Determine this coordinate based on what was the last calculated direction
|
||||
-- that didn't needed correction (last good dir). If this doesn't exists (e.g.
|
||||
-- there has been no correction for a while) then select the direction by
|
||||
-- trying to shorten the distance between NPC and the end node.
|
||||
-- -- If not walkable, attempt turn into the other coordinate
|
||||
-- -- Determine this coordinate based on what was the last calculated direction
|
||||
-- -- that didn't needed correction (last good dir). If this doesn't exists (e.g.
|
||||
-- -- there has been no correction for a while) then select the direction by
|
||||
-- -- trying to shorten the distance between NPC and the end node.
|
||||
|
||||
--minetest.log("Last known good dir: "..dump(last_good_dir))
|
||||
local step = 0
|
||||
if last_good_dir == nil then
|
||||
-- Store the current direction vector as the last non-corrected
|
||||
-- calculated direction
|
||||
last_good_dir = dir_vector
|
||||
|
||||
-- Determine which direction to move
|
||||
if dir_vector.x == 0 then
|
||||
--minetest.log("Choosing x direction")
|
||||
step = diff.x/math.abs(diff.x) * -1
|
||||
if diff.x == 0 then
|
||||
if last_dir ~= nil then
|
||||
step = last_dir.x
|
||||
else
|
||||
-- Set a default step to avoid locks
|
||||
step = 1
|
||||
end
|
||||
end
|
||||
dir_vector = {x = step, y = 0, z = 0}
|
||||
elseif dir_vector.z == 0 then
|
||||
step = diff.z/math.abs(diff.z) * -1
|
||||
if diff.z == 0 then
|
||||
if last_dir ~= nil then
|
||||
step = last_dir.z
|
||||
else
|
||||
-- Set a default step to avoid locks
|
||||
step = 1
|
||||
end
|
||||
end
|
||||
dir_vector = {x = 0, y = 0, z = step}
|
||||
end
|
||||
--minetest.log("Re-calculated dir vector: "..dump(dir_vector))
|
||||
next_pos = vector.add(start_pos, dir_vector)
|
||||
else
|
||||
dir_vector = last_good_dir
|
||||
if dir_vector.x == 0 then
|
||||
--minetest.log("Moving into x direction")
|
||||
step = last_dir.x
|
||||
elseif dir_vector.z == 0 then
|
||||
--minetest.log("Moving into z direction")
|
||||
step = last_dir.z
|
||||
end
|
||||
dir_vector = last_dir
|
||||
next_pos = vector.add(start_pos, dir_vector)
|
||||
end
|
||||
|
||||
-- Check if new node is walkable
|
||||
next_node = minetest.get_node(next_pos)
|
||||
-- minetest.log("Last known good dir: "..dump(last_good_dir))
|
||||
-- local step = 0
|
||||
-- if last_good_dir == nil then
|
||||
-- -- Store the current direction vector as the last non-corrected
|
||||
-- -- calculated direction
|
||||
-- last_good_dir = dir_vector
|
||||
|
||||
--minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
-- -- Determine which direction to move
|
||||
-- if dir_vector.x == 0 then
|
||||
-- minetest.log("Choosing x direction")
|
||||
-- step = diff.x/math.abs(diff.x) * -1
|
||||
-- if diff.x == 0 then
|
||||
-- if last_dir ~= nil and last_dir.x ~= 0 then--and last_good_try == 2 then
|
||||
-- step = last_dir.x
|
||||
-- else
|
||||
-- -- Set a default step to avoid locks
|
||||
-- step = 1
|
||||
-- end
|
||||
-- end
|
||||
-- dir_vector = {x = step, y = 0, z = 0}
|
||||
-- elseif dir_vector.z == 0 then
|
||||
-- step = diff.z/math.abs(diff.z) * -1
|
||||
-- if diff.z == 0 then
|
||||
-- if last_dir ~= nil and last_dir.z ~= 0 then -- and last_good_try == 2 then
|
||||
-- step = last_dir.z
|
||||
-- else
|
||||
-- -- Set a default step to avoid locks
|
||||
-- step = 1
|
||||
-- end
|
||||
-- end
|
||||
-- dir_vector = {x = 0, y = 0, z = step}
|
||||
-- end
|
||||
-- minetest.log("Re-calculated dir vector: "..dump(dir_vector))
|
||||
-- next_pos = vector.add(start_pos, dir_vector)
|
||||
-- else
|
||||
-- dir_vector = last_good_dir
|
||||
-- if dir_vector.x == 0 then
|
||||
-- minetest.log("Moving into x direction")
|
||||
-- step = last_dir.x
|
||||
-- elseif dir_vector.z == 0 then
|
||||
-- minetest.log("Moving into z direction")
|
||||
-- step = last_dir.z
|
||||
-- end
|
||||
-- dir_vector = last_dir
|
||||
-- next_pos = vector.add(start_pos, dir_vector)
|
||||
-- end
|
||||
|
||||
local node_type = is_good_node(next_node)
|
||||
if node_type ~= "N" then
|
||||
table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, last_good_dir)
|
||||
else
|
||||
last_good_dir = dir_vector
|
||||
--minetest.log("------------ Third attempt ------------")
|
||||
-- -- Check if new node is walkable
|
||||
-- next_node = minetest.get_node(next_pos)
|
||||
|
||||
-- If not walkable, then try the next node by finding the original
|
||||
-- direction vector, then choosing the opposite of that.
|
||||
if dir_vector.x ~= 0 then
|
||||
--minetest.log("Move into opposite z dir")
|
||||
dir_vector = get_unit_dir_vector_based_on_diff(start_pos, diff)
|
||||
vector.multiply(dir_vector, -1)
|
||||
elseif dir_vector.z ~= 0 then
|
||||
--minetest.log("Move into opposite x dir")
|
||||
dir_vector = get_unit_dir_vector_based_on_diff(start_pos, diff)
|
||||
vector.multiply(dir_vector, -1)
|
||||
end
|
||||
--minetest.log("New direction: "..dump(dir_vector))
|
||||
-- minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
|
||||
next_pos = vector.add(start_pos, dir_vector)
|
||||
--minetest.log("New next_pos: "..dump(next_pos))
|
||||
next_node = minetest.get_node(next_pos)
|
||||
--minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
local node_type = is_good_node(next_node)
|
||||
if node_type ~= "N" then
|
||||
table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, last_good_dir)
|
||||
else
|
||||
-- Try to return back, opposite of last dir. For now return nil as this code
|
||||
-- is not good enough to work correctly.
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
-- local node_type = is_good_node(next_node)
|
||||
-- if node_type ~= "N" then
|
||||
-- table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
-- return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, last_good_dir, 2)
|
||||
-- else
|
||||
|
||||
end
|
||||
-- minetest.log("Last known good dir: "..dump(last_good_dir))
|
||||
-- -- Only pick the second attempt's dir if it was actually good (meaning,
|
||||
-- -- it could step on that dir)
|
||||
-- if last_good_try == 2 then
|
||||
-- last_good_dir = dir_vector
|
||||
-- end
|
||||
-- minetest.log("------------ Third attempt ------------")
|
||||
|
||||
-- Calls the recursive function to calculate the path
|
||||
function npc.actions.find_path(start_pos, end_pos)
|
||||
return find_path_recursive(start_pos, end_pos, {}, nil, nil)
|
||||
end
|
||||
-- -- If not walkable, then try the next node by finding the original
|
||||
-- -- direction vector, then choosing the opposite of that.
|
||||
|
||||
-- minetest.log("Last dir: "..dump(last_dir))
|
||||
-- minetest.log("Last good try: "..dump(last_good_try))
|
||||
-- minetest.log("Last attempted direction: "..dump(dir_vector))
|
||||
|
||||
-- if vector.equals(last_good_dir, last_dir) then
|
||||
-- -- Go opposite the direction of second attempt
|
||||
-- minetest.log("Moving opposite of last attempted")
|
||||
-- dir_vector = vector.multiply(dir_vector, -1)
|
||||
-- else
|
||||
-- minetest.log("Moving opposite of last good dir")
|
||||
-- dir_vector = vector.multiply(last_good_dir, -1)
|
||||
-- last_good_dir = last_dir
|
||||
-- end
|
||||
|
||||
|
||||
-- -- if last_good_try > 1 or vector.equals(last_good_dir, last_dir) then
|
||||
-- -- if dir_vector.x ~= 0 then
|
||||
-- -- minetest.log("Move into opposite z dir")
|
||||
-- -- dir_vector = get_unit_dir_vector_based_on_diff(diff)
|
||||
-- -- dir_vector = vector.multiply(dir_vector, -1)
|
||||
-- -- elseif dir_vector.z ~= 0 then
|
||||
-- -- minetest.log("Move into opposite x dir")
|
||||
-- -- dir_vector = get_unit_dir_vector_based_on_diff(diff)
|
||||
-- -- dir_vector = vector.multiply(dir_vector, -1)
|
||||
-- -- end
|
||||
-- -- else
|
||||
-- -- minetest.log("Stuck in corner, try to move out of corner")
|
||||
-- -- dir_vector = vector.multiply(last_good_dir, -1)
|
||||
-- -- last_good_dir = last_dir
|
||||
-- -- end
|
||||
-- minetest.log("New direction: "..dump(dir_vector))
|
||||
-- minetest.log("New last good dir: "..dump(last_good_dir))
|
||||
|
||||
-- next_pos = vector.add(start_pos, dir_vector)
|
||||
-- minetest.log("New next_pos: "..dump(next_pos))
|
||||
-- next_node = minetest.get_node(next_pos)
|
||||
-- minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
-- local node_type = is_good_node(next_node)
|
||||
-- if node_type ~= "N" then
|
||||
-- table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
-- return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, last_good_dir, 3)
|
||||
-- else
|
||||
-- -- Move into the opposite of last good dir
|
||||
-- minetest.log("------------ Fourth attempt ------------")
|
||||
-- minetest.log("Last known good dir: "..dump(old_last_good_dir))
|
||||
|
||||
-- local old_dir_vector = dir_vector
|
||||
-- -- If not walkable, then try moving into the opposite of last good dir
|
||||
-- dir_vector = vector.multiply(last_good_dir, -1)
|
||||
-- minetest.log("New direction: "..dump(dir_vector))
|
||||
|
||||
-- next_pos = vector.add(start_pos, dir_vector)
|
||||
-- minetest.log("New next_pos: "..dump(next_pos))
|
||||
-- next_node = minetest.get_node(next_pos)
|
||||
-- minetest.log("Next node is walkable: "..dump(not minetest.registered_nodes[next_node.name].walkable))
|
||||
-- local node_type = is_good_node(next_node)
|
||||
-- if node_type ~= "N" then
|
||||
-- table.insert(path_nodes, {pos=next_pos, type=node_type})
|
||||
-- return find_path_recursive(next_pos, end_pos, path_nodes, dir_vector, old_dir_vector, 4)
|
||||
-- else
|
||||
-- minetest.log("Attempted to rotate 4 times, can't do anything else")
|
||||
-- return nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- end
|
||||
|
||||
-- -- Calls the recursive function to calculate the path
|
||||
-- function npc.actions.find_path(start_pos, end_pos)
|
||||
-- return find_path_recursive(start_pos, end_pos, {}, nil, nil, 0)
|
||||
-- end
|
247
actions/pathfinder.lua
Normal file
247
actions/pathfinder.lua
Normal file
@ -0,0 +1,247 @@
|
||||
-- Pathfinding code by Zorman2000
|
||||
---------------------------------------------------------------------------------------
|
||||
-- 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 external Jumper LUA library,
|
||||
-- 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 = {
|
||||
start = 0,
|
||||
goal = 1,
|
||||
walkable = 2,
|
||||
openable = 3,
|
||||
non_walkable = 4
|
||||
}
|
||||
|
||||
pathfinder.nodes = {
|
||||
openable_prefix = {
|
||||
"doors:",
|
||||
"cottages:gate",
|
||||
"cottages:half_door"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-- 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 pathfinder.find_path(start_pos, end_pos, range)
|
||||
-- Map the Minetest area to a 2D array
|
||||
local map = pathfinder.create_map(start_pos, end_pos, range, {})
|
||||
-- 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
|
||||
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.
|
||||
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 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
|
||||
|
||||
function pathfinder.create_map(start_pos, end_pos, extra_range, walkables)
|
||||
|
||||
-- Unused, will not use voxel areas for now
|
||||
--local c_air = minetest.get_content_id("air")
|
||||
minetest.log("Start pos: "..dump(start_pos))
|
||||
minetest.log("End pos: "..dump(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)
|
||||
--minetest.log("Start x sign: "..dump(start_x_sign)..", end x sign: "..dump(end_x_sign))
|
||||
--minetest.log("End z sign: "..dump(start_z_sign)..", end z sign: "..dump(end_z_sign))
|
||||
|
||||
-- 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("Pos 1: "..dump(pos1))
|
||||
--minetest.log("Pos 2: "..dump(pos2))
|
||||
|
||||
-- Get Voxel Area - Not used for the moment
|
||||
-- local vm = minetest.get_voxel_manip()
|
||||
-- local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
-- local area = VoxelArea:new({MinEdge=emin, MaxEdge=emax})
|
||||
-- local data = vm:get_data()
|
||||
|
||||
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)
|
||||
if node.name == "default:air" then
|
||||
-- If air do no more checks
|
||||
table.insert(current_row, {pos=current_pos, type=pathfinder.node_types.walkable})
|
||||
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
|
||||
end
|
||||
-- Insert the converted row into the grid
|
||||
table.insert(grid, current_row)
|
||||
end
|
||||
|
||||
return grid
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
-- 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
|
||||
end
|
||||
end
|
||||
minetest.log("Found start and end positions: "..dump(result))
|
||||
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)
|
||||
end
|
||||
end
|
||||
table.insert(result, result_row)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- This function returns an array of tables with to 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
|
||||
end
|
@ -17,7 +17,7 @@ npc.places.nodes = {
|
||||
"beds:bed_bottom",
|
||||
"beds:fancy_bed_bottom"
|
||||
},
|
||||
CHAIRS = {
|
||||
SITTABLE = {
|
||||
"cottages:bench"
|
||||
},
|
||||
CHESTS = {
|
||||
|
Reference in New Issue
Block a user