From 221cfa3105d096e78a91e22c06e07467e37b6073 Mon Sep 17 00:00:00 2001 From: Hector Franqui Date: Tue, 9 Jan 2018 18:43:33 -0500 Subject: [PATCH 1/5] NPC: Add execution context. --- npc.lua | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/npc.lua b/npc.lua index 91a6f48..9ac7dbf 100755 --- a/npc.lua +++ b/npc.lua @@ -476,6 +476,8 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- relative to the last position target_pos = {} }, + -- Execution context - map of user-declared variables when executing scripts + execution_context = {} } -- This flag is checked on every step. If it is true, the rest of @@ -886,6 +888,67 @@ function npc.unlock_actions(self) npc.log("DEBUG_ACTION", "Unlocked NPC "..dump(self.npc_id).." actions") end +-------------------------------------------- +-- Execution context management functions -- +-------------------------------------------- +-- These functions manage the execution context, where variables are +-- stored, whether internal (loops) or user-created. +npc.execution_context = {} + +-- This function adds a value to the execution context. +-- Readonly defaults to false. Returns false if failed due to +-- key-name conflict, or returns true if successful +function npc.execution_context.add(self, key, value, readonly) + -- Check if variable exists + if self.actions.execution_context[key] ~= nil then + npc.log("ERROR", "Attempt to create new variable with name "..key.." failed".. + "due to variable already existing: "..dump(self.actions.execution_context[key])) + return false + end + self.actions.execution_context[key] = {value = value, readonly = readonly} + return true +end + +-- Returns the value of a given key. If not found returns nil. +function npc.execution_context.get(self, key) + local result = self.actions.execution_context[key] + if result == nil then + return nil + else + return result.value + end +end + +-- This function updates a value in the execution context. +-- Returns false if the value is read-only or if key isn't found. +-- Returns true if able to update value +function npc.execution_context.set(self, key, new_value) + local var = self.actions.execution_context[key] + if var == nil then + return false + else + if var.readonly == true then + npc.log("ERROR", "Attempt to set value of readonly variable: "..key) + return false + end + var.value = new_value + end + return true +end + +-- This function removes a variable from the execution context. +-- If the key doesn't exist, returns nil, otherwise, returns +-- the value removed. +function npc.execution_context.remove(self, key) + local result = self.actions.execution_context[key] + if result == nil then + return nil + else + self.actions.execution_context[key] = nil + return result + end +end + --------------------------------------------------------------------------------------- -- Schedule functionality --------------------------------------------------------------------------------------- From 9125aa334ae829e228f4772f5b3e104ddf874989 Mon Sep 17 00:00:00 2001 From: Hector Franqui Date: Tue, 9 Jan 2018 19:04:51 -0500 Subject: [PATCH 2/5] Actions: Replace "action" for "command". Add declarative and control commands comments and function signature (no implementation yet). --- actions/actions.lua | 2170 +++++++++++++++++++++++-------------------- 1 file changed, 1157 insertions(+), 1013 deletions(-) diff --git a/actions/actions.lua b/actions/actions.lua index 4bd735d..9183a19 100644 --- a/actions/actions.lua +++ b/actions/actions.lua @@ -1,115 +1,121 @@ --- Actions code for Advanced NPC by Zorman2000 +-- Commands code for Advanced NPC by Zorman2000 --------------------------------------------------------------------------------------- --- Action functionality +-- Command functionality --------------------------------------------------------------------------------------- --- The NPCs will be able to perform six fundamental actions that will allow +-- The NPCs will be able to perform six fundamental commands 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, +-- fundamental commands 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. +-- walk a step on specific direction. These commands will be set on an command 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 commands are added to the command queue to make a complete task for the NPC. -npc.actions = {} +npc.commands = {} -npc.actions.default_interval = 1 +npc.commands.default_interval = 1 -npc.actions.dir_data = { - -- North - [0] = { - yaw = 0, - vel = {x=0, y=0, z=1} - }, - -- East - [1] = { - yaw = (3 * math.pi) / 2, - vel = {x=1, y=0, z=0} - }, - -- South - [2] = { - yaw = math.pi, - vel = {x=0, y=0, z=-1} - }, - -- West - [3] = { - yaw = math.pi / 2, - vel = {x=-1, y=0, z=0} - }, - -- North east - [4] = { - yaw = (7 * math.pi) / 4, - vel = {x=1, y=0, z=1} - }, - -- North west - [5] = { - yaw = math.pi / 4, - vel = {x=-1, y=0, z=1} - }, - -- South east - [6] = { - yaw = (5 * math.pi) / 4, - vel = {x=1, y=0, z=-1} - }, - -- South west - [7] = { - yaw = (3 * math.pi) / 4, - vel = {x=-1, y=0, z=-1} - } +npc.commands.dir_data = { + -- North + [0] = { + yaw = 0, + vel = {x=0, y=0, z=1} + }, + -- East + [1] = { + yaw = (3 * math.pi) / 2, + vel = {x=1, y=0, z=0} + }, + -- South + [2] = { + yaw = math.pi, + vel = {x=0, y=0, z=-1} + }, + -- West + [3] = { + yaw = math.pi / 2, + vel = {x=-1, y=0, z=0} + }, + -- North east + [4] = { + yaw = (7 * math.pi) / 4, + vel = {x=1, y=0, z=1} + }, + -- North west + [5] = { + yaw = math.pi / 4, + vel = {x=-1, y=0, z=1} + }, + -- South east + [6] = { + yaw = (5 * math.pi) / 4, + vel = {x=1, y=0, z=-1} + }, + -- South west + [7] = { + yaw = (3 * math.pi) / 4, + vel = {x=-1, y=0, z=-1} + } } --- 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 - } +-- Describes commands with doors or openable nodes +npc.commands.const = { + doors = { + command = { + 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, - DIG = 15, - PLACE = 16 +npc.commands.internal_values = { + POS = "self_pos", + -- Note: The following is by mobs_redo. + STANDING_IN = "node_standing_in" } ---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 +npc.commands.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, + DIG = 15, + PLACE = 16 +} -npc.actions.take_from_inventory = "take_from_inventory" -npc.actions.take_from_inventory_forced = "take_from_inventory_forced" -npc.actions.force_place = "force_place" +--npc.commands.one_nps_speed = 0.98 +--npc.commands.one_half_nps_speed = 1.40 +--npc.commands.two_nps_speed = 1.90' +npc.commands.one_nps_speed = 1 +npc.commands.one_half_nps_speed = 1.5 +npc.commands.two_nps_speed = 2 + +npc.commands.take_from_inventory = "take_from_inventory" +npc.commands.take_from_inventory_forced = "take_from_inventory_forced" +npc.commands.force_place = "force_place" -------------- -- Executor -- @@ -120,168 +126,275 @@ npc.actions.force_place = "force_place" -- 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) - elseif command == npc.actions.cmd.DIG then - -- Call dig node action - return npc.actions.dig(self, args) - elseif command == npc.actions.cmd.PLACE then - -- Call place node action - return npc.actions.place(self, args) - end +function npc.commands.execute(self, command, args) + if command == npc.commands.cmd.SET_INTERVAL then + -- + return npc.commands.set_interval(self, args) + elseif command == npc.commands.cmd.FREEZE then + -- + return npc.commands.freeze(self, args) + elseif command == npc.commands.cmd.ROTATE then + -- + return npc.commands.rotate(self, args) + elseif command == npc.commands.cmd.WALK_STEP then + -- + return npc.commands.walk_step(self, args) + elseif command == npc.commands.cmd.STAND then + -- + return npc.commands.stand(self, args) + elseif command == npc.commands.cmd.SIT then + -- + return npc.commands.sit(self, args) + elseif command == npc.commands.cmd.LAY then + -- + return npc.commands.lay(self, args) + elseif command == npc.commands.cmd.PUT_ITEM then + -- + return npc.commands.put_item_on_external_inventory(self, args) + elseif command == npc.commands.cmd.TAKE_ITEM then + -- + return npc.commands.take_item_from_external_inventory(self, args) + elseif command == npc.commands.cmd.CHECK_ITEM then + -- + return npc.commands.check_external_inventory_contains_item(self, args) + elseif command == npc.commands.cmd.USE_OPENABLE then + -- + return npc.commands.use_openable(self, args) + elseif command == npc.commands.cmd.USE_FURNACE then + -- + return npc.commands.use_furnace(self, args) + elseif command == npc.commands.cmd.USE_BED then + -- + return npc.commands.use_bed(self, args) + elseif command == npc.commands.cmd.USE_SITTABLE then + -- Call use sittable task + return npc.commands.use_sittable(self, args) + elseif command == npc.commands.cmd.WALK_TO_POS then + -- Call walk to position task + --minetest.log("Self: "..dump(self)..", Command: "..dump(command)..", args: "..dump(args)) + return npc.commands.walk_to_pos(self, args) + elseif command == npc.commands.cmd.DIG then + -- Call dig node command + return npc.commands.dig(self, args) + elseif command == npc.commands.cmd.PLACE then + -- Call place node command + return npc.commands.place(self, args) + end end --- TODO: Thanks to executor function, all the functions for Actions and Tasks +-- TODO: Thanks to executor function, all the functions for Commands and Tasks -- should be made into private API --------------------------------------------------------------------------------------- --- Actions +-- Commands --------------------------------------------------------------------------------------- --- The following action alters the timer interval for executing actions, therefore --- making waits and pauses possible, or increase timing when some actions want to + +-------------------------- +-- Declarative commands -- +-------------------------- +-- These commands declare, assign and fetch variable values + +-- This command sets the value of a variable in the execution context. +-- If the variable doesn't exists, then it creates the variable and +-- sets its value. +-- Arguments: +-- - key: variable name +-- - value: variable value +-- Returns: Nothing +function npc.commands.set_var(self, args) + +end + +-- This command returns the value of a variable in the execution context. +-- If the variable doesn't exists, returns nil. +-- Arguments: +-- - key: variable name +-- Returns: variable value if found, nil otherwise +function npc.commands.get_var(self, args) + +end + +-- This command returns the value of an internal NPC variable. +-- These variables are the self.* variables, limited for security +-- purposes. The list of retrievable values is defined in +-- npc.commands.internal_values.* +-- Arguments: +-- - key: internal value as specified in npc.commands.internal_values.* +-- Returns: internal value +function npc.commands.get_internal_var(self, args) + local key = args.key + if key then + if key == npc.commands.internal_values.POS then + return self.object:getpos() + elseif key == npc.commands.internal_values.STANDING_IN then + return self.standing_in + end + end +end + +----------------------- +-- Control commands -- +----------------------- +-- The following command alters the timer interval for executing commands, therefore +-- making waits and pauses possible, or increase timing when some commands 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 +function npc.commands.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 + self.commands.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 +-- The following command is for allowing the rest of mobs redo API to be executed +-- after this command ends. This is useful for times when no command is needed -- and the NPC is allowed to roam freely. -function npc.actions.freeze(self, args) - local freeze_mobs_api = args.freeze - local disable_rightclick = args.disable_rightclick - if disable_rightclick ~= nil then - npc.log("INFO", "Enabling interactions for NPC "..self.npc_name..": "..dump(not(disable_rightclick))) - self.enable_rightclick_interaction = not(disable_rightclick) - end --- minetest.log("Received: "..dump(freeze_mobs_api)) --- minetest.log("Returning: "..dump(not(freeze_mobs_api))) - return not(freeze_mobs_api) +function npc.commands.freeze(self, args) + local freeze_mobs_api = args.freeze + local disable_rightclick = args.disable_rightclick + if disable_rightclick ~= nil then + npc.log("INFO", "Enabling interactions for NPC "..self.npc_name..": "..dump(not(disable_rightclick))) + self.enable_rightclick_interaction = not(disable_rightclick) + end + + return not(freeze_mobs_api) end --- This action digs the node at the given position +-- This command allows the conditional execution of two array of commands +-- depending on the evaluation of a certain condition. This is the typical +-- if-else statement of a programming language. If-else can be nested. +-- Arguments: +-- - `condition`: Lua boolean expression, any expression or value that evaluates +-- to either `true` or `false`. +-- - `true_commands`: an array of commands to be executed when the condition +-- evaluates to `true` +-- - `false_commands`: an array of commands to be executed when the condition +-- evaluates to `false` +function npc.commands.if_else(self, args) + +end + +-- This command works as the types of loops, depending on the arguments +-- given. It can work as a while, a for and a for-each loop. Loops can +-- be nested. +-- While-loop arguments: +-- - `name`: string, a key-name for this loop. Default is nil. If given, +-- it gives access to the number of times the loop has executed. +-- - `condition`: boolean, the loop will be executed as long as this condition +-- evaluates to `true` +-- - `commands`: array, array of commands to be executed during the loop +-- +-- For-loop arguments: +-- - `name`: string, a key-name for this loop. Default is `nil`. If given, it +-- gives access to the number of times this loop has executed. +-- - `initial_value`: integer, the starting value of the for-loop. If left +-- blank, default value is `1`. +-- - `condition`: boolean, the loop will be executed as long as this condition +-- evaluates to `true`. +-- - `modifier`: function, the loop will execute this modifier at the end of +-- every iteration. If left blank, default is: initial_value + 1 +-- - `commands`: array, array of commands to be executed during the loop +-- +-- Both of these loops store how many times they have been executed. To +-- access it, it is required to give pass the argument `name`. Then the +-- value will be stored on the execution context and the value retrievable +-- with `npc.commands.get_context(key)`, where `key` is the `name` argument. +-- +-- For-each-loop arguments: +-- - `name`: string, a key-name for this loop. Default is `nil`. If given, it +-- gives access to the number of times this loop has executed and the current +-- value of the array/table being evaluated. +-- - `iterable`: array or table of key-value pairs, this is an iterable array +-- or table for which the loop will execute commands at every element in the +-- iterable array/table. +-- - `commands`: array, array of commands to be executed during the loop +-- To get the current element being iterated in a for-each loop, you need to define +-- the `name` argument. Then, the value will be stored in the execution context and +-- will be retrievable with `npc.commands.get_context(key)`. It will return a table +-- like this: {loop_count = x, current_value = y} +function npc.commands.loop(self, args) + +end + + + +------------------------- +-- Interaction commands -- +------------------------- +-- This command digs the node at the given position -- If 'add_to_inventory' is true, it will put the digged node in the NPC -- inventory. -- Returns true if dig is successful, otherwise false -function npc.actions.dig(self, args) - local pos = args.pos - local add_to_inventory = args.add_to_inventory - local bypass_protection = args.bypass_protection - local play_sound = args.play_sound or true - local node = minetest.get_node_or_nil(pos) - if node then - -- Set mine animation - self.object:set_animation({ - x = npc.ANIMATION_MINE_START, - y = npc.ANIMATION_MINE_END}, - self.animation.speed_normal, 0) +function npc.commands.dig(self, args) + local pos = args.pos + local add_to_inventory = args.add_to_inventory + local bypass_protection = args.bypass_protection + local play_sound = args.play_sound or true + local node = minetest.get_node_or_nil(pos) + if node then + -- Set mine animation + self.object:set_animation({ + x = npc.ANIMATION_MINE_START, + y = npc.ANIMATION_MINE_END}, + self.animation.speed_normal, 0) - -- Play dig sound - if play_sound == true then - minetest.sound_play( - minetest.registered_nodes[node.name].sounds.dug, - { - max_hear_distance = 10, - object = self.object - } - ) - end + -- Play dig sound + if play_sound == true then + minetest.sound_play( + minetest.registered_nodes[node.name].sounds.dug, + { + max_hear_distance = 10, + object = self.object + } + ) + end - -- Check if protection not enforced - if not bypass_protection then - -- Try to dig node - if minetest.dig_node(pos) then - -- Add to inventory the node drops - if add_to_inventory then - -- Get node drop - local drop = minetest.registered_nodes[node.name].drop - local drop_itemname = node.name - if drop and drop.items then - local random_item = drop.items[math.random(1, #drop.items)] - if random_item then - drop_itemname = random_item.items[1] - end - end - -- Add to NPC inventory - npc.add_item_to_inventory(self, drop_itemname, 1) - end - --return true - return - end - else - -- Add to inventory - if add_to_inventory then - -- Get node drop - local drop = minetest.registered_nodes[node.name].drop - local drop_itemname = node.name - if drop and drop.items then - local random_item = drop.items[math.random(1, #drop.items)] - if random_item then - drop_itemname = random_item.items[1] - end - end - -- Add to NPC inventory - npc.add_item_to_inventory(self, drop_itemname, 1) - end - -- Dig node - minetest.set_node(pos, {name="air"}) - end - end - --return false + -- Check if protection not enforced + if not bypass_protection then + -- Try to dig node + if minetest.dig_node(pos) then + -- Add to inventory the node drops + if add_to_inventory then + -- Get node drop + local drop = minetest.registered_nodes[node.name].drop + local drop_itemname = node.name + if drop and drop.items then + local random_item = drop.items[math.random(1, #drop.items)] + if random_item then + drop_itemname = random_item.items[1] + end + end + -- Add to NPC inventory + npc.add_item_to_inventory(self, drop_itemname, 1) + end + --return true + return + end + else + -- Add to inventory + if add_to_inventory then + -- Get node drop + local drop = minetest.registered_nodes[node.name].drop + local drop_itemname = node.name + if drop and drop.items then + local random_item = drop.items[math.random(1, #drop.items)] + if random_item then + drop_itemname = random_item.items[1] + end + end + -- Add to NPC inventory + npc.add_item_to_inventory(self, drop_itemname, 1) + end + -- Dig node + minetest.set_node(pos, {name="air"}) + end + end + --return false end --- This action places a given node at the given position +-- This command places a given node at the given position -- There are three ways to source the node: -- 1. take_from_inventory: takes node from inventory. If not in inventory, -- node isn't placed. @@ -289,458 +402,489 @@ end -- inventory, node will be placed anyways. -- 3. force_place: places node regardless of inventory - will not touch -- the NPCs inventory -function npc.actions.place(self, args) - local pos = args.pos - local node = args.node - local source = args.source - local bypass_protection = args.bypass_protection - local play_sound = args.play_sound or true - local node_at_pos = minetest.get_node_or_nil(pos) - -- Check if position is empty or has a node that can be built to - if node_at_pos and - (node_at_pos.name == "air" or minetest.registered_nodes[node_at_pos.name].buildable_to == true) then - -- Check protection - if (not bypass_protection and not minetest.is_protected(pos, self.npc_name)) - or bypass_protection == true then - -- Take from inventory if necessary - local place_item = false - if source == npc.actions.take_from_inventory then - if npc.take_item_from_inventory(self, node, 1) then - place_item = true - end - elseif source == npc.actions.take_from_inventory_forced then - npc.take_item_from_inventory(self, node, 1) - place_item = true - elseif source == npc.actions.force_place then - place_item = true - end - -- Place node - if place_item == true then - -- Set mine animation - self.object:set_animation({ - x = npc.ANIMATION_MINE_START, - y = npc.ANIMATION_MINE_END}, - self.animation.speed_normal, 0) - -- Place node - minetest.set_node(pos, {name=node}) - -- Play place sound - if play_sound == true then - minetest.sound_play( - minetest.registered_nodes[node].sounds.place, - { - max_hear_distance = 10, - object = self.object - } - ) - end - end - end - end +function npc.commands.place(self, args) + local pos = args.pos + local node = args.node + local source = args.source + local bypass_protection = args.bypass_protection + local play_sound = args.play_sound or true + local node_at_pos = minetest.get_node_or_nil(pos) + -- Check if position is empty or has a node that can be built to + if node_at_pos and + (node_at_pos.name == "air" or minetest.registered_nodes[node_at_pos.name].buildable_to == true) then + -- Check protection + if (not bypass_protection and not minetest.is_protected(pos, self.npc_name)) + or bypass_protection == true then + -- Take from inventory if necessary + local place_item = false + if source == npc.commands.take_from_inventory then + if npc.take_item_from_inventory(self, node, 1) then + place_item = true + end + elseif source == npc.commands.take_from_inventory_forced then + npc.take_item_from_inventory(self, node, 1) + place_item = true + elseif source == npc.commands.force_place then + place_item = true + end + -- Place node + if place_item == true then + -- Set mine animation + self.object:set_animation({ + x = npc.ANIMATION_MINE_START, + y = npc.ANIMATION_MINE_END}, + self.animation.speed_normal, 0) + -- Place node + minetest.set_node(pos, {name=node}) + -- Play place sound + if play_sound == true then + minetest.sound_play( + minetest.registered_nodes[node].sounds.place, + { + max_hear_distance = 10, + object = self.object + } + ) + end + end + end + end end +-- This function allows to query for nodes and entities within a radius. +-- Parameters: +-- - query_type: string, specifies whether to query nodes or entities. +-- Default value is "node". Accepted values are: +-- - "node" +-- - "entity" +-- - position: table or string, specifies the starting position +-- for query. This should be the center of a square box. If +-- given a String, the string should be the place type. +-- Two types of tables are accepted: +-- - A simple position table, {x=1, y=1, z=1} +-- - An improved position table in this format: +-- { +-- place_category = "", +-- place_type = "", +-- index = 1, (specific index in the places map) +-- use_access_node = false|true, +-- } +-- - radius: integer, specifies the radius of the square box to search +-- around the starting position. +-- - result_type: string, specifies how to return results. Accepted +-- values are: +-- - "first": Get the first result found (default if left blank), +-- - "nearest": Get the result nearest to the NPC, +-- - "all": Return array of all results +function npc.commands.query(self, args) + +end + + + -- This function allows to move into directions that are walkable. It -- avoids fences and allows to move on plants. -- This will make for nice wanderings, making the NPC move smartly instead -- of just getting stuck at places local function random_dir_helper(start_pos, speed, dir_start, dir_end) - -- Limit the number of tries - otherwise it could become an infinite loop - for i = 1, 8 do - local dir = math.random(dir_start, dir_end) - local vel = vector.multiply(npc.actions.dir_data[dir].vel, speed) - local pos = vector.add(start_pos, vel) - local node = minetest.get_node(pos) - if node then - if node.name == "air" - -- Any walkable node except fences - or (minetest.registered_nodes[node.name].walkable == true - and minetest.registered_nodes[node.name].groups.fence ~= 1) - -- Farming plants - or minetest.registered_nodes[node.name].groups.plant == 1 then - return dir - end - end - end - -- Return -1 signaling that no good direction could be found - return -1 + -- Limit the number of tries - otherwise it could become an infinite loop + for i = 1, 8 do + local dir = math.random(dir_start, dir_end) + local vel = vector.multiply(npc.commands.dir_data[dir].vel, speed) + local pos = vector.add(start_pos, vel) + local node = minetest.get_node(pos) + if node then + if node.name == "air" + -- Any walkable node except fences + or (minetest.registered_nodes[node.name].walkable == true + and minetest.registered_nodes[node.name].groups.fence ~= 1) + -- Farming plants + or minetest.registered_nodes[node.name].groups.plant == 1 then + return dir + end + end + end + -- Return -1 signaling that no good direction could be found + return -1 end --- This action is to rotate to mob to a specifc direction. Currently, the code +-- This command 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 = args.yaw or 0 - local start_pos = args.start_pos - local end_pos = args.end_pos - -- Calculate dir if positions are given - if start_pos and end_pos and not dir then - dir = npc.actions.get_direction(start_pos, end_pos) - end - -- Only yaw was given - if yaw and not dir and not start_pos and not end_pos then - self.object:setyaw(yaw) - return - end +function npc.commands.rotate(self, args) + local dir = args.dir + local yaw = args.yaw or 0 + local start_pos = args.start_pos + local end_pos = args.end_pos + -- Calculate dir if positions are given + if start_pos and end_pos and not dir then + dir = npc.commands.get_direction(start_pos, end_pos) + end + -- Only yaw was given + if yaw and not dir and not start_pos and not end_pos then + self.object:setyaw(yaw) + return + end - 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) + 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 +-- 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 step_into_air_only = args.step_into_air_only - local speed = args.speed - local target_pos = args.target_pos - local start_pos = args.start_pos - local vel = {} +function npc.commands.walk_step(self, args) + local dir = args.dir + local step_into_air_only = args.step_into_air_only + local speed = args.speed + local target_pos = args.target_pos + local start_pos = args.start_pos + local vel = {} - -- Set default node per seconds - if speed == nil then - speed = npc.actions.one_nps_speed - end + -- Set default node per seconds + if speed == nil then + speed = npc.commands.one_nps_speed + end - -- Check if dir should be random - if dir == "random_all" or dir == "random" then - dir = random_dir_helper(start_pos, speed, 0, 7) - end - if dir == "random_orthogonal" then - dir = random_dir_helper(start_pos, speed, 0, 3) - end + -- Check if dir should be random + if dir == "random_all" or dir == "random" then + dir = random_dir_helper(start_pos, speed, 0, 7) + end + if dir == "random_orthogonal" then + dir = random_dir_helper(start_pos, speed, 0, 3) + end - 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 } - else - -- No direction provided or NPC is trapped, don't move NPC - vel = {x=0, y=0, z=0} - end + 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 } + else + -- No direction provided or NPC is trapped, don't move NPC + vel = {x=0, y=0, z=0} + end - -- If there is a target position to reach, set it and set walking to true - if target_pos ~= nil then - self.actions.walking.target_pos = target_pos - -- Set is_walking = true - self.actions.walking.is_walking = true - end + -- If there is a target position to reach, set it and set walking to true + if target_pos ~= nil then + self.commands.walking.target_pos = target_pos + -- Set is_walking = true + self.commands.walking.is_walking = true + 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) + -- Rotate NPC + npc.commands.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 = false - 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) +-- This command makes the NPC stand and remain like that +function npc.commands.stand(self, args) + local pos = args.pos + local dir = args.dir + -- Set is_walking = false + self.commands.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.commands.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) +-- This command makes the NPC sit on the node where it is +function npc.commands.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.commands.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) +-- This command makes the NPC lay on the node where it is +function npc.commands.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 +-- 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 +function npc.commands.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) + -- 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 + -- 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 + 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 +function npc.commands.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 +function npc.commands.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) + -- 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, pos, npc_dir) - --minetest.log("Node name: "..dump(node.name)) - local state = npc.actions.const.doors.state.CLOSED - -- Check for MTG doors and gates - local mtg_door_closed = false - if minetest.get_item_group(node.name, "door") > 0 then - local back_pos = vector.add(pos, minetest.facedir_to_dir(node.param2)) - local back_node = minetest.get_node(back_pos) - if back_node.name == "air" or minetest.registered_nodes[back_node.name].walkable == false then - mtg_door_closed = true - end - end - -- 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 mtg_door_closed == false and open_i1 == nil and half_door_is_closed == false then - state = npc.actions.const.doors.state.OPEN - end - --minetest.log("Door state: "..dump(state)) - return state +function npc.commands.get_openable_node_state(node, pos, npc_dir) + --minetest.log("Node name: "..dump(node.name)) + local state = npc.commands.const.doors.state.CLOSED + -- Check for MTG doors and gates + local mtg_door_closed = false + if minetest.get_item_group(node.name, "door") > 0 then + local back_pos = vector.add(pos, minetest.facedir_to_dir(node.param2)) + local back_node = minetest.get_node(back_pos) + if back_node.name == "air" or minetest.registered_nodes[back_node.name].walkable == false then + mtg_door_closed = true + end + end + -- 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 mtg_door_closed == false and open_i1 == nil and half_door_is_closed == false then + state = npc.commands.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, pos, dir) +-- default doors API, and the cottages mod gates and doors. +function npc.commands.use_openable(self, args) + local pos = args.pos + local command = args.command + local dir = args.dir + local node = minetest.get_node(pos) + local state = npc.commands.get_openable_node_state(node, pos, dir) - local clicker = self.object - if action ~= state then - minetest.registered_nodes[node.name].on_rightclick(pos, node, clicker, nil, nil) - end + local clicker = self.object + if command ~= 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 +-- Tasks are operations that require many commands 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, use_access_node) - --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_category = "", - -- place_type = "", - -- index = 1, - -- use_access_node = false|true, - -- try_alternative_if_used = true|false - -- } - local index = pos.index or 1 - local use_access_node = pos.use_access_node or false - local try_alternative_if_used = pos.try_alternative_if_used or false - local places = npc.places.get_by_type(self, pos.place_type) - --minetest.log("Place type: "..dump(pos.place_type)) - --minetest.log("Places: "..dump(places)) - -- Check index is valid on the places map - if #places >= index then - local place = places[index] - -- Check if place is used, and if it is, find alternative if required - if try_alternative_if_used == true then - place = npc.places.find_unused_place(self, pos.place_category, pos.place_type, place) + --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_category = "", + -- place_type = "", + -- index = 1, + -- use_access_node = false|true, + -- try_alternative_if_used = true|false + -- } + local index = pos.index or 1 + local use_access_node = pos.use_access_node or false + local try_alternative_if_used = pos.try_alternative_if_used or false + local places = npc.places.get_by_type(self, pos.place_type) + --minetest.log("Place type: "..dump(pos.place_type)) + --minetest.log("Places: "..dump(places)) + -- Check index is valid on the places map + if #places >= index then + local place = places[index] + -- Check if place is used, and if it is, find alternative if required + if try_alternative_if_used == true then + place = npc.places.find_unused_place(self, pos.place_category, pos.place_type, place) - --minetest.log("Mark as used? "..dump(pos.mark_target_as_used)) - if pos.mark_target_as_used == true then - --minetest.log("Marking as used: "..minetest.pos_to_string(place.pos)) - npc.places.mark_place_used(place.pos, npc.places.USE_STATE.USED) - end + --minetest.log("Mark as used? "..dump(pos.mark_target_as_used)) + if pos.mark_target_as_used == true then + --minetest.log("Marking as used: "..minetest.pos_to_string(place.pos)) + npc.places.mark_place_used(place.pos, npc.places.USE_STATE.USED) + end - npc.places.add_shared_accessible_place( - self, {owner="", node_pos=place.pos}, npc.places.PLACE_TYPE.CALCULATED.TARGET, true, {}) - end - -- Check if access node is desired - if use_access_node == true then - -- Return actual node pos - return place.access_node, place.pos - else - -- Return node pos that allows access to node - return place.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, false) - -- 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 - if use_access_node == true then - return places_pos[i].access_node, places_pos[i].pos - else - return places_pos[i].pos - end - end - end - end - -- Return the first position only if it couldn't find an owned - -- place, or if it there is only one - if use_access_node == true then - return places_pos[1].access_node, places_pos[1].pos - else - return places_pos[1].pos - end - end + npc.places.add_shared_accessible_place( + self, {owner="", node_pos=place.pos}, npc.places.PLACE_TYPE.CALCULATED.TARGET, true, {}) + end + -- Check if access node is desired + if use_access_node == true then + -- Return actual node pos + return place.access_node, place.pos + else + -- Return node pos that allows access to node + return place.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, false) + -- 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 + if use_access_node == true then + return places_pos[i].access_node, places_pos[i].pos + else + return places_pos[i].pos + end + end + end + end + -- Return the first position only if it couldn't find an owned + -- place, or if it there is only one + if use_access_node == true then + return places_pos[1].access_node, places_pos[1].pos + else + return places_pos[1].pos + end + end end -- This function allows a NPC to use a furnace using only items from @@ -748,313 +892,313 @@ 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, 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 +function npc.commands.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 enable_usage_marking = args.enable_usage_marking or true - 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"} + local enable_usage_marking = args.enable_usage_marking or true + 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 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]) + -- 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)) + 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)) + -- 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)) + 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 + -- 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 - -- Set furnace as used if flag is enabled - if enable_usage_marking then - -- Set place as used - npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) - end + -- Set furnace as used if flag is enabled + if enable_usage_marking then + -- Set place as used + npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) + end - -- Calculate how much fuel is needed - local fuel_amount = total_cook_time / fuel_time - if fuel_amount < 1 then - fuel_amount = 1 - 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) + 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) + -- 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.commands.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.commands.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}) + -- Now, set NPC to wait until furnace is done. + npc.log("DEBUG", "Setting wait command for "..dump(total_cook_time)) + npc.add_action(self, npc.commands.cmd.SET_INTERVAL, {interval=total_cook_time, freeze=freeze}) - -- Reset timer - npc.add_action(self, npc.actions.cmd.SET_INTERVAL, {interval=1, freeze=true}) + -- Reset timer + npc.add_action(self, npc.commands.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 + -- 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.commands.cmd.WALK_TO_POS, {end_pos=pos, walkable={}}) + end - -- Take cooked items back - args = { + -- 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) + 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.commands.cmd.TAKE_ITEM, args) - npc.log("DEBUG", "Inventory: "..dump(self.inventory)) + npc.log("DEBUG", "Inventory: "..dump(self.inventory)) - -- Set furnace as unused if flag is enabled - if enable_usage_marking then - -- Set place as used - npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) - end + -- Set furnace as unused if flag is enabled + if enable_usage_marking then + -- Set place as used + npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) + end - return true - end - end - -- Couldn't use the furnace due to lack of items - return false + 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 enable_usage_marking = args.enable_usage_marking or true - local node = minetest.get_node(pos) - --minetest.log(dump(node)) - local dir = minetest.facedir_to_dir(node.param2) +-- pos is the location of the bed, command can be lay or get up +function npc.commands.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 command = args.command + local enable_usage_marking = args.enable_usage_marking or true + 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 - -- Error here due to ignore. Need to come up with better solution - if node.name == "ignore" then - return - end - 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, {}) - if enable_usage_marking then - -- Set place as used - npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) - end - self.actions.move_state.is_laying = true - else - -- Calculate position to get up - -- Error here due to ignore. Need to come up with better solution - if node.name == "ignore" then - return - end - 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 + if command == npc.commands.const.beds.LAY then + -- Get position + -- Error here due to ignore. Need to come up with better solution + if node.name == "ignore" then + return + end + local bed_pos = npc.commands.nodes.beds[node.name].get_lay_pos(pos, dir) + -- Sit down on bed, rotate to correct direction + npc.add_action(self, npc.commands.cmd.SIT, {pos=bed_pos, dir=(node.param2 + 2) % 4}) + -- Lay down + npc.add_action(self, npc.commands.cmd.LAY, {}) + if enable_usage_marking then + -- Set place as used + npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) + end + self.commands.move_state.is_laying = true + else + -- Calculate position to get up + -- Error here due to ignore. Need to come up with better solution + if node.name == "ignore" then + return + end + local bed_pos_y = npc.commands.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.commands.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.commands.nodes.beds[node.name].type == "mat" then + y_adjustment = 0 + end - local pos_out_of_bed = pos - local empty_nodes = npc.places.find_node_orthogonally(bed_pos, {"air", "cottages:bench"}, y_adjustment) - if empty_nodes ~= nil and #empty_nodes > 0 then - -- Get direction to the empty node - dir = npc.actions.get_direction(bed_pos, empty_nodes[1].pos) + local pos_out_of_bed = pos + local empty_nodes = npc.places.find_node_orthogonally(bed_pos, {"air", "cottages:bench"}, y_adjustment) + if empty_nodes ~= nil and #empty_nodes > 0 then + -- Get direction to the empty node + dir = npc.commands.get_direction(bed_pos, empty_nodes[1].pos) - -- Calculate position to get out of bed - 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 + -- Calculate position to get out of bed + 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 - end - -- Stand out of bed - npc.add_action(self, npc.actions.cmd.STAND, {pos=pos_out_of_bed, dir=dir}) - if enable_usage_marking then - -- Set place as unused - npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) - end - self.actions.move_state.is_laying = false - end + end + -- Stand out of bed + npc.add_action(self, npc.commands.cmd.STAND, {pos=pos_out_of_bed, dir=dir}) + if enable_usage_marking then + -- Set place as unused + npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) + end + self.commands.move_state.is_laying = false + 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 enable_usage_marking = args.enable_usage_marking or true - local node = minetest.get_node(pos) +-- pos is the location of the bed, command can be lay or get up +function npc.commands.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 command = args.command + local enable_usage_marking = args.enable_usage_marking or true + local node = minetest.get_node(pos) - if action == npc.actions.const.sittable.SIT then - -- Calculate position depending on bench - -- Error here due to ignore. Need to come up with better solution - if node.name == "ignore" then - return - end - 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}) - if enable_usage_marking then - -- Set place as used - npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) - end - self.actions.move_state.is_sitting = true - else - if self.actions.move_state.is_sitting == false then - npc.log("DEBUG_ACTION", "NPC "..self.npc_name.." attempted to get up from sit when it is not sitting.") - return - end - -- 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}) - minetest.log("Setting sittable at "..minetest.pos_to_string(pos).." as not used") - if enable_usage_marking then - -- Set place as unused - npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) - end - self.actions.move_state.is_sitting = false - end + if command == npc.commands.const.sittable.SIT then + -- Calculate position depending on bench + -- Error here due to ignore. Need to come up with better solution + if node.name == "ignore" then + return + end + local sit_pos = npc.commands.nodes.sittable[node.name].get_sit_pos(pos, node.param2) + -- Sit down on bench/chair/stairs + npc.add_action(self, npc.commands.cmd.SIT, {pos=sit_pos, dir=(node.param2 + 2) % 4}) + if enable_usage_marking then + -- Set place as used + npc.places.mark_place_used(pos, npc.places.USE_STATE.USED) + end + self.commands.move_state.is_sitting = true + else + if self.commands.move_state.is_sitting == false then + npc.log("DEBUG_ACTION", "NPC "..self.npc_name.." attempted to get up from sit when it is not sitting.") + return + end + -- 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.commands.get_direction: "..dump(npc.commands.get_direction)) + --minetest.log("Pos: "..dump(pos)) + -- Get direction to the empty node + dir = npc.commands.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.commands.cmd.STAND, {pos=pos_out_of_sittable, dir=dir}) + minetest.log("Setting sittable at "..minetest.pos_to_string(pos).." as not used") + if enable_usage_marking then + -- Set place as unused + npc.places.mark_place_used(pos, npc.places.USE_STATE.NOT_USED) + end + self.commands.move_state.is_sitting = false + end end -- This function returns the direction enum -- for the moving from v1 to v2 -function npc.actions.get_direction(v1, v2) - local vector_dir = vector.direction(v1, v2) - local dir = vector.round(vector_dir) +function npc.commands.get_direction(v1, v2) + local vector_dir = vector.direction(v1, v2) + local dir = vector.round(vector_dir) - 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 + 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 @@ -1063,133 +1207,133 @@ 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, args) - -- Get arguments for this task - local use_access_node = args.use_access_node or true - local end_pos, node_pos = get_pos_argument(self, args.end_pos, use_access_node) - 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 +function npc.commands.walk_to_pos(self, args) + -- Get arguments for this task + local use_access_node = args.use_access_node or true + local end_pos, node_pos = get_pos_argument(self, args.end_pos, use_access_node) + 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()) - 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)) + -- Round start_pos to make sure it can find start and end + local start_pos = vector.round(self.object:getpos()) + 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)) - -- Check if start_pos and end_pos are the same - if vector.equals(start_pos, end_pos) == true then - -- Check if it was using access node, if it was, add action to - -- rotate NPC into that direction - if use_access_node == true then - local dir = npc.actions.get_direction(end_pos, node_pos) - npc.add_action(self, npc.actions.cmd.STAND, {dir = dir}) - end - npc.log("WARNING", "walk_to_pos Found start_pos == end_pos") - return - end + -- Check if start_pos and end_pos are the same + if vector.equals(start_pos, end_pos) == true then + -- Check if it was using access node, if it was, add command to + -- rotate NPC into that direction + if use_access_node == true then + local dir = npc.commands.get_direction(end_pos, node_pos) + npc.add_action(self, npc.commands.cmd.STAND, {dir = dir}) + end + npc.log("WARNING", "walk_to_pos Found start_pos == end_pos") + return + end - -- Set walkable nodes to empty if the parameter hasn't been used - if walkable_nodes == nil then - walkable_nodes = {} - end + -- 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) + -- 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 + 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.commands.walking.path = path - -- Local variables - local door_opened = false - local speed = npc.actions.two_nps_speed + -- Local variables + local door_opened = false + local speed = npc.commands.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 command timer interval to half second. This is to account for + -- the increased speed when walking. + npc.add_action(self, npc.commands.cmd.SET_INTERVAL, {interval=0.5, freeze=true}) - -- Set the initial last and target positions - self.actions.walking.target_pos = path[1].pos + -- Set the initial last and target positions + self.commands.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 - if use_access_node == true then - dir = npc.actions.get_direction(end_pos, node_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.commands.get_direction(path[i].pos, end_pos) + -- Add the last step + npc.add_action(self, npc.commands.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+1].pos}) + -- Add stand animation at end + if use_access_node == true then + dir = npc.commands.get_direction(end_pos, node_pos) end minetest.log("Dir: "..dump(dir)) - -- Change dir if using access_node - 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, path[i+1].pos, 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}) + -- Change dir if using access_node + npc.add_action(self, npc.commands.cmd.STAND, {dir = dir}) + break + end + -- Get direction to move from path[i] to path[i+1] + local dir = npc.commands.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.commands.get_openable_node_state(node, path[i+1].pos, dir) == npc.commands.const.doors.state.CLOSED then + --minetest.log("Opening command to open door") + -- Stop to open door, this avoids misplaced movements later on + npc.add_action(self, npc.commands.cmd.STAND, {dir=dir}) + -- Open door + npc.add_action(self, npc.commands.cmd.USE_OPENABLE, {pos=path[i+1].pos, dir=dir, command=npc.commands.const.doors.command.OPEN}) - door_opened = true - end + door_opened = true + end - 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}) + -- Add walk command to command queue + npc.add_action(self, npc.commands.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}) + 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.commands.cmd.WALK_STEP, {dir = dir, speed = speed, target_pos = path[i+2].pos}) + -- Stop to close the door + npc.add_action(self, npc.commands.cmd.STAND, {dir=(dir + 2) % 4 })--, pos=pos_on_close}) + -- Close door + npc.add_action(self, npc.commands.cmd.USE_OPENABLE, {pos=path[i+1].pos, command=npc.commands.const.doors.command.CLOSE}) - door_opened = false - end + door_opened = false + end - 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}) + -- Return the command interval to default interval of 1 second + -- By default, always freeze. + npc.add_action(self, npc.commands.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 + 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 \ No newline at end of file From ff82f83c0ba434c6bbe20fb0855503ba3667cac4 Mon Sep 17 00:00:00 2001 From: Hector Franqui Date: Tue, 9 Jan 2018 20:04:20 -0500 Subject: [PATCH 3/5] NPC: Add execution environment Actions: Rename 'actions' to 'command' Add skeleton method for declarative and control commands --- ...ates_to_avoid_laying_sitting_bug__WIP_.xml | 4 + .../shelved.patch | 84 ++ .../Update_building_type_definitions_.xml | 4 + .../shelved.patch | 23 + .idea/workspace.xml | 1229 +++++++++++++++++ doc/new_api.md | 90 ++ npc.lua | 1 + 7 files changed, 1435 insertions(+) create mode 100644 .idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_.xml create mode 100644 .idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_/shelved.patch create mode 100644 .idea/shelf/Update_building_type_definitions_.xml create mode 100644 .idea/shelf/Update_building_type_definitions_/shelved.patch create mode 100644 .idea/workspace.xml create mode 100644 doc/new_api.md diff --git a/.idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_.xml b/.idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_.xml new file mode 100644 index 0000000..abe74e4 --- /dev/null +++ b/.idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_/shelved.patch b/.idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_/shelved.patch new file mode 100644 index 0000000..446ccc0 --- /dev/null +++ b/.idea/shelf/Add_move_states_to_avoid_laying_sitting_bug__WIP_/shelved.patch @@ -0,0 +1,84 @@ +Index: npc.lua +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>-- Advanced NPC by Zorman2000\n-- Based on original NPC by Tenplus1\n\nlocal S = mobs.intllib\n\nnpc = {}\n\n-- Constants\nnpc.FEMALE = \"female\"\nnpc.MALE = \"male\"\n\nnpc.age = {\n\tadult = \"adult\",\n\tchild = \"child\"\n}\n\nnpc.INVENTORY_ITEM_MAX_STACK = 99\n\nnpc.ANIMATION_STAND_START = 0\nnpc.ANIMATION_STAND_END = 79\nnpc.ANIMATION_SIT_START = 81\nnpc.ANIMATION_SIT_END = 160\nnpc.ANIMATION_LAY_START = 162\nnpc.ANIMATION_LAY_END = 166\nnpc.ANIMATION_WALK_START = 168\nnpc.ANIMATION_WALK_END = 187\nnpc.ANIMATION_MINE_START = 189\nnpc.ANIMATION_MINE_END =198\n\nnpc.direction = {\n\tnorth = 0,\n\teast = 1,\n\tsouth = 2,\n\twest = 3,\n\tnorth_east = 4,\n\tnorth_west = 5,\n\tsouth_east = 6,\n\tsouth_west = 7\n}\n\nnpc.action_state = {\n\tnone = 0,\n\texecuting = 1,\n\tinterrupted = 2\n}\n\nnpc.log_level = {\n\tINFO = true,\n\tWARNING = true,\n\tERROR = true,\n\tDEBUG = false,\n\tDEBUG_ACTION = false,\n\tDEBUG_SCHEDULE = false\n}\n\nnpc.texture_check = {\n\ttimer = 0,\n\tinterval = 2\n}\n\n---------------------------------------------------------------------------------------\n-- General functions\n---------------------------------------------------------------------------------------\n-- Logging\nfunction npc.log(level, message)\n\tif npc.log_level[level] then\n\t\tminetest.log(\"[advanced_npc] \"..level..\": \"..message)\n\tend\nend\n\n-- NPC chat\nfunction npc.chat(npc_name, player_name, message)\n\tminetest.chat_send_player(player_name, npc_name..\": \"..message)\nend\n\n-- Simple wrapper over minetest.add_particle()\n-- Copied from mobs_redo/api.lua\nfunction npc.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow)\n\n\tradius = radius or 2\n\tmin_size = min_size or 0.5\n\tmax_size = max_size or 1\n\tgravity = gravity or -10\n\tglow = glow or 0\n\n\tminetest.add_particlespawner({\n\t\tamount = amount,\n\t\ttime = 0.25,\n\t\tminpos = pos,\n\t\tmaxpos = pos,\n\t\tminvel = {x = -radius, y = -radius, z = -radius},\n\t\tmaxvel = {x = radius, y = radius, z = radius},\n\t\tminacc = {x = 0, y = gravity, z = 0},\n\t\tmaxacc = {x = 0, y = gravity, z = 0},\n\t\tminexptime = 0.1,\n\t\tmaxexptime = 1,\n\t\tminsize = min_size,\n\t\tmaxsize = max_size,\n\t\ttexture = texture,\n\t\tglow = glow,\n\t})\nend\n\n-- Gets name of player or NPC\nfunction npc.get_entity_name(entity)\n\tif entity:is_player() then\n\t\treturn entity:get_player_name()\n\telse\n\t\treturn entity:get_luaentity().name\n\tend\nend\n\n-- Returns the item \"wielded\" by player or NPC\n-- TODO: Implement NPC\nfunction npc.get_entity_wielded_item(entity)\n\tif entity:is_player() then\n\t\treturn entity:get_wielded_item()\n\tend\nend\n\n---------------------------------------------------------------------------------------\n-- Spawning functions\n---------------------------------------------------------------------------------------\n-- These functions are used at spawn time to determine several\n-- random attributes for the NPC in case they are not already\n-- defined. On a later phase, pre-defining many of the NPC values\n-- will be allowed.\n\nlocal function get_random_name(sex)\n\tlocal i = math.random(#npc.data.FIRST_NAMES[sex])\n\treturn npc.data.FIRST_NAMES[sex][i]\nend\n\nlocal function initialize_inventory()\n\treturn {\n\t\t[1] = \"\", [2] = \"\", [3] = \"\", [4] = \"\",\n\t\t[5] = \"\", [6] = \"\", [7] = \"\", [8] = \"\",\n\t\t[9] = \"\", [10] = \"\", [11] = \"\", [12] = \"\",\n\t\t[13] = \"\", [14] = \"\", [15] = \"\", [16] = \"\",\n\t}\nend\n\n-- This function checks for \"female\" text on the texture name\nlocal function is_female_texture(textures)\n\tfor i = 1, #textures do\n\t\tif string.find(textures[i], \"female\") ~= nil then\n\t\t\treturn true\n\t\tend\n\tend\n\treturn false\nend\n\nfunction npc.assign_sex_from_texture(self)\n\tif is_female_texture(self.base_texture) then\n\t\treturn npc.FEMALE\n\telse\n\t\treturn npc.MALE\n\tend\nend\n\nlocal function get_random_texture(sex, age)\n\tlocal textures = {}\n\tlocal filtered_textures = {}\n\t-- Find textures by sex and age\n\tif age == npc.age.adult then\n\t\t--minetest.log(\"Registered: \"..dump(minetest.registered_entities[\"advanced_npc:npc\"]))\n\t\ttextures = minetest.registered_entities[\"advanced_npc:npc\"].texture_list\n\telseif age == npc.age.child then\n\t\ttextures = minetest.registered_entities[\"advanced_npc:npc\"].child_texture\n\tend\n\n\tfor i = 1, #textures do\n\t\tlocal current_texture = textures[i][1]\n\t\tif (sex == npc.MALE\n\t\t\t\tand string.find(current_texture, sex)\n\t\t\t\tand not string.find(current_texture, npc.FEMALE))\n\t\t\t\tor (sex == npc.FEMALE\n\t\t\t\tand string.find(current_texture, sex)) then\n\t\t\ttable.insert(filtered_textures, current_texture)\n\t\tend\n\tend\n\n\t-- Check if filtered textures is empty\n\tif filtered_textures == {} then\n\t\treturn textures[1][1]\n\tend\n\n\treturn filtered_textures[math.random(1,#filtered_textures)]\nend\n\nfunction npc.get_random_texture_from_array(age, sex, textures)\n\tlocal filtered_textures = {}\n\n\tfor i = 1, #textures do\n\t\tlocal current_texture = textures[i]\n\t\t-- Filter by age\n\t\tif (sex == npc.MALE\n\t\t\t\tand string.find(current_texture, sex)\n\t\t\t\tand not string.find(current_texture, npc.FEMALE)\n\t\t\t\tand ((age == npc.age.adult\n\t\t\t\tand not string.find(current_texture, npc.age.child))\n\t\t\t\tor (age == npc.age.child\n\t\t\t\tand string.find(current_texture, npc.age.child))\n\t\t)\n\t\t)\n\t\t\t\tor (sex == npc.FEMALE\n\t\t\t\tand string.find(current_texture, sex)\n\t\t\t\tand ((age == npc.age.adult\n\t\t\t\tand not string.find(current_texture, npc.age.child))\n\t\t\t\tor (age == npc.age.child\n\t\t\t\tand string.find(current_texture, npc.age.child))\n\t\t)\n\t\t) then\n\t\t\ttable.insert(filtered_textures, current_texture)\n\t\tend\n\tend\n\n\t-- Check if there are no textures\n\tif #filtered_textures == 0 then\n\t\t-- Return whole array for re-evaluation\n\t\tnpc.log(\"DEBUG\", \"No textures found, returning original array\")\n\t\treturn textures\n\tend\n\n\treturn filtered_textures[math.random(1, #filtered_textures)]\nend\n\n-- Choose whether NPC can have relationships. Only 30% of NPCs\n-- cannot have relationships\nlocal function can_have_relationships(is_child)\n\t-- Children can't have relationships\n\tif is_child then\n\t\treturn false\n\tend\n\tlocal chance = math.random(1,10)\n\treturn chance > 3\nend\n\n-- Choose a maximum of two items that the NPC will have at spawn time\n-- These items are chosen from the favorite items list.\nlocal function choose_spawn_items(self)\n\tlocal number_of_items_to_add = math.random(1, 2)\n\tlocal number_of_items = #npc.FAVORITE_ITEMS[self.sex].phase1\n\n\tfor i = 1, number_of_items_to_add do\n\t\tnpc.add_item_to_inventory(\n\t\t\tself,\n\t\t\tnpc.FAVORITE_ITEMS[self.sex].phase1[math.random(1, number_of_items)].item,\n\t\t\tmath.random(1,5)\n\t\t)\n\tend\n\t-- Add currency to the items spawned with. Will add 5-10 tier 3\n\t-- currency items\n\tlocal currency_item_count = math.random(5, 10)\n\tnpc.add_item_to_inventory(self, npc.trade.prices.currency.tier3.string, currency_item_count)\n\n\t-- For test\n\t--npc.add_item_to_inventory(self, \"default:tree\", 10)\n\t--npc.add_item_to_inventory(self, \"default:cobble\", 10)\n\t--npc.add_item_to_inventory(self, \"default:diamond\", 2)\n\t--npc.add_item_to_inventory(self, \"default:mese_crystal\", 2)\n\t--npc.add_item_to_inventory(self, \"flowers:rose\", 2)\n\t--npc.add_item_to_inventory(self, \"advanced_npc:marriage_ring\", 2)\n\t--npc.add_item_to_inventory(self, \"flowers:geranium\", 2)\n\t--npc.add_item_to_inventory(self, \"mobs:meat\", 2)\n\t--npc.add_item_to_inventory(self, \"mobs:leather\", 2)\n\t--npc.add_item_to_inventory(self, \"default:sword_stone\", 2)\n\t--npc.add_item_to_inventory(self, \"default:shovel_stone\", 2)\n\t--npc.add_item_to_inventory(self, \"default:axe_stone\", 2)\n\n\t--minetest.log(\"Initial inventory: \"..dump(self.inventory))\nend\n\n-- Spawn function. Initializes all variables that the\n-- NPC will have and choose random, starting values\nfunction npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name)\n\tnpc.log(\"INFO\", \"Initializing NPC at \"..minetest.pos_to_string(pos))\n\n\t-- Get variables\n\tlocal ent = entity\n\tif not is_lua_entity then\n\t\tent = entity:get_luaentity()\n\tend\n\n\t-- Avoid NPC to be removed by mobs_redo API\n\tent.remove_ok = false\n\n\t-- Determine sex and age\n\t-- If there's no previous NPC data, sex and age will be randomly chosen.\n\t-- - Sex: Female or male will have each 50% of spawning\n\t-- - Age: 90% chance of spawning adults, 10% chance of spawning children.\n\t-- If there is previous data then:\n\t-- - Sex: The unbalanced sex will get a 75% chance of spawning\n\t-- - Example: If there's one male, then female will have 75% spawn chance.\n\t-- - If there's male and female, then each have 50% spawn chance.\n\t-- - Age: For each two adults, the chance of spawning a child next will be 50%\n\t-- If there's a child for two adults, the chance of spawning a child goes to\n\t-- 40% and keeps decreasing unless two adults have no child.\n\t-- Use NPC stats if provided\n\tif npc_stats then\n\t\t-- Default chances\n\t\tlocal male_s, male_e = 0, 50\n\t\tlocal female_s, female_e = 51, 100\n\t\tlocal adult_s, adult_e = 0, 85\n\t\tlocal child_s, child_e = 86, 100\n\t\t-- Determine sex probabilities\n\t\tif npc_stats[npc.FEMALE].total > npc_stats[npc.MALE].total then\n\t\t\tmale_e = 75\n\t\t\tfemale_s, female_e = 76, 100\n\t\telseif npc_stats[npc.FEMALE].total < npc_stats[npc.MALE].total then\n\t\t\tmale_e = 25\n\t\t\tfemale_s, female_e = 26, 100\n\t\tend\n\t\t-- Determine age probabilities\n\t\tif npc_stats[\"adult_total\"] >= 2 then\n\t\t\tif npc_stats[\"adult_total\"] % 2 == 0\n\t\t\t\t\tand (npc_stats[\"adult_total\"] / 2 > npc_stats[\"child_total\"]) then\n\t\t\t\tchild_s,child_e = 26, 100\n\t\t\t\tadult_e = 25\n\t\t\telse\n\t\t\t\tchild_s, child_e = 61, 100\n\t\t\t\tadult_e = 60\n\t\t\tend\n\t\tend\n\t\t-- Get sex and age based on the probabilities\n\t\tlocal sex_chance = math.random(1, 100)\n\t\tlocal age_chance = math.random(1, 100)\n\t\tlocal selected_sex = \"\"\n\t\tlocal selected_age = \"\"\n\t\t-- Select sex\n\t\tif male_s <= sex_chance and sex_chance <= male_e then\n\t\t\tselected_sex = npc.MALE\n\t\telseif female_s <= sex_chance and sex_chance <= female_e then\n\t\t\tselected_sex = npc.FEMALE\n\t\tend\n\t\t-- Set sex for NPC\n\t\tent.sex = selected_sex\n\t\t-- Select age\n\t\tif adult_s <= age_chance and age_chance <= adult_e then\n\t\t\tselected_age = npc.age.adult\n\t\telseif child_s <= age_chance and age_chance <= child_e then\n\t\t\tselected_age = npc.age.child\n\t\t\tent.visual_size = {\n\t\t\t\tx = 0.65,\n\t\t\t\ty = 0.65\n\t\t\t}\n\t\t\tent.collisionbox = {-0.10,-0.50,-0.10, 0.10,0.40,0.10}\n\t\t\tent.is_child = true\n\t\t\t-- For mobs_redo\n\t\t\tent.child = true\n\t\tend\n\t\t-- Store the selected age\n\t\tent.age = selected_age\n\n\t\t-- Set texture accordingly\n\t\tlocal selected_texture = get_random_texture(selected_sex, selected_age)\n\t\t--minetest.log(\"Selected texture: \"..dump(selected_texture))\n\t\t-- Store selected texture due to the need to restore it later\n\t\tent.selected_texture = selected_texture\n\t\t-- Set texture and base texture\n\t\tent.textures = {selected_texture}\n\t\tent.base_texture = {selected_texture}\n\telse\n\t\t-- Get sex based on texture. This is a 50% chance for\n\t\t-- each sex as there's same amount of textures for male and female.\n\t\t-- Do not spawn child as first NPC\n\t\tent.sex = npc.assign_sex_from_texture(ent)\n\t\tent.age = npc.age.adult\n\tend\n\n\t-- Initialize all gift data\n\tent.gift_data = {\n\t\t-- Choose favorite items. Choose phase1 per default\n\t\tfavorite_items = npc.relationships.select_random_favorite_items(ent.sex, \"phase1\"),\n\t\t-- Choose disliked items. Choose phase1 per default\n\t\tdisliked_items = npc.relationships.select_random_disliked_items(ent.sex),\n\t\t-- Enable/disable gift item hints dialogue lines\n\t\tenable_gift_items_hints = true\n\t}\n\n\t-- Flag that determines if NPC can have a relationship\n\tent.can_have_relationship = can_have_relationships(ent.is_child)\n\n\t--ent.infotext = \"Interested in relationships: \"..dump(ent.can_have_relationship)\n\n\t-- Flag to determine if NPC can receive gifts\n\tent.can_receive_gifts = ent.can_have_relationship\n\n\t-- Initialize relationships object\n\tent.relationships = {}\n\n\t-- Determines if NPC is married or not\n\tent.is_married_to = nil\n\n\t-- Initialize dialogues\n\tent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent, \"phase1\")\n\n\t-- Declare NPC inventory\n\tent.inventory = initialize_inventory()\n\n\t-- Choose items to spawn with\n\tchoose_spawn_items(ent)\n\n\t-- Flags: generic booleans or functions that help drive functionality\n\tent.flags = {}\n\n\t-- Declare trade data\n\tent.trader_data = {\n\t\t-- Type of trader\n\t\ttrader_status = npc.trade.get_random_trade_status(),\n\t\t-- Current buy offers\n\t\tbuy_offers = {},\n\t\t-- Current sell offers\n\t\tsell_offers = {},\n\t\t-- Items to buy change timer\n\t\tchange_offers_timer = 0,\n\t\t-- Items to buy change timer interval\n\t\tchange_offers_timer_interval = 60,\n\t\t-- Trading list: a list of item names the trader is expected to trade in.\n\t\t-- It is mostly related to its occupation.\n\t\t-- If empty, the NPC will revert to casual trading\n\t\t-- If not, it will try to sell those that it have, and buy the ones it not.\n\t\ttrade_list = {},\n\t\t-- Custom trade allows to specify more than one payment\n\t\t-- and a custom prompt (instead of the usual buy or sell prompts)\n\t\tcustom_trades = {}\n\t}\n\n\t-- Initialize trading offers for NPC\n\t--npc.trade.generate_trade_offers_by_status(ent)\n\t-- if ent.trader_data.trader_status == npc.trade.CASUAL then\n\t-- select_casual_trade_offers(ent)\n\t-- end\n\n\t-- Actions data\n\tent.actions = {\n\t\t-- The queue is a queue of actions to be performed on each interval\n\t\tqueue = {},\n\t\t-- Current value of the action timer\n\t\taction_timer = 0,\n\t\t-- Determines the interval for each action in the action queue\n\t\t-- Default is 1. This can be changed via actions\n\t\taction_interval = npc.actions.default_interval,\n\t\t-- Avoid the execution of the action timer\n\t\taction_timer_lock = false,\n\t\t-- Defines the state of the current action\n\t\tcurrent_action_state = npc.action_state.none,\n\t\t-- Store information about action on state before lock\n\t\tstate_before_lock = {\n\t\t\t-- State of the mobs_redo API\n\t\t\tfreeze = false,\n\t\t\t-- State of execution\n\t\t\taction_state = npc.action_state.none,\n\t\t\t-- Action executed while on lock\n\t\t\tinterrupted_action = {}\n\t\t},\n\t\t-- Walking variables -- required for implementing accurate movement code\n\t\twalking = {\n\t\t\t-- Defines whether NPC is walking to specific position or not\n\t\t\tis_walking = false,\n\t\t\t-- Path that the NPC is following\n\t\t\tpath = {},\n\t\t\t-- Target position the NPC is supposed to walk to in this step. NOTE:\n\t\t\t-- This is NOT the end of the path, but the next position in the path\n\t\t\t-- relative to the last position\n\t\t\ttarget_pos = {}\n\t\t}\n\t}\n\n\t-- This flag is checked on every step. If it is true, the rest of\n\t-- Mobs Redo API is not executed\n\tent.freeze = nil\n\n\t-- This map will hold all the places for the NPC\n\t-- Map entries should be like: \"bed\" = {x=1, y=1, z=1}\n\tent.places_map = {}\n\n\t-- Schedule data\n\tent.schedules = {\n\t\t-- Flag to enable or disable the schedules functionality\n\t\tenabled = true,\n\t\t-- Lock for when executing a schedule\n\t\tlock = false,\n\t\t-- Queue of schedules executed\n\t\t-- Used to calculate dependencies\n\t\ttemp_executed_queue = {},\n\t\t-- An array of schedules, meant to be one per day at some point\n\t\t-- when calendars are implemented. Allows for only 7 schedules,\n\t\t-- one for each day of the week\n\t\tgeneric = {},\n\t\t-- An array of schedules, meant to be for specific dates in the\n\t\t-- year. Can contain as many as possible. The keys will be strings\n\t\t-- in the format MM:DD\n\t\tdate_based = {},\n\t\t-- The following holds the check parameters provided by the\n\t\t-- current schedule\n\t\tcurrent_check_params = {}\n\t}\n\n\t-- If occupation name given, override properties with\n\t-- occupation values and initialize schedules\n\tif occupation_name and occupation_name ~= \"\" and ent.age == npc.age.adult then\n\t\t-- Set occupation name\n\t\tent.occupation_name = occupation_name\n\t\t-- Override relevant values\n\t\tnpc.occupations.initialize_occupation_values(ent, occupation_name)\n\tend\n\n\t-- Nametag is initialized to blank\n\tent.nametag = \"\"\n\n\t-- Set name\n\tent.npc_name = get_random_name(ent.sex)\n\n\t-- Set ID\n\tent.npc_id = tostring(math.random(1000, 9999))..\":\"..ent.npc_name\n\n\t-- TODO: Remove this - do inside occupation\n\t-- Dedicated trade test\n--\tent.trader_data.trade_list = {\n--\t\t[\"default:tree\"] = {},\n--\t\t[\"default:cobble\"] = {},\n--\t\t[\"default:wood\"] = {},\n--\t\t[\"default:diamond\"] = {},\n--\t\t[\"default:mese_crystal\"] = {},\n--\t\t[\"flowers:rose\"] = {},\n--\t\t[\"advanced_npc:marriage_ring\"] = {},\n--\t\t[\"flowers:geranium\"] = {},\n--\t\t[\"mobs:meat\"] = {},\n--\t\t[\"mobs:leather\"] = {},\n--\t\t[\"default:sword_stone\"] = {},\n--\t\t[\"default:shovel_stone\"] = {},\n--\t\t[\"default:axe_stone\"] = {}\n--\t}\n\n\t-- Generate trade offers\n\tnpc.trade.generate_trade_offers_by_status(ent)\n\n\t-- Add a custom trade offer\n\t-- local offer1 = npc.trade.create_custom_sell_trade_offer(\"Do you want me to fix your steel sword?\", \"Fix steel sword\", \"Fix steel sword\", \"default:sword_steel\", {\"default:sword_steel\", \"default:iron_lump 5\"})\n\t-- table.insert(ent.trader_data.custom_trades, offer1)\n\t--local offer2 = npc.trade.create_custom_sell_trade_offer(\"Do you want me to fix your mese sword?\", \"Fix mese sword\", \"Fix mese sword\", \"default:sword_mese\", {\"default:sword_mese\", \"default:copper_lump 10\"})\n\t--table.insert(ent.trader_data.custom_trades, offer2)\n\n\t-- Set initialized flag on\n\tent.initialized = true\n\t--npc.log(\"WARNING\", \"Spawned entity: \"..dump(ent))\n\tnpc.log(\"INFO\", \"Successfully initialized NPC with name \"..dump(ent.npc_name)\n\t\t\t..\", sex: \"..ent.sex..\", is child: \"..dump(ent.is_child)\n\t\t\t..\", texture: \"..dump(ent.textures))\n\t-- Refreshes entity\n\tent.object:set_properties(ent)\nend\n\n---------------------------------------------------------------------------------------\n-- Trading functions\n---------------------------------------------------------------------------------------\nfunction npc.generate_trade_list_from_inventory(self)\n\tlocal list = {}\n\tfor i = 1, #self.inventory do\n\t\tlist[npc.get_item_name(self.inventory[i])] = {}\n\tend\n\tself.trader_data.trade_list = list\nend\n\nfunction npc.set_trading_status(self, status)\n\t--minetest.log(\"Trader_data: \"..dump(self.trader_data))\n\t-- Set status\n\tself.trader_data.trader_status = status\n\t-- Re-generate trade offers\n\tnpc.trade.generate_trade_offers_by_status(self)\nend\n\n---------------------------------------------------------------------------------------\n-- Inventory functions\n---------------------------------------------------------------------------------------\n-- NPCs inventories are restrained to 16 slots.\n-- Each slot can hold one item up to 99 count.\n\n-- Utility function to get item name from a string\nfunction npc.get_item_name(item_string)\n\treturn ItemStack(item_string):get_name()\nend\n\n-- Utility function to get item count from a string\nfunction npc.get_item_count(item_string)\n\treturn ItemStack(item_string):get_count()\nend\n\n-- Add an item to inventory. Returns true if add successful\n-- These function can be used to give items to other NPCs\n-- given that the \"self\" variable can be any NPC\nfunction npc.add_item_to_inventory(self, item_name, count)\n\t-- Check if NPC already has item\n\tlocal existing_item = npc.inventory_contains(self, item_name)\n\tif existing_item ~= nil and existing_item.item_string ~= nil then\n\t\t-- NPC already has item. Get count and see\n\t\tlocal existing_count = npc.get_item_count(existing_item.item_string)\n\t\tif (existing_count + count) < npc.INVENTORY_ITEM_MAX_STACK then\n\t\t\t-- Set item here\n\t\t\tself.inventory[existing_item.slot] =\n\t\t\tnpc.get_item_name(existing_item.item_string)..\" \"..tostring(existing_count + count)\n\t\t\treturn true\n\t\telse\n\t\t\t--Find next free slot\n\t\t\tfor i = 1, #self.inventory do\n\t\t\t\tif self.inventory[i] == \"\" then\n\t\t\t\t\t-- Found slot, set item\n\t\t\t\t\tself.inventory[i] =\n\t\t\t\t\titem_name..\" \"..tostring((existing_count + count) - npc.INVENTORY_ITEM_MAX_STACK)\n\t\t\t\t\treturn true\n\t\t\t\tend\n\t\t\tend\n\t\t\t-- No free slot found\n\t\t\treturn false\n\t\tend\n\telse\n\t\t-- Find a free slot\n\t\tfor i = 1, #self.inventory do\n\t\t\tif self.inventory[i] == \"\" then\n\t\t\t\t-- Found slot, set item\n\t\t\t\tself.inventory[i] = item_name..\" \"..tostring(count)\n\t\t\t\treturn true\n\t\t\tend\n\t\tend\n\t\t-- No empty slot found\n\t\treturn false\n\tend\nend\n\n-- Same add method but with itemstring for convenience\nfunction npc.add_item_to_inventory_itemstring(self, item_string)\n\tlocal item_name = npc.get_item_name(item_string)\n\tlocal item_count = npc.get_item_count(item_string)\n\tnpc.add_item_to_inventory(self, item_name, item_count)\nend\n\n-- Checks if an item is contained in the inventory. Returns\n-- the item string or nil if not found\nfunction npc.inventory_contains(self, item_name)\n\tfor key,value in pairs(self.inventory) do\n\t\tif value ~= \"\" and string.find(value, item_name) then\n\t\t\treturn {slot=key, item_string=value}\n\t\tend\n\tend\n\t-- Item not found\n\treturn nil\nend\n\n-- Removes the item from an NPC inventory and returns the item\n-- with its count (as a string, e.g. \"default:apple 2\"). Returns\n-- nil if unable to get the item.\nfunction npc.take_item_from_inventory(self, item_name, count)\n\tlocal existing_item = npc.inventory_contains(self, item_name)\n\tif existing_item ~= nil then\n\t\t-- Found item\n\t\tlocal existing_count = npc.get_item_count(existing_item.item_string)\n\t\tlocal new_count = existing_count\n\t\tif existing_count - count < 0 then\n\t\t\t-- Remove item first\n\t\t\tself.inventory[existing_item.slot] = \"\"\n\t\t\t-- TODO: Support for retrieving from next stack. Too complicated\n\t\t\t-- and honestly might be unecessary.\n\t\t\treturn item_name..\" \"..tostring(new_count)\n\t\telse\n\t\t\tnew_count = existing_count - count\n\t\t\tif new_count == 0 then\n\t\t\t\tself.inventory[existing_item.slot] = \"\"\n\t\t\telse\n\t\t\t\tself.inventory[existing_item.slot] = item_name..\" \"..new_count\n\t\t\tend\n\t\t\treturn item_name..\" \"..tostring(count)\n\t\tend\n\telse\n\t\t-- Not able to take item because not found\n\t\treturn nil\n\tend\nend\n\n-- Same take method but with itemstring for convenience\nfunction npc.take_item_from_inventory_itemstring(self, item_string)\n\tlocal item_name = npc.get_item_name(item_string)\n\tlocal item_count = npc.get_item_count(item_string)\n\tnpc.take_item_from_inventory(self, item_name, item_count)\nend\n\n---------------------------------------------------------------------------------------\n-- Flag functionality\n---------------------------------------------------------------------------------------\n-- TODO: Consider removing them as they are pretty simple and straight forward.\n-- Generic variables or function that help drive some functionality for the NPC.\nfunction npc.add_flag(self, flag_name, value)\n\tself.flags[flag_name] = value\nend\n\nfunction npc.update_flag(self, flag_name, value)\n\tself.flags[flag_name] = value\nend\n\nfunction npc.get_flag(self, flag_name)\n\treturn self.flags[flag_name]\nend\n\n---------------------------------------------------------------------------------------\n-- Dialogue functionality\n---------------------------------------------------------------------------------------\nfunction npc.start_dialogue(self, clicker, show_married_dialogue)\n\n\t-- Call dialogue function as normal\n\tnpc.dialogue.start_dialogue(self, clicker, show_married_dialogue)\n\n\t-- Check and update relationship if needed\n\tnpc.relationships.dialogue_relationship_update(self, clicker)\n\nend\n\n---------------------------------------------------------------------------------------\n-- Action functionality\n---------------------------------------------------------------------------------------\n-- This function adds a function to the action queue.\n-- Actions should be added in strict order for tasks to work as expected.\nfunction npc.add_action(self, action, arguments)\n\tlocal action_entry = {action=action, args=arguments, is_task=false}\n\ttable.insert(self.actions.queue, action_entry)\nend\n\n-- This function adds task actions in-place, as opposed to\n-- at the end of the queue. This allows for continued order\nfunction npc.add_task(self, task, args)\n\tlocal action_entry = {action=task, args=args, is_task=true}\n\ttable.insert(self.actions.queue, action_entry)\nend\n\n-- This function removes the first action in the action queue\n-- and then executes it\nfunction npc.execute_action(self)\n\tnpc.log(\"DEBUG_ACTION\", \"Current actions queue: \"..dump(self.actions.queue))\n\t-- Check if an action was interrupted\n\tif self.actions.current_action_state == npc.action_state.interrupted then\n\t\tnpc.log(\"DEBUG_ACTION\", \"Re-inserting interrupted action for NPC: '\"..dump(self.npc_name)..\"': \"..dump(self.actions.state_before_lock.interrupted_action))\n\t\t-- Insert into queue the interrupted action\n\t\ttable.insert(self.actions.queue, 1, self.actions.state_before_lock.interrupted_action)\n\t\t-- Clear the action\n\t\tself.actions.state_before_lock.interrupted_action = {}\n\t\t-- Clear the position\n\t\tself.actions.state_before_lock.pos = {}\n\tend\n\tlocal result = nil\n\tif table.getn(self.actions.queue) == 0 then\n\t\t-- Set state to none\n\t\tself.actions.current_action_state = npc.action_state.none\n\t\t-- Keep state the same if there are no more actions in actions queue\n\t\treturn self.freeze\n\tend\n\tlocal action_obj = self.actions.queue[1]\n\t-- Check if action is null\n\tif action_obj.action == nil then\n\t\treturn\n\tend\n\t-- Check if action is an schedule check\n\tif action_obj.action == \"schedule_check\" then\n\t\t-- Remove table entry\n\t\ttable.remove(self.actions.queue, 1)\n\t\t-- Execute schedule check\n\t\tnpc.schedule_check(self)\n\t\t-- Return\n\t\treturn false\n\tend\n\t-- If the entry is a task, then push all this new operations in\n\t-- stack fashion\n\tif action_obj.is_task == true then\n\t\tnpc.log(\"DEBUG_ACTION\", \"Executing task for NPC '\"..dump(self.npc_name)..\"': \"..dump(action_obj))\n\t\t-- Backup current queue\n\t\tlocal backup_queue = self.actions.queue\n\t\t-- Remove this \"task\" action from queue\n\t\ttable.remove(self.actions.queue, 1)\n\t\t-- Clear queue\n\t\tself.actions.queue = {}\n\t\t-- Now, execute the task with its arguments\n\t\tresult = npc.actions.execute(self, action_obj.action, action_obj.args)\n\t\t--result = action_obj.action(self, action_obj.args)\n\t\t-- After all new actions has been added by task, add the previously\n\t\t-- queued actions back\n\t\tfor i = 1, #backup_queue do\n\t\t\ttable.insert(self.actions.queue, backup_queue[i])\n\t\tend\n\telse\n\t\tnpc.log(\"DEBUG_ACTION\", \"Executing action for NPC '\"..dump(self.npc_name)..\"': \"..dump(action_obj))\n\t\t-- Store the action that is being executed\n\t\tself.actions.state_before_lock.interrupted_action = action_obj\n\t\t-- Store current position\n\t\tself.actions.state_before_lock.pos = self.object:getpos()\n\t\t-- Execute action as normal\n\t\tresult = npc.actions.execute(self, action_obj.action, action_obj.args)\n\t\t-- Remove task\n\t\ttable.remove(self.actions.queue, 1)\n\t\t-- Set state\n\t\tself.actions.current_action_state = npc.action_state.executing\n\tend\n\treturn result\nend\n\nfunction npc.lock_actions(self)\n\n\t-- Avoid re-locking if already locked\n\tif self.actions.action_timer_lock == true then\n\t\treturn\n\tend\n\n\tlocal pos = self.object:getpos()\n\n\tif self.freeze == false then\n\t\t-- Round current pos to avoid the NPC being stopped on positions\n\t\t-- where later on can't walk to the correct positions\n\t\t-- Choose which position is to be taken as start position\n\t\tif self.actions.state_before_lock.pos ~= {} then\n\t\t\tpos = vector.round(self.actions.state_before_lock.pos)\n\t\telse\n\t\t\tpos = vector.round(self.object:getpos())\n\t\tend\n\t\tpos.y = self.object:getpos().y\n\tend\n\t-- Stop NPC\n\tnpc.actions.execute(self, npc.actions.cmd.STAND, {pos=pos})\n\t-- Avoid all timer execution\n\tself.actions.action_timer_lock = true\n\t-- Reset timer so that it has some time after interaction is done\n\tself.actions.action_timer = 0\n\t-- Check if there are is an action executing\n\tif self.actions.current_action_state == npc.action_state.executing\n\t\t\tand self.freeze == false then\n\t\t-- Store the current action state\n\t\tself.actions.state_before_lock.action_state = self.actions.current_action_state\n\t\t-- Set current action state to interrupted\n\t\tself.actions.current_action_state = npc.action_state.interrupted\n\tend\n\t-- Store the current freeze variable\n\tself.actions.state_before_lock.freeze = self.freeze\n\t-- Freeze mobs_redo API\n\tself.freeze = false\n\n\tnpc.log(\"DEBUG_ACTION\", \"Locking NPC \"..dump(self.npc_id)..\" actions\")\nend\n\nfunction npc.unlock_actions(self)\n\t-- Allow timers to execute\n\tself.actions.action_timer_lock = false\n\t-- Restore the value of self.freeze\n\tself.freeze = self.actions.state_before_lock.freeze\n\n\tif table.getn(self.actions.queue) == 0 then\n\t\t-- Allow mobs_redo API to execute since action queue is empty\n\t\tself.freeze = true\n\tend\n\n\tnpc.log(\"DEBUG_ACTION\", \"Unlocked NPC \"..dump(self.npc_id)..\" actions\")\nend\n\n---------------------------------------------------------------------------------------\n-- Schedule functionality\n---------------------------------------------------------------------------------------\n-- Schedules allow the NPC to do different things depending on the time of the day.\n-- The time of the day is in 24 hours and is consistent with the Minetest Game\n-- /time command. Hours will be written as numbers: 1 for 1:00, 13 for 13:00 or 1:00 PM\n-- The API is as following: a schedule can be created for a specific date or for a\n-- day of the week. A date is a string in the format MM:DD\nnpc.schedule_types = {\n\t[\"generic\"] = \"generic\",\n\t[\"date_based\"] = \"date_based\"\n}\n\nnpc.schedule_properties = {\n\tput_item = \"put_item\",\n\tput_multiple_items = \"put_multiple_items\",\n\ttake_item = \"take_item\",\n\ttrader_status = \"trader_status\",\n\tcan_receive_gifts = \"can_receive_gifts\",\n\tflag = \"flag\",\n\tenable_gift_items_hints = \"enable_gift_items_hints\",\n\tset_trade_list = \"set_trade_list\"\n}\n\nlocal function get_time_in_hours()\n\treturn minetest.get_timeofday() * 24\nend\n\n-- Create a schedule on a NPC.\n-- Schedule types:\n-- - Generic: Returns nil if there are already\n-- seven schedules, one for each day of the\n-- week or if the schedule attempting to add\n-- already exists. The date parameter is the\n-- day of the week it represents as follows:\n-- - 1: Monday\n-- - 2: Tuesday\n-- - 3: Wednesday\n-- - 4: Thursday\n-- - 5: Friday\n-- - 6: Saturday\n-- - 7: Sunday\n-- - Date-based: The date parameter should be a\n-- string of the format \"MM:DD\". If it already\n-- exists, function retuns nil\nfunction npc.create_schedule(self, schedule_type, date)\n\tif schedule_type == npc.schedule_types.generic then\n\t\t-- Check that there are no more than 7 schedules\n\t\tif #self.schedules.generic == 7 then\n\t\t\t-- Unable to add schedule\n\t\t\treturn nil\n\t\telseif #self.schedules.generic < 7 then\n\t\t\t-- Check schedule doesn't exists already\n\t\t\tif self.schedules.generic[date] == nil then\n\t\t\t\t-- Add schedule\n\t\t\t\tself.schedules.generic[date] = {}\n\t\t\telse\n\t\t\t\t-- Schedule already present\n\t\t\t\treturn nil\n\t\t\tend\n\t\tend\n\telseif schedule_type == npc.schedule_types.date then\n\t\t-- Check schedule doesn't exists already\n\t\tif self.schedules.date_based[date] == nil then\n\t\t\t-- Add schedule\n\t\t\tself.schedules.date_based[date] = {}\n\t\telse\n\t\t\t-- Schedule already present\n\t\t\treturn nil\n\t\tend\n\tend\nend\n\nfunction npc.delete_schedule(self, schedule_type, date)\n\t-- Delete schedule by setting entry to nil\n\tself.schedules[schedule_type][date] = nil\nend\n\n-- Schedule entries API\n-- Allows to add, get, update and delete entries from each\n-- schedule. Attempts to be as safe-fail as possible to avoid crashes.\n\n-- Actions is an array of actions and tasks that the NPC\n-- will perform at the scheduled time on the scheduled date\nfunction npc.add_schedule_entry(self, schedule_type, date, time, check, actions)\n\t-- Check that schedule for date exists\n\tif self.schedules[schedule_type][date] ~= nil then\n\t\t-- Add schedule entry\n\t\tif check == nil then\n\t\t\tself.schedules[schedule_type][date][time] = actions\n\t\telse\n\t\t\tself.schedules[schedule_type][date][time].check = check\n\t\tend\n\telse\n\t\t-- No schedule found, need to be created for date\n\t\treturn nil\n\tend\nend\n\nfunction npc.get_schedule_entry(self, schedule_type, date, time)\n\t-- Check if schedule for date exists\n\tif self.schedules[schedule_type][date] ~= nil then\n\t\t-- Return schedule\n\t\treturn self.schedules[schedule_type][date][time]\n\telse\n\t\t-- Schedule for date not found\n\t\treturn nil\n\tend\nend\n\nfunction npc.update_schedule_entry(self, schedule_type, date, time, check, actions)\n\t-- Check schedule for date exists\n\tif self.schedules[schedule_type][date] ~= nil then\n\t\t-- Check that a schedule entry for that time exists\n\t\tif self.schedules[schedule_type][date][time] ~= nil then\n\t\t\t-- Set the new actions\n\t\t\tif check == nil then\n\t\t\t\tself.schedules[schedule_type][date][time] = actions\n\t\t\telse\n\t\t\t\tself.schedules[schedule_type][date][time].check = check\n\t\t\tend\n\t\telse\n\t\t\t-- Schedule not found for specified time\n\t\t\treturn nil\n\t\tend\n\telse\n\t\t-- Schedule not found for date\n\t\treturn nil\n\tend\nend\n\nfunction npc.delete_schedule_entry(self, schedule_type, date, time)\n\t-- Check schedule for date exists\n\tif self.schedules[schedule_type][date] ~= nil then\n\t\t-- Remove schedule entry by setting to nil\n\t\tself.schedules[schedule_type][date][time] = nil\n\telse\n\t\t-- Schedule not found for date\n\t\treturn nil\n\tend\nend\n\nfunction npc.schedule_change_property(self, property, args)\n\tif property == npc.schedule_properties.trader_status then\n\t\t-- Get status from args\n\t\tlocal status = args.status\n\t\t-- Set status to NPC\n\t\tnpc.set_trading_status(self, status)\n\telseif property == npc.schedule_properties.put_item then\n\t\tlocal itemstring = args.itemstring\n\t\t-- Add item\n\t\tnpc.add_item_to_inventory_itemstring(self, itemstring)\n\telseif property == npc.schedule_properties.put_multiple_items then\n\t\tlocal itemlist = args.itemlist\n\t\tfor i = 1, #itemlist do\n\t\t\tlocal itemlist_entry = itemlist[i]\n\t\t\tlocal current_itemstring = itemlist[i].name\n\t\t\tif itemlist_entry.random == true then\n\t\t\t\tcurrent_itemstring = current_itemstring\n\t\t\t\t\t\t..\" \"..dump(math.random(itemlist_entry.min, itemlist_entry.max))\n\t\t\telse\n\t\t\t\tcurrent_itemstring = current_itemstring..\" \"..tostring(itemlist_entry.count)\n\t\t\tend\n\t\t\t-- Add item to inventory\n\t\t\tnpc.add_item_to_inventory_itemstring(self, current_itemstring)\n\t\tend\n\telseif property == npc.schedule_properties.take_item then\n\t\tlocal itemstring = args.itemstring\n\t\t-- Add item\n\t\tnpc.take_item_from_inventory_itemstring(self, itemstring)\n\telseif property == npc.schedule_properties.can_receive_gifts then\n\t\tlocal value = args.can_receive_gifts\n\t\t-- Set status\n\t\tself.can_receive_gifts = value\n\telseif property == npc.schedule_properties.flag then\n\t\tlocal action = args.action\n\t\tif action == \"set\" then\n\t\t\t-- Adds or overwrites an existing flag and sets it to the given value\n\t\t\tself.flags[args.flag_name] = args.flag_value\n\t\telseif action == \"reset\" then\n\t\t\t-- Sets value of flag to false or to 0\n\t\t\tlocal flag_type = type(self.flags[args.flag_name])\n\t\t\tif flag_type == \"number\" then\n\t\t\t\tself.flags[args.flag_name] = 0\n\t\t\telseif flag_type == \"boolean\" then\n\t\t\t\tself.flags[args.flag_name] = false\n\t\t\tend\n\t\tend\n\telseif property == npc.schedule_properties.enable_gift_item_hints then\n\t\tself.gift_data.enable_gift_items_hints = args.value\n\telseif property == npc.schedule_properties.set_trade_list then\n\t\t-- Insert items\n\t\tfor i = 1, #args.items do\n\t\t\t-- Insert entry into trade list\n\t\t\tself.trader_data.trade_list[args.items[i].name] = {\n\t\t\t\tmax_item_buy_count = args.items[i].buy,\n\t\t\t\tmax_item_sell_count = args.items[i].sell,\n\t\t\t\tamount_to_keep = args.items[i].keep\n\t\t\t}\n\n\t\tend\n \tend\nend\n\nfunction npc.add_schedule_check(self)\n\ttable.insert(self.actions.queue, {action=\"schedule_check\", args={}, is_task=false})\nend\n\nfunction npc.enqueue_schedule_action(self, entry)\n\tif entry.task ~= nil then\n\t\t-- Add task\n\t\tnpc.add_task(self, entry.task, entry.args)\n\telseif entry.action ~= nil then\n\t\t-- Add action\n\t\tnpc.add_action(self, entry.action, entry.args)\n\telseif entry.property ~= nil then\n\t\t-- Change NPC property\n\t\tnpc.schedule_change_property(self, entry.property, entry.args)\n\tend\nend\n\n-- Range: integer, radius in which nodes will be searched. Recommended radius is\n--\t\t between 1-3\n-- Nodes: array of node names\n-- Actions: map of node names to entries {action=, args={}}.\n--\t\t\tArguments can be empty - the check function will try to determine most\n--\t\t\targuments anyways (like pos and dir).\n--\t\t\tSpecial node \"any\" will execute those actions on any node except the\n--\t\t\talready specified ones.\n-- None-action: array of entries {action=, args={}}.\n--\t\t\t\tWill be executed when no node is found.\nfunction npc.schedule_check(self)\n\tnpc.log(\"DEBUG_SCHEDULE\", \"Prev Actions queue: \"..dump(self.actions.queue))\n\tlocal range = self.schedules.current_check_params.range\n\tlocal walkable_nodes = self.schedules.current_check_params.walkable_nodes\n\tlocal nodes = self.schedules.current_check_params.nodes\n\tlocal actions = self.schedules.current_check_params.actions\n\tlocal none_actions = self.schedules.current_check_params.none_actions\n\t-- Get NPC position\n\tlocal start_pos = self.object:getpos()\n\t-- Search nodes\n\tlocal found_nodes = npc.places.find_node_nearby(start_pos, nodes, range)\n\t-- Check if any node was found\n\tnpc.log(\"DEBUG_SCHEDULE\", \"Found nodes using radius: \"..dump(found_nodes))\n\tif found_nodes and #found_nodes > 0 then\n\t\tlocal node_pos\n\t\tlocal node\n\t\t-- Check if there is preference to act on nodes already acted upon\n\t\tif self.schedules.current_check_params.prefer_last_acted_upon_node == true then\n\t\t\t-- Find a node other than the acted upon - try 3 times\n\t\t\tfor i = 1, #found_nodes do\n\t\t\t\tnode_pos = found_nodes[i]\n\t\t\t\t-- Get node info\n\t\t\t\tnode = minetest.get_node(node_pos)\n\t\t\t\tif node.name == self.schedules.current_check_params.last_node_acted_upon then\n\t\t\t\t\tbreak\n\t\t\t\tend\n\t\t\tend\n\t\telse\n\t\t\t-- Pick a random node to act upon\n\t\t\tnode_pos = found_nodes[math.random(1, #found_nodes)]\n\t\t\t-- Get node info\n\t\t\tnode = minetest.get_node(node_pos)\n\t\tend\n\t\t-- Save this node as the last acted upon\n\t\tself.schedules.current_check_params.last_node_acted_upon = node.name\n\t\t-- Set node as a place\n\t\t-- Note: Code below isn't *adding* a node, but overwriting the\n\t\t-- place with \"schedule_target_pos\" place type\n\t\tnpc.log(\"DEBUG_SCHEDULE\", \"Found \"..dump(node.name)..\" at pos: \"..minetest.pos_to_string(node_pos))\n\t\tnpc.places.add_shared_accessible_place(\n\t\t\tself, {owner=\"\", node_pos=node_pos}, npc.places.PLACE_TYPE.SCHEDULE.TARGET, true, walkable_nodes)\n\t\t-- Get actions related to node and enqueue them\n\t\tfor i = 1, #actions[node.name] do\n\t\t\tlocal args = {}\n\t\t\tlocal action\n\t\t\t-- Calculate arguments for the following supported actions:\n\t\t\t-- - Dig\n\t\t\t-- - Place\n\t\t\t-- - Walk step\n\t\t\t-- - Walk to position\n\t\t\t-- - Use furnace\n\t\t\tif actions[node.name][i].action == npc.actions.cmd.DIG then\n\t\t\t\t-- Defaults: items will be added to inventory if not specified\n\t\t\t\t-- otherwise, and protection will be respected, if not specified\n\t\t\t\t-- otherwise\n\t\t\t\targs = {\n\t\t\t\t\tpos = node_pos,\n\t\t\t\t\tadd_to_inventory = actions[node.name][i].args.add_to_inventory or true,\n\t\t\t\t\tbypass_protection = actions[node.name][i].args.bypass_protection or false\n\t\t\t\t}\n\t\t\t\tnpc.add_action(self, actions[node.name][i].action, args)\n\t\t\telseif actions[node.name][i].action == npc.actions.cmd.PLACE then\n\t\t\t\t-- Position: providing node_pos is because the currently planned\n\t\t\t\t-- behavior for placing nodes is replacing digged nodes. A NPC farmer,\n\t\t\t\t-- for instance, might dig a plant node and plant another one on the\n\t\t\t\t-- same position.\n\t\t\t\t-- Defaults: items will be taken from inventory if existing,\n\t\t\t\t-- if not will be force-placed (item comes from thin air)\n\t\t\t\t-- Protection will be respected\n\t\t\t\targs = {\n\t\t\t\t\tpos = actions[node.name][i].args.pos or node_pos,\n\t\t\t\t\tsource = actions[node.name][i].args.source or npc.actions.take_from_inventory_forced,\n\t\t\t\t\tnode = actions[node.name][i].args.node,\n\t\t\t\t\tbypass_protection = actions[node.name][i].args.bypass_protection or false\n\t\t\t\t}\n\t\t\t\t--minetest.log(\"Enqueue dig action with args: \"..dump(args))\n\t\t\t\tnpc.add_action(self, actions[node.name][i].action, args)\n\t\t\telseif actions[node.name][i].action == npc.actions.cmd.ROTATE then\n\t\t\t\t-- Set arguments\n\t\t\t\targs = {\n\t\t\t\t\tdir = actions[node.name][i].dir,\n\t\t\t\t\tstart_pos = actions[node.name][i].start_pos\n\t\t\t\t\t\t\tor {x=start_pos.x, y=node_pos.y, z=start_pos.z},\n\t\t\t\t\tend_pos = actions[node.name][i].end_pos or node_pos\n\t\t\t\t}\n\t\t\t\t-- Enqueue action\n\t\t\t\tnpc.add_action(self, actions[node.name][i].action, args)\n\t\t\telseif actions[node.name][i].action == npc.actions.cmd.WALK_STEP then\n\t\t\t\t-- Defaults: direction is calculated from start node to node_pos.\n\t\t\t\t-- Speed is default wandering speed. Target pos is node_pos\n\t\t\t\t-- Calculate dir if dir is random\n\t\t\t\tlocal dir = npc.actions.get_direction(start_pos, node_pos)\n\t\t\t\tminetest.log(\"actions: \"..dump(actions[node.name][i]))\n\t\t\t\tif actions[node.name][i].args.dir == \"random\" then\n\t\t\t\t\tdir = math.random(0,7)\n\t\t\t\telseif type(actions[node.name][i].args.dir) == \"number\" then\n\t\t\t\t\tdir = actions[node.name][i].args.dir\n\t\t\t\tend\n\t\t\t\targs = {\n\t\t\t\t\tdir = dir,\n\t\t\t\t\tspeed = actions[node.name][i].args.speed or npc.actions.one_nps_speed,\n\t\t\t\t\ttarget_pos = actions[node.name][i].args.target_pos or node_pos\n\t\t\t\t}\n\t\t\t\tnpc.add_action(self, actions[node.name][i].action, args)\n\t\t\telseif actions[node.name][i].task == npc.actions.cmd.WALK_TO_POS then\n\t\t\t\t-- Optimize walking -- since distances can be really short,\n\t\t\t\t-- a simple walk_step() action can do most of the times. For\n\t\t\t\t-- this, however, we need to calculate direction\n\t\t\t\t-- First of all, check distance\n\t\t\t\tlocal distance = vector.distance(start_pos, node_pos)\n\t\t\t\tif distance < 3 then\n\t\t\t\t\t-- Will do walk_step based instead\n\t\t\t\t\tif distance > 1 then\n\t\t\t\t\t\targs = {\n\t\t\t\t\t\t\tdir = npc.actions.get_direction(start_pos, node_pos),\n\t\t\t\t\t\t\tspeed = npc.actions.one_nps_speed\n\t\t\t\t\t\t}\n\t\t\t\t\t\t-- Enqueue walk step\n\t\t\t\t\t\tnpc.add_action(self, npc.actions.cmd.WALK_STEP, args)\n\t\t\t\t\tend\n\t\t\t\t\t-- Add standing action to look at node\n\t\t\t\t\tnpc.add_action(self, npc.actions.cmd.STAND,\n\t\t\t\t\t\t{dir = npc.actions.get_direction(self.object:getpos(), node_pos)}\n\t\t\t\t\t)\n\t\t\t\telse\n\t\t\t\t\t-- Set end pos to be node_pos\n\t\t\t\t\targs = {\n\t\t\t\t\t\tend_pos = actions[node.name][i].args.end_pos or node_pos,\n\t\t\t\t\t\twalkable = actions[node.name][i].args.walkable or walkable_nodes or {}\n\t\t\t\t\t}\n\t\t\t\t\t-- Enqueue\n\t\t\t\t\tnpc.add_task(self, actions[node.name][i].task, args)\n\t\t\t\tend\n\t\t\telseif actions[node.name][i].task == npc.actions.cmd.USE_FURNACE then\n\t\t\t\t-- Defaults: pos is node_pos. Freeze is true\n\t\t\t\targs = {\n\t\t\t\t\tpos = actions[node.name][i].args.pos or node_pos,\n\t\t\t\t\titem = actions[node.name][i].args.item,\n\t\t\t\t\tfreeze = actions[node.name][i].args.freeze or true\n\t\t\t\t}\n\t\t\t\tnpc.add_task(self, actions[node.name][i].task, args)\n\t\t\telse\n\t\t\t\t-- Action or task that is not supported for value calculation\n\t\t\t\tnpc.enqueue_schedule_action(self, actions[node.name][i])\n\t\t\tend\n\t\tend\n\t\t-- Increase execution count\n\t\tself.schedules.current_check_params.execution_count =\n\t\t\tself.schedules.current_check_params.execution_count + 1\n\t\t-- Enqueue next schedule check\n\t\tif self.schedules.current_check_params.execution_count\n\t\t\t\t< self.schedules.current_check_params.execution_times then\n\t\t\tnpc.add_schedule_check(self)\n\t\tend\n\t\tnpc.log(\"DEBUG_SCHEDULE\", \"Actions queue: \"..dump(self.actions.queue))\n\telse\n\t\t-- No nodes found, enqueue none_actions\n\t\tfor i = 1, #none_actions do\n\t\t\t-- Add start_pos to none_actions\n\t\t\tnone_actions[i].args[\"start_pos\"] = start_pos\n\t\t\t-- Enqueue actions\n\t\t\tnpc.add_action(self, none_actions[i].action, none_actions[i].args)\n\t\tend\n\t\t-- Increase execution count\n\t\tself.schedules.current_check_params.execution_count =\n\t\t\tself.schedules.current_check_params.execution_count + 1\n\t\t-- Enqueue next schedule check\n\t\tif self.schedules.current_check_params.execution_count\n\t\t\t\t< self.schedules.current_check_params.execution_times then\n\t\t\tnpc.add_schedule_check(self)\n\t\tend\n\t\t-- No nodes found\n\t\tnpc.log(\"DEBUG_SCHEDULE\", \"Actions queue: \"..dump(self.actions.queue))\n\tend\nend\n\n---------------------------------------------------------------------------------------\n-- NPC Definition\n---------------------------------------------------------------------------------------\nmobs:register_mob(\"advanced_npc:npc\", {\n\ttype = \"npc\",\n\tpassive = false,\n\tdamage = 3,\n\tattack_type = \"dogfight\",\n\tattacks_monsters = true,\n\t-- Added group attack\n\tgroup_attack = true,\n\t-- Pathfinder = 2 to make NPCs more smart when attacking\n\tpathfinding = 2,\n\thp_min = 10,\n\thp_max = 20,\n\tarmor = 100,\n\tcollisionbox = {-0.20,0,-0.20, 0.20,1.8,0.20},\n\t--collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20},\n\t--collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},\n\tvisual = \"mesh\",\n\tmesh = \"character.b3d\",\n\tdrawtype = \"front\",\n\ttextures = {\n\t\t{\"npc_male1.png\"},\n\t\t{\"npc_male2.png\"},\n\t\t{\"npc_male3.png\"},\n\t\t{\"npc_male4.png\"},\n\t\t{\"npc_male5.png\"},\n\t\t{\"npc_male6.png\"},\n\t\t{\"npc_male7.png\"},\n\t\t{\"npc_male8.png\"},\n\t\t{\"npc_male9.png\"},\n\t\t{\"npc_male10.png\"},\n\t\t{\"npc_male11.png\"},\n\t\t{\"npc_male12.png\"},\n\t\t{\"npc_male13.png\"},\n\t\t{\"npc_male14.png\"},\n\t\t{\"npc_male15.png\"},\n\t\t{\"npc_female1.png\"}, -- female by nuttmeg20\n\t\t{\"npc_female2.png\"},\n\t\t{\"npc_female3.png\"},\n\t\t{\"npc_female4.png\"},\n\t\t{\"npc_female5.png\"},\n\t\t{\"npc_female6.png\"},\n\t\t{\"npc_female7.png\"},\n\t\t{\"npc_female8.png\"},\n\t\t{\"npc_female9.png\"},\n\t\t{\"npc_female10.png\"},\n\t\t{\"npc_female11.png\"},\n\t},\n\tchild_texture = {\n\t\t{\"npc_child_male1.png\"},\n\t\t{\"npc_child_female1.png\"},\n\t},\n\tmakes_footstep_sound = true,\n\tsounds = {},\n\t-- Added walk chance\n\twalk_chance = 30,\n\t-- Added stepheight\n\tstepheight = 0.6,\n\twalk_velocity = 1,\n\trun_velocity = 3,\n\tjump = false,\n\tdrops = {\n\t\t{name = \"default:wood\", chance = 1, min = 1, max = 3},\n\t\t{name = \"default:apple\", chance = 2, min = 1, max = 2},\n\t\t{name = \"default:axe_stone\", chance = 5, min = 1, max = 1},\n\t},\n\twater_damage = 0,\n\tlava_damage = 2,\n\tlight_damage = 0,\n\t--follow = {\"farming:bread\", \"mobs:meat\", \"default:diamond\"},\n\tview_range = 15,\n\towner = \"\",\n\torder = \"follow\",\n\t--order = \"stand\",\n\tfear_height = 3,\n\tanimation = {\n\t\tspeed_normal = 30,\n\t\tspeed_run = 30,\n\t\tstand_start = 0,\n\t\tstand_end = 79,\n\t\twalk_start = 168,\n\t\twalk_end = 187,\n\t\trun_start = 168,\n\t\trun_end = 187,\n\t\tpunch_start = 200,\n\t\tpunch_end = 219,\n\t},\n\ton_rightclick = function(self, clicker)\n\n\t\t-- Rotate NPC toward its clicker\n\t\tnpc.dialogue.rotate_npc_to_player(self)\n\n\t\t-- Get information from clicker\n\t\tlocal item = clicker:get_wielded_item()\n\t\tlocal name = clicker:get_player_name()\n\n\t\tnpc.log(\"DEBUG\", \"Right-clicked NPC: \"..dump(self))\n\n\t\t-- Receive gift or start chat. If player has no item in hand\n\t\t-- then it is going to start chat directly\n\t\t--minetest.log(\"self.can_have_relationship: \"..dump(self.can_have_relationship)..\", self.can_receive_gifts: \"..dump(self.can_receive_gifts)..\", table: \"..dump(item:to_table()))\n\t\tif self.can_have_relationship\n\t\t\t\tand self.can_receive_gifts\n\t\t\t\tand item:to_table() ~= nil then\n\t\t\t-- Get item name\n\t\t\tlocal item = minetest.registered_items[item:get_name()]\n\t\t\tlocal item_name = item.description\n\n\t\t\t-- Show dialogue to confirm that player is giving item as gift\n\t\t\tnpc.dialogue.show_yes_no_dialogue(\n\t\t\t\tself,\n\t\t\t\t\"Do you want to give \"..item_name..\" to \"..self.npc_name..\"?\",\n\t\t\t\tnpc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name,\n\t\t\t\tfunction()\n\t\t\t\t\tnpc.relationships.receive_gift(self, clicker)\n\t\t\t\tend,\n\t\t\t\tnpc.dialogue.NEGATIVE_ANSWER_LABEL,\n\t\t\t\tfunction()\n\t\t\t\t\tnpc.start_dialogue(self, clicker, true)\n\t\t\t\tend,\n\t\t\t\tname\n\t\t\t)\n\t\telse\n\t\t\tnpc.start_dialogue(self, clicker, true)\n\t\tend\n\tend,\n\tdo_custom = function(self, dtime)\n\t\tif self.initialized == nil then\n\t\t\t-- Initialize NPC if spawned using the spawn egg built in from\n\t\t\t-- mobs_redo. This functionality will be removed in the future in\n\t\t\t-- favor of a better manual spawning method with customization\n\t\t\tnpc.log(\"WARNING\", \"Initializing NPC from entity step. This message should only be appearing if an NPC is being spawned from inventory with egg!\")\n\t\t\tnpc.initialize(self, self.object:getpos(), true)\n\t\t\tself.tamed = false\n\t\t\tself.owner = nil\n\t\telse\n\t\t\t-- NPC is initialized, check other variables\n\t\t\t-- Check child texture issues\n\t\t\tif self.is_child then\n\t\t\t\t-- Check texture\n\t\t\t\tnpc.texture_check.timer = npc.texture_check.timer + dtime\n\t\t\t\tif npc.texture_check.timer > npc.texture_check.interval then\n\t\t\t\t\t-- Reset timer\n\t\t\t\t\tnpc.texture_check.timer = 0\n\t\t\t\t\t-- Set hornytimer to zero every 60 seconds so that children\n\t\t\t\t\t-- don't grow automatically\n\t\t\t\t\tself.hornytimer = 0\n\t\t\t\t\t-- Set correct textures\n\t\t\t\t\tself.texture = {self.selected_texture}\n\t\t\t\t\tself.base_texture = {self.selected_texture}\n\t\t\t\t\tself.object:set_properties(self)\n\t\t\t\t\tnpc.log(\"WARNING\", \"Corrected textures on NPC child \"..dump(self.npc_name))\n\t\t\t\t\t-- Set interval to large interval so this code isn't called frequently\n\t\t\t\t\tnpc.texture_check.interval = 60\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\t-- Timer function for casual traders to reset their trade offers\n\t\tself.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime\n\t\t-- Check if time has come to change offers\n\t\tif self.trader_data.trader_status == npc.trade.CASUAL and\n\t\t\t\tself.trader_data.change_offers_timer >= self.trader_data.change_offers_timer_interval then\n\t\t\t-- Reset timer\n\t\t\tself.trader_data.change_offers_timer = 0\n\t\t\t-- Re-select casual trade offers\n\t\t\tnpc.trade.generate_trade_offers_by_status(self)\n\t\tend\n\n\t\t-- Timer function for gifts\n\t\tfor i = 1, #self.relationships do\n\t\t\tlocal relationship = self.relationships[i]\n\t\t\t-- Gift timer check\n\t\t\tif relationship.gift_timer_value < relationship.gift_interval then\n\t\t\t\trelationship.gift_timer_value = relationship.gift_timer_value + dtime\n\t\t\telseif relationship.talk_timer_value < relationship.gift_interval then\n\t\t\t\t-- Relationship talk timer - only allows players to increase relationship\n\t\t\t\t-- by talking on the same intervals as gifts\n\t\t\t\trelationship.talk_timer_value = relationship.talk_timer_value + dtime\n\t\t\telse\n\t\t\t\t-- Relationship decrease timer\n\t\t\t\tif relationship.relationship_decrease_timer_value\n\t\t\t\t\t\t< relationship.relationship_decrease_interval then\n\t\t\t\t\trelationship.relationship_decrease_timer_value =\n\t\t\t\t\trelationship.relationship_decrease_timer_value + dtime\n\t\t\t\telse\n\t\t\t\t\t-- Check if married to decrease half\n\t\t\t\t\tif relationship.phase == \"phase6\" then\n\t\t\t\t\t\t-- Avoid going below the marriage phase limit\n\t\t\t\t\t\tif (relationship.points - 0.5) >=\n\t\t\t\t\t\t\t\tnpc.relationships.RELATIONSHIP_PHASE[\"phase5\"].limit then\n\t\t\t\t\t\t\trelationship.points = relationship.points - 0.5\n\t\t\t\t\t\tend\n\t\t\t\t\telse\n\t\t\t\t\t\trelationship.points = relationship.points - 1\n\t\t\t\t\tend\n\t\t\t\t\trelationship.relationship_decrease_timer_value = 0\n\t\t\t\t\t--minetest.log(dump(self))\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\t-- Action queue timer\n\t\t-- Check if actions and timers aren't locked\n\t\tif self.actions.action_timer_lock == false then\n\t\t\t-- Increment action timer\n\t\t\tself.actions.action_timer = self.actions.action_timer + dtime\n\t\t\tif self.actions.action_timer >= self.actions.action_interval then\n\t\t\t\t-- Reset action timer\n\t\t\t\tself.actions.action_timer = 0\n\t\t\t\t-- Check if NPC is walking\n\t\t\t\tif self.actions.walking.is_walking == true then\n\t\t\t\t\t-- Move NPC to expected position to ensure not getting lost\n\t\t\t\t\tlocal pos = self.actions.walking.target_pos\n\t\t\t\t\tself.object:moveto({x=pos.x, y=pos.y, z=pos.z})\n\t\t\t\tend\n\t\t\t\t-- Execute action\n\t\t\t\tself.freeze = npc.execute_action(self)\n\t\t\t\t-- Check if there are still remaining actions in the queue\n\t\t\t\tif self.freeze == nil and table.getn(self.actions.queue) > 0 then\n\t\t\t\t\tself.freeze = false\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\t-- Schedule timer\n\t\t-- Check if schedules are enabled\n\t\tif self.schedules.enabled == true then\n\t\t\t-- Get time of day\n\t\t\tlocal time = get_time_in_hours()\n\t\t\t-- Check if time is an hour\n\t\t\tif ((time % 1) < dtime) and self.schedules.lock == false then\n\t\t\t\t-- Activate lock to avoid more than one entry to this code\n\t\t\t\tself.schedules.lock = true\n\t\t\t\t-- Get integer part of time\n\t\t\t\ttime = (time) - (time % 1)\n\t\t\t\t-- Check if there is a schedule entry for this time\n\t\t\t\t-- Note: Currently only one schedule is supported, for day 0\n\t\t\t\tnpc.log(\"DEBUG_SCHEDULE\", \"Time: \"..dump(time))\n\t\t\t\tlocal schedule = self.schedules.generic[0]\n\t\t\t\tif schedule ~= nil then\n\t\t\t\t\t-- Check if schedule for this time exists\n\t\t\t\t\tif schedule[time] ~= nil then\n\t\t\t\t\t\tnpc.log(\"DEBUG_SCHEDULE\", \"Adding actions to action queue\")\n\t\t\t\t\t\t-- Add to action queue all actions on schedule\n\t\t\t\t\t\tfor i = 1, #schedule[time] do\n\t\t\t\t\t\t\t-- Check if schedule has a check function\n\t\t\t\t\t\t\tif schedule[time][i].check then\n\t\t\t\t\t\t\t\t-- Add parameters for check function and run for first time\n\t\t\t\t\t\t\t\t\tnpc.log(\"DEBUG\", \"NPC \"..dump(self.npc_id)..\" is starting check on \"..minetest.pos_to_string(self.object:getpos()))\n\t\t\t\t\t\t\t\tlocal check_params = schedule[time][i]\n\t\t\t\t\t\t\t\t-- Calculates how many times check will be executed\n\t\t\t\t\t\t\t\tlocal execution_times = check_params.count\n\t\t\t\t\t\t\t\tif check_params.random_execution_times then\n\t\t\t\t\t\t\t\t\texecution_times = math.random(check_params.min_count, check_params.max_count)\n\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\t-- Set current parameters\n\t\t\t\t\t\t\t\tself.schedules.current_check_params = {\n\t\t\t\t\t\t\t\t\trange = check_params.range,\n\t\t\t\t\t\t\t\t\twalkable_nodes = check_params.walkable_nodes,\n\t\t\t\t\t\t\t\t\tnodes = check_params.nodes,\n\t\t\t\t\t\t\t\t\tactions = check_params.actions,\n\t\t\t\t\t\t\t\t\tnone_actions = check_params.none_actions,\n\t\t\t\t\t\t\t\t\tprefer_last_acted_upon_node = check_params.prefer_last_acted_upon_node or false,\n\t\t\t\t\t\t\t\t\tlast_node_acted_upon = \"\",\n\t\t\t\t\t\t\t\t\texecution_count = 0,\n\t\t\t\t\t\t\t\t\texecution_times = execution_times\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t-- Enqueue the schedule check\n\t\t\t\t\t\t\t\tnpc.add_schedule_check(self)\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tnpc.log(\"DEBUG_SCHEDULE\", \"Executing schedule entry for NPC \"..dump(self.npc_id)..\": \"\n\t\t\t\t\t\t\t\t\t\t..dump(schedule[time][i]))\n\t\t\t\t\t\t\t\t-- Run usual schedule entry\n\t\t\t\t\t\t\t\t-- Check chance\n\t\t\t\t\t\t\t\tlocal execution_chance = math.random(1, 100)\n\t\t\t\t\t\t\t\tif not schedule[time][i].chance or\n\t\t\t\t\t\t\t\t\t\t(schedule[time][i].chance and execution_chance <= schedule[time][i].chance) then\n\t\t\t\t\t\t\t\t\t-- Check if entry has dependency on other entry\n\t\t\t\t\t\t\t\t\tlocal dependencies_met = nil\n\t\t\t\t\t\t\t\t\tif schedule[time][i].depends then\n\t\t\t\t\t\t\t\t\t\tdependencies_met = npc.utils.array_is_subset_of_array(\n\t\t\t\t\t\t\t\t\t\t\tself.schedules.temp_executed_queue,\n\t\t\t\t\t\t\t\t\t\t\tschedule[time][i].depends)\n\t\t\t\t\t\t\t\t\tend\n\n\t\t\t\t\t\t\t\t\t-- Check for dependencies being met\n\t\t\t\t\t\t\t\t\tif dependencies_met == nil or dependencies_met == true then\n\t\t\t\t\t\t\t\t\t\t-- Add tasks\n\t\t\t\t\t\t\t\t\t\tif schedule[time][i].task ~= nil then\n\t\t\t\t\t\t\t\t\t\t\t-- Add task\n\t\t\t\t\t\t\t\t\t\t\tnpc.add_task(self, schedule[time][i].task, schedule[time][i].args)\n\t\t\t\t\t\t\t\t\t\telseif schedule[time][i].action ~= nil then\n\t\t\t\t\t\t\t\t\t\t\t-- Add action\n\t\t\t\t\t\t\t\t\t\t\tnpc.add_action(self, schedule[time][i].action, schedule[time][i].args)\n\t\t\t\t\t\t\t\t\t\telseif schedule[time][i].property ~= nil then\n\t\t\t\t\t\t\t\t\t\t\t-- Change NPC property\n\t\t\t\t\t\t\t\t\t\t\tnpc.schedule_change_property(self, schedule[time][i].property, schedule[time][i].args)\n\t\t\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\t\t\t-- Backward compatibility check\n\t\t\t\t\t\t\t\t\t\tif self.schedules.temp_executed_queue then\n\t\t\t\t\t\t\t\t\t\t\t-- Add into execution queue to meet dependency\n\t\t\t\t\t\t\t\t\t\t\ttable.insert(self.schedules.temp_executed_queue, i)\n\t\t\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\t-- TODO: Change to debug\n\t\t\t\t\t\t\t\t\tnpc.log(\"DEBUG\", \"Skipping schedule entry for time \"..dump(time)..\": \"..dump(schedule[time][i]))\n\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\tend\n\t\t\t\t\t\tend\n\t\t\t\t\t\t-- Clear execution queue\n\t\t\t\t\t\tself.schedules.temp_executed_queue = {}\n\t\t\t\t\t\tnpc.log(\"DEBUG\", \"New action queue: \"..dump(self.actions.queue))\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\telse\n\t\t\t\t-- Check if lock can be released\n\t\t\t\tif (time % 1) > dtime + 0.1 then\n\t\t\t\t\t-- Release lock\n\t\t\t\t\tself.schedules.lock = false\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\treturn self.freeze\n\tend\n})\n\n-- Spawn\n-- mobs:spawn({\n-- name = \"advanced_npc:npc\",\n-- nodes = {\"advanced_npc:plotmarker_auto_spawner\", \"mg_villages:plotmarker\"},\n-- min_light = 3,\n-- active_object_count = 1,\n-- interval = 5,\n-- chance = 1,\n-- --max_height = 0,\n-- on_spawn = npc.initialize\n-- })\n\n-------------------------------------------------------------------------\n-- Item definitions\n-------------------------------------------------------------------------\n\nmobs:register_egg(\"advanced_npc:npc\", S(\"NPC\"), \"default_brick.png\", 1)\n\n-- compatibility\nmobs:alias_mob(\"mobs:npc\", \"advanced_npc:npc\")\n\n-- Marriage ring\nminetest.register_craftitem(\"advanced_npc:marriage_ring\", {\n\tdescription = S(\"Marriage Ring\"),\n\tinventory_image = \"marriage_ring.png\",\n})\n\n-- Marriage ring craft recipe\nminetest.register_craft({\n\toutput = \"advanced_npc:marriage_ring\",\n\trecipe = { {\"\", \"\", \"\"},\n\t\t{\"\", \"default:diamond\", \"\"},\n\t\t{\"\", \"default:gold_ingot\", \"\"} },\n})\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- npc.lua (revision d95c8504ec181c2bba72a11fb7230eae284e6610) ++++ npc.lua (revision ) +@@ -454,6 +454,13 @@ + -- Action executed while on lock + interrupted_action = {} + }, ++ -- Variables that allows preserving the movement state and NPC animation ++ move_state = { ++ -- Whether a NPC is sitted or not ++ is_sitting = false, ++ -- Whether a NPC is laying or not ++ is_laying = false ++ }, + -- Walking variables -- required for implementing accurate movement code + walking = { + -- Defines whether NPC is walking to specific position or not +@@ -464,7 +471,7 @@ + -- This is NOT the end of the path, but the next position in the path + -- relative to the last position + target_pos = {} +- } ++ }, + } + + -- This flag is checked on every step. If it is true, the rest of +@@ -806,6 +813,12 @@ + if self.actions.action_timer_lock == true then + return + end ++ -- Check if NPC is in unmovable state ++ if self.actions.move_state ++ and (self.actions.move_state.is_sitting == true or self.actions.move_state.is_laying == true) then ++ -- Can't lock actions since NPC is in a non-movable state ++ return ++ end + + local pos = self.object:getpos() + +@@ -843,6 +856,12 @@ + end + + function npc.unlock_actions(self) ++ -- Check if the NPC is sitting or laying states ++ if self.actions.move_state ++ and (self.actions.move_state.is_sitting == true or self.actions.move_state.is_laying == true) then ++ -- Can't unlock actions since NPC is in a non-movable state ++ return ++ end + -- Allow timers to execute + self.actions.action_timer_lock = false + -- Restore the value of self.freeze +@@ -1320,7 +1339,7 @@ + makes_footstep_sound = true, + sounds = {}, + -- Added walk chance +- walk_chance = 30, ++ walk_chance = 20, + -- Added stepheight + stepheight = 0.6, + walk_velocity = 1, +@@ -1401,6 +1420,16 @@ + self.tamed = false + self.owner = nil + else ++ ++ -- Restore sit/lay state ++-- if self.actions.move_state then ++-- if self.actions.move_state.is_sitting == true then ++-- npc.actions.sit(self, {pos=self.object:getpos()}) ++-- elseif self.actions.move_state.is_laying == true then ++-- npc.actions.lay(self, {pos=self.object:getpos()}) ++-- end ++-- end ++ + -- NPC is initialized, check other variables + -- Check child texture issues + if self.is_child then diff --git a/.idea/shelf/Update_building_type_definitions_.xml b/.idea/shelf/Update_building_type_definitions_.xml new file mode 100644 index 0000000..66908e0 --- /dev/null +++ b/.idea/shelf/Update_building_type_definitions_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/shelf/Update_building_type_definitions_/shelved.patch b/.idea/shelf/Update_building_type_definitions_/shelved.patch new file mode 100644 index 0000000..6025197 --- /dev/null +++ b/.idea/shelf/Update_building_type_definitions_/shelved.patch @@ -0,0 +1,23 @@ +Index: data/occupations/default_priest.lua +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>----------------------------------------------------\n-- Basic priest occupation for Advanced NPC (WIP)\n-- By Zorman2000\n----------------------------------------------------\n-- The basic priest occupation is given to NPCs that spawn on houses\n-- surrounding churchs. While on the church, the priest provides\n-- universal wisdom and advice, and also heals the player a limited number of times.\n-- DISCLAIMER: The \"teachings\" in this file come from a compilation of 15 principles shared\n-- among religions around the world. Zorman2000 and other contributors are not\n-- necessarily aligned with the principles and morals in these teachings, nor affiliated\n-- to religions that promote them.\n\nlocal priest_def = {\n dialogues = {\n type = \"given\",\n data = {\n {\n text = \"Blessings be upon you, my child!\",\n tags = {\"unisex\"}\n },\n {\n text = \"The temple will always open the doors to everyone.\",\n tags = {\"unisex\"}\n },\n {\n text = \"Following the teachings is the path to a good life.\",\n tags = {\"unisex\"}\n },\n {\n text = \"Thanks for coming to greet me, I hope you have a blessed day! \",\n tags = {\"unisex\"}\n },\n {\n text = \"Welcome to the temple, how can I help you today?\",\n flag = {name=\"on_church\", value=true},\n tags = {\"unisex\"},\n responses =\n {\n [1] = {\n text = \"I'm injured. Can you heal me?\",\n action_type = \"function\",\n action = function(self, player)\n local heal_count = self.flags[\"heal_count\"]\n if heal_count then\n -- Increase heal count\n self.flags[\"heal_count\"] = self.flags[\"heal_count\"] + 1\n else\n self.flags[\"heal_count\"] = 1\n heal_count = 1\n end\n -- Check if heal count is achieved\n if heal_count > 5 then\n npc.chat(self.npc_name, player:get_player_name(), \"I cannot heal you anymore, \"\n ..\"my child.\\nTo mortals like you and me, the power of the Creator is\\n\"\n ..\" limited. Only though learning the teachings we are able to understand more\"\n ..\"...\\nBe safe my child.\")\n else\n npc.chat(self.npc_name, player:get_player_name(),\n \"Receive the blessings of the Creator!\")\n effect(self.object:getpos(), 20, \"default_coral_skeleton.png\", 0.1, 0.3, 3, 10)\n -- Heal one heart\n player:set_hp(player:get_hp() + 2)\n end\n end\n },\n [2] = {\n text = \"What are your teachings?\",\n action_type = \"function\",\n action = function(self, player)\n local teachings = {\n [1] = \"Do unto others what you would have them do unto you\",\n [2] = \"Honor your Father and Mother. Knowing them is the key to knowing ourselves\",\n [3] = \"Sincerity is the way to heaven,\\nand to think how to be sincere is the way of the man\",\n [4] = \"Generosity, charity and kindness will open an individual to an unbounded reservoir of riches\",\n [5] = \"Even as the scent dwells within the flower, so God within thine own heart forever abides\",\n [6] = \"Acts of faith, prayer and meditation provide us with the strength that allows love for our fellow man to become an abiding force. Love is unifying.\",\n [7] = \"Peacemakers are blessed.\\nPeace is the natural result of individuals and nations living in close kinship\",\n [8] = \"You reap what you sow.\\nEven if it is a mystery, we are all ruled by this inevitable law of nature\",\n [9] = \"The blessings of life are deeper than what can be appreciated by the senses\",\n [10] = \"Do no harm, as we are part of the whole, and shouldn't perceive others as foreign or separate from ownself\",\n [11] = \"The most beautiful thing a man can do is to forgive wrong\",\n [12] = \"Judge not, lest ye be judged. Mankind is nothing but a great family and we all spring from common source\",\n [13] = \"Anger clouds the mind in the very moments that clarity and objectivity are needed most.\",\n [14] = \"Nature, Being, The Absolute, Creator... whatever name man chooses, there is but one force in the universe. All people and things are of one essence\",\n [15] = \"Study the words, no doubt, but look behind them to the thought they indicate;\\nhaving fond it, throw the words away. Live the spirit of them\"\n }\n npc.chat(self.npc_name, player:get_player_name(), teachings[math.random(1, #teachings)]\n ..\". \\nThese are the teachings of our Creator.\")\n end\n }\n }\n }\n }\n },\n textures = {\n \"npc_male_priest.png\"\n },\n initial_inventory = {\n {name=\"farming:bread\", count=1}\n },\n properties = {\n initial_trader_status = npc.trade.NONE,\n enable_gift_items_hints = false\n },\n\n building_types = {\n \"hut\", \"house\", \"farm_tiny\", \"lumberjack\"\n },\n surrounding_building_types = {\n \"church\"\n },\n schedules_entries = {\n [7] = {\n -- Get out of bed\n [1] = {\n task = npc.actions.cmd.USE_BED,\n args = {\n pos = npc.places.PLACE_TYPE.BED.PRIMARY,\n action = npc.actions.const.beds.GET_UP\n }\n },\n -- Walk to home inside\n [2] = {\n task = npc.actions.cmd.WALK_TO_POS,\n chance = 95,\n args = {\n end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,\n walkable = {}\n }\n },\n -- Allow mobs_redo wandering\n [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}}\n },\n [8] = {\n -- Walk to workplace\n [1] =\n {\n task = npc.actions.cmd.WALK_TO_POS,\n args = {\n end_pos = npc.places.PLACE_TYPE.WORKPLACE.PRIMARY,\n walkable = {},\n use_access_node = true\n }\n },\n [2] =\n {\n property = npc.schedule_properties.flag,\n args = {\n action = \"set\",\n flag_name = \"on_church\",\n flag_value = true\n }\n }\n },\n [17] = {\n [1] =\n {\n property = npc.schedule_properties.flag,\n args = {\n action = \"set\",\n flag_name = \"on_church\",\n flag_value = false\n }\n },\n [2] =\n {\n task = npc.actions.cmd.WALK_TO_POS,\n args = {\n end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE,\n walkable = {}\n }\n }\n },\n [21] = {\n [1] = {\n task = npc.actions.cmd.WALK_TO_POS,\n args = {\n end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true},\n walkable = {}\n }\n },\n -- Use bed\n [2] = {\n task = npc.actions.cmd.USE_BED,\n args = {\n pos = npc.places.PLACE_TYPE.BED.PRIMARY,\n action = npc.actions.const.beds.LAY\n }\n },\n -- Stay put on bed\n [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}}\n }\n }\n}\n\n-- Register occupation\nnpc.occupations.register_occupation(\"basic_priest\", priest_def) +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- data/occupations/default_priest.lua (date 1505308148000) ++++ data/occupations/default_priest.lua (revision ) +@@ -103,11 +103,9 @@ + enable_gift_items_hints = false + }, + +- building_types = { +- "hut", "house", "farm_tiny", "lumberjack" +- }, ++ building_types = {}, + surrounding_building_types = { +- "church" ++ {type="church", origin_building_types={"hut", "house", "farm_tiny", "lumberjack"}} + }, + schedules_entries = { + [7] = { diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..2779b79 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,1229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + step_into_air_only + ItemStack + inventory_contains + enter + trader_data.trade_list.both + access + minetest.get_item + vector. + find_node_in_front_of_door + facedir + get_openable_node_state + private + minetest.get_meta + minetest.set_meta + Trader_data + set_string + on_rightclick + start_dialogue + timer + registered_entities + mobs:register_mob + mob_activate + rotate_ + texture_list + is_female_texture + minetest.after + unlock_actions + return args.freeze + self.commands + actions + + + trader_data.trade_list + return not(args.freeze) + self.actions + commands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1503755656989 - - - 1503851524044 - - - 1503852653192 - - - 1503859608834 - - - 1503949438049 - - - 1504032897882 - - - 1504032921345 - - - 1504032938673 - - - 1504048341592 - - - 1504048608193 - - - 1504185103196 - - - 1504220386441 - - - 1504289593329 - - - 1504289675070 - - - 1504289701990 - - - 1504289723095 - - - 1504304153364 - - - 1504304837990 - - - 1504652570969 - - - 1504666023099 - - - 1504788710879 - - - 1505221724376 - - - 1505221755771 - - - 1505308018178 - - - 1505308148559 - - - 1505501064410 - - - 1505501168312 - - - 1505501224543 - - - 1505501497065 - - - 1505513839593 - - - 1505514578631 - - - 1505664691939 - - - 1505711600212 - - - 1505998961186 - - - 1506015909658 - - - 1506015934468 - - - 1506034423702 - - - 1506034502442 - - - 1506034529193 - - - 1506038581353 - - - 1506177831743 - - - 1507244026444 - - - 1508193869778 - - - 1508194082842 - - - 1508331908511 - - - 1508353740385 - - - 1508464055337 - - - 1508464276530 - - - 1515541413216 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file