From e79fb91ff31a22fdfea3b7a2c07593bc2cda8425 Mon Sep 17 00:00:00 2001 From: Hector Franqui Date: Thu, 13 Jul 2017 19:01:28 -0400 Subject: [PATCH] Dialogues: Re-write dialogues to allow Minetest's like registration system instead of the actual table lookup system. In general, dialogues now are registered in similar fashion as nodes, entities and others are registered in Minetest. Relationships: Re-write to the way favorite/disliked items are defined, now allow Minetest's like registration as well. Random data: Random data now includes registrations for gift items and dialogues. --- dialogue.lua | 330 ++++++--- init.lua | 5 + npc.lua | 1186 ++++++++++++++++--------------- random_data/dialogues_data.lua | 0 random_data/gift_items_data.lua | 237 ++++++ random_data/names_data.lua | 0 relationships.lua | 191 ++++- 7 files changed, 1274 insertions(+), 675 deletions(-) create mode 100644 random_data/dialogues_data.lua create mode 100644 random_data/gift_items_data.lua create mode 100644 random_data/names_data.lua diff --git a/dialogue.lua b/dialogue.lua index eb817ea..0288eff 100644 --- a/dialogue.lua +++ b/dialogue.lua @@ -1,20 +1,6 @@ +------------------------------------------------------------------------------------- -- NPC dialogue code by Zorman2000 --- Dialogue definitions: --- TODO: Complete --- { --- text: "", --- ^ The "spoken" dialogue line --- flag: --- ^ If the flag with the specified name has the specified value --- then this dialogue is valid --- { --- name: "" --- ^ Name of the flag --- value: --- ^ Expected value of the flag. A flag can be a function. In such a case, it is --- expected the function will return this value. --- } --- } +------------------------------------------------------------------------------------- npc.dialogue = {} @@ -37,11 +23,144 @@ npc.dialogue.dialogue_results = { yes_no_dialogue = {} } ---------------------------------------------------------------------------------------- +npc.dialogue.tags = { + UNISEX = "unisex", + MALE = "male", + FEMALE = "female", + -- Relationship based tags - these are one-to-one with the + -- phase names. + PHASE_1 = "phase1", + PHASE_2 = "phase2", + PHASE_3 = "phase3", + PHASE_4 = "phase4", + PHASE_5 = "phase5", + GIFT_ITEM_HINT = "gift_item_hint", + GIFT_ITEM_RESPONSE = "gift_item_response", + GIFT_ITEM_LIKED = "gift_item_liked", + GIFT_ITEM_UNLIKED = "gift_item_unliked", + -- Occupation-based tags - these are one-to-one with the + -- default occupation names + BASIC = "basic", -- Dialogues related to the basic occupation should + -- use this. As basic occupation is generic, any occupation + -- should be able to use these dialogues. + DEFAULT_FARMER = "default_farmer", + DEFAULT_COOKER = "default_cooker" +} + +-- This table will contain all the registered dialogues for NPCs +npc.dialogue.registered_dialogues = {} + +-------------------------------------------------------------------------------------- +-- Dialogue registration functions +-- All dialogues will be registered by providing a definition. +-- A unique key will be assigned to them. The dialogue definition is the following: +-- { +-- text: "", +-- ^ The "spoken" dialogue line +-- flag: +-- ^ If the flag with the specified name has the specified value +-- then this dialogue is valid +-- { +-- name: "" +-- ^ Name of the flag +-- value: +-- ^ Expected value of the flag. A flag can be a function. In such a case, it is +-- expected the function will return this value. +-- }, +-- tags = { +-- -- Tags are an array of string that allow to classify dialogues +-- -- A dialogue can have as many tags as desired and can take any form. +-- -- However, for consistency, some predefined tags can be found at +-- -- npc.dialogue.tags. +-- -- Example: +-- "phase1", +-- "any" +-- } +-- responses = { +-- -- Array of responses the player can choose. A response can be of +-- -- two types: as [1] or as [2] (see example below) +-- [1] = { +-- text = "Yes", +-- -- Text displayed to the player +-- action_type = "dialogue", +-- -- Type of action that happens when the player chooses this response. +-- -- can be "dialogue" or "function". This example shows "dialogue" +-- action = { +-- text = "It's so beautiful, and big, and large, and infinite, and..." +-- }, +-- }, +-- -- A table containing a dialogue. This means you can include not only +-- -- text but also flag and responses as well. Dialogues are recursive. +-- [2] = { +-- text = "No", +-- action_type = "function", +-- action = function(self, player) +-- -- A function will have access to self, which is the NPC +-- -- and the player, which is the player ObjectRef. You can +-- -- pretty much do anything here. The example here is very simple, +-- -- just sending a chat message. But you can add items to players +-- -- or to NPCs and so on. +-- minetest.chat_send_player(player:get_player_name(), "Oh, ok...") +-- end, +-- }, +-- } +-- } +-------------------------------------------------------------------------------------- +-- The register dialogue function will just receive the definition as +-- explained above. The unique key will be the index it gets into the +-- array when inserted. +function npc.dialogue.register_dialogue(def) + -- If def has not tags then apply the default ones + if not def.tags then + def.tags = {npc.dialogue.tags.UNISEX, npc.dialogue.tags.PHASE_1} + end + -- Insert dialogue into table + table.insert(npc.dialogue.registered_dialogues, def) + --minetest.log("Dialogues: "..dump(npc.dialogue.registered_dialogues)) +end + +-- This function returns a table of dialogues that meet the given +-- tags array. The keys in the table are the keys in +-- npc.dialogue.registered_dialogues, therefore you can use them to +--retrieve specific dialogues. However, it should be stored by the NPC. +function npc.dialogue.search_dialogue_by_tags(tags, find_all) + --minetest.log("Tags being searched: "..dump(tags)) + local result = {} + for key, def in pairs(npc.dialogue.registered_dialogues) do + -- Check if def.tags have any of the provided tags + local tags_found = 0 + --minetest.log("Tags on dialogue def: "..dump(def.tags)) + for i = 1, #tags do + if npc.utils.array_contains(def.tags, tags[i]) then + tags_found = tags_found + 1 + end + end + --minetest.log("Tags found: "..dump(tags_found)) + -- Check if we found all tags + if find_all then + if tags_found == #tags then + -- Add result + result[key] = def + end + elseif not find_all then + if tags_found == #tags or tags_found == #def.tags then + -- Add result + result[key] = def + end + end + -- if (find_all == true and tags_found == #tags) + -- or (not find_all and tags_found == #def.tags) then + + -- end + end + return result +end + +-------------------------------------------------------------------------------------- -- Dialogue box definitions -- The dialogue boxes are used for the player to interact with the -- NPC in dialogues. ---------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------- -- Creates and shows a multi-option dialogue based on the number of responses -- that the dialogue object contains function npc.dialogue.show_options_dialogue(self, @@ -67,10 +186,14 @@ function npc.dialogue.show_options_dialogue(self, -- Create entry on options_dialogue table npc.dialogue.dialogue_results.options_dialogue[player_name] = { npc = self, - is_married_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.married), - is_casual_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.casual_trade), - is_dedicated_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.dedicated_trade), - is_custom_trade_dialogue = (dialogue.dialogue_type == npc.dialogue.dialogue_type.custom_trade), + is_married_dialogue = + (dialogue.dialogue_type == npc.dialogue.dialogue_type.married), + is_casual_trade_dialogue = + (dialogue.dialogue_type == npc.dialogue.dialogue_type.casual_trade), + is_dedicated_trade_dialogue = + (dialogue.dialogue_type == npc.dialogue.dialogue_type.dedicated_trade), + is_custom_trade_dialogue = + (dialogue.dialogue_type == npc.dialogue.dialogue_type.custom_trade), casual_trade_type = dialogue.casual_trade_type, options = responses } @@ -104,9 +227,9 @@ function npc.dialogue.show_yes_no_dialogue(self, minetest.show_formspec(player_name, "advanced_npc:yes_no", formspec) end ---------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------- -- Dialogue methods ---------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------- -- This function sets a unique response ID (made of :) to -- each response that features a function. This is to be able to locate the -- function easily later @@ -135,43 +258,79 @@ end -- Select random dialogue objects for an NPC based on sex -- and the relationship phase with player -function npc.dialogue.select_random_dialogues_for_npc(sex, phase, favorite_items, disliked_items) +function npc.dialogue.select_random_dialogues_for_npc(self, phase) local result = { normal = {}, hints = {} } - local dialogues = npc.data.DIALOGUES.female - if sex == npc.MALE then - dialogues = npc.data.DIALOGUES.male + local phase_tag = "phase1" + if phase then + phase_tag = phase end - dialogues = dialogues[phase] + + local search_tags = { + "unisex", + self.sex, + phase_tag, + self.occupation + } + + local dialogues = npc.dialogue.search_dialogue_by_tags(search_tags) + minetest.log("dialogues found by tag search: "..dump(dialogues)) -- Determine how many dialogue lines the NPC will have local number_of_dialogues = math.random(npc.dialogue.MIN_DIALOGUES, npc.dialogue.MAX_DIALOGUES) for i = 1,number_of_dialogues do local dialogue_id = math.random(1, #dialogues) - result.normal[i] = dialogues[dialogue_id] + result.normal[i] = dialogue_id - set_response_ids_recursively(result.normal[i], 0, dialogue_id) + --set_response_ids_recursively(result.normal[i], 0, dialogue_id) end -- Add item hints. - -- Favorite items for i = 1, 2 do - result.hints[i] = {} - result.hints[i].text = - npc.relationships.get_hint_for_favorite_item(favorite_items["fav"..tostring(i)], sex, phase) + local hints = npc.relationships.get_dialogues_for_gift_item( + self.gift_data.favorite_items["fav"..tostring(i)], + npc.dialogue.tags.GIFT_ITEM_HINT, + npc.dialogue.tags.GIFT_ITEM_LIKED, + self.sex, + phase_tag) + for key, value in pairs(hints) do + result.hints[i] = key + end end - -- Disliked items for i = 3, 4 do - result.hints[i] = {} - result.hints[i].text = - npc.relationships.get_hint_for_disliked_item(disliked_items["dis"..tostring(i-2)], sex) + local hints = npc.relationships.get_dialogues_for_gift_item( + self.gift_data.disliked_items["dis"..tostring(i-2)], + npc.dialogue.tags.GIFT_ITEM_HINT, + npc.dialogue.tags.GIFT_ITEM_UNLIKED, + self.sex) + for key, value in pairs(hints) do + result.hints[i] = key + end end + + -- Favorite items + -- for i = 1, 2 do + -- result.hints[i] = {} + -- result.hints[i].text = + -- npc.relationships.get_hint_for_favorite_item( + -- self.gift_data.favorite_items["fav"..tostring(i)], self.sex, phase_tag) + -- end + + -- -- Disliked items + -- for i = 3, 4 do + -- result.hints[i] = {} + -- result.hints[i].text = + -- npc.relationships.get_hint_for_disliked_item( + -- self.gift_data.disliked_items["dis"..tostring(i-2)], self.sex, phase_tag) + -- end + + minetest.log("Dialogue results:"..dump(result)) return result end @@ -181,7 +340,10 @@ function npc.dialogue.create_custom_trade_options(self, player) -- Create the action for each option local actions = {} for i = 1, #self.trader_data.custom_trades do - table.insert(actions, function() npc.trade.show_custom_trade_offer(self, player, self.trader_data.custom_trades[i]) end) + table.insert(actions, + function() + npc.trade.show_custom_trade_offer(self, player, self.trader_data.custom_trades[i]) + end) end -- Default text to be shown for dialogue prompt local text = npc.trade.CUSTOM_TRADES_PROMPT_TEXT @@ -211,60 +373,61 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue) -- Construct dialogue for marriage if npc.relationships.get_relationship_phase(self, player:get_player_name()) == "phase6" and show_married_dialogue == true then - dialogue = npc.relationships.MARRIED_NPC_DIALOGUE - npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) + dialogue = npc.relationships.MARRIED_NPC_DIALOGUE + npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) return end - -- Show options dialogue for dedicated trader - if self.trader_data.trader_status == npc.trade.TRADER then - dialogue = npc.trade.DEDICATED_TRADER_PROMPT - npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) - return - end + -- Show options dialogue for dedicated trader + if self.trader_data.trader_status == npc.trade.TRADER then + dialogue = npc.trade.DEDICATED_TRADER_PROMPT + npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) + return + end local chance = math.random(1, 100) - minetest.log("Chance: "..dump(chance)) if chance < 30 then - -- If NPC is a casual trader, show a sell or buy dialogue 30% of the time, depending - -- on the state of the casual trader. - if self.trader_data.trader_status == npc.trade.NONE then - -- Show custom trade options if available - if table.getn(self.trader_data.custom_trades) > 0 then - -- Show custom trade options - dialogue = npc.dialogue.create_custom_trade_options(self, player) - end - elseif self.trader_data.trader_status == npc.trade.CASUAL then - local max_trade_chance = 2 - if table.getn(self.trader_data.custom_trades) > 0 then - max_trade_chance = 3 - end - -- Show buy/sell with 50% chance each - local trade_chance = math.random(1, max_trade_chance) - if trade_chance == 1 then - -- Show casual buy dialogue - dialogue = npc.trade.CASUAL_TRADE_BUY_DIALOGUE - elseif trade_chance == 2 then - -- Show casual sell dialogue - dialogue = npc.trade.CASUAL_TRADE_SELL_DIALOGUE - elseif trade_chance == 3 then - -- Show custom trade options - dialogue = npc.dialogue.create_custom_trade_options(self, player) - end - end + -- Show trading options for casual traders + -- If NPC has custom trading options, these will be + -- shown as well with equal chance as the casual + -- buy/sell options + if self.trader_data.trader_status == npc.trade.NONE then + -- Show custom trade options if available + if table.getn(self.trader_data.custom_trades) > 0 then + -- Show custom trade options + dialogue = npc.dialogue.create_custom_trade_options(self, player) + end + elseif self.trader_data.trader_status == npc.trade.CASUAL then + local max_trade_chance = 2 + if table.getn(self.trader_data.custom_trades) > 0 then + max_trade_chance = 3 + end + -- Show buy/sell with 50% chance each + local trade_chance = math.random(1, max_trade_chance) + if trade_chance == 1 then + -- Show casual buy dialogue + dialogue = npc.trade.CASUAL_TRADE_BUY_DIALOGUE + elseif trade_chance == 2 then + -- Show casual sell dialogue + dialogue = npc.trade.CASUAL_TRADE_SELL_DIALOGUE + elseif trade_chance == 3 then + -- Show custom trade options + dialogue = npc.dialogue.create_custom_trade_options(self, player) + end + end elseif chance >= 30 and chance < 90 then - -- Choose a random dialogue from the common ones + -- Choose a random dialogue from the common ones dialogue = self.dialogues.normal[math.random(1, #self.dialogues.normal)] elseif chance >= 90 then - -- Choose a random dialogue line from the favorite/disliked item hints + -- Choose a random dialogue line from the favorite/disliked item hints dialogue = self.dialogues.hints[math.random(1, 4)] end local dialogue_result = npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) - if dialogue_result == false then - -- Try to find another dialogue line - npc.dialogue.start_dialogue(self, player, show_married_dialogue) - end + if dialogue_result == false then + -- Try to find another dialogue line + npc.dialogue.start_dialogue(self, player, show_married_dialogue) + end end -- This function processes a dialogue object and performs @@ -274,6 +437,11 @@ function npc.dialogue.process_dialogue(self, dialogue, player_name) -- Freeze NPC actions npc.lock_actions(self) + if type(dialogue) ~= "table" then + dialogue = npc.dialogue.registered_dialogues[dialogue] + minetest.log("Found dialogue: "..dump(dialogue)) + end + -- Check if this dialogue has a flag definition if dialogue.flag then -- Check if the NPC has this flag diff --git a/init.lua b/init.lua index 6718988..e7e9fdd 100755 --- a/init.lua +++ b/init.lua @@ -24,6 +24,7 @@ end mobs.intllib = S dofile(path .. "/npc.lua") +dofile(path .. "/utils.lua") dofile(path .. "/spawner.lua") dofile(path .. "/relationships.lua") dofile(path .. "/dialogue.lua") @@ -33,6 +34,10 @@ dofile(path .. "/actions/actions.lua") dofile(path .. "/actions/places.lua") dofile(path .. "/actions/pathfinder.lua") dofile(path .. "/actions/node_registry.lua") +-- Load random data definitions dofile(path .. "/random_data.lua") +dofile(path .. "/random_data/dialogues_data.lua") +dofile(path .. "/random_data/gift_items_data.lua") +dofile(path .. "/random_data/names_data.lua") print (S("[Mod] Advanced NPC loaded")) diff --git a/npc.lua b/npc.lua index c4ea3db..3cedeb2 100755 --- a/npc.lua +++ b/npc.lua @@ -60,7 +60,7 @@ npc.texture_check = { -- Logging function npc.log(level, message) if npc.log_level[level] then - minetest.log("[advanced_npc] "..level..": "..message) + minetest.log("[advanced_npc] "..level..": "..message) end end @@ -72,9 +72,9 @@ end -- Gets name of player or NPC function npc.get_entity_name(entity) if entity:is_player() then - return entity:get_player_name() + return entity:get_player_name() else - return entity:get_luaentity().name + return entity:get_luaentity().name end end @@ -82,7 +82,7 @@ end -- TODO: Implement NPC function npc.get_entity_wielded_item(entity) if entity:is_player() then - return entity:get_wielded_item() + return entity:get_wielded_item() end end @@ -101,19 +101,19 @@ end local function initialize_inventory() return { - [1] = "", [2] = "", [3] = "", [4] = "", - [5] = "", [6] = "", [7] = "", [8] = "", - [9] = "", [10] = "", [11] = "", [12] = "", - [13] = "", [14] = "", [15] = "", [16] = "", + [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 - end + if string.find(textures[i], "female") ~= nil then + return true + end end return false end @@ -121,38 +121,69 @@ end local function get_random_texture(sex, age) local textures = {} local filtered_textures = {} - -- Find textures by sex and age + -- Find textures by sex and age 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 + --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 - textures = minetest.registered_entities["advanced_npc:npc"].child_texture + 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 + 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] + return textures[1][1] end return filtered_textures[math.random(1,#filtered_textures)] end --- Choose whether NPC can have relationships. Only 30% of NPCs cannot have relationships +function npc.get_random_texture_from_array(age, sex, textures) + local filtered_textures = {} + + for i = 1, #textures do + local current_texture = textures[i] + -- Filter by age + if (sex == npc.MALE + 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 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) + end + end + + 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 + return false end local chance = math.random(1,10) return chance > 3 @@ -165,11 +196,11 @@ local function choose_spawn_items(self) 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) - ) + 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 @@ -177,18 +208,18 @@ local function choose_spawn_items(self) 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) + --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)) end @@ -201,11 +232,9 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) -- Get variables local ent = entity if not is_lua_entity then - ent = entity:get_luaentity() + ent = entity:get_luaentity() end - ent.initialized = true - -- Avoid NPC to be removed by mobs_redo API ent.remove_ok = false @@ -221,73 +250,77 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) -- 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. if npc_stats then - -- Default chances - local male_s, male_e = 0, 50 - local female_s, female_e = 51, 100 - local adult_s, adult_e = 0, 90 - local child_s, child_e = 91, 100 - -- Determine sex probabilities - if npc_stats[npc.FEMALE].total > npc_stats[npc.MALE].total then - male_e = 75 - 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 - end - -- 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 - else - child_s, child_e = 61, 100 - adult_e = 60 - end - end - -- Get sex and age based on the probabilities - local sex_chance = math.random(1, 100) - local age_chance = math.random(1, 100) - local selected_sex = "" - local selected_age = "" - -- Select sex - if male_s <= sex_chance and sex_chance <= male_e then - selected_sex = npc.MALE - elseif female_s <= sex_chance and sex_chance <= female_e then - selected_sex = npc.FEMALE - end - -- Set sex for NPC - ent.sex = selected_sex - -- Select age - if adult_s <= age_chance and age_chance <= adult_e then - selected_age = npc.age.adult - elseif child_s <= age_chance and age_chance <= child_e then - selected_age = npc.age.child - ent.visual_size = { - x = 0.75, - y = 0.75 - } - ent.collisionbox = {-0.10,-0.50,-0.10, 0.10,0.40,0.10} - ent.is_child = true - ent.child = true - end - -- Set texture accordingly - local selected_texture = get_random_texture(selected_sex, selected_age) - --minetest.log("Selected texture: "..dump(selected_texture)) - -- Store selected texture due to the need to restore it later - ent.selected_texture = selected_texture - -- Set texture and base texture - ent.textures = {selected_texture} - ent.base_texture = {selected_texture} + -- Default chances + local male_s, male_e = 0, 50 + local female_s, female_e = 51, 100 + local adult_s, adult_e = 0, 90 + local child_s, child_e = 91, 100 + -- Determine sex probabilities + if npc_stats[npc.FEMALE].total > npc_stats[npc.MALE].total then + male_e = 75 + 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 + end + -- 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 + else + child_s, child_e = 61, 100 + adult_e = 60 + end + end + -- Get sex and age based on the probabilities + local sex_chance = math.random(1, 100) + local age_chance = math.random(1, 100) + local selected_sex = "" + local selected_age = "" + -- Select sex + if male_s <= sex_chance and sex_chance <= male_e then + selected_sex = npc.MALE + elseif female_s <= sex_chance and sex_chance <= female_e then + selected_sex = npc.FEMALE + end + -- Set sex for NPC + ent.sex = selected_sex + -- Select age + if adult_s <= age_chance and age_chance <= adult_e then + selected_age = npc.age.adult + elseif child_s <= age_chance and age_chance <= child_e then + selected_age = npc.age.child + ent.visual_size = { + x = 0.60, + y = 0.60 + } + 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 + + -- Set texture accordingly + local selected_texture = get_random_texture(selected_sex, selected_age) + --minetest.log("Selected texture: "..dump(selected_texture)) + -- Store selected texture due to the need to restore it later + ent.selected_texture = selected_texture + -- Set texture and base texture + ent.textures = {selected_texture} + ent.base_texture = {selected_texture} else - -- 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 + -- 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 end -- Nametag is initialized to blank @@ -301,16 +334,16 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) -- 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), + -- 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) - 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 @@ -322,10 +355,7 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) ent.is_married_to = nil -- Initialize dialogues - ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent.sex, - "phase1", - ent.gift_data.favorite_items, - ent.gift_data.disliked_items) + ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent, "phase1") -- Declare NPC inventory ent.inventory = initialize_inventory() @@ -338,28 +368,28 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) -- 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 = {} + -- 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 @@ -370,26 +400,26 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) -- 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 = 1, - -- 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 = {} - }, + -- 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 = 1, + -- 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 @@ -413,35 +443,38 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) -- Schedule data ent.schedules = { - -- Flag to enable or disable the schedules functionality - enabled = true, - -- Lock for when executing a schedule - lock = false, - -- An array of schedules, meant to be one per day at some point - -- when calendars are implemented. Allows for only 7 schedules, - -- one for each day of the week - generic = {}, - -- An array of schedules, meant to be for specific dates in the - -- year. Can contain as many as possible. The keys will be strings - -- in the format MM:DD - date_based = {} + -- Flag to enable or disable the schedules functionality + enabled = true, + -- Lock for when executing a schedule + lock = false, + -- Queue of schedules executed + -- Used to calculate dependencies + temp_executed_queue = {}, + -- An array of schedules, meant to be one per day at some point + -- when calendars are implemented. Allows for only 7 schedules, + -- one for each day of the week + generic = {}, + -- An array of schedules, meant to be for specific dates in the + -- year. Can contain as many as possible. The keys will be strings + -- in the format MM:DD + date_based = {} } -- Dedicated trade test ent.trader_data.trade_list.both = { - ["default:tree"] = {}, - ["default:cobble"] = {}, - ["default:wood"] = {}, - ["default:diamond"] = {}, - ["default:mese_crystal"] = {}, - ["flowers:rose"] = {}, - ["advanced_npc:marriage_ring"] = {}, - ["flowers:geranium"] = {}, - ["mobs:meat"] = {}, - ["mobs:leather"] = {}, - ["default:sword_stone"] = {}, - ["default:shovel_stone"] = {}, - ["default:axe_stone"] = {} + ["default:tree"] = {}, + ["default:cobble"] = {}, + ["default:wood"] = {}, + ["default:diamond"] = {}, + ["default:mese_crystal"] = {}, + ["flowers:rose"] = {}, + ["advanced_npc:marriage_ring"] = {}, + ["flowers:geranium"] = {}, + ["mobs:meat"] = {}, + ["mobs:leather"] = {}, + ["default:sword_stone"] = {}, + ["default:shovel_stone"] = {}, + ["default:axe_stone"] = {} } npc.trade.generate_trade_offers_by_status(ent) @@ -452,10 +485,11 @@ function npc.initialize(entity, pos, is_lua_entity, npc_stats) 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) + ent.initialized = true --minetest.log(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)) + ..", sex: "..ent.sex..", is child: "..dump(ent.is_child) + ..", texture: "..dump(ent.textures)) -- Refreshes entity ent.object:set_properties(ent) end @@ -466,7 +500,7 @@ end function npc.generate_trade_list_from_inventory(self) local list = {} for i = 1, #self.inventory do - list[npc.get_item_name(self.inventory[i])] = {} + list[npc.get_item_name(self.inventory[i])] = {} end self.trader_data.trade_list.both = list end @@ -501,37 +535,37 @@ 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 - end - end - -- No free slot found - return false - end + -- 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 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 + -- 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 @@ -546,9 +580,9 @@ end -- 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} - end + if value ~= "" and string.find(value, item_name) then + return {slot=key, item_string=value} + end end -- Item not found return nil @@ -560,27 +594,27 @@ end 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) - 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 + -- 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 - -- Not able to take item because not found - return nil + -- Not able to take item because not found + return nil end end @@ -643,56 +677,56 @@ end function npc.execute_action(self) -- Check if an action was interrupted 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) - -- Clear the action - self.actions.state_before_lock.interrupted_action = {} - -- Clear the position - self.actions.state_before_lock.pos = {} + 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) + -- Clear the action + 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 - -- 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 + -- 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] if action_obj.action == nil then - return + return 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 - -- Remove this "task" action from queue - table.remove(self.actions.queue, 1) - -- Clear queue - self.actions.queue = {} - -- Now, execute the task with its arguments - result = npc.actions.execute(self, action_obj.action, action_obj.args) - --result = action_obj.action(self, action_obj.args) - -- After all new actions has been added by task, add the previously - -- queued actions back - for i = 1, #backup_queue do - table.insert(self.actions.queue, backup_queue[i]) - end + npc.log("DEBUG", "Executing task for NPC '"..dump(self.npc_name).."': "..dump(action_obj)) + -- Backup current queue + local backup_queue = self.actions.queue + -- Remove this "task" action from queue + table.remove(self.actions.queue, 1) + -- Clear queue + self.actions.queue = {} + -- Now, execute the task with its arguments + result = npc.actions.execute(self, action_obj.action, action_obj.args) + --result = action_obj.action(self, action_obj.args) + -- After all new actions has been added by task, add the previously + -- queued actions back + for i = 1, #backup_queue do + table.insert(self.actions.queue, backup_queue[i]) + end 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 - -- Store current position - self.actions.state_before_lock.pos = self.object:getpos() - -- Execute action as normal - result = npc.actions.execute(self, action_obj.action, action_obj.args) - --result = action_obj.action(self, action_obj.args) - -- Remove task - table.remove(self.actions.queue, 1) - -- Set state - self.actions.current_action_state = npc.action_state.executing + 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 + -- Store current position + self.actions.state_before_lock.pos = self.object:getpos() + -- Execute action as normal + result = npc.actions.execute(self, action_obj.action, action_obj.args) + --result = action_obj.action(self, action_obj.args) + -- Remove task + table.remove(self.actions.queue, 1) + -- Set state + self.actions.current_action_state = npc.action_state.executing end return result end @@ -701,21 +735,21 @@ function npc.lock_actions(self) -- Avoid re-locking if already locked if self.actions.action_timer_lock == true then - return + 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()) - end - pos.y = self.object:getpos().y + -- 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}) @@ -725,11 +759,11 @@ function npc.lock_actions(self) 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 + 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 @@ -746,8 +780,8 @@ function npc.unlock_actions(self) 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 + -- 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") @@ -797,29 +831,29 @@ end -- 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 - end + -- 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 + -- 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 @@ -837,93 +871,93 @@ end function npc.add_schedule_entry(self, schedule_type, date, time, check, actions) -- Check that schedule for date exists if self.schedules[schedule_type][date] ~= nil then - -- Add schedule entry - if check == nil then - self.schedules[schedule_type][date][time] = actions - else - self.schedules[schedule_type][date][time].check = check - end + -- Add schedule entry + if check == nil then + self.schedules[schedule_type][date][time] = actions + else + self.schedules[schedule_type][date][time].check = check + end else - -- No schedule found, need to be created for date - return nil + -- No schedule found, need to be created for date + return nil end end function npc.get_schedule_entry(self, schedule_type, date, time) -- Check if schedule for date exists if self.schedules[schedule_type][date] ~= nil then - -- Return schedule - return self.schedules[schedule_type][date][time] + -- Return schedule + return self.schedules[schedule_type][date][time] else - -- Schedule for date not found - return nil + -- Schedule for date not found + return nil end end function npc.update_schedule_entry(self, schedule_type, date, time, check, actions) -- Check schedule for date exists 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 - if check == nil then - self.schedules[schedule_type][date][time] = actions - else - self.schedules[schedule_type][date][time].check = check - end - else - -- Schedule not found for specified time - return nil - end + -- Check that a schedule entry for that time exists + if self.schedules[schedule_type][date][time] ~= nil then + -- Set the new actions + if check == nil then + self.schedules[schedule_type][date][time] = actions + else + self.schedules[schedule_type][date][time].check = check + end + else + -- Schedule not found for specified time + return nil + end else - -- Schedule not found for date - return nil + -- Schedule not found for date + return nil end end function npc.delete_schedule_entry(self, schedule_type, date, time) -- Check schedule for date exists if self.schedules[schedule_type][date] ~= nil then - -- Remove schedule entry by setting to nil - self.schedules[schedule_type][date][time] = nil + -- Remove schedule entry by setting to nil + self.schedules[schedule_type][date][time] = nil else - -- Schedule not found for date - return nil + -- Schedule not found for date + return nil end end function npc.schedule_change_property(self, property, args) if property == npc.schedule_properties.trader_status then - -- Get status from args - local status = args.status - -- Set status to NPC - npc.set_trading_status(self, status) + -- Get status from args + local status = args.status + -- Set status to NPC + npc.set_trading_status(self, status) elseif property == npc.schedule_properties.put_item then - local itemstring = args.itemstring - -- Add item - npc.add_item_to_inventory_itemstring(self, itemstring) + local itemstring = args.itemstring + -- Add item + npc.add_item_to_inventory_itemstring(self, itemstring) elseif property == npc.schedule_properties.put_multiple_items then - local itemlist = args.itemlist - for i = 1, #itemlist do - local itemlist_entry = itemlist[i] - local current_itemstring = itemlist[i].name - if itemlist_entry.random == true then - current_itemstring = current_itemstring - .." "..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 + local itemlist = args.itemlist + for i = 1, #itemlist do + local itemlist_entry = itemlist[i] + local current_itemstring = itemlist[i].name + if itemlist_entry.random == true then + current_itemstring = current_itemstring + .." "..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 elseif property == npc.schedule_properties.take_item then - local itemstring = args.itemstring - -- Add item - npc.take_item_from_inventory_itemstring(self, itemstring) + local itemstring = args.itemstring + -- Add item + npc.take_item_from_inventory_itemstring(self, itemstring) elseif property == npc.schedule_properties.can_receive_gifts then - local value = args.can_receive_gifts - -- Set status - self.can_receive_gifts = value + local value = args.can_receive_gifts + -- Set status + self.can_receive_gifts = value end end @@ -950,16 +984,16 @@ mobs:register_mob("advanced_npc:npc", { drawtype = "front", textures = { {"npc_male1.png"}, - {"npc_male2.png"}, - {"npc_male3.png"}, - {"npc_male4.png"}, - {"npc_male5.png"}, - {"npc_male6.png"}, + {"npc_male2.png"}, + {"npc_male3.png"}, + {"npc_male4.png"}, + {"npc_male5.png"}, + {"npc_male6.png"}, {"npc_female1.png"}, -- female by nuttmeg20 }, child_texture = { - {"npc_baby_male1.png"}, - {"npc_baby_female1.png"}, + {"npc_child_male1.png"}, + {"npc_child_female1.png"}, }, makes_footstep_sound = true, sounds = {}, @@ -998,196 +1032,220 @@ 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 + -- 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 - else - -- 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 - 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 - end - end + 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 + else + -- 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 + 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 + 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 - -- Reset timer - self.trader_data.change_offers_timer = 0 - -- Re-select casual trade offers - npc.trade.generate_trade_offers_by_status(self) - 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 + -- Reset timer + self.trader_data.change_offers_timer = 0 + -- Re-select casual trade offers + npc.trade.generate_trade_offers_by_status(self) + end - -- Timer function for gifts - for i = 1, #self.relationships do - 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 - 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 - else - -- 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 - -- 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 - relationship.points = relationship.points - 0.5 - end - else - relationship.points = relationship.points - 1 - end - relationship.relationship_decrease_timer_value = 0 - --minetest.log(dump(self)) - end - end - end + -- Timer function for gifts + for i = 1, #self.relationships do + 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 + 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 + else + -- 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 + -- 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 + relationship.points = relationship.points - 0.5 + end + else + relationship.points = relationship.points - 1 + end + relationship.relationship_decrease_timer_value = 0 + --minetest.log(dump(self)) + end + end + end - -- Action queue timer - -- Check if actions and timers aren't locked - if self.actions.action_timer_lock == false then - -- Increment action timer - self.actions.action_timer = self.actions.action_timer + dtime - if self.actions.action_timer >= self.actions.action_interval then - -- Reset action timer - self.actions.action_timer = 0 - -- Check if NPC is walking - if self.actions.walking.is_walking == true then - 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 - self.freeze = false - end - end - end + -- Action queue timer + -- Check if actions and timers aren't locked + if self.actions.action_timer_lock == false then + -- Increment action timer + self.actions.action_timer = self.actions.action_timer + dtime + if self.actions.action_timer >= self.actions.action_interval then + -- Reset action timer + self.actions.action_timer = 0 + -- Check if NPC is walking + if self.actions.walking.is_walking == true then + 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 + self.freeze = false + 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 < 0.1 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 - --minetest.log("Found default schedule") - if schedule[time] ~= nil then - -- Check if schedule has a check function - if schedule[time].check ~= nil then - -- Execute check function and then add corresponding action - -- to action queue. This is for jobs. - -- TODO: Need to implement - else - npc.log("DEBUG", "Adding actions to action queue") - -- Add to action queue all actions on schedule - for i = 1, #schedule[time] do - --minetest.log("schedule[time]: "..dump(schedule[time])) - if schedule[time][i].task ~= nil then - -- Add task - npc.add_task(self, schedule[time][i].task, schedule[time][i].args) - elseif schedule[time][i].action ~= nil then - -- Add action - npc.add_action(self, schedule[time][i].action, schedule[time][i].args) - elseif schedule[time][i].property ~= nil then - -- Change NPC property - npc.schedule_change_property(self, schedule[time][i].property, schedule[time][i].args) - end - end - npc.log("DEBUG", "New action queue: "..dump(self.actions)) - end - end - end - else - -- Check if lock can be released - if time % 1 > 0.1 then - -- Release lock - self.schedules.lock = false - end - end - end - - return self.freeze - 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 < 0.1 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 + --minetest.log("Found default schedule") + if schedule[time] ~= nil then + -- Check if schedule has a check function + if schedule[time].check ~= nil then + -- Execute check function and then add corresponding action + -- to action queue. This is for jobs. + -- TODO: Need to implement + else + npc.log("DEBUG", "Adding actions to action queue") + -- Add to action queue all actions on schedule + for i = 1, #schedule[time] do + --minetest.log("schedule[time]: "..dump(schedule[time])) + -- 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 + -- Add tasks + if schedule[time][i].task ~= nil then + -- Add task + npc.add_task(self, schedule[time][i].task, schedule[time][i].args) + elseif schedule[time][i].action ~= nil then + -- Add action + npc.add_action(self, schedule[time][i].action, schedule[time][i].args) + elseif schedule[time][i].property ~= nil then + -- Change NPC property + npc.schedule_change_property(self, schedule[time][i].property, schedule[time][i].args) + end + -- Backward compatibility check + if self.schedules.temp_executed_queue then + -- Add into execution queue to meet dependency + table.insert(self.schedules.temp_executed_queue, i) + end + end + end + end + -- Clear execution queue + self.schedules.temp_executed_queue = {} + npc.log("DEBUG", "New action queue: "..dump(self.actions)) + end + end + end + else + -- Check if lock can be released + if time % 1 > 0.1 then + -- Release lock + self.schedules.lock = false + end + end + end + + return self.freeze + end end }) @@ -1222,6 +1280,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/random_data/dialogues_data.lua b/random_data/dialogues_data.lua new file mode 100644 index 0000000..e69de29 diff --git a/random_data/gift_items_data.lua b/random_data/gift_items_data.lua new file mode 100644 index 0000000..a0e4d25 --- /dev/null +++ b/random_data/gift_items_data.lua @@ -0,0 +1,237 @@ +------------------------------------------------------------------------------ +-- Gift Items data definitions +------------------------------------------------------------------------------ + +------------------------------------------------------------------------------ +-- PHASE 1 +------------------------------------------------------------------------------ + +npc.relationships.register_favorite_item("default:apple", "phase1", "female", { + responses = {"Hey, I really wanted an apple, thank you!"}, + hints = {"I could really do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase1", "female", { + responses = {"Thanks, you didn't have to, but thanks..."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("farming:seed_cotton", "phase1", "female", { + responses = {"Thank you, I will plant this really soon"}, + hints = {"I would like to have some cotton plants around"} +}) + +npc.relationships.register_favorite_item("farming:seed_wheat", "phase1", "female", { + responses = {"Thank you! These seeds will make a good wheat plant!"}, + hints = {"I've been thinking I should get wheat seeds"} +}) + +npc.relationships.register_favorite_item("flowers:rose", "phase1", "female", { + responses = {"Thanks..."}, + hints = {"Red roses make a nice gift!"} +}) + +npc.relationships.register_favorite_item("flowers:geranium", "phase1", "female", { + responses = {"Oh, for me? Thank you!"}, + hints = {"Blue geraniums are so beautiful"} +}) + +npc.relationships.register_favorite_item("default:clay_lump", "phase1", "female", { + responses = {"Thanks! Now, what can I do with this..."}, + hints = {"If I had some clay lump, I may do some pottery"} +}) + +npc.relationships.register_favorite_item("mobs:meat_raw", "phase1", "female", { + responses = {"This will be great for tonight! Thanks"}, + hints = {"A good dinner always have meat"} +}) + +npc.relationships.register_favorite_item("mobs:leather", "phase1", "female", { + responses = {"Thank you! I needed this!"}, + hints = {"If only I could get some leather"} +}) + +npc.relationships.register_favorite_item("default:sapling", "phase1", "female", { + responses = {"Now I can plant that tree..."}, + hints = {"I really would like an apple tree close by."} +}) + + +npc.relationships.register_favorite_item("farming:cotton", "phase2", "female", { + responses = {"This is going to be very helpful, thank you!"}, + hints = {"If I just had some cotton lying around..."} +}) + +npc.relationships.register_favorite_item("wool:white", "phase2", "female", { + responses = {"Thanks, you didn't have to, but thanks..."}, + hints = {"Have you seen a sheep? I wish I had some white wool..."} +}) + + +npc.relationships.register_favorite_item("default:apple", "phase3", "female", { + responses = {"Hey, I really wanted an apple, thank you!"}, + hints = {"I could really do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase3", "female", { + responses = {"Thanks, you didn't have to, but thanks..."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase4", "female", { + responses = {"Hey, I really wanted an apple, thank you!"}, + hints = {"I could really do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase4", "female", { + responses = {"Thanks, you didn't have to, but thanks..."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase5", "female", { + responses = {"Hey, I really wanted an apple, thank you!"}, + hints = {"I could really do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase5", "female", { + responses = {"Thanks, you didn't have to, but thanks..."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase6", "female", { + responses = {"Hey, I really wanted an apple, thank you!"}, + hints = {"I could really do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase6", "female", { + responses = {"Thanks, you didn't have to, but thanks..."}, + hints = {"Some fresh bread would be good!"} +}) + + +-- Male +npc.relationships.register_favorite_item("default:apple", "phase1", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase1", "male", { + responses = {"Thank you! I was hungry."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("farming:seed_cotton", "phase1", "male", { + responses = {"Thank you, I will plant this soon"}, + hints = {"I would like to have some cotton plants around."} +}) + +npc.relationships.register_favorite_item("farming:seed_wheat", "phase1", "male", { + responses = {"Thank you! These seeds will make a good wheat plant!"}, + hints = {"I've been thinking I should get wheat seeds."} +}) + +npc.relationships.register_favorite_item("default:wood", "phase1", "male", { + responses = {"Thanks, I needed this."}, + hints = {"Some wood without having to cut a tree would be good.}"} +}) + +npc.relationships.register_favorite_item("default:tree", "phase1", "male", { + responses = {"Excellent to get that furnace going!"}, + hints = {"I'm looking for some logs"} +}) + +npc.relationships.register_favorite_item("default:clay_lump", "phase1", "male", { + responses = {"Thanks! Now, what can I do with this..."}, + hints = {"Now, some clay would be good."} +}) + +npc.relationships.register_favorite_item("mobs:meat_raw", "phase1", "male", { + responses = {"This makes a great meal. Thank you"}, + hints = {"Meat is always great"}, +}) + +npc.relationships.register_favorite_item("mobs:leather", "phase1", "male", { + responses = {"Time to tan some leathers!"}, + hints = {"I have been needing leather these days."} +}) + +npc.relationships.register_favorite_item("default:sapling", "phase1", "male", { + responses = {"Thanks, I will plant this right now"}, + hints = {"I really would like an apple tree close by."} +}) + +npc.relationships.register_favorite_item("default:apple", "phase2", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase2", "male", { + responses = {"Thank you! I was hungry."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase3", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase3", "male", { + responses = {"Thank you! I was hungry."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase4", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase4", "male", { + responses = {"Thank you! I was hungry."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase5", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase5", "male", { + responses = {"Thank you! I was hungry."}, + hints = {"Some fresh bread would be good!"} +}) + +npc.relationships.register_favorite_item("default:apple", "phase6", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_favorite_item("farming:bread", "phase6", "male", { + responses = {"Thank you! I was hungry."}, + hints = {"Some fresh bread would be good!"} +}) + +-- Disliked items +-- Female +npc.relationships.register_disliked_item("default:stone", "female", { + responses = {"A stone, oh... why do you give this to me?"}, + hints = {"Why would someone want a stone?"} +}) + +npc.relationships.register_disliked_item("default:cobble", "female", { + responses = {"Cobblestone? No, no, why?"}, + hints = {"Anything worst than stone is cobblestone."} +}) + +-- Male +npc.relationships.register_disliked_item("default:stone", "male", { + responses = {"Good apple, thank you!"}, + hints = {"I could do with an apple..."} +}) + +npc.relationships.register_disliked_item("default:cobble", "male", { + responses = {"Cobblestone!? Wow, you sure think a lot before giving a gift..."}, + hints = {"If I really hate something, that's cobblestone!"} +}) + +npc.log("INFO", "Registered gift items: "..dump(npc.relationships.gift_items)) +npc.log("INFO", "Registered dialogues: "..dump(npc.dialogue.registered_dialogues)) \ No newline at end of file diff --git a/random_data/names_data.lua b/random_data/names_data.lua new file mode 100644 index 0000000..e69de29 diff --git a/relationships.lua b/relationships.lua index 9bd1301..3c8cf5d 100644 --- a/relationships.lua +++ b/relationships.lua @@ -37,6 +37,40 @@ npc.relationships.RELATIONSHIP_PHASE["phase2"] = {limit = 25} npc.relationships.RELATIONSHIP_PHASE["phase3"] = {limit = 45} npc.relationships.RELATIONSHIP_PHASE["phase4"] = {limit = 70} npc.relationships.RELATIONSHIP_PHASE["phase5"] = {limit = 100} + +npc.relationships.GIFT_ITEM_LIKED = "liked" +npc.relationships.GIFT_ITEM_DISLIKED = "disliked" +npc.relationships.GIFT_ITEM_HINT = "hint" +npc.relationships.GIFT_ITEM_RESPONSE = "response" + +-- Favorite and disliked items tables +npc.relationships.gift_items = { + liked = { + female = { + ["phase1"] = {}, + ["phase2"] = {}, + ["phase3"] = {}, + ["phase4"] = {}, + ["phase5"] = {}, + ["phase6"] = {} + }, + male = { + ["phase1"] = {}, + ["phase2"] = {}, + ["phase3"] = {}, + ["phase4"] = {}, + ["phase5"] = {}, + ["phase6"] = {} + } + }, + disliked = { + female = {}, + male = {} + } +} + +npc.relationships.DEFAULT_RESPONSE_NO_GIFT_RECEIVE = + "Thank you, but I don't need anything for now." -- Married NPC dialogue definition npc.relationships.MARRIED_NPC_DIALOGUE = { @@ -90,6 +124,96 @@ function npc.relationships.get_relationship_phase_by_points(points) end end +-- Registration functions +----------------------------------------------------------------------------- +-- Items can be registered to be part of the gift system using the +-- below function. The def is the following: +-- { +-- dialogues = { +-- liked = { +-- -- ^ This is an array of the following table: +-- [1] = {dialogue_type="", sex="", text=""} +-- -- ^ dialogue_type: defines is this is a hint or a response. +-- -- valid values are: "hint", "response" +-- -- sex: valid values are: "male", female" +-- -- text: the dialogue text +-- }, +-- disliked = { +-- -- ^ This is an array with the same type of tables as above +-- } +-- } +-- } + +function npc.relationships.register_favorite_item(item_name, phase, sex, def) + local dialogues = {} + -- Register dialogues based on the hints and responses + -- Liked + for i = 1, #def.hints do + table.insert(dialogues, { + text = def.hints[i], + tags = {phase, item_name, sex, + npc.dialogue.tags.GIFT_ITEM_HINT, npc.dialogue.tags.GIFT_ITEM_LIKED} + }) + end + for i = 1, #def.responses do + table.insert(dialogues, { + text = def.responses[i], + tags = {phase, item_name, sex, + npc.dialogue.tags.GIFT_ITEM_RESPONSE, npc.dialogue.tags.GIFT_ITEM_LIKED} + }) + end + -- Register all dialogues + for i = 1, #dialogues do + npc.dialogue.register_dialogue(dialogues[i]) + end + + -- Insert item into table + table.insert(npc.relationships.gift_items.liked[sex][phase], item_name) +end + +function npc.relationships.register_disliked_item(item_name, sex, def) + local dialogues = {} + -- Register dialogues based on the hints and responses + -- Liked + for i = 1, #def.hints do + table.insert(dialogues, { + text = def.hints[i], + tags = {item_name, sex, + npc.dialogue.tags.GIFT_ITEM_HINT, npc.dialogue.tags.GIFT_ITEM_UNLIKED} + }) + end + for i = 1, #def.responses do + table.insert(dialogues, { + text = def.responses[i], + tags = {item_name, sex, + npc.dialogue.tags.GIFT_ITEM_RESPONSE, npc.dialogue.tags.GIFT_ITEM_UNLIKED} + }) + end + -- Register all dialogues + for i = 1, #dialogues do + npc.dialogue.register_dialogue(dialogues[i]) + end + + -- Insert item into table + table.insert(npc.relationships.gift_items.disliked[sex], item_name) +end + + +function npc.relationships.get_dialogues_for_gift_item(item_name, dialogue_type, item_type, sex, phase) + local tags = { + [1] = item_name, + [2] = dialogue_type, + [3] = item_type, + [4] = sex + } + if phase ~= nil then + tags[5] = phase + end + minetest.log("Searching with tags: "..dump(tags)) + + return npc.dialogue.search_dialogue_by_tags(tags, true) +end + -- Returns the response message for a given item function npc.relationships.get_response_for_favorite_item(item_name, sex, phase) local items = npc.FAVORITE_ITEMS.female @@ -144,7 +268,7 @@ end -- Relationship functions ---------------------------------------------------------------------------------------- +----------------------------------------------------------------------------- -- This function selects two random items from the npc.favorite_items table -- It checks for sex and phase for choosing the items @@ -152,18 +276,19 @@ function npc.relationships.select_random_favorite_items(sex, phase) local result = {} local items = {} - -- Filter sex - if sex == npc.FEMALE then - items = npc.FAVORITE_ITEMS.female - else - items = npc.FAVORITE_ITEMS.male - end + -- -- Filter sex + -- if sex == npc.FEMALE then + -- items = npc.FAVORITE_ITEMS.female + -- else + -- items = npc.FAVORITE_ITEMS.male + -- end -- Select the phase - items = items[phase] + -- items = items[phase] + items = npc.relationships.gift_items.liked[sex][phase] - result.fav1 = items[math.random(1, #items)].item - result.fav2 = items[math.random(1, #items)].item + result.fav1 = items[math.random(1, #items)] + result.fav2 = items[math.random(1, #items)] return result end @@ -174,15 +299,16 @@ function npc.relationships.select_random_disliked_items(sex) local result = {} local items = {} - -- Filter sex - if sex == npc.FEMALE then - items = npc.DISLIKED_ITEMS.female - else - items = npc.DISLIKED_ITEMS.male - end + -- -- Filter sex + -- if sex == npc.FEMALE then + -- items = npc.DISLIKED_ITEMS.female + -- else + -- items = npc.DISLIKED_ITEMS.male + -- end + items = npc.relationships.gift_items.disliked[sex] - result.dis1 = items[math.random(1, #items)].item - result.dis2 = items[math.random(1, #items)].item + result.dis1 = items[math.random(1, #items)] + result.dis2 = items[math.random(1, #items)] return result end @@ -233,10 +359,8 @@ local function update_relationship(self, clicker_name, modifier) npc.relationships.select_random_favorite_items(self.sex, self.relationships[i].phase) -- Re-select dialogues per new self.dialogues = - npc.dialogue.select_random_dialogues_for_npc(self.sex, - self.relationships[i].phase, - self.gift_data.favorite_items, - self.gift_data.disliked_items) + npc.dialogue.select_random_dialogues_for_npc(self, + self.relationships[i].phase) return true end return false @@ -352,14 +476,22 @@ local function show_receive_gift_reaction(self, item_name, modifier, clicker_nam -- Send message -- TODO: There might be an error with getting the message... --minetest.log("Item_name: "..dump(item_name)..", sex: "..dump(self.sex)..", phase: "..dump(phase)) - local message_to_send = - npc.relationships.get_response_for_favorite_item(item_name, self.sex, phase) + local message_to_send = npc.relationships.get_dialogues_for_gift_item( + item_name, + npc.relationships.GIFT_ITEM_RESPONSE, + npc.relationships.GIFT_ITEM_LIKED, + self.sex, + phase) npc.chat(self.npc_name, clicker_name, message_to_send) -- Disliked items reactions elseif modifier < 0 then effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "default_item_smoke.png") --minetest.log("Item name: "..item_name..", sex: "..self.sex) - local message_to_send = npc.relationships.get_response_for_disliked_item(item_name, self.sex) + local message_to_send = npc.relationships.get_dialogues_for_gift_item( + item_name, + npc.relationships.GIFT_ITEM_RESPONSE, + npc.relationships.GIFT_ITEM_DISLIKED, + self.sex) npc.chat(self.npc_name, clicker_name, message_to_send) end @@ -390,7 +522,7 @@ function npc.relationships.receive_gift(self, clicker) -- and that with only a certain chance. The self.owner is to whom is married -- this NPC... he he. if get_relationship_points(self, clicker_name) >= - npc.relationships.RELATIONSHIP_PHASE["phase5"].limit + npc.relationships.RELATIONSHIP_PHASE["phase5"].limit and self.owner ~= clicker_name and item:get_name() ~= "advanced_npc:marriage_ring" then npc.chat(self.npc_name, clicker_name, @@ -401,7 +533,7 @@ function npc.relationships.receive_gift(self, clicker) reset_gift_timer(self, clicker_name) return true elseif get_relationship_points(self, clicker_name) >= - npc.relationships.RELATIONSHIP_PHASE["phase5"].limit + npc.relationships.RELATIONSHIP_PHASE["phase5"].limit and item:get_name() == "advanced_npc:marriage_ring" then -- If the player/entity is offering a marriage ring, then NPC will accept with a 50% -- chance to marry the clicker @@ -425,21 +557,20 @@ function npc.relationships.receive_gift(self, clicker) else npc.chat(self.npc_name, clicker_name, "Dear, I feel the same as you. But maybe not yet...") - end -- Reset gift timer reset_gift_timer(self, clicker_name) return true end -- Marriage gifts: except for disliked items, all product a 0.5 * npc.ITEM_GIFT_EFFECT - -- Disliked items cause only a -1 point effect + -- Disliked items cause only a -0.5 point effect if get_relationship_points(self, clicker_name) >= npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then local modifier = 0.5 * npc.ITEM_GIFT_EFFECT -- Check for disliked items if item:get_name() == self.gift_data.disliked_items.dis1 or item:get_name() == self.gift_data.disliked_items.dis2 then - modifier = -1 + modifier = -0.5 show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false) elseif item:get_name() == self.gift_data.favorite_items.fav1 or item:get_name() == self.gift_data.favorite_items.fav2 then