From 9556e76b791c7f21085c98e54aba6b9db6731136 Mon Sep 17 00:00:00 2001 From: zorman2000 Date: Fri, 13 Jan 2017 22:54:27 -0500 Subject: [PATCH] Actions: Add ability for NPC to fully use a furnace. Add ability to add tasks to action queue. --- actions/actions.lua | 145 ++++++++++++++++++++++++++++++++++------- actions/pathfinder.lua | 2 +- npc.lua | 74 +++++++++++++++------ 3 files changed, 177 insertions(+), 44 deletions(-) diff --git a/actions/actions.lua b/actions/actions.lua index 8a35bb5..a3519b0 100644 --- a/actions/actions.lua +++ b/actions/actions.lua @@ -93,15 +93,20 @@ end function npc.actions.walk_step(args) local self = args.self local dir = args.dir + local speed = args.speed local vel = {} + -- Set default node per seconds + if speed == nil then + speed = 0.98 + end if dir == npc.direction.north then - vel = {x=0, y=0, z=0.98} + vel = {x=0, y=0, z=speed} elseif dir == npc.direction.east then - vel = {x=0.98, y=0, z=0} + vel = {x=speed, y=0, z=0} elseif dir == npc.direction.south then - vel = {x=0, y=0, z=-0.98} + vel = {x=0, y=0, z=-speed} elseif dir == npc.direction.west then - vel = {x=-0.98, y=0, z=0} + vel = {x=-speed, y=0, z=0} end -- Rotate NPC npc.actions.rotate({self=self, dir=dir}) @@ -228,7 +233,7 @@ function npc.actions.take_item_from_external_inventory(args) if player ~= nil then inv = minetest.get_inventory({type="player", name=player}) else - inv = minetest.get_inventory({type="node", pos}) + inv = minetest.get_inventory({type="node", pos=pos}) end -- Create ItemSTack to take from external inventory local item = ItemStack(item_name.." "..count) @@ -255,7 +260,7 @@ function npc.actions.check_external_inventory_contains_item(args) if player ~= nil then inv = minetest.get_inventory({type="player", name=player}) else - inv = minetest.get_inventory({type="node", pos}) + inv = minetest.get_inventory({type="node", pos=pos}) end -- Create ItemStack for checking the external inventory @@ -315,16 +320,66 @@ end -- 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 fuels - 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) +function npc.actions.use_furnace(self, pos, item, 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) + minetest.log("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) + minetest.log("Cook: "..dump(cook_result)) + + minetest.log("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 + + minetest.log("Amount of fuel needed: "..fuel_amount) - if fuel_item ~= nil and src_item ~= nil then -- Put this item on the fuel inventory list of the furnace local args = { self = self, @@ -332,7 +387,7 @@ function npc.actions.use_furnace(self, pos, item) pos = pos, inv_list = "fuel", item_name = npc.get_item_name(fuel_item.item_string), - count = npc.get_item_count(fuel_item.item_string) + count = fuel_amount } npc.add_action(self, npc.actions.put_item_on_external_inventory, args) -- Put the item that we want to cook on the furnace @@ -342,16 +397,39 @@ function npc.actions.use_furnace(self, pos, item) pos = pos, inv_list = "src", item_name = npc.get_item_name(src_item.item_string), - count = npc.get_item_count(src_item.item_string), + count = npc.get_item_count(item), is_furnace = true } npc.add_action(self, npc.actions.put_item_on_external_inventory, args) - -- TODO: Need to add a way to calculate how many seconds will pass - -- until the furnace is done, or at least the items that we expect - -- to get (assume all items to be cooked are the ones ewe expect back) - -- Then, add that many stand actions, then an action to take the items. + -- Now, set NPC to wait until furnace is done. + minetest.log("Setting wait action for "..dump(total_cook_time)) + npc.add_action(self, npc.actions.set_interval, {self=self, interval=total_cook_time, freeze=freeze}) + -- Reset timer + npc.add_action(self, npc.actions.set_interval, {self=self, 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 + minetest.log("Adding walk to position to wandering: "..dump(pos)) + npc.add_task(self, npc.actions.walk_to_pos, {self=self, end_pos=pos, walkable={}}) + end + + -- Take cooked items back + args = { + self = self, + player = nil, + pos = pos, + inv_list = "dst", + item_name = cook_result.item:get_name(), + count = npc.get_item_count(item), + is_furnace = false + } + minetest.log("Taking item back: "..dump(pos)) + npc.add_action(self, npc.actions.take_item_from_external_inventory, args) + + minetest.log("Inventory: "..dump(self.inventory)) return true end @@ -465,9 +543,17 @@ end -- 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) +function npc.actions.walk_to_pos(args) - local start_pos = self.object:getpos() + local self = args.self + local end_pos = args.end_pos + 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 + minetest.log("Walk to pos: Using start position: "..dump(start_pos)) -- Set walkable nodes to empty if the parameter hasn't been used if walkable_nodes == nil then @@ -513,7 +599,18 @@ function npc.actions.walk_to_pos(self, end_pos, walkable_nodes) if door_opened then -- Stop to close door, this avoids misplaced movements later on - npc.add_action(self, npc.actions.stand, {self=self, dir=(dir + 2)% 4}) + 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} + npc.add_action(self, npc.actions.stand, {self=self, dir=(dir + 2)% 4, pos=pos_on_close}) -- 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/pathfinder.lua b/actions/pathfinder.lua index cb2a61f..932843d 100644 --- a/actions/pathfinder.lua +++ b/actions/pathfinder.lua @@ -201,7 +201,7 @@ end -- for the pathfinding algorithm to use function pathfinder.find_start_and_end_pos(map) -- This is for debug - --print_map(map) + print_map(map) local result = {} for z,row in pairs(map) do for x,node in pairs(row) do diff --git a/npc.lua b/npc.lua index f99aa9f..38fa90c 100755 --- a/npc.lua +++ b/npc.lua @@ -188,22 +188,52 @@ end 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} + local action_entry = {action=action, args=arguments, is_task=false} --minetest.log(dump(action_entry)) table.insert(self.actions.queue, action_entry) end +-- This function adds task actions in-place, as opposed to +-- at the end of the queue. This allows for continued order +function npc.add_task(self, task, args) + local action_entry = {action=task, args=args, is_task=true} + table.insert(self.actions.queue, action_entry) +end + -- This function removes the first action in the action queue --- and then exexcutes it +-- and then executes it function npc.execute_action(self) + local result = nil if table.getn(self.actions.queue) == 0 then -- Keep state the same if there are no more actions in actions queue return self.freeze end - minetest.log("Executing action") local action_obj = self.actions.queue[1] - local result = action_obj.action(action_obj.args) - table.remove(self.actions.queue, 1) + -- If the entry is a task, then push all this new operations in + -- stack fashion + if action_obj.is_task == true then + minetest.log("Executing task") + -- Remove from queue + table.remove(self.actions.queue, 1) + -- Backup current queue + local backup_queue = self.actions.queue + -- Clear queue + self.actions.queue = {} + -- Now, execute the task with its arguments + action_obj.action(action_obj.args) + -- After all new actions has been added by task, add the previously + -- queued actions back + for i = 1, #backup_queue do + table.insert(self.actions.queue, backup_queue[i]) + end + minetest.log("New actions queue: "..dump(self)) + else + minetest.log("Executing action") + -- Execute action as normal + result = action_obj.action(action_obj.args) + -- Remove executed action from queue + table.remove(self.actions.queue, 1) + end return result end @@ -250,6 +280,10 @@ local function choose_spawn_items(self) local currency_item_count = math.random(5, 10) npc.add_item_to_inventory(self, npc.trade.prices.currency.tier3, currency_item_count) + -- For test + npc.add_item_to_inventory(self, "default:tree", 10) + npc.add_item_to_inventory(self, "default:cobble", 10) + minetest.log("Initial inventory: "..dump(self.inventory)) end @@ -352,7 +386,7 @@ local function npc_spawn(self, pos) ent.places_map = {} -- Temporary initialization of actions for testing - local nodes = npc.places.find_sittable_nodes_nearby(ent.object:getpos(), 20) + local nodes = npc.places.find_node_nearby(ent.object:getpos(), {"default:furnace"}, 20) minetest.log("Found nodes: "..dump(nodes)) --local path = pathfinder.find_path(ent.object:getpos(), nodes[1], 20) @@ -361,14 +395,14 @@ local function npc_spawn(self, pos) --npc.add_action(ent, npc.actions.stand, {self = ent}) --npc.add_action(ent, npc.actions.stand, {self = ent}) if nodes[1] ~= nil then - npc.actions.walk_to_pos(ent, nodes[1], {}) - npc.actions.use_sittable(ent, nodes[1], npc.actions.const.sittable.SIT) - npc.add_action(ent, npc.actions.sit, {self = ent}) + npc.add_task(ent, npc.actions.walk_to_pos, {self=ent, end_pos=nodes[1], walkable={}}) + npc.actions.use_furnace(ent, nodes[1], "default:cobble 10", false) + --npc.add_action(ent, npc.actions.sit, {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_sittable(ent, nodes[1], npc.actions.const.sittable.GET_UP) - npc.add_action(ent, npc.actions.set_interval, {self=ent, interval=10, freeze=true}) + --npc.actions.use_sittable(ent, nodes[1], npc.actions.const.sittable.GET_UP) + --npc.add_action(ent, npc.actions.set_interval, {self=ent, interval=10, freeze=true}) npc.add_action(ent, npc.actions.freeze, {freeze = false}) end @@ -496,14 +530,16 @@ mobs:register_mob("advanced_npc:npc", { do_custom = function(self, dtime) -- Timer function for casual traders to reset their trade offers - self.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime - -- Check if time has come to change offers - if self.trader_data.trader_status == npc.trade.CASUAL and - self.trader_data.change_offers_timer >= self.trader_data.change_offers_timer_interval then - -- Reset timer - self.trader_data.change_offers_timer = 0 - -- Re-select casual trade offers - select_casual_trade_offers(self) + if self.trader_data ~= nil then + self.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime + -- Check if time has come to change offers + if self.trader_data.trader_status == npc.trade.CASUAL and + self.trader_data.change_offers_timer >= self.trader_data.change_offers_timer_interval then + -- Reset timer + self.trader_data.change_offers_timer = 0 + -- Re-select casual trade offers + select_casual_trade_offers(self) + end end -- Timer function for gifts