diff --git a/actions/actions.lua b/actions/actions.lua new file mode 100644 index 0000000..31e404f --- /dev/null +++ b/actions/actions.lua @@ -0,0 +1,85 @@ +-- Actions code for Advanced NPC by Zorman2000 +--------------------------------------------------------------------------------------- +-- Action functionality +--------------------------------------------------------------------------------------- +-- The NPCs will be able to perform five fundamental actions that will allow +-- for them to perform any other kind of interaction in the world. These +-- fundamental actions are: place a node, dig a node, put items on an inventory, +-- take items from an inventory, find a node closeby (radius 3) and +-- walk a step on specific direction. These actions will be set on an action queue. +-- The queue will have the specific steps, in order, for the NPC to be able to do +-- something (example, go to a specific place and put a chest there). The +-- fundamental actions are added to the action queue to make a complete task for the NPC. + +npc.actions = {} + +function npc.actions.rotate(args) + local self = args.self + local dir = args.dir + local yaw = 0 + self.rotate = 0 + if dir == npc.direction.north then + yaw = 315 + elseif dir == npc.direction.east then + yaw = 225 + elseif dir == npc.direction.south then + yaw = 135 + elseif dir == npc.direction.west then + yaw = 45 + end + self.object:setyaw(yaw) +end + +-- This function will make the NPC walk one step on a +-- specifc direction. One step means one node. It returns +-- true if it can move on that direction, and false if there is an obstacle +function npc.actions.walk_step(args) + local self = args.self + local dir = args.dir + local vel = {} + if dir == npc.direction.north then + vel = {x=0, y=0, z=1} + elseif dir == npc.direction.east then + vel = {x=1, y=0, z=0} + elseif dir == npc.direction.south then + vel = {x=0, y=0, z=-1} + elseif dir == npc.direction.west then + vel = {x=-1, y=0, z=0} + end + set_animation(self, "walk") + npc.rotate({self=self, dir=dir}) + self.object:setvelocity(vel) +end + +-- This action makes the NPC stand and remain like that +function npc.actions.stand(args) + local self = args.self + -- Stop NPC + self.object:setvelocity({x=0, y=0, z=0}) + -- Set stand animation + set_animation(self, "stand") +end + +-- This action makes the NPC sit on the node where it is +function npc.actions.sit(args) + local self = args.self + -- Stop NPC + self.object:setvelocity({x=0, y=0, z=0}) + -- 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(args) + local self = args.self + -- Stop NPC + self.object:setvelocity({x=0, y=0, z=0}) + -- Set sit animation + self.object:set_animation({ + x = npc.ANIMATION_LAY_START, + y = npc.ANIMATION_LAY_END}, + self.animation.speed_normal, 0) +end diff --git a/actions/places.lua b/actions/places.lua new file mode 100644 index 0000000..1a462c7 --- /dev/null +++ b/actions/places.lua @@ -0,0 +1,82 @@ +-- Places code for Advanced NPC by Zorman2000 +--------------------------------------------------------------------------------------- +-- Places functionality +--------------------------------------------------------------------------------------- +-- In addition, the NPCs need to know where some places are, and know +-- where there are nodes they can use. For example, they need to know where the +-- chest they use is located, both to walk to it and to use it. They also need +-- to know where the farm they work is located, or where the bed they sleep is. +-- Other mods have to be supported for this to work correctly, as there are +-- many sitting nodes, many beds, many tables, chests, etc. For now, by default, +-- support for default and cottages is going to be provided. + +npc.places = {} + +npc.places.nodes = { + BEDS = { + "beds:bed_bottom", + "beds:fancy_bed_bottom" + }, + CHAIRS = { + "cottages:bench" + }, + CHESTS = { + "default:chest", + "default:chest_locked" + } +} + +npc.places.PLACE_TYPE = { + "OWN_BED", + "OWN_CHEST", + "HOUSE_CHAIR", + "HOUSE_TABLE", + "HOUSE_FURNACE", + "HOUSE_ENTRANCE" +} + + +function npc.places.add(self, place_name, place_type, pos) + self.places_map[place_name] = {type=place_type, pos=pos} +end + +-- Adds a specific node to the NPC places, and modifies the +-- node metadata to identify the NPC as the owner. This allows +-- other NPCs to avoid to take this as their own. +function npc.places.add_owned(self, place_name, place_type, pos) + -- Get node metadata + local meta = minetest.get_meta(pos) + -- Check if it is owned by an NPC? + if meta:get_string("npc_owner") == "" then + -- Set owned by NPC + meta:set_string("npc_owner", self.npc_id) + -- Add place to list + npc.places.add(self, place_name, place_type, pos) + return true + end + return false +end + +function npc.places.get_by_type(self, place_type) + local result = {} + for place_name, place_entry in pairs(self.places_map) do + if place_entry.type == place_type then + table.insert(result, place_name) + end + end + return result +end + +-- This function searches on a squared are of the given radius +-- for nodes of the given type. The type should be npc.places.nodes +function npc.places.find_new_nearby(self, type, radius) + -- Get current pos + local current_pos = self.object:getpos() + -- Determine area points + local start_pos = {x=current_pos.x - radius, y=current_pos.y - 1, z=current_pos.z - radius} + local end_pos = {x=current_pos.x + radius, y=current_pos.y + 1, z=current_pos.z + radius} + -- Get nodes + local nodes = minetest.find_nodes_in_area(start_pos, end_pos, type) + + return nodes +end \ No newline at end of file diff --git a/init.lua b/init.lua index 1f6c0a8..340ef90 100755 --- a/init.lua +++ b/init.lua @@ -1,4 +1,4 @@ - +-- Advanced NPC mod by Zorman2000 local path = minetest.get_modpath("advanced_npc") -- Intllib @@ -30,5 +30,7 @@ dofile(path .. "/dialogue.lua") dofile(path .. "/random_data.lua") dofile(path .. "/trade/trade.lua") dofile(path .. "/trade/prices.lua") +dofile(path .. "/actions/actions.lua") +dofile(path .. "/actions/places.lua") -print (S("[MOD] Advanced NPC loaded")) +print (S("[Mod] Advanced NPC loaded")) diff --git a/npc.lua b/npc.lua index 789790d..b766d5e 100755 --- a/npc.lua +++ b/npc.lua @@ -11,6 +11,18 @@ npc.MALE = "male" npc.INVENTORY_ITEM_MAX_STACK = 99 +npc.ANIMATION_SIT_START = 81 +npc.ANIMATION_SIT_END = 160 +npc.ANIMATION_LAY_START = 162 +npc.ANIMATION_LAY_END = 166 + +npc.direction = { + north = 1, + east = 2, + south = 3, + west = 4 +} + --------------------------------------------------------------------------------------- -- General functions --------------------------------------------------------------------------------------- @@ -212,6 +224,32 @@ function npc.start_dialogue(self, clicker, show_married_dialogue) end +--------------------------------------------------------------------------------------- +-- Action functionality +--------------------------------------------------------------------------------------- +-- This function adds a function to the action queue. +-- Actions should be added in strict order for tasks to work as expected. +function npc.add_action(self, action, arguments) + self.freeze = true + minetest.log("Current Pos: "..dump(self.object:getpos())) + local action_entry = {action=action, args=arguments} + minetest.log(dump(action_entry)) + table.insert(self.actions.queue, action_entry) +end + +-- This function removes the first action in the action queue +-- and then exexcutes it +function npc.execute_action(self) + if table.getn(self.actions.queue) == 0 then + return false + end + minetest.log("Executing action") + local action_obj = self.actions.queue[1] + action_obj.action(action_obj.args) + table.remove(self.actions.queue, 1) + return true +end + --------------------------------------------------------------------------------------- -- Spawning functions @@ -273,8 +311,15 @@ end -- NPC will have and choose random, starting values local function npc_spawn(self, pos) minetest.log("Spawning new NPC:") + + -- Get Lua Entity local ent = self:get_luaentity() + + -- Set name ent.nametag = "Kio" + + -- Set ID + ent.npc_id = tostring(math.random(1000, 9999))..":"..ent.nametag -- Determine sex based on textures if (is_female_texture(ent.base_texture)) then @@ -330,6 +375,44 @@ local function npc_spawn(self, pos) select_casual_trade_offers(ent) end + -- Action queue + ent.actions = { + -- The queue is a queue of actions to be performed on each interval + queue = {}, + -- Current value of the action timer + action_timer = 0, + -- Determines the interval for each action in the action queue + action_interval = 1 + } + + -- This flag is checked on every step. If it is true, the rest of + -- Mobs Redo API is not executed + ent.freeze = false + + -- This map will hold all the places for the NPC + -- Map entries should be like: "bed" = {x=1, y=1, z=1} + ent.places_map = {} + + -- Temporary initialization of actions for testing + -- npc.add_action(ent, npc.action.stand, {self = ent}) + -- npc.add_action(ent, npc.action.stand, {self = ent}) + -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) + -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) + -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) + -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) + -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) + -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) + -- npc.add_action(ent, npc.action.sit, {self = ent}) + -- npc.add_action(ent, npc.action.rotate, {self = ent, dir = npc.direction.south}) + -- npc.add_action(ent, npc.action.lay, {self = ent}) + + -- Temporary initialization of places + local bed_nodes = npc.places.find_new_nearby(ent, npc.places.nodes.BEDS, 8) + minetest.log("Number of bed nodes: "..dump(#bed_nodes)) + if #bed_nodes > 0 then + npc.places.add_owned(ent, "bed1", npc.places.PLACE_TYPE.OWN_BED, bed_nodes[1]) + end + minetest.log(dump(ent)) -- Refreshes entity @@ -432,6 +515,7 @@ mobs:register_mob("advanced_npc:npc", { end, do_custom = function(self, dtime) + -- Timer function for casual traders to reset their trade offers self.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime -- Check if time has come to change offers @@ -474,7 +558,23 @@ mobs:register_mob("advanced_npc:npc", { minetest.log(dump(self)) end end - end + end + + -- Action queue timer + self.actions.action_timer = self.actions.action_timer + dtime + if self.actions.action_timer >= self.actions.action_interval then + -- Reset action timer + self.actions.action_timer = 0 + -- Execute action + npc.execute_action(self) + -- Check if there are more actions to execute + if table.getn(self.actions.queue) == 0 then + -- Unfreeze NPC so the rest of Mobs API work + --self.freeze = false + end + end + + return not self.freeze end }) @@ -494,7 +594,7 @@ mobs:spawn({ -- Item definitions ------------------------------------------------------------------------- -mobs:register_egg("advanced_npc:npc", S("Npc"), "default_brick.png", 1) +mobs:register_egg("advanced_npc:npc", S("NPC"), "default_brick.png", 1) -- compatibility mobs:alias_mob("mobs:npc", "advanced_npc:npc") diff --git a/relationships.lua b/relationships.lua index 0b3f767..001e6bb 100644 --- a/relationships.lua +++ b/relationships.lua @@ -1,3 +1,4 @@ +-- Relationships code for Advanced NPC by Zorman2000 --------------------------------------------------------------------------------------- -- Gift and relationship system --------------------------------------------------------------------------------------- diff --git a/trade/trade.lua b/trade/trade.lua index 8b40624..76f521e 100644 --- a/trade/trade.lua +++ b/trade/trade.lua @@ -1,4 +1,4 @@ --- NPC trading abilities by Zorman2000 +-- Trading code for Advanced NPC by Zorman2000 npc.trade = {} @@ -125,8 +125,6 @@ end -- This function will return an offer object, based -- on the items the NPC has. --- Criteria: The NPC will offer to sell its items --- if it doesn't has any currency. function npc.trade.get_casual_trade_offer(self, offer_type) local result = {} -- Check offer type