diff --git a/actions/actions.lua b/actions/actions.lua index 1b4f9c6..c068e01 100644 --- a/actions/actions.lua +++ b/actions/actions.lua @@ -76,29 +76,53 @@ function npc.actions.walk_step(args) elseif dir == npc.direction.west then vel = {x=-0.98, y=0, z=0} end - set_animation(self, "walk") + -- Rotate NPC npc.actions.rotate({self=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(args) local self = args.self + 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=self, dir=dir}) + end -- Set stand animation - set_animation(self, "stand") + 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(args) local self = args.self local pos = args.pos + local dir = args.dir -- Stop NPC self.object:setvelocity({x=0, y=0, z=0}) - -- If position give, set to that position + -- If position given, set to that position if pos ~= nil then - self.object:setpos(pos) + self.object:moveto(pos) + end + -- If dir given, set to that dir + if dir ~= nil then + npc.actions.rotate({self=self, dir=dir}) end -- Set sit animation self.object:set_animation({ @@ -115,7 +139,7 @@ function npc.actions.lay(args) self.object:setvelocity({x=0, y=0, z=0}) -- If position give, set to that position if pos ~= nil then - self.object:setpos(pos) + self.object:moveto(pos) end -- Set sit animation self.object:set_animation({ @@ -213,26 +237,37 @@ function npc.actions.check_external_inventory_contains_item(args) return inv:contains_item(inv_list, item) end -function npc.actions.get_openable_node_state(node) +-- 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") - if a_i1 == nil and open_i1 == nil then + -- 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 doors from --- that use the default doors mod +-- 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_door(args) local self = args.self 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) + local state = npc.actions.get_openable_node_state(node, dir) local clicker = self.object if action ~= state then @@ -301,28 +336,48 @@ 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, pos, action) - local param2 = minetest.get_node(pos) - minetest.log(dump(param2)) - local dir = minetest.facedir_to_dir(param2.param2) + 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 - -- 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 - npc.add_action(self, npc.actions.sit, {self=self}) - -- Rotate to the correct position - npc.add_action(self, npc.actions.rotate, {self=self, dir=param2.param2 + 2 % 4}) + -- Get position + local bed_pos = npc.actions.nodes.beds[node.name].get_lay_pos(pos) + -- Sit down on bed, rotate to correct direction + npc.add_action(self, npc.actions.sit, {self=self, pos=bed_pos, dir=(node.param2 + 2) % 4}) -- Lay down - npc.add_action(self, npc.actions.lay, {self=self, pos=bed_pos}) + npc.add_action(self, npc.actions.lay, {self=self}) else -- Calculate position to get up local bed_pos = {x = pos.x, y = pos.y + 1, z = pos.z} -- Sit up npc.add_action(self, npc.actions.sit, {self=self, pos=bed_pos}) - -- 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}) + -- Initialize direction: Default is front of bottom of bed + local dir = (node.param2 + 2) % 4 + -- Find empty node around node + local empty_nodes = npc.places.find_node_orthogonally(bed_pos, {"air", "cottages:bench"}, -1) + 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.stand, {self=self, pos=pos_out_of_bed, dir=dir}) end end @@ -387,36 +442,35 @@ function npc.actions.get_direction(v1, v2) end -- This function can be used to make the NPC walk from one --- position to another. -function npc.actions.walk_to_pos(self, end_pos) +-- 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, end_pos, walkable_nodes) local start_pos = self.object:getpos() + -- Set walkable nodes to empty if the parameter hasn't been used + if walkable_nodes == nil then + walkable_nodes = {} + end + -- Find path - local path = pathfinder.find_path(start_pos, end_pos, 20) + local path = pathfinder.find_path(start_pos, end_pos, 20, walkable_nodes) 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 + -- Add steps to path 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 + -- 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 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}) + npc.add_action(self, npc.actions.stand, {self = self, dir = dir}) break end -- Get direction to move from path[i] to path[i+1] @@ -425,12 +479,12 @@ function npc.actions.walk_to_pos(self, end_pos) 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 + 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.stand, {self = self}) + npc.add_action(self, npc.actions.stand, {self=self, dir=dir}) -- Open door - npc.add_action(self, npc.actions.use_door, {self=self, pos=path[i+1].pos, action=npc.actions.const.doors.action.OPEN}) + npc.add_action(self, npc.actions.use_door, {self=self, pos=path[i+1].pos, dir=dir, action=npc.actions.const.doors.action.OPEN}) door_opened = true end @@ -440,7 +494,7 @@ function npc.actions.walk_to_pos(self, end_pos) if door_opened then -- Stop to close door, this avoids misplaced movements later on - npc.add_action(self, npc.actions.stand, {self = self}) + npc.add_action(self, npc.actions.stand, {self=self, dir=(dir + 2)% 4}) -- Close door npc.add_action(self, npc.actions.use_door, {self=self, pos=path[i+1].pos, action=npc.actions.const.doors.action.CLOSE}) diff --git a/actions/nodes_registry.lua b/actions/nodes_registry.lua new file mode 100644 index 0000000..705cfc0 --- /dev/null +++ b/actions/nodes_registry.lua @@ -0,0 +1,25 @@ +-- Node functionality for actions by Zorman2000 +-- In this script, some functionality and information required for nodes +-- to be used correctly by an NPC is described. + +-- Attempt to keep a register of how to use some nodes +npc.actions.nodes = { + doors = {}, + beds = {}, + sittable = {} +} + +-- Register default beds. Always register bottom node only +npc.actions.nodes.beds["beds:bed_bottom"] = { + get_lay_pos = function(pos) + return {x = pos.x + dir.x / 2, y = pos.y + 1, z = pos.z + dir.z / 2} + end +} + +npc.actions.nodes.beds["beds:fancy_bed_bottom"] = { + get_lay_pos = function(pos) + return {x = pos.x + dir.x / 2, y = pos.y + 1, z = pos.z + dir.z / 2} + end +} + +npc.actions.nodes.beds[""] \ No newline at end of file diff --git a/actions/pathfinder.lua b/actions/pathfinder.lua index b207bc3..cb2a61f 100644 --- a/actions/pathfinder.lua +++ b/actions/pathfinder.lua @@ -54,9 +54,13 @@ pathfinder.nodes = { -- This function uses the mapping functions and the A* algorithm implementation -- of the Jumper library to find a path from start_pos to end_pos. The range is -- an extra amount of nodes to search in both the x and z coordinates. -function pathfinder.find_path(start_pos, end_pos, range) +function pathfinder.find_path(start_pos, end_pos, range, walkable_nodes) + -- Set walkable nodes to empty if parameter wasn't used + if walkable_nodes == nil then + walkable_nodes = {} + end -- Map the Minetest area to a 2D array - local map = pathfinder.create_map(start_pos, end_pos, range, {}) + local map = pathfinder.create_map(start_pos, end_pos, range, walkable_nodes) -- Find start and end positions local pos = pathfinder.find_start_and_end_pos(map) -- Normalize the map @@ -67,7 +71,7 @@ function pathfinder.find_path(start_pos, end_pos, range) local walkable = 0 -- Pathfinder object using A* algorithm - local finder = Pathfinder(grid_object, 'ASTAR', walkable) + local finder = Pathfinder(grid_object, "ASTAR", walkable) -- Set orthogonal mode meaning it will not move in diagonal directions finder:setMode("ORTHOGONAL") diff --git a/actions/places.lua b/actions/places.lua index aa3b370..296aec5 100644 --- a/actions/places.lua +++ b/actions/places.lua @@ -69,7 +69,7 @@ end -- This function searches on a squared are of the given radius -- for nodes of the given type. The type should be npc.places.nodes -function npc.places.find_new_nearby(self, type, radius) +function npc.places.find_node_nearby(self, type, radius) -- Get current pos local current_pos = self.object:getpos() -- Determine area points @@ -81,7 +81,28 @@ function npc.places.find_new_nearby(self, type, radius) return nodes end -function npc.places.find_in_area(start_pos, end_pos, type) +-- TODO: This function can be improved to support a radius greater than 1. +function npc.places.find_node_orthogonally(pos, nodes, y_adjustment) + -- Calculate orthogonal points + local points = {} + table.insert(points, {x=pos.x+1,y=pos.y+y_adjustment,z=pos.z}) + table.insert(points, {x=pos.x-1,y=pos.y+y_adjustment,z=pos.z}) + table.insert(points, {x=pos.x,y=pos.y+y_adjustment,z=pos.z+1}) + table.insert(points, {x=pos.x,y=pos.y+y_adjustment,z=pos.z-1}) + local result = {} + for _,point in pairs(points) do + local node = minetest.get_node(point) + minetest.log("Found node: "..dump(node)..", at pos: "..dump(point)) + for _,node_name in pairs(nodes) do + if node.name == node_name then + table.insert(result, {name=node.name, pos=point, param2=node.param2}) + end + end + end + return result +end + +function npc.places.find_node_in_area(start_pos, end_pos, type) local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type) return nodes end \ No newline at end of file diff --git a/init.lua b/init.lua index b9ed104..5f95add 100755 --- a/init.lua +++ b/init.lua @@ -33,5 +33,6 @@ dofile(path .. "/trade/prices.lua") dofile(path .. "/actions/actions.lua") dofile(path .. "/actions/places.lua") dofile(path .. "/actions/pathfinder.lua") +dofile(path .. "/actions/node_registry.lua") print (S("[Mod] Advanced NPC loaded")) diff --git a/npc.lua b/npc.lua index 17a7b42..96eee49 100755 --- a/npc.lua +++ b/npc.lua @@ -11,10 +11,14 @@ npc.MALE = "male" npc.INVENTORY_ITEM_MAX_STACK = 99 +npc.ANIMATION_STAND_START = 0 +npc.ANIMATION_STAND_END = 79 npc.ANIMATION_SIT_START = 81 npc.ANIMATION_SIT_END = 160 npc.ANIMATION_LAY_START = 162 npc.ANIMATION_LAY_END = 166 +npc.ANIMATION_WALK_START = 168 +npc.ANIMATION_WALK_END = 187 npc.direction = { north = 0, @@ -346,7 +350,7 @@ local function npc_spawn(self, pos) ent.places_map = {} -- Temporary initialization of actions for testing - local nodes = npc.places.find_new_nearby(ent, {"cottages:tub"}, 30) + local nodes = npc.places.find_node_nearby(ent, {"beds:bed_bottom"}, 30) minetest.log("Found nodes: "..dump(nodes)) --local path = pathfinder.find_path(ent.object:getpos(), nodes[1], 20) @@ -354,13 +358,13 @@ local function npc_spawn(self, pos) --npc.add_action(ent, npc.actions.use_door, {self = ent, pos = nodes[1], action = npc.actions.door_action.OPEN}) --npc.add_action(ent, npc.actions.stand, {self = ent}) --npc.add_action(ent, npc.actions.stand, {self = ent}) - npc.actions.walk_to_pos(ent, nodes[1]) - --npc.actions.use_bed(ent, nodes[1], npc.actions.const.beds.LAY) - --npc.add_action(ent, npc.actions.lay, {self = ent}) + npc.actions.walk_to_pos(ent, nodes[1], {"cottages:bench"}) + npc.actions.use_bed(ent, nodes[1], npc.actions.const.beds.LAY) + npc.add_action(ent, npc.actions.lay, {self = ent}) -- npc.add_action(ent, npc.actions.lay, {self = ent}) -- npc.add_action(ent, npc.actions.lay, {self = ent}) -- npc.add_action(ent, npc.actions.lay, {self = ent}) - --npc.actions.use_bed(ent, nodes[1], npc.actions.const.beds.GET_UP) + npc.actions.use_bed(ent, nodes[1], npc.actions.const.beds.GET_UP) -- npc.add_action(ent, npc.action.stand, {self = ent})