diff --git a/actions/actions.lua b/actions/actions.lua index 31e404f..0b5d80c 100644 --- a/actions/actions.lua +++ b/actions/actions.lua @@ -19,13 +19,13 @@ function npc.actions.rotate(args) local yaw = 0 self.rotate = 0 if dir == npc.direction.north then - yaw = 315 + yaw = 0 elseif dir == npc.direction.east then - yaw = 225 + yaw = (3 * math.pi) / 2 elseif dir == npc.direction.south then - yaw = 135 + yaw = math.pi elseif dir == npc.direction.west then - yaw = 45 + yaw = math.pi / 2 end self.object:setyaw(yaw) end @@ -47,7 +47,7 @@ function npc.actions.walk_step(args) vel = {x=-1, y=0, z=0} end set_animation(self, "walk") - npc.rotate({self=self, dir=dir}) + npc.actions.rotate({self=self, dir=dir}) self.object:setvelocity(vel) end @@ -83,3 +83,387 @@ function npc.actions.lay(args) 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 \ No newline at end of file diff --git a/actions/places.lua b/actions/places.lua index 1a462c7..5fa0e63 100644 --- a/actions/places.lua +++ b/actions/places.lua @@ -36,7 +36,7 @@ npc.places.PLACE_TYPE = { } -function npc.places.add(self, place_name, place_type, pos) +function npc.places.add_public(self, place_name, place_type, pos) self.places_map[place_name] = {type=place_type, pos=pos} end @@ -77,6 +77,11 @@ function npc.places.find_new_nearby(self, type, radius) local end_pos = {x=current_pos.x + radius, y=current_pos.y + 1, z=current_pos.z + radius} -- Get nodes local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type) - + + return nodes +end + +function npc.places.find_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/npc.lua b/npc.lua index b766d5e..46a2cc8 100755 --- a/npc.lua +++ b/npc.lua @@ -76,7 +76,6 @@ function npc.add_item_to_inventory(self, item_name, count) local existing_item = npc.inventory_contains(self, item_name) if existing_item ~= nil and existing_item.item_string ~= nil then -- NPC already has item. Get count and see - minetest.log("What is this? "..dump(existing_item)) local existing_count = npc.get_item_count(existing_item.item_string) if (existing_count + count) < npc.INVENTORY_ITEM_MAX_STACK then -- Set item here @@ -136,7 +135,7 @@ function npc.take_item_from_inventory(self, item_name, count) local existing_item = npc.inventory_contains(self, item_name) if existing_item ~= nil then -- Found item - local existing_count = get_item_count(existing_item.item_string) + local existing_count = npc.get_item_count(existing_item.item_string) local new_count = existing_count if existing_count - count < 0 then -- Remove item first @@ -146,7 +145,11 @@ function npc.take_item_from_inventory(self, item_name, count) return item_name.." "..tostring(new_count) else new_count = existing_count - count - self.inventory[existing_item.slot] = item_name.." "..new_count + if new_count == 0 then + self.inventory[existing_item.slot] = "" + else + self.inventory[existing_item.slot] = item_name.." "..new_count + end return item_name.." "..tostring(count) end else @@ -162,57 +165,6 @@ function npc.take_item_from_inventory_itemstring(self, item_string) npc.take_item_from_inventory(self, item_name, item_count) 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.put_item_on_external_inventory(player, pos, inv_list, item_name, count) - local inv - if player_name ~= nil then - inv = minetest.get_inventory({type="player", name=player:get_player_name()}) - else - inv = minetest.get_inventory({type="node", 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, price_stack) 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) - - return true - end - -- Not able to put on external inventory - return false -end - -function npc.take_item_from_external_inventory(player, pos, item_name, count) - local inv - if player_name ~= nil then - inv = minetest.get_inventory({type="player", name=player:get_player_name()}) - 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 - -- Dialogue functions function npc.start_dialogue(self, clicker, show_married_dialogue) @@ -233,7 +185,7 @@ function npc.add_action(self, action, arguments) self.freeze = true minetest.log("Current Pos: "..dump(self.object:getpos())) local action_entry = {action=action, args=arguments} - minetest.log(dump(action_entry)) + --minetest.log(dump(action_entry)) table.insert(self.actions.queue, action_entry) end @@ -316,7 +268,7 @@ local function npc_spawn(self, pos) local ent = self:get_luaentity() -- Set name - ent.nametag = "Kio" + ent.nametag = "" -- Set ID ent.npc_id = tostring(math.random(1000, 9999))..":"..ent.nametag @@ -394,6 +346,10 @@ local function npc_spawn(self, pos) ent.places_map = {} -- Temporary initialization of actions for testing + local nodes = npc.places.find_new_nearby(ent, {"default:furnace"}, 20) + 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.add_action(ent, npc.action.stand, {self = ent}) -- npc.add_action(ent, npc.action.stand, {self = ent}) -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) @@ -436,7 +392,8 @@ mobs:register_mob("advanced_npc:npc", { hp_min = 10, hp_max = 20, armor = 100, - collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35}, + --collisionbox = {-0.35, -1.0, -0.5, 0.35, 0.8, 0.25}, + collisionbox = {-0.35, -1.0, -0.15, 0.35, 0.8, 0.35}, visual = "mesh", mesh = "character.b3d", drawtype = "front", @@ -555,7 +512,7 @@ mobs:register_mob("advanced_npc:npc", { relationship.points = relationship.points - 1 end relationship.relationship_decrease_timer_value = 0 - minetest.log(dump(self)) + --minetest.log(dump(self)) end end end diff --git a/trade/prices.lua b/trade/prices.lua index 47b62f9..ea3b555 100644 --- a/trade/prices.lua +++ b/trade/prices.lua @@ -27,6 +27,7 @@ npc.trade.prices.table["default:stone"] = {tier = npc.trade.prices.curre npc.trade.prices.table["farming:seed_cotton"] = {tier = npc.trade.prices.currency.tier3, count = 3} npc.trade.prices.table["farming:seed_wheat"] = {tier = npc.trade.prices.currency.tier3, count = 3} npc.trade.prices.table["default:clay_lump"] = {tier = npc.trade.prices.currency.tier3, count = 3} +npc.trade.prices.table["default:wood"] = {tier = npc.trade.prices.currency.tier3, count = 3} npc.trade.prices.table["mobs:meat_raw"] = {tier = npc.trade.prices.currency.tier3, count = 4} npc.trade.prices.table["default:sapling"] = {tier = npc.trade.prices.currency.tier3, count = 5} npc.trade.prices.table["mobs:meat"] = {tier = npc.trade.prices.currency.tier3, count = 5} diff --git a/trade/trade.lua b/trade/trade.lua index 76f521e..53b9b00 100644 --- a/trade/trade.lua +++ b/trade/trade.lua @@ -18,11 +18,11 @@ npc.trade.results = { -- Casual trader NPC dialogues definition -- Casual buyer npc.trade.CASUAL_TRADE_BUY_DIALOGUE = { - text = "I'm looking to buy some items, are you interested?", + text = "I'm looking to buy some items, are you interested?", casual_trade_type = npc.trade.OFFER_BUY, responses = { [1] = { - text = "Yes, let's see what are you looking for", + text = "Yes, let's see what you are looking for", action_type = "function", response_id = 1, action = function(self, player)