From df56e44bbdce4fd053d378ba34162b0543d7db42 Mon Sep 17 00:00:00 2001 From: Hector Franqui Date: Tue, 29 Aug 2017 14:54:57 -0400 Subject: [PATCH] Add workplaces support. Places: Workplaces are now usable nodes. Plotmarkers around a building can be scanned and their information stored into current plotmarker. Slightly optimize plotmarker search. Occupations: Add test "priest" occupation definition. Fix lots of bugs in initialization code. Spawner: Add ability to determine occupation name out of surrounding workplaces. Assign occupation to spawned NPC using simple algorithm. Others: Reduce log noise a bit. Fix some warnings. --- actions/places.lua | 6 +- data/occupations_data.lua | 116 ++-- npc.lua | 1250 ++++++++++++++++++----------------- occupations/occupations.lua | 153 +++-- spawner.lua | 556 +++++++++------- 5 files changed, 1093 insertions(+), 988 deletions(-) diff --git a/actions/places.lua b/actions/places.lua index 331b6d8..5a3276c 100644 --- a/actions/places.lua +++ b/actions/places.lua @@ -302,7 +302,7 @@ if minetest.get_modpath("mg_villages") ~= nil then if nearby_plotmarkers[i].workplaces then -- Insert all workplaces in this plotmarker for j = 1, #nearby_plotmarkers[i].workplaces do - minetest.log("Nearby plotmarker workplace #"..dump(j)..": "..dump(nearby_plotmarkers[i].workplaces[j])) + --minetest.log("Nearby plotmarker workplace #"..dump(j)..": "..dump(nearby_plotmarkers[i].workplaces[j])) table.insert(result, { workplace=nearby_plotmarkers[i].workplaces[j], building_type = nearby_plotmarkers[i].building_type, @@ -328,7 +328,7 @@ function npc.places.find_plotmarkers(pos, radius, exclude_current_pos) local result = {} local start_pos = {x=pos.x - radius, y=pos.y - 1, z=pos.z - radius} local end_pos = {x=pos.x + radius, y=pos.y + 1, z=pos.z + radius} - local nodes = minetest.find_nodes_in_area_under_air(start_pos, end_pos, + local nodes = minetest.find_nodes_in_area(start_pos, end_pos, npc.places.nodes.PLOTMARKER_TYPE) -- Scan nodes for i = 1, #nodes do @@ -357,7 +357,7 @@ function npc.places.find_plotmarkers(pos, radius, exclude_current_pos) end end -- Add building - minetest.log("Adding building: "..dump(def)) + --minetest.log("Adding building: "..dump(def)) table.insert(result, def) end end diff --git a/data/occupations_data.lua b/data/occupations_data.lua index 778200d..badf16d 100644 --- a/data/occupations_data.lua +++ b/data/occupations_data.lua @@ -1,24 +1,28 @@ -- Occupations definitions -- Register default occupation -npc.occupations.register_occupation("default_basic", npc.occupations.basic_def) +npc.occupations.register_occupation(npc.occupations.basic_name, npc.occupations.basic_def) -- Test priest npc.occupations.register_occupation("test_priest", { dialogues = { - { - text = "How are you today my child?", - tags = "male" + type = "given", + data = { + { + text = "How are you today my child?", + tags = {"male"} + } } }, textures = { "npc_male_priest.png" }, initial_inventory = { - "farming:bread 1" + {name="farming:bread", count=1} }, + initial_trader_status = npc.trade.NONE, building_types = { - "home" + "hut", "house", "farm_tiny", "lumberjack" }, surrounding_building_types = { "church" @@ -28,63 +32,63 @@ npc.occupations.register_occupation("test_priest", { -- Test farmer npc.occupations.register_occupation("test_farmer", { - dialogues = {}, - textures = {}, - initial_inventory = {}, - schedule_entries = { - [7] = { - [1] = + dialogues = {}, + textures = {}, + initial_inventory = {}, + schedule_entries = { + [7] = { + [1] = + { + task = npc.actions.cmd.WALK_TO_POS, + args = { + end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, + walkable = {} + } + }, + [2] = + { + check = true, + range = 2, + random_execution_times = true, + min_count = 10, + max_count = 12, + nodes = {"farming:cotton_3"}, + actions = { - task = npc.actions.cmd.WALK_TO_POS, - args = { - end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, - walkable = {} - } - }, - [2] = - { - check = true, - range = 2, - random_execution_times = true, - min_count = 10, - max_count = 12, - nodes = {"farming:cotton_3"}, - actions = + -- Actions for grown cotton - harvest and replant + ["farming:cotton_3"] = { - -- Actions for grown cotton - harvest and replant - ["farming:cotton_3"] = + [1] = { - [1] = + action = npc.actions.cmd.WALK_STEP, + }, + [2] = + { + action = npc.actions.cmd.DIG, + }, + [3] = + { + action = npc.actions.cmd.PLACE, + args = { - action = npc.actions.cmd.WALK_STEP, - }, - [2] = - { - action = npc.actions.cmd.DIG, - }, - [3] = - { - action = npc.actions.cmd.PLACE, - args = - { - node = "farming:cotton_1" - } + node = "farming:cotton_1" } } - - }, - none_actions = - { - -- Walk a single step in a random direction - [1] = { - action = npc.actions.cmd.WALK_STEP, - args = - { - dir = "random" - } - }, } + + }, + none_actions = + { + -- Walk a single step in a random direction + [1] = { + action = npc.actions.cmd.WALK_STEP, + args = + { + dir = "random" + } + }, } } } - }) \ No newline at end of file + } +}) \ No newline at end of file diff --git a/npc.lua b/npc.lua index 7758c35..e29d3f4 100755 --- a/npc.lua +++ b/npc.lua @@ -10,8 +10,8 @@ npc.FEMALE = "female" npc.MALE = "male" npc.age = { - adult = "adult", - child = "child" + adult = "adult", + child = "child" } npc.INVENTORY_ITEM_MAX_STACK = 99 @@ -28,32 +28,32 @@ npc.ANIMATION_MINE_START = 189 npc.ANIMATION_MINE_END =198 npc.direction = { - north = 0, - east = 1, - south = 2, - west = 3, - north_east = 4, - north_west = 5, - south_east = 6, - south_west = 7 + north = 0, + east = 1, + south = 2, + west = 3, + north_east = 4, + north_west = 5, + south_east = 6, + south_west = 7 } npc.action_state = { - none = 0, - executing = 1, - interrupted = 2 + none = 0, + executing = 1, + interrupted = 2 } npc.log_level = { - INFO = true, - WARNING = true, - ERROR = true, - DEBUG = false + INFO = true, + WARNING = true, + ERROR = true, + DEBUG = false } npc.texture_check = { - timer = 0, - interval = 2 + timer = 0, + interval = 2 } --------------------------------------------------------------------------------------- @@ -61,31 +61,31 @@ npc.texture_check = { --------------------------------------------------------------------------------------- -- Logging function npc.log(level, message) - if npc.log_level[level] then - minetest.log("[advanced_npc] "..level..": "..message) - end + if npc.log_level[level] then + minetest.log("[advanced_npc] "..level..": "..message) + end end -- NPC chat function npc.chat(npc_name, player_name, message) - minetest.chat_send_player(player_name, npc_name..": "..message) + minetest.chat_send_player(player_name, npc_name..": "..message) end -- Gets name of player or NPC function npc.get_entity_name(entity) - if entity:is_player() then - return entity:get_player_name() - else - return entity:get_luaentity().name - end + if entity:is_player() then + return entity:get_player_name() + else + return entity:get_luaentity().name + end end -- Returns the item "wielded" by player or NPC -- TODO: Implement NPC function npc.get_entity_wielded_item(entity) - if entity:is_player() then - return entity:get_wielded_item() - end + if entity:is_player() then + return entity:get_wielded_item() + end end --------------------------------------------------------------------------------------- @@ -97,57 +97,65 @@ end -- will be allowed. local function get_random_name(sex) - local i = math.random(#npc.data.FIRST_NAMES[sex]) - return npc.data.FIRST_NAMES[sex][i] + local i = math.random(#npc.data.FIRST_NAMES[sex]) + return npc.data.FIRST_NAMES[sex][i] end local function initialize_inventory() - return { - [1] = "", [2] = "", [3] = "", [4] = "", - [5] = "", [6] = "", [7] = "", [8] = "", - [9] = "", [10] = "", [11] = "", [12] = "", - [13] = "", [14] = "", [15] = "", [16] = "", - } + return { + [1] = "", [2] = "", [3] = "", [4] = "", + [5] = "", [6] = "", [7] = "", [8] = "", + [9] = "", [10] = "", [11] = "", [12] = "", + [13] = "", [14] = "", [15] = "", [16] = "", + } end -- This function checks for "female" text on the texture name local function is_female_texture(textures) - for i = 1, #textures do - if string.find(textures[i], "female") ~= nil then - return true + for i = 1, #textures do + if string.find(textures[i], "female") ~= nil then + return true + end + end + return false +end + +function npc.assign_sex_from_texture(self) + if is_female_texture(self.base_texture) then + return npc.FEMALE + else + return npc.MALE end - end - return false end local function get_random_texture(sex, age) - local textures = {} - local filtered_textures = {} + local textures = {} + local filtered_textures = {} -- Find textures by sex and age - if age == npc.age.adult then + if age == npc.age.adult then --minetest.log("Registered: "..dump(minetest.registered_entities["advanced_npc:npc"])) textures = minetest.registered_entities["advanced_npc:npc"].texture_list - elseif age == npc.age.child then + elseif age == npc.age.child then textures = minetest.registered_entities["advanced_npc:npc"].child_texture - end - - for i = 1, #textures do - local current_texture = textures[i][1] - if (sex == npc.MALE - and string.find(current_texture, sex) - and not string.find(current_texture, npc.FEMALE)) - or (sex == npc.FEMALE - and string.find(current_texture, sex)) then - table.insert(filtered_textures, current_texture) end - end - -- Check if filtered textures is empty - if filtered_textures == {} then - return textures[1][1] - end + for i = 1, #textures do + local current_texture = textures[i][1] + if (sex == npc.MALE + and string.find(current_texture, sex) + and not string.find(current_texture, npc.FEMALE)) + or (sex == npc.FEMALE + and string.find(current_texture, sex)) then + table.insert(filtered_textures, current_texture) + end + end - return filtered_textures[math.random(1,#filtered_textures)] + -- Check if filtered textures is empty + if filtered_textures == {} then + return textures[1][1] + end + + return filtered_textures[math.random(1,#filtered_textures)] end function npc.get_random_texture_from_array(age, sex, textures) @@ -160,75 +168,77 @@ function npc.get_random_texture_from_array(age, sex, textures) and string.find(current_texture, sex) and not string.find(current_texture, npc.FEMALE) and ((age == npc.age.adult - and not string.find(current_texture, npc.age.child)) - or (age == npc.age.child - and string.find(current_texture, npc.age.child)) - ) - ) - or (sex == npc.FEMALE + and not string.find(current_texture, npc.age.child)) + or (age == npc.age.child + and string.find(current_texture, npc.age.child)) + ) + ) + or (sex == npc.FEMALE and string.find(current_texture, sex) and ((age == npc.age.adult - and not string.find(current_texture, npc.age.child)) - or (age == npc.age.child - and string.find(current_texture, npc.age.child)) - ) - ) then - table.insert(filtered_textures, current_texture) + and not string.find(current_texture, npc.age.child)) + or (age == npc.age.child + and string.find(current_texture, npc.age.child)) + ) + ) then + table.insert(filtered_textures, current_texture) end - end + end - -- Check if there are no textures - if #filtered_textures == 0 then - return nil - end + -- Check if there are no textures + if #filtered_textures == 0 then + -- Return whole array for re-evaluation + npc.log("DEBUG", "No textures found, returning original array") + return textures + end - return filtered_textures[math.random(1, #filtered_textures)] + return filtered_textures[math.random(1, #filtered_textures)] end -- Choose whether NPC can have relationships. Only 30% of NPCs -- cannot have relationships local function can_have_relationships(is_child) - -- Children can't have relationships - if is_child then - return false - end - local chance = math.random(1,10) - return chance > 3 + -- Children can't have relationships + if is_child then + return false + end + local chance = math.random(1,10) + return chance > 3 end -- Choose a maximum of two items that the NPC will have at spawn time -- These items are chosen from the favorite items list. local function choose_spawn_items(self) - local number_of_items_to_add = math.random(1, 2) - local number_of_items = #npc.FAVORITE_ITEMS[self.sex].phase1 + local number_of_items_to_add = math.random(1, 2) + local number_of_items = #npc.FAVORITE_ITEMS[self.sex].phase1 - for i = 1, number_of_items_to_add do - npc.add_item_to_inventory( - self, - npc.FAVORITE_ITEMS[self.sex].phase1[math.random(1, number_of_items)].item, - math.random(1,5) - ) - end - -- Add currency to the items spawned with. Will add 5-10 tier 3 - -- currency items - local currency_item_count = math.random(5, 10) - npc.add_item_to_inventory(self, npc.trade.prices.currency.tier3.string, currency_item_count) + for i = 1, number_of_items_to_add do + npc.add_item_to_inventory( + self, + npc.FAVORITE_ITEMS[self.sex].phase1[math.random(1, number_of_items)].item, + math.random(1,5) + ) + end + -- Add currency to the items spawned with. Will add 5-10 tier 3 + -- currency items + local currency_item_count = math.random(5, 10) + npc.add_item_to_inventory(self, npc.trade.prices.currency.tier3.string, currency_item_count) - -- For test - --npc.add_item_to_inventory(self, "default:tree", 10) - --npc.add_item_to_inventory(self, "default:cobble", 10) - --npc.add_item_to_inventory(self, "default:diamond", 2) - --npc.add_item_to_inventory(self, "default:mese_crystal", 2) - --npc.add_item_to_inventory(self, "flowers:rose", 2) - --npc.add_item_to_inventory(self, "advanced_npc:marriage_ring", 2) - --npc.add_item_to_inventory(self, "flowers:geranium", 2) - --npc.add_item_to_inventory(self, "mobs:meat", 2) - --npc.add_item_to_inventory(self, "mobs:leather", 2) - --npc.add_item_to_inventory(self, "default:sword_stone", 2) - --npc.add_item_to_inventory(self, "default:shovel_stone", 2) - --npc.add_item_to_inventory(self, "default:axe_stone", 2) + -- For test + --npc.add_item_to_inventory(self, "default:tree", 10) + --npc.add_item_to_inventory(self, "default:cobble", 10) + --npc.add_item_to_inventory(self, "default:diamond", 2) + --npc.add_item_to_inventory(self, "default:mese_crystal", 2) + --npc.add_item_to_inventory(self, "flowers:rose", 2) + --npc.add_item_to_inventory(self, "advanced_npc:marriage_ring", 2) + --npc.add_item_to_inventory(self, "flowers:geranium", 2) + --npc.add_item_to_inventory(self, "mobs:meat", 2) + --npc.add_item_to_inventory(self, "mobs:leather", 2) + --npc.add_item_to_inventory(self, "default:sword_stone", 2) + --npc.add_item_to_inventory(self, "default:shovel_stone", 2) + --npc.add_item_to_inventory(self, "default:axe_stone", 2) - --minetest.log("Initial inventory: "..dump(self.inventory)) + --minetest.log("Initial inventory: "..dump(self.inventory)) end -- Spawn function. Initializes all variables that the @@ -236,29 +246,29 @@ end function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) npc.log("INFO", "Initializing NPC at "..minetest.pos_to_string(pos)) - -- Get variables - local ent = entity - if not is_lua_entity then + -- Get variables + local ent = entity + if not is_lua_entity then ent = entity:get_luaentity() - end + end - -- Avoid NPC to be removed by mobs_redo API - ent.remove_ok = false + -- Avoid NPC to be removed by mobs_redo API + ent.remove_ok = false - -- Determine sex and age - -- If there's no previous NPC data, sex and age will be randomly chosen. - -- - Sex: Female or male will have each 50% of spawning - -- - Age: 90% chance of spawning adults, 10% chance of spawning children. - -- If there is previous data then: - -- - Sex: The unbalanced sex will get a 75% chance of spawning - -- - Example: If there's one male, then female will have 75% spawn chance. - -- - If there's male and female, then each have 50% spawn chance. - -- - Age: For each two adults, the chance of spawning a child next will be 50% - -- If there's a child for two adults, the chance of spawning a child goes to - -- 40% and keeps decreasing unless two adults have no child. + -- Determine sex and age + -- If there's no previous NPC data, sex and age will be randomly chosen. + -- - Sex: Female or male will have each 50% of spawning + -- - Age: 90% chance of spawning adults, 10% chance of spawning children. + -- If there is previous data then: + -- - Sex: The unbalanced sex will get a 75% chance of spawning + -- - Example: If there's one male, then female will have 75% spawn chance. + -- - If there's male and female, then each have 50% spawn chance. + -- - Age: For each two adults, the chance of spawning a child next will be 50% + -- If there's a child for two adults, the chance of spawning a child goes to + -- 40% and keeps decreasing unless two adults have no child. -- Use NPC stats if provided if npc_stats then - -- Default chances + -- Default chances local male_s, male_e = 0, 50 local female_s, female_e = 51, 100 local adult_s, adult_e = 0, 85 @@ -266,7 +276,7 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- Determine sex probabilities if npc_stats[npc.FEMALE].total > npc_stats[npc.MALE].total then male_e = 75 - female_s, female_e = 76, 100 + female_s, female_e = 76, 100 elseif npc_stats[npc.FEMALE].total < npc_stats[npc.MALE].total then male_e = 25 female_s, female_e = 26, 100 @@ -274,13 +284,13 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- Determine age probabilities if npc_stats["adult_total"] >= 2 then if npc_stats["adult_total"] % 2 == 0 - and (npc_stats["adult_total"] / 2 > npc_stats["child_total"]) then - child_s,child_e = 26, 100 - adult_e = 25 + and (npc_stats["adult_total"] / 2 > npc_stats["child_total"]) then + child_s,child_e = 26, 100 + adult_e = 25 else child_s, child_e = 61, 100 adult_e = 60 - end + end end -- Get sex and age based on the probabilities local sex_chance = math.random(1, 100) @@ -303,11 +313,11 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) ent.visual_size = { x = 0.65, y = 0.65 - } - ent.collisionbox = {-0.10,-0.50,-0.10, 0.10,0.40,0.10} - ent.is_child = true - -- For mobs_redo - ent.child = true + } + ent.collisionbox = {-0.10,-0.50,-0.10, 0.10,0.40,0.10} + ent.is_child = true + -- For mobs_redo + ent.child = true end -- Store the selected age ent.age = selected_age @@ -324,111 +334,98 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- Get sex based on texture. This is a 50% chance for -- each sex as there's same amount of textures for male and female. -- Do not spawn child as first NPC - if (is_female_texture(ent.base_texture)) then - ent.sex = npc.FEMALE - else - ent.sex = npc.MALE - end + ent.sex = npc.assign_sex_from_texture(ent) ent.age = npc.age.adult end - -- Nametag is initialized to blank - ent.nametag = "" - - -- Set name - ent.npc_name = get_random_name(ent.sex) - - -- Set ID - ent.npc_id = tostring(math.random(1000, 9999))..":"..ent.npc_name - - -- Initialize all gift data - ent.gift_data = { + -- Initialize all gift data + ent.gift_data = { -- Choose favorite items. Choose phase1 per default favorite_items = npc.relationships.select_random_favorite_items(ent.sex, "phase1"), -- Choose disliked items. Choose phase1 per default disliked_items = npc.relationships.select_random_disliked_items(ent.sex), - } + } - -- Flag that determines if NPC can have a relationship - ent.can_have_relationship = can_have_relationships(ent.is_child) + -- Flag that determines if NPC can have a relationship + ent.can_have_relationship = can_have_relationships(ent.is_child) - --ent.infotext = "Interested in relationships: "..dump(ent.can_have_relationship) + --ent.infotext = "Interested in relationships: "..dump(ent.can_have_relationship) - -- Flag to determine if NPC can receive gifts - ent.can_receive_gifts = ent.can_have_relationship + -- Flag to determine if NPC can receive gifts + ent.can_receive_gifts = ent.can_have_relationship - -- Initialize relationships object - ent.relationships = {} + -- Initialize relationships object + ent.relationships = {} - -- Determines if NPC is married or not - ent.is_married_to = nil + -- Determines if NPC is married or not + ent.is_married_to = nil - -- Initialize dialogues - ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent, "phase1") + -- Initialize dialogues + ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent, "phase1") - -- Declare NPC inventory - ent.inventory = initialize_inventory() + -- Declare NPC inventory + ent.inventory = initialize_inventory() - -- Choose items to spawn with - choose_spawn_items(ent) + -- Choose items to spawn with + choose_spawn_items(ent) - -- Flags: generic booleans or functions that help drive functionality - ent.flags = {} + -- Flags: generic booleans or functions that help drive functionality + ent.flags = {} - -- Declare trade data - ent.trader_data = { - -- Type of trader - trader_status = npc.trade.get_random_trade_status(), - -- Current buy offers - buy_offers = {}, - -- Current sell offers - sell_offers = {}, - -- Items to buy change timer - change_offers_timer = 0, - -- Items to buy change timer interval - change_offers_timer_interval = 60, - -- Trading list: a list of item names the trader is expected to trade in. - -- It is mostly related to its occupation. - -- If empty, the NPC will revert to casual trading - -- If not, it will try to sell those that it have, and buy the ones it not. - trade_list = { - sell = {}, - buy = {}, - both = {} - }, - -- Custom trade allows to specify more than one payment - -- and a custom prompt (instead of the usual buy or sell prompts) - custom_trades = {} - } + -- Declare trade data + ent.trader_data = { + -- Type of trader + trader_status = npc.trade.get_random_trade_status(), + -- Current buy offers + buy_offers = {}, + -- Current sell offers + sell_offers = {}, + -- Items to buy change timer + change_offers_timer = 0, + -- Items to buy change timer interval + change_offers_timer_interval = 60, + -- Trading list: a list of item names the trader is expected to trade in. + -- It is mostly related to its occupation. + -- If empty, the NPC will revert to casual trading + -- If not, it will try to sell those that it have, and buy the ones it not. + trade_list = { + sell = {}, + buy = {}, + both = {} + }, + -- Custom trade allows to specify more than one payment + -- and a custom prompt (instead of the usual buy or sell prompts) + custom_trades = {} + } - -- Initialize trading offers for NPC - --npc.trade.generate_trade_offers_by_status(ent) - -- if ent.trader_data.trader_status == npc.trade.CASUAL then - -- select_casual_trade_offers(ent) - -- end + -- Initialize trading offers for NPC + --npc.trade.generate_trade_offers_by_status(ent) + -- if ent.trader_data.trader_status == npc.trade.CASUAL then + -- select_casual_trade_offers(ent) + -- end - -- Actions data - 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 - -- Default is 1. This can be changed via actions - action_interval = npc.actions.default_interval, - -- Avoid the execution of the action timer - action_timer_lock = false, - -- Defines the state of the current action - current_action_state = npc.action_state.none, - -- Store information about action on state before lock - state_before_lock = { - -- State of the mobs_redo API - freeze = false, - -- State of execution - action_state = npc.action_state.none, - -- Action executed while on lock - interrupted_action = {} - }, + -- Actions data + 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 + -- Default is 1. This can be changed via actions + action_interval = npc.actions.default_interval, + -- Avoid the execution of the action timer + action_timer_lock = false, + -- Defines the state of the current action + current_action_state = npc.action_state.none, + -- Store information about action on state before lock + state_before_lock = { + -- State of the mobs_redo API + freeze = false, + -- State of execution + action_state = npc.action_state.none, + -- Action executed while on lock + interrupted_action = {} + }, -- Walking variables -- required for implementing accurate movement code walking = { -- Defines whether NPC is walking to specific position or not @@ -440,18 +437,18 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- relative to the last position target_pos = {} } - } + } - -- This flag is checked on every step. If it is true, the rest of - -- Mobs Redo API is not executed - ent.freeze = nil + -- This flag is checked on every step. If it is true, the rest of + -- Mobs Redo API is not executed + ent.freeze = nil - -- 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 = {} + -- 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 = {} - -- Schedule data - ent.schedules = { + -- Schedule data + ent.schedules = { -- Flag to enable or disable the schedules functionality enabled = true, -- Lock for when executing a schedule @@ -470,19 +467,26 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) -- The following holds the check parameters provided by the -- current schedule current_check_params = {} - } + } - -- If occupation name given, override properties with - -- occupation values and initialize schedules - --minetest.log("Entity age: "..dump(ent.age)..", afult? "..dump(ent.age==npc.age.adult)) - npc.log("INFO", "Overriding NPC values with occupation: "..dump(occupation_name)) - if occupation_name and occupation_name ~= "" and ent.age == npc.age.adult then - npc.occupations.initialize_occupation_values(ent, occupation_name) - end + -- If occupation name given, override properties with + -- occupation values and initialize schedules + if occupation_name and occupation_name ~= "" and ent.age == npc.age.adult then + npc.occupations.initialize_occupation_values(ent, occupation_name) + end - -- TODO: Remove this - do inside occupation - -- Dedicated trade test - ent.trader_data.trade_list.both = { + -- Nametag is initialized to blank + ent.nametag = "" + + -- Set name + ent.npc_name = get_random_name(ent.sex) + + -- Set ID + ent.npc_id = tostring(math.random(1000, 9999))..":"..ent.npc_name + + -- TODO: Remove this - do inside occupation + -- Dedicated trade test + ent.trader_data.trade_list.both = { ["default:tree"] = {}, ["default:cobble"] = {}, ["default:wood"] = {}, @@ -496,43 +500,43 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats, occupation_name) ["default:sword_stone"] = {}, ["default:shovel_stone"] = {}, ["default:axe_stone"] = {} - } + } - -- Generate trade offers - npc.trade.generate_trade_offers_by_status(ent) + -- Generate trade offers + npc.trade.generate_trade_offers_by_status(ent) - -- Add a custom trade offer - -- 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"}) - -- table.insert(ent.trader_data.custom_trades, offer1) - --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"}) - --table.insert(ent.trader_data.custom_trades, offer2) + -- Add a custom trade offer + -- 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"}) + -- table.insert(ent.trader_data.custom_trades, offer1) + --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"}) + --table.insert(ent.trader_data.custom_trades, offer2) - -- Set initialized flag on + -- Set initialized flag on ent.initialized = true - --npc.log("WARNING", "Spawned entity: "..dump(ent)) - npc.log("INFO", "Successfully initialized NPC with name "..dump(ent.npc_name) - ..", sex: "..ent.sex..", is child: "..dump(ent.is_child) - ..", texture: "..dump(ent.textures)) - -- Refreshes entity - ent.object:set_properties(ent) + --npc.log("WARNING", "Spawned entity: "..dump(ent)) + npc.log("INFO", "Successfully initialized NPC with name "..dump(ent.npc_name) + ..", sex: "..ent.sex..", is child: "..dump(ent.is_child) + ..", texture: "..dump(ent.textures)) + -- Refreshes entity + ent.object:set_properties(ent) end --------------------------------------------------------------------------------------- -- Trading functions --------------------------------------------------------------------------------------- function npc.generate_trade_list_from_inventory(self) - local list = {} - for i = 1, #self.inventory do - list[npc.get_item_name(self.inventory[i])] = {} - end - self.trader_data.trade_list.both = list + local list = {} + for i = 1, #self.inventory do + list[npc.get_item_name(self.inventory[i])] = {} + end + self.trader_data.trade_list.both = list end function npc.set_trading_status(self, status) - -- Set status - self.trader_data.trader_status = status - -- Re-generate trade offers - npc.trade.generate_trade_offers_by_status(self) + -- Set status + self.trader_data.trader_status = status + -- Re-generate trade offers + npc.trade.generate_trade_offers_by_status(self) end --------------------------------------------------------------------------------------- @@ -543,109 +547,109 @@ end -- Utility function to get item name from a string function npc.get_item_name(item_string) - return ItemStack(item_string):get_name() + return ItemStack(item_string):get_name() end -- Utility function to get item count from a string function npc.get_item_count(item_string) - return ItemStack(item_string):get_count() + return ItemStack(item_string):get_count() end -- Add an item to inventory. Returns true if add successful -- These function can be used to give items to other NPCs -- given that the "self" variable can be any NPC function npc.add_item_to_inventory(self, item_name, count) - -- Check if NPC already has item - local existing_item = npc.inventory_contains(self, item_name) - if existing_item ~= nil and existing_item.item_string ~= nil then - -- NPC already has item. Get count and see - local existing_count = npc.get_item_count(existing_item.item_string) - if (existing_count + count) < npc.INVENTORY_ITEM_MAX_STACK then - -- Set item here - self.inventory[existing_item.slot] = - npc.get_item_name(existing_item.item_string).." "..tostring(existing_count + count) - return true - else - --Find next free slot - for i = 1, #self.inventory do - if self.inventory[i] == "" then - -- Found slot, set item - self.inventory[i] = - item_name.." "..tostring((existing_count + count) - npc.INVENTORY_ITEM_MAX_STACK) - return true + -- Check if NPC already has item + local existing_item = npc.inventory_contains(self, item_name) + if existing_item ~= nil and existing_item.item_string ~= nil then + -- NPC already has item. Get count and see + local existing_count = npc.get_item_count(existing_item.item_string) + if (existing_count + count) < npc.INVENTORY_ITEM_MAX_STACK then + -- Set item here + self.inventory[existing_item.slot] = + npc.get_item_name(existing_item.item_string).." "..tostring(existing_count + count) + return true + else + --Find next free slot + for i = 1, #self.inventory do + if self.inventory[i] == "" then + -- Found slot, set item + self.inventory[i] = + item_name.." "..tostring((existing_count + count) - npc.INVENTORY_ITEM_MAX_STACK) + return true + end + end + -- No free slot found + return false end - end - -- No free slot found - return false + else + -- Find a free slot + for i = 1, #self.inventory do + if self.inventory[i] == "" then + -- Found slot, set item + self.inventory[i] = item_name.." "..tostring(count) + return true + end + end + -- No empty slot found + return false end - else - -- Find a free slot - for i = 1, #self.inventory do - if self.inventory[i] == "" then - -- Found slot, set item - self.inventory[i] = item_name.." "..tostring(count) - return true - end - end - -- No empty slot found - return false - end end -- Same add method but with itemstring for convenience function npc.add_item_to_inventory_itemstring(self, item_string) - local item_name = npc.get_item_name(item_string) - local item_count = npc.get_item_count(item_string) - npc.add_item_to_inventory(self, item_name, item_count) + local item_name = npc.get_item_name(item_string) + local item_count = npc.get_item_count(item_string) + npc.add_item_to_inventory(self, item_name, item_count) end -- Checks if an item is contained in the inventory. Returns -- the item string or nil if not found function npc.inventory_contains(self, item_name) - for key,value in pairs(self.inventory) do - if value ~= "" and string.find(value, item_name) then - return {slot=key, item_string=value} + for key,value in pairs(self.inventory) do + if value ~= "" and string.find(value, item_name) then + return {slot=key, item_string=value} + end end - end - -- Item not found - return nil + -- Item not found + return nil end -- Removes the item from an NPC inventory and returns the item -- with its count (as a string, e.g. "default:apple 2"). Returns -- nil if unable to get the item. function npc.take_item_from_inventory(self, item_name, count) - local existing_item = npc.inventory_contains(self, item_name) - if existing_item ~= nil then - -- Found item - local existing_count = npc.get_item_count(existing_item.item_string) - local new_count = existing_count - if existing_count - count < 0 then - -- Remove item first - self.inventory[existing_item.slot] = "" - -- TODO: Support for retrieving from next stack. Too complicated - -- and honestly might be unecessary. - return item_name.." "..tostring(new_count) + local existing_item = npc.inventory_contains(self, item_name) + if existing_item ~= nil then + -- Found item + local existing_count = npc.get_item_count(existing_item.item_string) + local new_count = existing_count + if existing_count - count < 0 then + -- Remove item first + self.inventory[existing_item.slot] = "" + -- TODO: Support for retrieving from next stack. Too complicated + -- and honestly might be unecessary. + return item_name.." "..tostring(new_count) + else + new_count = existing_count - count + if new_count == 0 then + self.inventory[existing_item.slot] = "" + else + self.inventory[existing_item.slot] = item_name.." "..new_count + end + return item_name.." "..tostring(count) + end else - new_count = existing_count - count - if new_count == 0 then - self.inventory[existing_item.slot] = "" - else - self.inventory[existing_item.slot] = item_name.." "..new_count - end - return item_name.." "..tostring(count) + -- Not able to take item because not found + return nil end - else - -- Not able to take item because not found - return nil - end end -- Same take method but with itemstring for convenience function npc.take_item_from_inventory_itemstring(self, item_string) - local item_name = npc.get_item_name(item_string) - local item_count = npc.get_item_count(item_string) - npc.take_item_from_inventory(self, item_name, item_count) + local item_name = npc.get_item_name(item_string) + local item_count = npc.get_item_count(item_string) + npc.take_item_from_inventory(self, item_name, item_count) end --------------------------------------------------------------------------------------- @@ -654,15 +658,15 @@ end -- TODO: Consider removing them as they are pretty simple and straight forward. -- Generic variables or function that help drive some functionality for the NPC. function npc.add_flag(self, flag_name, value) - self.flags[flag_name] = value + self.flags[flag_name] = value end function npc.update_flag(self, flag_name, value) - self.flags[flag_name] = value + self.flags[flag_name] = value end function npc.get_flag(self, flag_name) - return self.flags[flag_name] + return self.flags[flag_name] end --------------------------------------------------------------------------------------- @@ -670,11 +674,11 @@ end --------------------------------------------------------------------------------------- function npc.start_dialogue(self, clicker, show_married_dialogue) - -- Call dialogue function as normal - npc.dialogue.start_dialogue(self, clicker, show_married_dialogue) + -- Call dialogue function as normal + npc.dialogue.start_dialogue(self, clicker, show_married_dialogue) - -- Check and update relationship if needed - npc.relationships.dialogue_relationship_update(self, clicker) + -- Check and update relationship if needed + npc.relationships.dialogue_relationship_update(self, clicker) end @@ -684,22 +688,22 @@ end -- 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) - local action_entry = {action=action, args=arguments, is_task=false} - table.insert(self.actions.queue, action_entry) + local action_entry = {action=action, args=arguments, is_task=false} + table.insert(self.actions.queue, action_entry) end -- This function adds task actions in-place, as opposed to -- at the end of the queue. This allows for continued order function npc.add_task(self, task, args) - local action_entry = {action=task, args=args, is_task=true} - table.insert(self.actions.queue, action_entry) + local action_entry = {action=task, args=args, is_task=true} + table.insert(self.actions.queue, action_entry) end -- This function removes the first action in the action queue -- and then executes it function npc.execute_action(self) -- Check if an action was interrupted - if self.actions.current_action_state == npc.action_state.interrupted then + if self.actions.current_action_state == npc.action_state.interrupted then npc.log("DEBUG", "Re-inserting interrupted action for NPC: '"..dump(self.npc_name).."': "..dump(self.actions.state_before_lock.interrupted_action)) -- Insert into queue the interrupted action table.insert(self.actions.queue, 1, self.actions.state_before_lock.interrupted_action) @@ -707,31 +711,31 @@ function npc.execute_action(self) self.actions.state_before_lock.interrupted_action = {} -- Clear the position self.actions.state_before_lock.pos = {} - end - local result = nil - if table.getn(self.actions.queue) == 0 then + end + local result = nil + if table.getn(self.actions.queue) == 0 then -- Set state to none self.actions.current_action_state = npc.action_state.none -- Keep state the same if there are no more actions in actions queue return self.freeze - end - local action_obj = self.actions.queue[1] - -- Check if action is null - if action_obj.action == nil then + end + local action_obj = self.actions.queue[1] + -- Check if action is null + if action_obj.action == nil then return - end - -- Check if action is an schedule check - if action_obj.action == "schedule_check" then - -- Execute schedule check - npc.schedule_check(self) - -- Remove table entry - table.remove(self.actions.queue, 1) - -- Return - return false - end - -- If the entry is a task, then push all this new operations in - -- stack fashion - if action_obj.is_task == true then + end + -- Check if action is an schedule check + if action_obj.action == "schedule_check" then + -- Execute schedule check + npc.schedule_check(self) + -- Remove table entry + table.remove(self.actions.queue, 1) + -- Return + return false + end + -- If the entry is a task, then push all this new operations in + -- stack fashion + if action_obj.is_task == true then npc.log("DEBUG", "Executing task for NPC '"..dump(self.npc_name).."': "..dump(action_obj)) -- Backup current queue local backup_queue = self.actions.queue @@ -745,9 +749,9 @@ function npc.execute_action(self) -- After all new actions has been added by task, add the previously -- queued actions back for i = 1, #backup_queue do - table.insert(self.actions.queue, backup_queue[i]) + table.insert(self.actions.queue, backup_queue[i]) end - else + else npc.log("DEBUG", "Executing action for NPC '"..dump(self.npc_name).."': "..dump(action_obj)) -- Store the action that is being executed self.actions.state_before_lock.interrupted_action = action_obj @@ -759,64 +763,64 @@ function npc.execute_action(self) table.remove(self.actions.queue, 1) -- Set state self.actions.current_action_state = npc.action_state.executing - end - return result + end + return result end function npc.lock_actions(self) - -- Avoid re-locking if already locked - if self.actions.action_timer_lock == true then - return - end - - local pos = self.object:getpos() - - if self.freeze == false then - -- Round current pos to avoid the NPC being stopped on positions - -- where later on can't walk to the correct positions - -- Choose which position is to be taken as start position - if self.actions.state_before_lock.pos ~= {} then - pos = vector.round(self.actions.state_before_lock.pos) - else - pos = vector.round(self.object:getpos()) + -- Avoid re-locking if already locked + if self.actions.action_timer_lock == true then + return end - pos.y = self.object:getpos().y - end - -- Stop NPC - npc.actions.execute(self, npc.actions.cmd.STAND, {pos=pos}) - -- Avoid all timer execution - self.actions.action_timer_lock = true - -- Reset timer so that it has some time after interaction is done - self.actions.action_timer = 0 - -- Check if there are is an action executing - if self.actions.current_action_state == npc.action_state.executing - and self.freeze == false then - -- Store the current action state - self.actions.state_before_lock.action_state = self.actions.current_action_state - -- Set current action state to interrupted - self.actions.current_action_state = npc.action_state.interrupted - end - -- Store the current freeze variable - self.actions.state_before_lock.freeze = self.freeze - -- Freeze mobs_redo API - self.freeze = false - npc.log("DEBUG", "Locking NPC "..dump(self.npc_id).." actions") + local pos = self.object:getpos() + + if self.freeze == false then + -- Round current pos to avoid the NPC being stopped on positions + -- where later on can't walk to the correct positions + -- Choose which position is to be taken as start position + if self.actions.state_before_lock.pos ~= {} then + pos = vector.round(self.actions.state_before_lock.pos) + else + pos = vector.round(self.object:getpos()) + end + pos.y = self.object:getpos().y + end + -- Stop NPC + npc.actions.execute(self, npc.actions.cmd.STAND, {pos=pos}) + -- Avoid all timer execution + self.actions.action_timer_lock = true + -- Reset timer so that it has some time after interaction is done + self.actions.action_timer = 0 + -- Check if there are is an action executing + if self.actions.current_action_state == npc.action_state.executing + and self.freeze == false then + -- Store the current action state + self.actions.state_before_lock.action_state = self.actions.current_action_state + -- Set current action state to interrupted + self.actions.current_action_state = npc.action_state.interrupted + end + -- Store the current freeze variable + self.actions.state_before_lock.freeze = self.freeze + -- Freeze mobs_redo API + self.freeze = false + + npc.log("DEBUG", "Locking NPC "..dump(self.npc_id).." actions") end function npc.unlock_actions(self) - -- Allow timers to execute - self.actions.action_timer_lock = false - -- Restore the value of self.freeze - self.freeze = self.actions.state_before_lock.freeze + -- Allow timers to execute + self.actions.action_timer_lock = false + -- Restore the value of self.freeze + self.freeze = self.actions.state_before_lock.freeze - if table.getn(self.actions.queue) == 0 then - -- Allow mobs_redo API to execute since action queue is empty - self.freeze = true - end + if table.getn(self.actions.queue) == 0 then + -- Allow mobs_redo API to execute since action queue is empty + self.freeze = true + end - npc.log("DEBUG", "Unlocked NPC "..dump(self.npc_id).." actions") + npc.log("DEBUG", "Unlocked NPC "..dump(self.npc_id).." actions") end --------------------------------------------------------------------------------------- @@ -828,20 +832,20 @@ end -- The API is as following: a schedule can be created for a specific date or for a -- day of the week. A date is a string in the format MM:DD npc.schedule_types = { - ["generic"] = "generic", - ["date_based"] = "date_based" + ["generic"] = "generic", + ["date_based"] = "date_based" } npc.schedule_properties = { - put_item = "put_item", - put_multiple_items = "put_multiple_items", - take_item = "take_item", - trader_status = "trader_status", - can_receive_gifts = "can_receive_gifts" + put_item = "put_item", + put_multiple_items = "put_multiple_items", + take_item = "take_item", + trader_status = "trader_status", + can_receive_gifts = "can_receive_gifts" } local function get_time_in_hours() - return minetest.get_timeofday() * 24 + return minetest.get_timeofday() * 24 end -- Create a schedule on a NPC. @@ -862,31 +866,31 @@ end -- string of the format "MM:DD". If it already -- exists, function retuns nil function npc.create_schedule(self, schedule_type, date) - if schedule_type == npc.schedule_types.generic then - -- Check that there are no more than 7 schedules - if #self.schedules.generic == 7 then - -- Unable to add schedule - return nil - elseif #self.schedules.generic < 7 then - -- Check schedule doesn't exists already - if self.schedules.generic[date] == nil then - -- Add schedule - self.schedules.generic[date] = {} - else - -- Schedule already present - return nil - end + if schedule_type == npc.schedule_types.generic then + -- Check that there are no more than 7 schedules + if #self.schedules.generic == 7 then + -- Unable to add schedule + return nil + elseif #self.schedules.generic < 7 then + -- Check schedule doesn't exists already + if self.schedules.generic[date] == nil then + -- Add schedule + self.schedules.generic[date] = {} + else + -- Schedule already present + return nil + end + end + elseif schedule_type == npc.schedule_types.date then + -- Check schedule doesn't exists already + if self.schedules.date_based[date] == nil then + -- Add schedule + self.schedules.date_based[date] = {} + else + -- Schedule already present + return nil + end end - elseif schedule_type == npc.schedule_types.date then - -- Check schedule doesn't exists already - if self.schedules.date_based[date] == nil then - -- Add schedule - self.schedules.date_based[date] = {} - else - -- Schedule already present - return nil - end - end end function npc.delete_schedule(self, schedule_type, date) @@ -931,7 +935,7 @@ function npc.update_schedule_entry(self, schedule_type, date, time, check, actio if self.schedules[schedule_type][date] ~= nil then -- Check that a schedule entry for that time exists if self.schedules[schedule_type][date][time] ~= nil then - -- Set the new actions + -- Set the new actions if check == nil then self.schedules[schedule_type][date][time] = actions else @@ -973,14 +977,14 @@ function npc.schedule_change_property(self, property, args) for i = 1, #itemlist do local itemlist_entry = itemlist[i] local current_itemstring = itemlist[i].name - if itemlist_entry.random == true then + if itemlist_entry.random == true then current_itemstring = current_itemstring - .." "..dump(math.random(itemlist_entry.min, itemlist_entry.max)) - else + .." "..dump(math.random(itemlist_entry.min, itemlist_entry.max)) + else current_itemstring = current_itemstring.." "..tostring(itemlist_entry.count) - end - -- Add item to inventory - npc.add_item_to_inventory_itemstring(self, current_itemstring) + end + -- Add item to inventory + npc.add_item_to_inventory_itemstring(self, current_itemstring) end elseif property == npc.schedule_properties.take_item then local itemstring = args.itemstring @@ -990,7 +994,7 @@ function npc.schedule_change_property(self, property, args) local value = args.can_receive_gifts -- Set status self.can_receive_gifts = value - end + end end function npc.add_schedule_check(self) @@ -1029,7 +1033,7 @@ function npc.schedule_check(self) -- Get actions related to node and enqueue them for i = 1, #actions[node.name] do local args = {} - local action = nil + local action -- Calculate arguments for the following supported actions: -- - Dig -- - Place @@ -1107,7 +1111,7 @@ function npc.schedule_check(self) end -- Enqueue next schedule check if self.schedules.current_check_params.execution_count - < self.schedules.current_check_params.execution_times then + < self.schedules.current_check_params.execution_times then npc.add_schedule_check() end -- Nodes found @@ -1120,7 +1124,7 @@ function npc.schedule_check(self) end -- Enqueue next schedule check if self.schedules.current_check_params.execution_count - < self.schedules.current_check_params.execution_times then + < self.schedules.current_check_params.execution_times then npc.add_schedule_check() end -- No nodes found @@ -1145,8 +1149,8 @@ mobs:register_mob("advanced_npc:npc", { hp_max = 20, armor = 100, collisionbox = {-0.20,0,-0.20, 0.20,1.8,0.20}, - --collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20}, - --collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35}, + --collisionbox = {-0.20,-1.0,-0.20, 0.20,0.8,0.20}, + --collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35}, visual = "mesh", mesh = "character.b3d", drawtype = "front", @@ -1157,22 +1161,22 @@ mobs:register_mob("advanced_npc:npc", { {"npc_male4.png"}, {"npc_male5.png"}, {"npc_male6.png"}, - {"npc_male7.png"}, + {"npc_male7.png"}, {"npc_male8.png"}, {"npc_male9.png"}, {"npc_male10.png"}, {"npc_male11.png"}, {"npc_male12.png"}, - {"npc_male13.png"}, + {"npc_male13.png"}, {"npc_male14.png"}, {"npc_male15.png"}, {"npc_female1.png"}, -- female by nuttmeg20 - {"npc_female2.png"}, + {"npc_female2.png"}, {"npc_female3.png"}, {"npc_female4.png"}, {"npc_female5.png"}, {"npc_female6.png"}, - {"npc_female7.png"}, + {"npc_female7.png"}, {"npc_female8.png"}, {"npc_female9.png"}, {"npc_female10.png"}, @@ -1180,8 +1184,8 @@ mobs:register_mob("advanced_npc:npc", { }, child_texture = { {"npc_child_male1.png"}, - {"npc_child_female1.png"}, - }, + {"npc_child_female1.png"}, + }, makes_footstep_sound = true, sounds = {}, -- Added walk chance @@ -1219,80 +1223,80 @@ mobs:register_mob("advanced_npc:npc", { }, on_rightclick = function(self, clicker) - -- Rotate NPC toward its clicker - npc.dialogue.rotate_npc_to_player(self) + -- Rotate NPC toward its clicker + npc.dialogue.rotate_npc_to_player(self) - -- Get information from clicker + -- Get information from clicker local item = clicker:get_wielded_item() local name = clicker:get_player_name() - npc.log("DEBUG", "Right-clicked NPC: "..dump(self)) + npc.log("DEBUG", "Right-clicked NPC: "..dump(self)) - -- Receive gift or start chat. If player has no item in hand - -- then it is going to start chat directly - --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())) - if self.can_have_relationship - and self.can_receive_gifts - and item:to_table() ~= nil then - -- Get item name - local item = minetest.registered_items[item:get_name()] - local item_name = item.description + -- Receive gift or start chat. If player has no item in hand + -- then it is going to start chat directly + --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())) + if self.can_have_relationship + and self.can_receive_gifts + and item:to_table() ~= nil then + -- Get item name + local item = minetest.registered_items[item:get_name()] + local item_name = item.description - -- Show dialogue to confirm that player is giving item as gift - npc.dialogue.show_yes_no_dialogue( - self, - "Do you want to give "..item_name.." to "..self.npc_name.."?", - npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name, - function() - npc.relationships.receive_gift(self, clicker) - end, - npc.dialogue.NEGATIVE_ANSWER_LABEL, - function() - npc.start_dialogue(self, clicker, true) - end, - name - ) - else - npc.start_dialogue(self, clicker, true) - end - end, + -- Show dialogue to confirm that player is giving item as gift + npc.dialogue.show_yes_no_dialogue( + self, + "Do you want to give "..item_name.." to "..self.npc_name.."?", + npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name, + function() + npc.relationships.receive_gift(self, clicker) + end, + npc.dialogue.NEGATIVE_ANSWER_LABEL, + function() + npc.start_dialogue(self, clicker, true) + end, + name + ) + else + npc.start_dialogue(self, clicker, true) + end + end, do_custom = function(self, dtime) if self.initialized == nil then -- Initialize NPC if spawned using the spawn egg built in from - -- mobs_redo. This functionality will be removed in the future in - -- favor of a better manual spawning method with customization - npc.log("WARNING", "Initializing NPC from entity step. This message should only be appearing if an NPC is being spawned from inventory with egg!") - npc.initialize(self, self.object:getpos(), true) - self.tamed = false - self.owner = nil + -- mobs_redo. This functionality will be removed in the future in + -- favor of a better manual spawning method with customization + npc.log("WARNING", "Initializing NPC from entity step. This message should only be appearing if an NPC is being spawned from inventory with egg!") + npc.initialize(self, self.object:getpos(), true) + self.tamed = false + self.owner = nil else - -- NPC is initialized, check other variables - -- Check child texture issues - if self.is_child then + -- NPC is initialized, check other variables + -- Check child texture issues + if self.is_child then -- Check texture npc.texture_check.timer = npc.texture_check.timer + dtime if npc.texture_check.timer > npc.texture_check.interval then - -- Reset timer + -- Reset timer npc.texture_check.timer = 0 - -- Set hornytimer to zero every 60 seconds so that children - -- don't grow automatically - self.hornytimer = 0 - -- Set correct textures - self.texture = {self.selected_texture} - self.base_texture = {self.selected_texture} - self.object:set_properties(self) - npc.log("WARNING", "Corrected textures on NPC child "..dump(self.npc_name)) - -- Set interval to large interval so this code isn't called frequently - npc.texture_check.interval = 60 + -- Set hornytimer to zero every 60 seconds so that children + -- don't grow automatically + self.hornytimer = 0 + -- Set correct textures + self.texture = {self.selected_texture} + self.base_texture = {self.selected_texture} + self.object:set_properties(self) + npc.log("WARNING", "Corrected textures on NPC child "..dump(self.npc_name)) + -- Set interval to large interval so this code isn't called frequently + npc.texture_check.interval = 60 end - end + end end -- Timer function for casual traders to reset their trade offers self.trader_data.change_offers_timer = self.trader_data.change_offers_timer + dtime -- Check if time has come to change offers if self.trader_data.trader_status == npc.trade.CASUAL and - self.trader_data.change_offers_timer >= self.trader_data.change_offers_timer_interval then + self.trader_data.change_offers_timer >= self.trader_data.change_offers_timer_interval then -- Reset timer self.trader_data.change_offers_timer = 0 -- Re-select casual trade offers @@ -1304,31 +1308,31 @@ mobs:register_mob("advanced_npc:npc", { local relationship = self.relationships[i] -- Gift timer check if relationship.gift_timer_value < relationship.gift_interval then - relationship.gift_timer_value = relationship.gift_timer_value + dtime + relationship.gift_timer_value = relationship.gift_timer_value + dtime elseif relationship.talk_timer_value < relationship.gift_interval then - -- Relationship talk timer - only allows players to increase relationship - -- by talking on the same intervals as gifts - relationship.talk_timer_value = relationship.talk_timer_value + dtime + -- Relationship talk timer - only allows players to increase relationship + -- by talking on the same intervals as gifts + relationship.talk_timer_value = relationship.talk_timer_value + dtime else - -- Relationship decrease timer - if relationship.relationship_decrease_timer_value - < relationship.relationship_decrease_interval then + -- Relationship decrease timer + if relationship.relationship_decrease_timer_value + < relationship.relationship_decrease_interval then relationship.relationship_decrease_timer_value = - relationship.relationship_decrease_timer_value + dtime - else + relationship.relationship_decrease_timer_value + dtime + else -- Check if married to decrease half if relationship.phase == "phase6" then - -- Avoid going below the marriage phase limit - if (relationship.points - 0.5) >= - npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then + -- Avoid going below the marriage phase limit + if (relationship.points - 0.5) >= + npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then relationship.points = relationship.points - 0.5 - end + end else - relationship.points = relationship.points - 1 + relationship.points = relationship.points - 1 end relationship.relationship_decrease_timer_value = 0 --minetest.log(dump(self)) - end + end end end @@ -1339,78 +1343,78 @@ mobs:register_mob("advanced_npc:npc", { 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 + self.actions.action_timer = 0 -- Check if NPC is walking if self.actions.walking.is_walking == true then -- Move NPC to expected position to ensure not getting lost local pos = self.actions.walking.target_pos self.object:moveto({x=pos.x, y=pos.y-0.5, z=pos.z}) - end - -- Execute action - self.freeze = npc.execute_action(self) - -- Check if there are still remaining actions in the queue - if self.freeze == nil and table.getn(self.actions.queue) > 0 then + end + -- Execute action + self.freeze = npc.execute_action(self) + -- Check if there are still remaining actions in the queue + if self.freeze == nil and table.getn(self.actions.queue) > 0 then self.freeze = false - end + end end end - -- Schedule timer - -- Check if schedules are enabled - if self.schedules.enabled == true then - -- Get time of day - local time = get_time_in_hours() - -- Check if time is an hour - if ((time % 1) < dtime) and self.schedules.lock == false then - -- Activate lock to avoid more than one entry to this code - self.schedules.lock = true - -- Get integer part of time - time = (time) - (time % 1) - -- Check if there is a schedule entry for this time - -- Note: Currently only one schedule is supported, for day 0 - minetest.log("Time: "..dump(time)) - local schedule = self.schedules.generic[0] - if schedule ~= nil then - -- Check if schedule for this time exists - if schedule[time] ~= nil then - npc.log("DEBUG", "Adding actions to action queue") - -- Add to action queue all actions on schedule - for i = 1, #schedule[time] do - -- Check if schedule has a check function - if schedule[time][i].check then - -- Add parameters for check function and run for first time - npc.log("DEBUG", "NPC "..dump(self.npc_name).." is starting check on "..minetest.pos_to_string(self.object:getpos())) - local check_params = schedule[time][i] - -- Calculates how many times check will be executed - local execution_times = check_params.count - if check_params.random_execution_times then - execution_times = math.random(check_params.min_count, check_params.max_count) - end - -- Set current parameters - self.schedules.current_check_params = { - range = check_params.range, - nodes = check_params.nodes, - actions = check_params.actions, - none_actions = check_params.none_actions, - execution_count = 0, - execution_times = execution_times - } - -- Execute check for the first time - npc.schedule_check(self) - else - npc.log("DEBUG", "Executing schedule entry: "..dump(schedule[time][i])) - -- Run usual schedule entry - -- Check chance - local execution_chance = math.random(1, 100) - if not schedule[time][i].chance or - (schedule[time][i].chance and execution_chance <= schedule[time][i].chance) then - -- Check if entry has dependency on other entry - local dependencies_met = nil - if schedule[time][i].depends then - dependencies_met = npc.utils.array_is_subset_of_array( - self.schedules.temp_executed_queue, - schedule[time][i].depends) - end + -- Schedule timer + -- Check if schedules are enabled + if self.schedules.enabled == true then + -- Get time of day + local time = get_time_in_hours() + -- Check if time is an hour + if ((time % 1) < dtime) and self.schedules.lock == false then + -- Activate lock to avoid more than one entry to this code + self.schedules.lock = true + -- Get integer part of time + time = (time) - (time % 1) + -- Check if there is a schedule entry for this time + -- Note: Currently only one schedule is supported, for day 0 + minetest.log("Time: "..dump(time)) + local schedule = self.schedules.generic[0] + if schedule ~= nil then + -- Check if schedule for this time exists + if schedule[time] ~= nil then + npc.log("DEBUG", "Adding actions to action queue") + -- Add to action queue all actions on schedule + for i = 1, #schedule[time] do + -- Check if schedule has a check function + if schedule[time][i].check then + -- Add parameters for check function and run for first time + npc.log("DEBUG", "NPC "..dump(self.npc_name).." is starting check on "..minetest.pos_to_string(self.object:getpos())) + local check_params = schedule[time][i] + -- Calculates how many times check will be executed + local execution_times = check_params.count + if check_params.random_execution_times then + execution_times = math.random(check_params.min_count, check_params.max_count) + end + -- Set current parameters + self.schedules.current_check_params = { + range = check_params.range, + nodes = check_params.nodes, + actions = check_params.actions, + none_actions = check_params.none_actions, + execution_count = 0, + execution_times = execution_times + } + -- Execute check for the first time + npc.schedule_check(self) + else + npc.log("DEBUG", "Executing schedule entry: "..dump(schedule[time][i])) + -- Run usual schedule entry + -- Check chance + local execution_chance = math.random(1, 100) + if not schedule[time][i].chance or + (schedule[time][i].chance and execution_chance <= schedule[time][i].chance) then + -- Check if entry has dependency on other entry + local dependencies_met = nil + if schedule[time][i].depends then + dependencies_met = npc.utils.array_is_subset_of_array( + self.schedules.temp_executed_queue, + schedule[time][i].depends) + end -- Check for dependencies being met if dependencies_met == nil or dependencies_met == true then @@ -1431,25 +1435,25 @@ mobs:register_mob("advanced_npc:npc", { table.insert(self.schedules.temp_executed_queue, i) end end - else - -- TODO: Change to debug - npc.log("DEBUG", "Skipping schedule entry for time "..dump(time)..": "..dump(schedule[time][i])) - end - end - end + else + -- TODO: Change to debug + npc.log("DEBUG", "Skipping schedule entry for time "..dump(time)..": "..dump(schedule[time][i])) + end + end + end -- Clear execution queue self.schedules.temp_executed_queue = {} npc.log("DEBUG", "New action queue: "..dump(self.actions)) - end - end - else - -- Check if lock can be released - if (time % 1) > dtime then - -- Release lock - self.schedules.lock = false - end - end - end + end + end + else + -- Check if lock can be released + if (time % 1) > dtime then + -- Release lock + self.schedules.lock = false + end + end + end return self.freeze end @@ -1486,6 +1490,6 @@ minetest.register_craftitem("advanced_npc:marriage_ring", { minetest.register_craft({ output = "advanced_npc:marriage_ring", recipe = { {"", "", ""}, - {"", "default:diamond", ""}, - {"", "default:gold_ingot", ""} }, + {"", "default:diamond", ""}, + {"", "default:gold_ingot", ""} }, }) diff --git a/occupations/occupations.lua b/occupations/occupations.lua index fa344fc..6065387 100644 --- a/occupations/occupations.lua +++ b/occupations/occupations.lua @@ -70,6 +70,9 @@ -- -- items and the specified count, or, a count between min and max -- -- when the entry contains random=true -- -- If left empty, it will initialize with random items. +-- initial_trader_status = "", +-- -- String that specifies initial trader value. Valid values are: +-- -- "casual", "trader", "none" -- schedules_entries = {}, -- -- This is a table of tables in the following format: -- -- { @@ -95,6 +98,9 @@ local occupations = {} -- The key is the name of the occupation. npc.occupations.registered_occupations = {} +-- Basic occupation name +npc.occupations.basic_name = "default_basic" + -- This is the basic occupation definition, this is for all NPCs that -- don't have a specific occupation. It serves as an example. npc.occupations.basic_def = { @@ -110,15 +116,15 @@ npc.occupations.basic_def = { [7] = { -- Get out of bed [1] = {task = npc.actions.cmd.USE_BED, args = { - pos = npc.places.PLACE_TYPE.BED.PRIMARY, - action = npc.actions.const.beds.GET_UP - } + pos = npc.places.PLACE_TYPE.BED.PRIMARY, + action = npc.actions.const.beds.GET_UP + } }, -- Walk to home inside [2] = {task = npc.actions.cmd.WALK_TO_POS, args = { - end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, - walkable = {} - }, + end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, + walkable = {} + }, chance = 75 }, -- Allow mobs_redo wandering @@ -128,9 +134,9 @@ npc.occupations.basic_def = { [8] = { -- Walk to outside of home [1] = {task = npc.actions.cmd.WALK_TO_POS, args = { - end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, - walkable = {} - }, + end_pos = npc.places.PLACE_TYPE.OTHER.HOME_OUTSIDE, + walkable = {} + }, chance = 75 }, -- Allow mobs_redo wandering @@ -140,36 +146,36 @@ npc.occupations.basic_def = { [12] = { -- Walk to a sittable node [1] = {task = npc.actions.cmd.WALK_TO_POS, args = { - end_pos = {place_type=npc.places.PLACE_TYPE.SITTABLE.PRIMARY, use_access_node=true}, - walkable = {"cottages:bench"} - }, + end_pos = {place_type=npc.places.PLACE_TYPE.SITTABLE.PRIMARY, use_access_node=true}, + walkable = {"cottages:bench"} + }, chance = 75 }, -- Sit on the node [2] = {task = npc.actions.cmd.USE_SITTABLE, args = { - pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY, - action = npc.actions.const.sittable.SIT - }, + pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY, + action = npc.actions.const.sittable.SIT + }, depends = {1} }, -- Stay put into place [3] = {action = npc.actions.cmd.SET_INTERVAL, args = { - freeze = true, - interval = 35 - }, + freeze = true, + interval = 35 + }, depends = {2} }, [4] = {action = npc.actions.cmd.SET_INTERVAL, args = { - freeze = true, - interval = npc.actions.default_interval - }, + freeze = true, + interval = npc.actions.default_interval + }, depends = {3} }, -- Get up from sit [5] = {action = npc.actions.cmd.USE_SITTABLE, args = { - pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY, - action = npc.actions.const.sittable.GET_UP - }, + pos = npc.places.PLACE_TYPE.SITTABLE.PRIMARY, + action = npc.actions.const.sittable.GET_UP + }, depends = {4} } }, @@ -177,21 +183,21 @@ npc.occupations.basic_def = { [13] = { -- Give NPC money to buy from player [1] = {property = npc.schedule_properties.put_multiple_items, args = { - itemlist = { - {name="default:iron_lump", random=true, min=2, max=4} - } - }, + itemlist = { + {name="default:iron_lump", random=true, min=2, max=4} + } + }, chance = 75 }, -- Change trader status to "trader" [2] = {property = npc.schedule_properties.trader_status, args = { - status = npc.trade.TRADER - }, + status = npc.trade.TRADER + }, chance = 75 }, [3] = {property = npc.schedule_properties.can_receive_gifts, args = { - can_receive_gifts = false - }, + can_receive_gifts = false + }, depends = {1} }, -- Allow mobs_redo wandering @@ -201,19 +207,19 @@ npc.occupations.basic_def = { [18] = { -- Change trader status to "none" [1] = {property = npc.schedule_properties.trader_status, args = { - status = npc.trade.NONE - } + status = npc.trade.NONE + } }, -- Enable gift receiving again [2] = {property = npc.schedule_properties.can_receive_gifts, args = { - can_receive_gifts = true - } + can_receive_gifts = true + } }, -- Get inside home [3] = {task = npc.actions.cmd.WALK_TO_POS, args = { - end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, - walkable = {} - } + end_pos = npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, + walkable = {} + } }, -- Allow mobs_redo wandering [4] = {action = npc.actions.cmd.FREEZE, args = {freeze = false}} @@ -221,15 +227,15 @@ npc.occupations.basic_def = { -- Schedule entry for 10 in the evening [22] = { [1] = {task = npc.actions.cmd.WALK_TO_POS, args = { - end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true}, - walkable = {} - } + end_pos = {place_type=npc.places.PLACE_TYPE.BED.PRIMARY, use_access_node=true}, + walkable = {} + } }, -- Use bed [2] = {task = npc.actions.cmd.USE_BED, args = { - pos = npc.places.PLACE_TYPE.BED.PRIMARY, - action = npc.actions.const.beds.LAY - } + pos = npc.places.PLACE_TYPE.BED.PRIMARY, + action = npc.actions.const.beds.LAY + } }, -- Stay put on bed [3] = {action = npc.actions.cmd.FREEZE, args = {freeze = true}} @@ -249,7 +255,7 @@ end -- of occupation names (strings) function npc.occupations.get_for_building(building_type, surrounding_building_types) local result = {} - for name,def in pairs(npc.registered_occupations) do + for name,def in pairs(npc.occupations.registered_occupations) do -- Check for empty or nil building types, in that case, any building if def.building_types == nil or def.building_types == {} then -- Empty building types, add to result @@ -259,13 +265,18 @@ function npc.occupations.get_for_building(building_type, surrounding_building_ty if npc.utils.array_contains(def.building_types, building_type) then -- Check for empty or nil surrounding building types if def.surrounding_building_types == nil - or def.surrounding_building_types == {} then + or def.surrounding_building_types == {} then -- Add this occupation table.insert(result, name) else -- Check if surround type is included in the def - if npc.utils.array_is_subset_of_array(def.surrounding_building_types, - surrounding_building_types) then + if npc.utils.array_is_subset_of_array( + def.surrounding_building_types, + surrounding_building_types) + or npc.utils.array_is_subset_of_array( + surrounding_building_types, + def.surrounding_building_types) + then -- Add this occupation table.insert(result, name) end @@ -294,16 +305,20 @@ function npc.occupations.initialize_occupation_values(self, occupation_name) minetest.log("Texture entries: "..dump(table.getn(def.textures))) if def.textures and table.getn(def.textures) > 0 then self.selected_texture = - npc.get_random_texture_from_array(self.sex, self.age, def.textures) + npc.get_random_texture_from_array(self.sex, self.age, def.textures) -- Set texture if it found for sex and age - minetest.log("No textures found for sex "..dump(self.sex).." and age "..dump(self.age)) - if self.selected_texture then - -- Set texture and base texture - self.textures = self.selected_texture - self.base_texture = self.selected_texture - -- Refresh entity - self.object:set_properties(self) + -- If an array was returned, select a random texture from it + if type(self.selected_texture) == "table" then + local selected_texture = self.selected_texture[math.random(1, #self.selected_texture)] + self.selected_texture = selected_texture end + -- Set texture and base texture + self.textures = {self.selected_texture} + self.base_texture = {self.selected_texture } + -- Assign sex based on texture + self.sex = npc.assign_sex_from_texture(self) + -- Refresh entity + self.object:set_properties(self) end -- Initialize inventory @@ -321,16 +336,17 @@ function npc.occupations.initialize_occupation_values(self, occupation_name) end -- Initialize dialogues - if def.dialogues and table.getn(def.dialogues) > 0 then + if def.dialogues then local dialogue_keys = {} -- Check which type of dialogues we have if def.dialogues.type == "given" then -- We have been given the dialogues, so def.dialogues.data contains -- an array of dialogues - for _, dialogue in def.dialogues.data do + for _, dialogue in pairs(def.dialogues.data) do -- Add to the dialogue tags the "occupation name" table.insert(dialogue.tags, occupation_name) -- Register dialogue + npc.log("INFO", "Registering dialogue for occupation "..dump(occupation_name)..": "..dump(dialogue)) local key = npc.dialogue.register_dialogue(dialogue) -- Add key to set of dialogue keys table.insert(dialogue_keys, key) @@ -340,7 +356,7 @@ function npc.occupations.initialize_occupation_values(self, occupation_name) -- an array of dialogues and def.dialogues.tags contains an array of -- tags that we will use to search -- Register dialogues - for _, dialogue in def.dialogues.data do + for _, dialogue in pairs(def.dialogues.data) do -- Add to the dialogue tags the "occupation name" table.insert(dialogue.tags, occupation_name) -- Register dialogue @@ -351,7 +367,7 @@ function npc.occupations.initialize_occupation_values(self, occupation_name) -- Find dialogues using tags local dialogues = npc.search_dialogue_by_tags(def.dialogues.tags, true) -- Add keys to set of dialogue keys - for _, key in npc.utils.get_map_keys(dialogues) do + for _, key in pairs(npc.utils.get_map_keys(dialogues)) do table.insert(dialogue_keys, key) end elseif def.dialogues.type == "tags" then @@ -368,16 +384,21 @@ function npc.occupations.initialize_occupation_values(self, occupation_name) max_dialogue_count = def.dialogues.max_count end -- Add dialogues to the normal dialogues for NPC - self.dialogues.normal = {} - for i = 1, max_dialogue_count do - self.dialogues.normal[i] = dialogue_keys[i] - end + self.dialogues.normal = dialogue_keys +-- for i = 1, max_dialogue_count do +-- self.dialogues.normal[i] = dialogue_keys[i] +-- end + end + + -- Initialize trader status + if def.initial_trader_status then + self.trader_data.trader_status = def.initial_trader_status end -- Initialize schedule entries if def.schedules_entries and table.getn(npc.utils.get_map_keys(def.schedules_entries)) > 0 then -- Create schedule in NPC - npc.create_schedule(self, npc.schedule_types.generic, 0) + npc.create_schedule(self, npc.schedule_types.generic, 0) -- Traverse schedules for time, entries in pairs(def.schedules_entries) do -- Add schedule entry for each time diff --git a/spawner.lua b/spawner.lua index 2405d31..4e6345f 100644 --- a/spawner.lua +++ b/spawner.lua @@ -61,14 +61,14 @@ npc.spawner.replacement_interval = 60 npc.spawner.spawn_delay = 10 npc.spawner.spawn_data = { - status = { - dead = 0, - alive = 1 - }, - age = { - adult = "adult", - child = "child" - } + status = { + dead = 0, + alive = 1 + }, + age = { + adult = "adult", + child = "child" + } } -- Array of nodes that serve as plotmarker of a plot, and therefore @@ -158,18 +158,18 @@ function npc.spawner.scan_area_for_spawn(start_pos, end_pos, player_name) -- Initialize NPC stats -- Initialize NPC stats local npc_stats = { - male = { - total = 0, - adult = 0, - child = 0 - }, - female = { - total = 0, - adult = 0, - child = 0 - }, - adult_total = 0, - child_total = 0 + male = { + total = 0, + adult = 0, + child = 0 + }, + female = { + total = 0, + adult = 0, + child = 0 + }, + adult_total = 0, + child_total = 0 } result.building_npc_stats = npc_stats @@ -180,6 +180,78 @@ end -- Spawning functions --------------------------------------------------------------------------------------- +function npc.spawner.determine_npc_occupation(building_type, workplace_nodes, npcs) + local surrounding_buildings_map = {} + local current_building_map = {} + local current_building_npc_occupations = {} + -- Get all occupation names in the current building + for i = 1, #npcs do + if not npc.utils.array_contains(current_building_npc_occupations, npcs[i].occupation) then + table.insert(current_building_npc_occupations, npcs[i].occupations) + end + end + minetest.log("Occupation names in current building: "..dump(current_building_npc_occupations)) + -- Classify workplaces + for i = 1, #workplace_nodes do + local workplace = workplace_nodes[i] + if workplace.surrounding_workplace == true then + surrounding_buildings_map[workplace.building_type] = workplace + else + current_building_map[workplace.building_type] = workplace + end + end + + minetest.log("Surrounding workplaces map: "..dump(surrounding_buildings_map)) + minetest.log("Current building type: "..dump(building_type)) + -- Get occupation names for the buildings + local occupation_names = npc.occupations.get_for_building( + building_type, + npc.utils.get_map_keys(surrounding_buildings_map) + ) + ----------------------- + -- Determine occupation + ----------------------- + -- First of all, iterate through all names, discard the default basic occupation. + -- Next, check if no-one in this builiding has this occupation name. + -- Next, check if the workplace node has no data assigned to it. + -- Finally, if not, return an table with the occupation name, and the selected + -- workplace node. + -- Note: Much more can be done here. This is a simplistic implementation, + -- given this is already complicated enough. For example, existing NPCs' occupation + -- can play a much more important role, not only taken in consideration for discarding. + for i = 1, #occupation_names do + -- Check if this occupation name is the default occupation, and if it is, continue + if occupation_names[i] ~= npc.occupations.basic_name then + -- Check if someone already works on this + if not npc.utils.array_contains(current_building_npc_occupations, occupation_names[i]) then + -- Check if someone else already has this occupation at the same workplace + for j = 1, #workplace_nodes do + -- Get building types from occupation + local local_building_types = + npc.occupations.registered_occupations[occupation_names[i]].building_type or {} + local surrounding_building_types = + npc.occupations.registered_occupations[occupation_names[i]].surrounding_building_types or {} + minetest.log("Occupation btype: "..dump(local_building_types)) + minetest.log("Surrounding btypes: "..dump(surrounding_building_types)) + -- Check the workplace_node is of any of those building_types + if npc.utils.array_contains(local_building_types, workplace_nodes[j].building_type) or + npc.utils.array_contains(surrounding_building_types, workplace_nodes[j].building_type) then + minetest.log("Found corresponding node: "..dump(workplace_nodes[j])) + local meta = minetest.get_meta(workplace_nodes[j].node_pos) + local worker_data = minetest.deserialize(meta:get_string("worker_data") or "") + -- If no worker data is found, then create it + if not worker_data then + return {name=occupation_names[i], node=workplace_nodes[j]} + end + end + end + + end + end + end + return nil +end + -- This function is called when the node timer for spawning NPC -- is expired. Can be called manually by supplying either: -- - Position of mg_villages plotmarker, or, @@ -204,11 +276,15 @@ function npc.spawner.spawn_npc_on_plotmarker(pos) area_info["npc_count"] = meta:get_int("npc_count") area_info["spawned_npc_count"] = meta:get_int("spawned_npc_count") - area_info["building_type"] = minetest.deserialize(meta:get_string("building_type")) + -- Determine occupation + area_info["building_type"] = meta:get_string("building_type") local nearby_plotmarkers = minetest.deserialize(meta:get_string("nearby_plotmarkers")) + local occupation_data = npc.spawner.determine_npc_occupation( + area_info.building_type, + area_info.node_data.workplace_type, + area_info.npcs) - - local metadata = npc.spawner.spawn_npc(pos, area_info) + local metadata = npc.spawner.spawn_npc(pos, area_info, occupation_data.name) -- Set all metadata back into the node -- Increase NPC spawned count @@ -273,7 +349,7 @@ function npc.spawner.spawn_npc(pos, area_info, occupation_name) if npc_table and #npc_table > 0 then npc.initialize(ent, pos, false, npc_stats, occupation) else - npc.initialize(ent, pos, nil, nil, occupation) + npc.initialize(ent, pos, nil, nil, occupation) end -- If entrance and node_data are present, assign nodes if entrance and node_data then @@ -284,13 +360,13 @@ function npc.spawner.spawn_npc(pos, area_info, occupation_name) if ent:get_luaentity().child then age = npc.age.child end - -- TODO: Add more information here at some time... local entry = { status = npc.spawner.spawn_data.status.alive, name = ent:get_luaentity().name, id = ent:get_luaentity().npc_id, sex = ent:get_luaentity().sex, age = age, + occupation = occupation, born_day = minetest.get_day_count() } minetest.log("Area info: "..dump(area_info)) @@ -298,12 +374,12 @@ function npc.spawner.spawn_npc(pos, area_info, occupation_name) -- Update and store stats -- Increase total of NPCs for specific sex npc_stats[ent:get_luaentity().sex].total = - npc_stats[ent:get_luaentity().sex].total + 1 + npc_stats[ent:get_luaentity().sex].total + 1 -- Increase total number of NPCs by age npc_stats[age.."_total"] = npc_stats[age.."_total"] + 1 -- Increase number of NPCs by age and sex npc_stats[ent:get_luaentity().sex][age] = - npc_stats[ent:get_luaentity().sex][age] + 1 + npc_stats[ent:get_luaentity().sex][age] + 1 area_info.npc_stats = npc_stats -- Return npc.log("INFO", "Spawning successful!") @@ -337,7 +413,7 @@ function npc.spawner.assign_places(self, entrance, node_data, pos) -- Assign plotmarker if position given if pos then npc.places.add_shared(self, npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER, - npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER, pos) + npc.places.PLACE_TYPE.OTHER.HOME_PLOTMARKER, pos) end -- Assign entrance door and related locations @@ -353,11 +429,11 @@ function npc.spawner.assign_places(self, entrance, node_data, pos) -- Assign beds if #node_data.bed_type > 0 then - -- Assign a specific sittable node to a NPC. - npc.places.add_owned_accessible_place(self, node_data.bed_type, - npc.places.PLACE_TYPE.BED.PRIMARY) - -- Store changes to node_data - --meta:set_string("node_data", minetest.serialize(node_data)) + -- Assign a specific sittable node to a NPC. + npc.places.add_owned_accessible_place(self, node_data.bed_type, + npc.places.PLACE_TYPE.BED.PRIMARY) + -- Store changes to node_data + --meta:set_string("node_data", minetest.serialize(node_data)) end -- Assign sits @@ -389,7 +465,7 @@ function npc.spawner.assign_places(self, entrance, node_data, pos) -- Add all furnaces to places as shared since NPC should be able to use -- any accessible furnace npc.places.add_shared_accessible_place(self, node_data.furnace_type, - npc.places.PLACE_TYPE.FURNACE.SHARED) + npc.places.PLACE_TYPE.FURNACE.SHARED) end -- Assign storage nodes @@ -417,14 +493,14 @@ function npc.spawner.assign_places(self, entrance, node_data, pos) npc.log("DEBUG", "Places for NPC "..self.npc_name..": "..dump(self.places_map)) - -- Make NPC go into their house + -- Make NPC go into their house -- If entrance is available let NPC if entrance then - npc.add_task(self, - npc.actions.cmd.WALK_TO_POS, - {end_pos=npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, - walkable={}}) - npc.add_action(self, npc.actions.cmd.FREEZE, {freeze = false}) + npc.add_task(self, + npc.actions.cmd.WALK_TO_POS, + {end_pos=npc.places.PLACE_TYPE.OTHER.HOME_INSIDE, + walkable={}}) + npc.add_action(self, npc.actions.cmd.FREEZE, {freeze = false}) end return node_data @@ -438,7 +514,7 @@ function npc.spawner.calculate_npc_spawning_on_plotmarker(pos) -- Check node metadata local meta = minetest.get_meta(pos) if meta:get_string("replaced") ~= "true" then - return + return end -- Get nodes for this building local node_data = minetest.deserialize(meta:get_string("node_data")) @@ -497,61 +573,61 @@ if minetest.get_modpath("mg_villages") ~= nil then --minetest.log("Building data: "..dump(building_data)) --minetest.log("--------------------------------------------") -- Get area of the building - local x_size = building_data.bsizex - local y_size = building_data.ysize - local z_size = building_data.bsizez - local brotate = building_data.brotate - local start_pos = {x=pos.x, y=pos.y, z=pos.z} - local x_sign, z_sign = 1, 1 + local x_size = building_data.bsizex + local y_size = building_data.ysize + local z_size = building_data.bsizez + local brotate = building_data.brotate + local start_pos = {x=pos.x, y=pos.y, z=pos.z} + local x_sign, z_sign = 1, 1 - -- Check plot direction - -- NOTE: Below values may be wrong, very wrong! - -- 0 - facing West, -X - -- 1 - facing North, +Z - -- 2 - facing East, +X - -- 3 - facing South -Z - if brotate == 0 then - x_sign, z_sign = 1, -1 - elseif brotate == 1 then - x_sign, z_sign = -1, -1 - local temp = z_size - z_size = x_size - x_size = temp - elseif brotate == 2 then - x_sign, z_sign = -1, 1 - elseif brotate == 3 then - x_sign, z_sign = 1, 1 - end + -- Check plot direction + -- NOTE: Below values may be wrong, very wrong! + -- 0 - facing West, -X + -- 1 - facing North, +Z + -- 2 - facing East, +X + -- 3 - facing South -Z + if brotate == 0 then + x_sign, z_sign = 1, -1 + elseif brotate == 1 then + x_sign, z_sign = -1, -1 + local temp = z_size + z_size = x_size + x_size = temp + elseif brotate == 2 then + x_sign, z_sign = -1, 1 + elseif brotate == 3 then + x_sign, z_sign = 1, 1 + end - ------------------------ - -- For debug: - ------------------------ - -- Red is x marker - --minetest.set_node({x=pos.x + (x_sign * x_size),y=pos.y,z=pos.z}, {name = "wool:red"}) - --minetest.get_meta({x=pos.x + (x_sign * x_size),y=pos.y,z=pos.z}):set_string("infotext", minetest.get_meta(pos):get_string("infotext")..", Axis: x, Sign: "..dump(x_sign)) - -- Blue is z marker - --minetest.set_node({x=pos.x,y=pos.y,z=pos.z + (z_sign * z_size)}, {name = "wool:blue"}) - --minetest.get_meta({x=pos.x,y=pos.y,z=pos.z + (z_sign * z_size)}):set_string("infotext", minetest.get_meta(pos):get_string("infotext")..", Axis: z, Sign: "..dump(z_sign)) + ------------------------ + -- For debug: + ------------------------ + -- Red is x marker + --minetest.set_node({x=pos.x + (x_sign * x_size),y=pos.y,z=pos.z}, {name = "wool:red"}) + --minetest.get_meta({x=pos.x + (x_sign * x_size),y=pos.y,z=pos.z}):set_string("infotext", minetest.get_meta(pos):get_string("infotext")..", Axis: x, Sign: "..dump(x_sign)) + -- Blue is z marker + --minetest.set_node({x=pos.x,y=pos.y,z=pos.z + (z_sign * z_size)}, {name = "wool:blue"}) + --minetest.get_meta({x=pos.x,y=pos.y,z=pos.z + (z_sign * z_size)}):set_string("infotext", minetest.get_meta(pos):get_string("infotext")..", Axis: z, Sign: "..dump(z_sign)) - npc.log("DEBUG", "Start pos: "..minetest.pos_to_string(start_pos)) - npc.log("DEBUG", "Plot: "..dump(minetest.get_meta(start_pos):get_string("infotext"))) + npc.log("DEBUG", "Start pos: "..minetest.pos_to_string(start_pos)) + npc.log("DEBUG", "Plot: "..dump(minetest.get_meta(start_pos):get_string("infotext"))) - npc.log("DEBUG", "Brotate: "..dump(brotate)) - npc.log("DEBUG", "X_sign: "..dump(x_sign)) - npc.log("DEBUG", "X_adj: "..dump(x_sign*x_size)) - npc.log("DEBUG", "Z_sign: "..dump(z_sign)) - npc.log("DEBUG", "Z_adj: "..dump(z_sign*z_size)) + npc.log("DEBUG", "Brotate: "..dump(brotate)) + npc.log("DEBUG", "X_sign: "..dump(x_sign)) + npc.log("DEBUG", "X_adj: "..dump(x_sign*x_size)) + npc.log("DEBUG", "Z_sign: "..dump(z_sign)) + npc.log("DEBUG", "Z_adj: "..dump(z_sign*z_size)) - local end_pos = {x=pos.x + (x_sign * x_size), y=pos.y + y_size, z=pos.z + (z_sign * z_size)} + local end_pos = {x=pos.x + (x_sign * x_size), y=pos.y + y_size, z=pos.z + (z_sign * z_size)} - -- For debug: - --minetest.set_node(start_pos, {name="default:mese_block"}) - --minetest.set_node(end_pos, {name="default:mese_block"}) - --minetest.get_meta(end_pos):set_string("infotext", minetest.get_meta(start_pos):get_string("infotext")) + -- For debug: + --minetest.set_node(start_pos, {name="default:mese_block"}) + --minetest.set_node(end_pos, {name="default:mese_block"}) + --minetest.get_meta(end_pos):set_string("infotext", minetest.get_meta(start_pos):get_string("infotext")) - npc.log("DEBUG", "Calculated end pos: "..minetest.pos_to_string(end_pos)) + npc.log("DEBUG", "Calculated end pos: "..minetest.pos_to_string(end_pos)) - return npc.places.scan_area_for_usable_nodes(start_pos, end_pos) + return npc.places.scan_area_for_usable_nodes(start_pos, end_pos) end -- This function "adapts" an existent mg_villages:plotmarker for NPC spawning. @@ -567,7 +643,7 @@ if minetest.get_modpath("mg_villages") ~= nil then local infotext = meta:get_string("infotext") -- Check for nil values above if (not village_id or (village_id and village_id == "")) - or (not plot_nr or (plot_nr and plot_nr == 0)) then + or (not plot_nr or (plot_nr and plot_nr == 0)) then return end @@ -612,8 +688,8 @@ if minetest.get_modpath("mg_villages") ~= nil then -- Store nodedata into the spawner's metadata meta:set_string("node_data", minetest.serialize(nodedata)) -- Find nearby plotmarkers, excluding current plotmarker - local nearby_plotmarkers = npc.places.find_plotmarkers(pos, 40, true) - minetest.log("Found nearby plotmarkers: "..dump(nearby_plotmarkers)) + local nearby_plotmarkers = npc.places.find_plotmarkers(pos, 35, true) + --minetest.log("Found nearby plotmarkers: "..dump(nearby_plotmarkers)) meta:set_string("nearby_plotmarkers", minetest.serialize(nearby_plotmarkers)) -- Check if building position data is also available (recent mg_villages) if building_pos_data then @@ -624,22 +700,22 @@ if minetest.get_modpath("mg_villages") ~= nil then meta:set_string("npcs", minetest.serialize(npcs)) -- Initialize NPC stats local npc_stats = { - male = { - total = 0, + male = { + total = 0, adult = 0, - child = 0 - }, - female = { - total = 0, + child = 0 + }, + female = { + total = 0, adult = 0, child = 0 }, adult_total = 0, child_total = 0 - } - meta:set_string("npc_stats", minetest.serialize(npc_stats)) - -- Set replaced - meta:set_string("replaced", "true") + } + meta:set_string("npc_stats", minetest.serialize(npc_stats)) + -- Set replaced + meta:set_string("replaced", "true") -- Calculate how many NPCs will spawn npc.spawner.calculate_npc_spawning_on_plotmarker(pos) -- Stop searching for building type @@ -648,107 +724,107 @@ if minetest.get_modpath("mg_villages") ~= nil then end end - -- Node registration - -- This node is currently a slightly modified mg_villages:plotmarker - -- TODO: Change formspec to a more detailed one. - minetest.override_item("mg_villages:plotmarker", { - -- description = "Automatic NPC Spawner", - -- drawtype = "nodebox", - -- tiles = {"default_stone.png"}, - -- paramtype = "light", - -- paramtype2 = "facedir", - -- node_box = { - -- type = "fixed", - -- fixed = { - -- {-0.5+2/16, -0.5, -0.5+2/16, 0.5-2/16, -0.5+2/16, 0.5-2/16}, - -- --{-0.5+0/16, -0.5, -0.5+0/16, 0.5-0/16, -0.5+0/16, 0.5-0/16}, - -- } - -- }, - walkable = false, - groups = {cracky=3,stone=2}, + -- Node registration + -- This node is currently a slightly modified mg_villages:plotmarker + -- TODO: Change formspec to a more detailed one. + minetest.override_item("mg_villages:plotmarker", { + -- description = "Automatic NPC Spawner", + -- drawtype = "nodebox", + -- tiles = {"default_stone.png"}, + -- paramtype = "light", + -- paramtype2 = "facedir", + -- node_box = { + -- type = "fixed", + -- fixed = { + -- {-0.5+2/16, -0.5, -0.5+2/16, 0.5-2/16, -0.5+2/16, 0.5-2/16}, + -- --{-0.5+0/16, -0.5, -0.5+0/16, 0.5-0/16, -0.5+0/16, 0.5-0/16}, + -- } + -- }, + walkable = false, + groups = {cracky=3,stone=2}, - on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) - -- NOTE: This is temporary code for testing... - local nodedata = minetest.deserialize(minetest.get_meta(pos):get_string("node_data")) - --minetest.log("Node data: "..dump(nodedata)) - --minetest.log("Entrance: "..dump(minetest.deserialize(minetest.get_meta(pos):get_string("entrance")))) - --minetest.log("First-floor beds: "..dump(spawner.filter_first_floor_nodes(nodedata.bed_type, pos))) - --local entrance = npc.places.find_entrance_from_openable_nodes(nodedata.openable_type, pos) - --minetest.log("Found entrance: "..dump(entrance)) - minetest.log("Replaced: "..dump(minetest.get_meta(pos):get_string("replaced"))) - -- for i = 1, #nodedata.bed_type do - -- nodedata.bed_type[i].owner = "" - -- end - -- minetest.get_meta(pos):set_string("node_data", minetest.serialize(nodedata)) - -- minetest.log("Cleared bed owners") - --minetest.log("NPC stats: "..dump(minetest.deserialize(minetest.get_meta(pos):get_string("npc_stats")))) + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + -- NOTE: This is temporary code for testing... + local nodedata = minetest.deserialize(minetest.get_meta(pos):get_string("node_data")) + --minetest.log("Node data: "..dump(nodedata)) + --minetest.log("Entrance: "..dump(minetest.deserialize(minetest.get_meta(pos):get_string("entrance")))) + --minetest.log("First-floor beds: "..dump(spawner.filter_first_floor_nodes(nodedata.bed_type, pos))) + --local entrance = npc.places.find_entrance_from_openable_nodes(nodedata.openable_type, pos) + --minetest.log("Found entrance: "..dump(entrance)) + minetest.log("Replaced: "..dump(minetest.get_meta(pos):get_string("replaced"))) + -- for i = 1, #nodedata.bed_type do + -- nodedata.bed_type[i].owner = "" + -- end + -- minetest.get_meta(pos):set_string("node_data", minetest.serialize(nodedata)) + -- minetest.log("Cleared bed owners") + --minetest.log("NPC stats: "..dump(minetest.deserialize(minetest.get_meta(pos):get_string("npc_stats")))) - return mg_villages.plotmarker_formspec( pos, nil, {}, clicker ) - end, + return mg_villages.plotmarker_formspec( pos, nil, {}, clicker ) + end, - -- on_receive_fields = function(pos, formname, fields, sender) - -- return mg_villages.plotmarker_formspec( pos, formname, fields, sender ); - -- end, + -- on_receive_fields = function(pos, formname, fields, sender) + -- return mg_villages.plotmarker_formspec( pos, formname, fields, sender ); + -- end, - on_timer = function(pos, elapsed) - npc.spawner.spawn_npc_on_plotmarker(pos) - end, + on_timer = function(pos, elapsed) + npc.spawner.spawn_npc_on_plotmarker(pos) + end, - -- protect against digging - -- can_dig = function(pos, player) - -- local meta = minetest.get_meta(pos); - -- if (meta and meta:get_string("village_id") ~= "" and meta:get_int("plot_nr") and meta:get_int("plot_nr") > 0 ) then - -- return false; + -- protect against digging + -- can_dig = function(pos, player) + -- local meta = minetest.get_meta(pos); + -- if (meta and meta:get_string("village_id") ~= "" and meta:get_int("plot_nr") and meta:get_int("plot_nr") > 0 ) then + -- return false; + -- end + -- return true; + -- end + }) + + -- LBM Registration + -- Used to modify plotmarkers and replace them with advanced_npc:plotmarker_auto_spawner + -- minetest.register_lbm({ + -- label = "Replace mg_villages:plotmarker with Advanced NPC auto spawners", + -- name = "advanced_npc:mg_villages_plotmarker_replacer", + -- nodenames = {"mg_villages:plotmarker"}, + -- run_at_every_load = false, + -- action = function(pos, node) + -- -- Check if replacement is activated + -- if npc.spawner.replace_activated then + -- -- Replace mg_villages:plotmarker + -- spawner.replace_mg_villages_plotmarker(pos) + -- -- Set NPCs to spawn + -- spawner.calculate_npc_spawning(pos) + -- end -- end - -- return true; - -- end - }) + -- }) - -- LBM Registration - -- Used to modify plotmarkers and replace them with advanced_npc:plotmarker_auto_spawner - -- minetest.register_lbm({ - -- label = "Replace mg_villages:plotmarker with Advanced NPC auto spawners", - -- name = "advanced_npc:mg_villages_plotmarker_replacer", - -- nodenames = {"mg_villages:plotmarker"}, - -- run_at_every_load = false, - -- action = function(pos, node) - -- -- Check if replacement is activated - -- if npc.spawner.replace_activated then - -- -- Replace mg_villages:plotmarker - -- spawner.replace_mg_villages_plotmarker(pos) - -- -- Set NPCs to spawn - -- spawner.calculate_npc_spawning(pos) - -- end - -- end - -- }) - - -- ABM Registration - minetest.register_abm({ - label = "Replace mg_villages:plotmarker with Advanced NPC auto spawners", - nodenames = {"mg_villages:plotmarker"}, - interval = 10,--npc.spawner.replacement_interval, - chance = 1,--5, - catch_up = true, - action = function(pos, node, active_object_count, active_object_count_wider) - -- Check if replacement is needed - local meta = minetest.get_meta(pos) - if meta then - -- minetest.log("------ Plotmarker metadata -------") - -- local plot_nr = meta:get_int("plot_nr") - -- local village_id = meta:get_string("village_id") - -- minetest.log("Plot nr: "..dump(plot_nr)..", village ID: "..dump(village_id)) - -- minetest.log(dump(mg_villages.get_plot_and_building_data( village_id, plot_nr ))) - end - if minetest.get_meta(pos):get_string("replaced") == "true" then - return - end - -- Check if replacement is activated - if npc.spawner.replace_activated then - -- Replace mg_villages:plotmarker - spawner.adapt_mg_villages_plotmarker(pos) - end - end - }) + -- ABM Registration + minetest.register_abm({ + label = "Replace mg_villages:plotmarker with Advanced NPC auto spawners", + nodenames = {"mg_villages:plotmarker"}, + interval = 10,--npc.spawner.replacement_interval, + chance = 1,--5, + catch_up = true, + action = function(pos, node, active_object_count, active_object_count_wider) + -- Check if replacement is needed + local meta = minetest.get_meta(pos) + if meta then + -- minetest.log("------ Plotmarker metadata -------") + -- local plot_nr = meta:get_int("plot_nr") + -- local village_id = meta:get_string("village_id") + -- minetest.log("Plot nr: "..dump(plot_nr)..", village ID: "..dump(village_id)) + -- minetest.log(dump(mg_villages.get_plot_and_building_data( village_id, plot_nr ))) + end + if minetest.get_meta(pos):get_string("replaced") == "true" then + return + end + -- Check if replacement is activated + if npc.spawner.replace_activated then + -- Replace mg_villages:plotmarker + spawner.adapt_mg_villages_plotmarker(pos) + end + end + }) end @@ -756,50 +832,50 @@ end -- Chat commands to manage spawners minetest.register_chatcommand("restore_plotmarkers", { - description = "Replaces all advanced_npc:plotmarker_auto_spawner with mg_villages:plotmarker in the specified radius.", - privs = {server=true}, - func = function(name, param) - -- Check if radius is null - if param == nil and type(param) ~= "number" then - minetest.chat_send_player(name, "Need to enter a radius as an integer number. Ex. /restore_plotmarkers 10 for a radius of 10") - return + description = "Replaces all advanced_npc:plotmarker_auto_spawner with mg_villages:plotmarker in the specified radius.", + privs = {server=true}, + func = function(name, param) + -- Check if radius is null + if param == nil and type(param) ~= "number" then + minetest.chat_send_player(name, "Need to enter a radius as an integer number. Ex. /restore_plotmarkers 10 for a radius of 10") + return + end + -- Get player position + local pos = {} + for _,player in pairs(minetest.get_connected_players()) do + if player:get_player_name() == name then + pos = player:get_pos() + break + end + end + -- Search for nodes + local radius = tonumber(param) + local start_pos = {x=pos.x - radius, y=pos.y - 5, z=pos.z - radius} + local end_pos = {x=pos.x + radius, y=pos.y + 5, z=pos.z + radius} + local nodes = minetest.find_nodes_in_area_under_air(start_pos, end_pos, + {"mg_villages:plotmarker"}) + -- Check if we have nodes to replace + minetest.chat_send_player(name, "Found "..dump(#nodes).." nodes to replace...") + if #nodes == 0 then + return + end + -- Replace all nodes + for i = 1, #nodes do + local meta = minetest.get_meta(nodes[i]) + local village_id = meta:get_string("village_id") + local plot_nr = meta:get_int("plot_nr") + local infotext = meta:get_string("infotext") + -- Set metadata + meta = minetest.get_meta(nodes[i]) + meta:set_string("village_id", village_id) + meta:set_int("plot_nr", plot_nr) + meta:set_string("infotext", infotext) + -- Clear NPC stats, NPC data and node data + meta:set_string("node_data", nil) + meta:set_string("npcs", nil) + meta:set_string("npc_stats", nil) + meta:set_string("replaced", "false") + end + minetest.chat_send_player(name, "Finished replacement of "..dump(#nodes).." auto-spawners successfully") end - -- Get player position - local pos = {} - for _,player in pairs(minetest.get_connected_players()) do - if player:get_player_name() == name then - pos = player:get_pos() - break - end - end - -- Search for nodes - local radius = tonumber(param) - local start_pos = {x=pos.x - radius, y=pos.y - 5, z=pos.z - radius} - local end_pos = {x=pos.x + radius, y=pos.y + 5, z=pos.z + radius} - local nodes = minetest.find_nodes_in_area_under_air(start_pos, end_pos, - {"mg_villages:plotmarker"}) - -- Check if we have nodes to replace - minetest.chat_send_player(name, "Found "..dump(#nodes).." nodes to replace...") - if #nodes == 0 then - return - end - -- Replace all nodes - for i = 1, #nodes do - local meta = minetest.get_meta(nodes[i]) - local village_id = meta:get_string("village_id") - local plot_nr = meta:get_int("plot_nr") - local infotext = meta:get_string("infotext") - -- Set metadata - meta = minetest.get_meta(nodes[i]) - meta:set_string("village_id", village_id) - meta:set_int("plot_nr", plot_nr) - meta:set_string("infotext", infotext) - -- Clear NPC stats, NPC data and node data - meta:set_string("node_data", nil) - meta:set_string("npcs", nil) - meta:set_string("npc_stats", nil) - meta:set_string("replaced", "false") - end - minetest.chat_send_player(name, "Finished replacement of "..dump(#nodes).." auto-spawners successfully") - end })