-- 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