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