fb549e7f93
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.
838 lines
28 KiB
Lua
838 lines
28 KiB
Lua
-- Actions code for Advanced NPC by Zorman2000
|
|
---------------------------------------------------------------------------------------
|
|
-- Action functionality
|
|
---------------------------------------------------------------------------------------
|
|
-- The NPCs will be able to perform six 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 = {}
|
|
|
|
-- Describes actions with doors or openable nodes
|
|
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
|
|
}
|
|
}
|
|
|
|
npc.actions.cmd = {
|
|
SET_INTERVAL = 0,
|
|
FREEZE = 1,
|
|
ROTATE = 2,
|
|
WALK_STEP = 3,
|
|
STAND = 4,
|
|
SIT = 5,
|
|
LAY = 6,
|
|
PUT_ITEM = 7,
|
|
TAKE_ITEM = 8,
|
|
CHECK_ITEM = 9,
|
|
USE_OPENABLE = 10,
|
|
USE_FURNACE = 11,
|
|
USE_BED = 12,
|
|
USE_SITTABLE = 13,
|
|
WALK_TO_POS = 14
|
|
}
|
|
|
|
--npc.actions.one_nps_speed = 0.98
|
|
--npc.actions.one_half_nps_speed = 1.40
|
|
--npc.actions.two_nps_speed = 1.90'
|
|
npc.actions.one_nps_speed = 1
|
|
npc.actions.one_half_nps_speed = 1.5
|
|
npc.actions.two_nps_speed = 2
|
|
|
|
-- Executor --
|
|
--------------
|
|
-- Function references aren't reliable in Minetest entities. Objects get serialized
|
|
-- and deserialized, as well as loaded and unloaded frequently which causes many
|
|
-- function references to be lost and then crashes occurs due to nil variables.
|
|
-- Using constants to refer to each method of this API and a function that
|
|
-- understands those constants and executes the proper function is the way to avoid
|
|
-- this frequent crashes.
|
|
function npc.actions.execute(self, command, args)
|
|
if command == npc.actions.cmd.SET_INTERVAL then
|
|
--
|
|
return npc.actions.set_interval(self, args)
|
|
elseif command == npc.actions.cmd.FREEZE then
|
|
--
|
|
return npc.actions.freeze(self, args)
|
|
elseif command == npc.actions.cmd.ROTATE then
|
|
--
|
|
return npc.actions.rotate(self, args)
|
|
elseif command == npc.actions.cmd.WALK_STEP then
|
|
--
|
|
return npc.actions.walk_step(self, args)
|
|
elseif command == npc.actions.cmd.STAND then
|
|
--
|
|
return npc.actions.stand(self, args)
|
|
elseif command == npc.actions.cmd.SIT then
|
|
--
|
|
return npc.actions.sit(self, args)
|
|
elseif command == npc.actions.cmd.LAY then
|
|
--
|
|
return npc.actions.lay(self, args)
|
|
elseif command == npc.actions.cmd.PUT_ITEM then
|
|
--
|
|
return npc.actions.put_item_on_external_inventory(self, args)
|
|
elseif command == npc.actions.cmd.TAKE_ITEM then
|
|
--
|
|
return npc.actions.take_item_from_external_inventory(self, args)
|
|
elseif command == npc.actions.cmd.CHECK_ITEM then
|
|
--
|
|
return npc.actions.check_external_inventory_contains_item(self, args)
|
|
elseif command == npc.actions.cmd.USE_OPENABLE then
|
|
--
|
|
return npc.actions.use_openable(self, args)
|
|
elseif command == npc.actions.cmd.USE_FURNACE then
|
|
--
|
|
return npc.actions.use_furnace(self, args)
|
|
elseif command == npc.actions.cmd.USE_BED then
|
|
--
|
|
return npc.actions.use_bed(self, args)
|
|
elseif command == npc.actions.cmd.USE_SITTABLE then
|
|
-- Call use sittable task
|
|
return npc.actions.use_sittable(self, args)
|
|
elseif command == npc.actions.cmd.WALK_TO_POS then
|
|
-- Call walk to position task
|
|
--minetest.log("Self: "..dump(self)..", Command: "..dump(command)..", args: "..dump(args))
|
|
return npc.actions.walk_to_pos(self, args)
|
|
end
|
|
end
|
|
|
|
-- TODO: Thanks to executor function, all the functions for Actions and Tasks
|
|
-- should be made into private API
|
|
|
|
---------------------------------------------------------------------------------------
|
|
-- Actions
|
|
---------------------------------------------------------------------------------------
|
|
-- The following action alters the timer interval for executing actions, therefore
|
|
-- making waits and pauses possible, or increase timing when some actions want to
|
|
-- be performed faster, like walking.
|
|
function npc.actions.set_interval(self, args)
|
|
local self_actions = args.self_actions
|
|
local new_interval = args.interval
|
|
local freeze_mobs_api = args.freeze
|
|
|
|
self.actions.action_interval = new_interval
|
|
return not freeze_mobs_api
|
|
end
|
|
|
|
-- The following action is for allowing the rest of mobs redo API to be executed
|
|
-- after this action ends. This is useful for times when no action is needed
|
|
-- and the NPC is allowed to roam freely.
|
|
function npc.actions.freeze(self, args)
|
|
local freeze_mobs_api = args.freeze
|
|
--minetest.log("Received: "..dump(freeze_mobs_api))
|
|
--minetest.log("Returning: "..dump(not(freeze_mobs_api)))
|
|
return not(freeze_mobs_api)
|
|
end
|
|
|
|
-- This action is to rotate to mob to a specifc direction. Currently, the code
|
|
-- contains also for diagonals, but remaining in the orthogonal domain is preferrable.
|
|
function npc.actions.rotate(self, args)
|
|
local dir = args.dir
|
|
local yaw = 0
|
|
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
|
|
|
|
-- 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(self, args)
|
|
local dir = args.dir
|
|
local speed = args.speed
|
|
local target_pos = args.target_pos
|
|
local vel = {}
|
|
-- Set default node per seconds
|
|
if speed == nil then
|
|
speed = npc.actions.one_nps_speed
|
|
end
|
|
-- If there is a target position to reach, set it
|
|
if target_pos ~= nil then
|
|
self.actions.walking.target_pos = target_pos
|
|
end
|
|
|
|
-- Set is_walking = true
|
|
self.actions.walking.is_walking = true
|
|
|
|
if dir == npc.direction.north then
|
|
vel = {x=0, y=0, z=speed}
|
|
elseif dir == npc.direction.north_east then
|
|
vel = {x=speed, y=0, z=speed}
|
|
elseif dir == npc.direction.east then
|
|
vel = {x=speed, y=0, z=0}
|
|
elseif dir == npc.direction.south_east then
|
|
vel = {x=speed, y=0, z=-speed}
|
|
elseif dir == npc.direction.south then
|
|
vel = {x=0, y=0, z=-speed}
|
|
elseif dir == npc.direction.south_west then
|
|
vel = {x=-speed, y=0, z=-speed}
|
|
elseif dir == npc.direction.west then
|
|
vel = {x=-speed, y=0, z=0}
|
|
elseif dir == npc.direction.north_west then
|
|
vel = {x=-speed, y=0, z=speed}
|
|
end
|
|
-- Rotate NPC
|
|
npc.actions.rotate(self, {dir=dir})
|
|
-- Set velocity so that NPC walks
|
|
self.object:setvelocity(vel)
|
|
-- Set walk animation
|
|
self.object:set_animation({
|
|
x = npc.ANIMATION_WALK_START,
|
|
y = npc.ANIMATION_WALK_END},
|
|
self.animation.speed_normal, 0)
|
|
end
|
|
|
|
-- This action makes the NPC stand and remain like that
|
|
function npc.actions.stand(self, args)
|
|
local pos = args.pos
|
|
local dir = args.dir
|
|
-- Set is_walking = true
|
|
self.actions.walking.is_walking = false
|
|
-- Stop NPC
|
|
self.object:setvelocity({x=0, y=0, z=0})
|
|
-- If position given, set to that position
|
|
if pos ~= nil then
|
|
self.object:moveto(pos)
|
|
end
|
|
-- If dir given, set to that dir
|
|
if dir ~= nil then
|
|
npc.actions.rotate(self, {dir=dir})
|
|
end
|
|
-- Set stand animation
|
|
self.object:set_animation({
|
|
x = npc.ANIMATION_STAND_START,
|
|
y = npc.ANIMATION_STAND_END},
|
|
self.animation.speed_normal, 0)
|
|
end
|
|
|
|
-- This action makes the NPC sit on the node where it is
|
|
function npc.actions.sit(self, args)
|
|
local pos = args.pos
|
|
local dir = args.dir
|
|
-- Stop NPC
|
|
self.object:setvelocity({x=0, y=0, z=0})
|
|
-- If position given, set to that position
|
|
if pos ~= nil then
|
|
self.object:moveto(pos)
|
|
end
|
|
-- If dir given, set to that dir
|
|
if dir ~= nil then
|
|
npc.actions.rotate(self, {dir=dir})
|
|
end
|
|
-- 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(self, args)
|
|
local pos = args.pos
|
|
-- Stop NPC
|
|
self.object:setvelocity({x=0, y=0, z=0})
|
|
-- If position give, set to that position
|
|
if pos ~= nil then
|
|
self.object:moveto(pos)
|
|
end
|
|
-- 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(self, args)
|
|
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(self, args)
|
|
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=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
|
|
|
|
function npc.actions.check_external_inventory_contains_item(self, args)
|
|
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=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
|
|
|
|
-- TODO: Refactor this function so that it uses a table to check
|
|
-- for doors instead of having separate logic for each door type
|
|
function npc.actions.get_openable_node_state(node, npc_dir)
|
|
--minetest.log("Node name: "..dump(node.name))
|
|
local state = npc.actions.const.doors.state.CLOSED
|
|
-- Check for default doors and gates
|
|
local a_i1, a_i2 = string.find(node.name, "_a")
|
|
-- Check for cottages gates
|
|
local open_i1, open_i2 = string.find(node.name, "_close")
|
|
-- Check for cottages half door
|
|
local half_door_is_closed = false
|
|
if node.name == "cottages:half_door" then
|
|
half_door_is_closed = (node.param2 + 2) % 4 == npc_dir
|
|
end
|
|
if a_i1 == nil and open_i1 == nil and not half_door_is_closed then
|
|
state = npc.actions.const.doors.state.OPEN
|
|
end
|
|
--minetest.log("Door state: "..dump(state))
|
|
return state
|
|
end
|
|
|
|
-- This function is used to open or close openable nodes.
|
|
-- Currently supported openable nodes are: any doors using the
|
|
-- default doors API, and the cottages mod gates and doors.
|
|
function npc.actions.use_openable(self, args)
|
|
local pos = args.pos
|
|
local action = args.action
|
|
local dir = args.dir
|
|
local node = minetest.get_node(pos)
|
|
local state = npc.actions.get_openable_node_state(node, dir)
|
|
|
|
local clicker = self.object
|
|
if action ~= state then
|
|
minetest.registered_nodes[node.name].on_rightclick(pos, node, clicker, nil, nil)
|
|
end
|
|
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, are provided here.
|
|
|
|
local function get_pos_argument(self, pos)
|
|
--minetest.log("Type of pos: "..dump(type(pos)))
|
|
-- Check which type of position argument we received
|
|
if type(pos) == "table" then
|
|
--minetest.log("Received table pos: "..dump(pos))
|
|
-- Check if table is position
|
|
if pos.x ~= nil and pos.y ~= nil and pos.z ~= nil then
|
|
-- Position received, return position
|
|
return pos
|
|
elseif pos.place_type ~= nil then
|
|
-- Received table in the following format:
|
|
-- {place_type = "", index = 1, use_access_node = false}
|
|
local index = pos.index or 1
|
|
local use_access_node = pos.use_access_node or false
|
|
local places = npc.places.get_by_type(self, pos.place_type)
|
|
-- Check index is valid on the places map
|
|
if #places >= index then
|
|
-- Check if access node is desired
|
|
if use_access_node then
|
|
-- Return actual node pos
|
|
return places[index].access_node
|
|
else
|
|
-- Return node pos that allows access to node
|
|
return places[index].pos
|
|
end
|
|
end
|
|
end
|
|
elseif type(pos) == "string" then
|
|
-- Received name of place, so we are going to look for the actual pos
|
|
local places_pos = npc.places.get_by_type(self, pos)
|
|
-- Return nil if no position found
|
|
if places_pos == nil or #places_pos == 0 then
|
|
return nil
|
|
end
|
|
-- Check if received more than one position
|
|
if #places_pos > 1 then
|
|
-- Check all places, return owned if existent, else return the first one
|
|
for i = 1, #places_pos do
|
|
if places_pos[i].status == "owned" then
|
|
return places_pos[i].pos
|
|
end
|
|
end
|
|
end
|
|
-- Return the first position only if it couldn't find an owned
|
|
-- place, or if it there is only oneg
|
|
return places_pos[1].pos
|
|
end
|
|
end
|
|
|
|
-- 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, args)
|
|
local pos = get_pos_argument(self, args.pos)
|
|
if pos == nil then
|
|
npc.log("WARNING", "Got nil position in 'use_furnace' using args.pos: "..dump(args.pos))
|
|
return
|
|
end
|
|
|
|
local item = args.item
|
|
local freeze = args.freeze
|
|
-- Define which items are usable as fuels. The NPC
|
|
-- will mainly use this as fuels to avoid getting useful
|
|
-- items (such as coal lumps) for burning
|
|
local fuels = {"default:leaves",
|
|
"default:pine_needles",
|
|
"default:tree",
|
|
"default:acacia_tree",
|
|
"default:aspen_tree",
|
|
"default:jungletree",
|
|
"default:pine_tree",
|
|
"default:coalblock",
|
|
"farming:straw"}
|
|
|
|
-- Check if NPC has item to cook
|
|
local src_item = npc.inventory_contains(self, npc.get_item_name(item))
|
|
if src_item == nil then
|
|
-- Unable to cook item that is not in inventory
|
|
return false
|
|
end
|
|
|
|
-- Check if NPC has a fuel item
|
|
for i = 1,9 do
|
|
local fuel_item = npc.inventory_contains(self, fuels[i])
|
|
|
|
if fuel_item ~= nil then
|
|
-- Get fuel item's burn time
|
|
local fuel_time =
|
|
minetest.get_craft_result({method="fuel", width=1, items={ItemStack(fuel_item.item_string)}}).time
|
|
local total_fuel_time = fuel_time * npc.get_item_count(fuel_item.item_string)
|
|
npc.log("DEBUG", "Fuel time: "..dump(fuel_time))
|
|
|
|
-- Get item to cook's cooking time
|
|
local cook_result =
|
|
minetest.get_craft_result({method="cooking", width=1, items={ItemStack(src_item.item_string)}})
|
|
local total_cook_time = cook_result.time * npc.get_item_count(item)
|
|
npc.log("DEBUG", "Cook: "..dump(cook_result))
|
|
|
|
npc.log("DEBUG", "Total cook time: "..total_cook_time
|
|
..", total fuel burn time: "..dump(total_fuel_time))
|
|
|
|
-- Check if there is enough fuel to cook all items
|
|
if total_cook_time > total_fuel_time then
|
|
-- Don't have enough fuel to cook item. Return the difference
|
|
-- so it may help on trying to acquire the fuel later.
|
|
-- NOTE: Yes, returning here means that NPC could probably have other
|
|
-- items usable as fuels and ignore them. This should be ok for now,
|
|
-- considering that fuel items are ordered in a way where cheaper, less
|
|
-- useless items come first, saving possible valuable items.
|
|
return cook_result.time - fuel_time
|
|
end
|
|
|
|
-- Calculate how much fuel is needed
|
|
local fuel_amount = total_cook_time / fuel_time
|
|
if fuel_amount < 1 then
|
|
fuel_amount = 1
|
|
end
|
|
|
|
npc.log("DEBUG", "Amount of fuel needed: "..fuel_amount)
|
|
|
|
-- Put this item on the fuel inventory list of the furnace
|
|
local args = {
|
|
player = nil,
|
|
pos = pos,
|
|
inv_list = "fuel",
|
|
item_name = npc.get_item_name(fuel_item.item_string),
|
|
count = fuel_amount
|
|
}
|
|
npc.add_action(self, npc.actions.cmd.PUT_ITEM, args)
|
|
-- Put the item that we want to cook on the furnace
|
|
args = {
|
|
player = nil,
|
|
pos = pos,
|
|
inv_list = "src",
|
|
item_name = npc.get_item_name(src_item.item_string),
|
|
count = npc.get_item_count(item),
|
|
is_furnace = true
|
|
}
|
|
npc.add_action(self, npc.actions.cmd.PUT_ITEM, args)
|
|
|
|
-- Now, set NPC to wait until furnace is done.
|
|
npc.log("DEBUG", "Setting wait action for "..dump(total_cook_time))
|
|
npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {interval=total_cook_time, freeze=freeze})
|
|
|
|
-- Reset timer
|
|
npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {interval=1, freeze=true})
|
|
|
|
-- If freeze is false, then we will have to find the way back to the furnace
|
|
-- once cooking is done.
|
|
if freeze == false then
|
|
npc.log("DEBUG", "Adding walk to position to wandering: "..dump(pos))
|
|
npc.add_task(self, npc.actions.cmd.WALK_TO_POS, {end_pos=pos, walkable={}})
|
|
end
|
|
|
|
-- Take cooked items back
|
|
args = {
|
|
|
|
player = nil,
|
|
pos = pos,
|
|
inv_list = "dst",
|
|
item_name = cook_result.item:get_name(),
|
|
count = npc.get_item_count(item),
|
|
is_furnace = false
|
|
}
|
|
npc.log("DEBUG", "Taking item back: "..minetest.pos_to_string(pos))
|
|
npc.add_action(self, npc.actions.cmd.TAKE_ITEM, args)
|
|
|
|
npc.log("DEBUG", "Inventory: "..dump(self.inventory))
|
|
|
|
return true
|
|
end
|
|
end
|
|
-- Couldn't use the furnace due to lack of items
|
|
return false
|
|
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_bed(self, args)
|
|
local pos = get_pos_argument(self, args.pos)
|
|
if pos == nil then
|
|
npc.log("WARNING", "Got nil position in 'use_bed' using args.pos: "..dump(args.pos))
|
|
return
|
|
end
|
|
local action = args.action
|
|
local node = minetest.get_node(pos)
|
|
--minetest.log(dump(node))
|
|
local dir = minetest.facedir_to_dir(node.param2)
|
|
|
|
if action == npc.actions.const.beds.LAY then
|
|
-- Get position
|
|
local bed_pos = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir)
|
|
-- Sit down on bed, rotate to correct direction
|
|
npc.add_action(self, npc.actions.cmd.SIT, {pos=bed_pos, dir=(node.param2 + 2) % 4})
|
|
-- Lay down
|
|
npc.add_action(self, npc.actions.cmd.LAY, {})
|
|
else
|
|
-- Calculate position to get up
|
|
local bed_pos_y = npc.actions.nodes.beds[node.name].get_lay_pos(pos, dir).y
|
|
local bed_pos = {x = pos.x, y = bed_pos_y, z = pos.z}
|
|
-- Sit up
|
|
npc.add_action(self, npc.actions.cmd.SIT, {pos=bed_pos})
|
|
-- Initialize direction: Default is front of bottom of bed
|
|
local dir = (node.param2 + 2) % 4
|
|
-- Find empty node around node
|
|
-- Take into account that mats are close to the floor, so y adjustmen is zero
|
|
local y_adjustment = -1
|
|
if npc.actions.nodes.beds[node.name].type == "mat" then
|
|
y_adjustment = 0
|
|
end
|
|
local empty_nodes = npc.places.find_node_orthogonally(bed_pos, {"air", "cottages:bench"}, y_adjustment)
|
|
if empty_nodes ~= nil then
|
|
-- Get direction to the empty node
|
|
dir = npc.actions.get_direction(bed_pos, empty_nodes[1].pos)
|
|
end
|
|
-- Calculate position to get out of bed
|
|
local pos_out_of_bed =
|
|
{x=empty_nodes[1].pos.x, y=empty_nodes[1].pos.y + 1, z=empty_nodes[1].pos.z}
|
|
-- Account for benches if they are present to avoid standing over them
|
|
if empty_nodes[1].name == "cottages:bench" then
|
|
pos_out_of_bed = {x=empty_nodes[1].pos.x, y=empty_nodes[1].pos.y + 1, z=empty_nodes[1].pos.z}
|
|
if empty_nodes[1].param2 == 0 then
|
|
pos_out_of_bed.z = pos_out_of_bed.z - 0.3
|
|
elseif empty_nodes[1].param2 == 1 then
|
|
pos_out_of_bed.x = pos_out_of_bed.x - 0.3
|
|
elseif empty_nodes[1].param2 == 2 then
|
|
pos_out_of_bed.z = pos_out_of_bed.z + 0.3
|
|
elseif empty_nodes[1].param2 == 3 then
|
|
pos_out_of_bed.x = pos_out_of_bed.x + 0.3
|
|
end
|
|
end
|
|
-- Stand out of bed
|
|
npc.add_action(self, npc.actions.cmd.STAND, {pos=pos_out_of_bed, dir=dir})
|
|
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, args)
|
|
local pos = get_pos_argument(self, args.pos)
|
|
if pos == nil then
|
|
npc.log("WARNING", "Got nil position in 'use_sittable' using args.pos: "..dump(args.pos))
|
|
return
|
|
end
|
|
local action = args.action
|
|
local node = minetest.get_node(pos)
|
|
|
|
if action == npc.actions.const.sittable.SIT then
|
|
-- Calculate position depending on bench
|
|
local sit_pos = npc.actions.nodes.sittable[node.name].get_sit_pos(pos, node.param2)
|
|
-- Sit down on bench/chair/stairs
|
|
npc.add_action(self, npc.actions.cmd.SIT, {pos=sit_pos, dir=(node.param2 + 2) % 4})
|
|
else
|
|
-- Find empty areas around chair
|
|
local dir = node.param2 + 2 % 4
|
|
-- Default it to the current position in case it can't find empty
|
|
-- position around sittable node. Weird
|
|
local pos_out_of_sittable = pos
|
|
local empty_nodes = npc.places.find_node_orthogonally(pos, {"air"}, 0)
|
|
if empty_nodes ~= nil and #empty_nodes > 0 then
|
|
--minetest.log("Empty nodes: "..dump(empty_nodes))
|
|
--minetest.log("Npc.actions.get_direction: "..dump(npc.actions.get_direction))
|
|
--minetest.log("Pos: "..dump(pos))
|
|
-- Get direction to the empty node
|
|
dir = npc.actions.get_direction(pos, empty_nodes[1].pos)
|
|
-- Calculate position to get out of sittable node
|
|
pos_out_of_sittable =
|
|
{x=empty_nodes[1].pos.x, y=empty_nodes[1].pos.y + 1, z=empty_nodes[1].pos.z}
|
|
end
|
|
-- Stand
|
|
npc.add_action(self, npc.actions.cmd.STAND, {pos=pos_out_of_sittable, dir=dir})
|
|
end
|
|
end
|
|
|
|
-- This function returns the direction enum
|
|
-- for the moving from v1 to v2
|
|
function npc.actions.get_direction(v1, v2)
|
|
local dir = vector.subtract(v2, v1)
|
|
|
|
if dir.x ~= 0 and dir.z ~= 0 then
|
|
if dir.x > 0 and dir.z > 0 then
|
|
return npc.direction.north_east
|
|
elseif dir.x > 0 and dir.z < 0 then
|
|
return npc.direction.south_east
|
|
elseif dir.x < 0 and dir.z > 0 then
|
|
return npc.direction.north_west
|
|
elseif dir.x < 0 and dir.z < 0 then
|
|
return npc.direction.south_west
|
|
end
|
|
elseif dir.x ~= 0 and dir.z == 0 then
|
|
if dir.x > 0 then
|
|
return npc.direction.east
|
|
else
|
|
return npc.direction.west
|
|
end
|
|
elseif dir.z ~= 0 and dir.x == 0 then
|
|
if dir.z > 0 then
|
|
return npc.direction.north
|
|
else
|
|
return npc.direction.south
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- This function can be used to make the NPC walk from one
|
|
-- position to another. If the optional parameter walkable_nodes
|
|
-- is included, which is a table of node names, these nodes are
|
|
-- going to be considered walkable for the algorithm to find a
|
|
-- path.
|
|
function npc.actions.walk_to_pos(self, args)
|
|
-- Get arguments for this task
|
|
local end_pos = get_pos_argument(self, args.end_pos)
|
|
if end_pos == nil then
|
|
npc.log("WARNING", "Got nil position in 'walk_to_pos' using args.pos: "..dump(args.end_pos))
|
|
return
|
|
end
|
|
local enforce_move = args.enforce_move or true
|
|
local walkable_nodes = args.walkable
|
|
|
|
-- Round start_pos to make sure it can find start and end
|
|
local start_pos = vector.round(self.object:getpos())
|
|
-- Use y of end_pos (this can only be done assuming flat terrain)
|
|
--start_pos.y = self.object:getpos().y
|
|
npc.log("DEBUG", "walk_to_pos: Start pos: "..minetest.pos_to_string(start_pos))
|
|
npc.log("DEBUG", "walk_to_pos: End pos: "..minetest.pos_to_string(end_pos))
|
|
|
|
-- Set walkable nodes to empty if the parameter hasn't been used
|
|
if walkable_nodes == nil then
|
|
walkable_nodes = {}
|
|
end
|
|
|
|
-- Find path
|
|
local path = npc.pathfinder.find_path(start_pos, end_pos, self, true)
|
|
|
|
if path ~= nil and #path > 1 then
|
|
npc.log("INFO", "walk_to_pos Found path to node: "..minetest.pos_to_string(end_pos))
|
|
-- Store path
|
|
self.actions.walking.path = path
|
|
|
|
-- Local variables
|
|
local door_opened = false
|
|
local speed = npc.actions.two_nps_speed
|
|
|
|
-- Set the action timer interval to half second. This is to account for
|
|
-- the increased speed when walking.
|
|
npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {interval=0.5, freeze=true})
|
|
|
|
-- Set the initial last and target positions
|
|
self.actions.walking.target_pos = path[1].pos
|
|
|
|
-- Add steps to path
|
|
for i = 1, #path do
|
|
-- Do not add an extra step if reached the goal node
|
|
if (i+1) == #path then
|
|
-- Add direction to last node
|
|
local dir = npc.actions.get_direction(path[i].pos, end_pos)
|
|
-- Add the last step
|
|
npc.add_action(self, npc.actions.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+1].pos})
|
|
-- Add stand animation at end
|
|
npc.add_action(self, npc.actions.cmd.STAND, {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 == npc.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, dir) == 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.cmd.STAND, {dir=dir})
|
|
-- Open door
|
|
npc.add_action(self, npc.actions.cmd.USE_OPENABLE, {pos=path[i+1].pos, dir=dir, action=npc.actions.const.doors.action.OPEN})
|
|
|
|
door_opened = true
|
|
end
|
|
|
|
end
|
|
|
|
-- Add walk action to action queue
|
|
npc.add_action(self, npc.actions.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+1].pos})
|
|
|
|
if door_opened then
|
|
-- Stop to close door, this avoids misplaced movements later on
|
|
-- local x_adj, z_adj = 0, 0
|
|
-- if dir == 0 then
|
|
-- z_adj = 0.1
|
|
-- elseif dir == 1 then
|
|
-- x_adj = 0.1
|
|
-- elseif dir == 2 then
|
|
-- z_adj = -0.1
|
|
-- elseif dir == 3 then
|
|
-- x_adj = -0.1
|
|
-- end
|
|
-- local pos_on_close = {x=path[i+1].pos.x + x_adj, y=path[i+1].pos.y + 1, z=path[i+1].pos.z + z_adj}
|
|
-- Add extra walk step to ensure that one is standing at other side of openable node
|
|
-- npc.add_action(self, npc.actions.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+2].pos})
|
|
-- Stop to close the door
|
|
npc.add_action(self, npc.actions.cmd.STAND, {dir=(dir + 2) % 4 })--, pos=pos_on_close})
|
|
-- Close door
|
|
npc.add_action(self, npc.actions.cmd.USE_OPENABLE, {pos=path[i+1].pos, action=npc.actions.const.doors.action.CLOSE})
|
|
|
|
door_opened = false
|
|
end
|
|
|
|
end
|
|
|
|
-- Return the action interval to default interval of 1 second
|
|
-- By default, always freeze.
|
|
npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {interval=1, freeze=true})
|
|
|
|
else
|
|
-- Unable to find path
|
|
npc.log("WARNING", "walk_to_pos Unable to find path. Teleporting to: "..minetest.pos_to_string(end_pos))
|
|
-- Check if movement is enforced
|
|
if enforce_move then
|
|
-- Move to end pos
|
|
self.object:moveto({x=end_pos.x, y=end_pos.y+1, z=end_pos.z})
|
|
end
|
|
end
|
|
end |