diff --git a/dialogue.lua b/dialogue.lua index 82f4aee..2030cac 100644 --- a/dialogue.lua +++ b/dialogue.lua @@ -116,14 +116,14 @@ function npc.dialogue.select_random_dialogues_for_npc(sex, phase, favorite_items for i = 1, 2 do result.hints[i] = {} result.hints[i].text = - npc.get_hint_for_favorite_item(favorite_items["fav"..tostring(i)], sex, phase) + npc.relationships.get_hint_for_favorite_item(favorite_items["fav"..tostring(i)], sex, phase) end -- Disliked items for i = 3, 4 do result.hints[i] = {} result.hints[i].text = - npc.get_hint_for_disliked_item(disliked_items["dis"..tostring(i-2)], sex) + npc.relationships.get_hint_for_disliked_item(disliked_items["dis"..tostring(i-2)], sex) end return result @@ -137,9 +137,9 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue) local dialogue = {} -- Construct dialogue for marriage - if npc.get_relationship_phase(self, player:get_player_name()) == "phase6" + if npc.relationships.get_relationship_phase(self, player:get_player_name()) == "phase6" and show_married_dialogue == true then - dialogue = npc.MARRIED_NPC_DIALOGUE + dialogue = npc.relationships.MARRIED_NPC_DIALOGUE npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) return end @@ -251,10 +251,12 @@ minetest.register_on_player_receive_fields(function (player, formname, fields) elseif player_response.options[i].action_type == "function" then -- Execute function - get it directly from definition -- Find NPC relationship phase with player - local phase = npc.get_relationship_phase(player_response.npc, player_name) + local phase = + npc.relationships.get_relationship_phase(player_response.npc, player_name) -- Check if NPC is married and the married NPC dialogue should be shown if phase == "phase6" and player_response.is_married_dialogue == true then - npc.MARRIED_NPC_DIALOGUE.responses[player_response.options[i].response_id] + npc.relationships.MARRIED_NPC_DIALOGUE + .responses[player_response.options[i].response_id] .action(player_response.npc, player) else -- Get dialogues for sex and phase diff --git a/init.lua b/init.lua index 0d08718..1f6c0a8 100755 --- a/init.lua +++ b/init.lua @@ -25,8 +25,10 @@ mobs.intllib = S -- NPC dofile(path .. "/npc.lua") +dofile(path .. "/relationships.lua") dofile(path .. "/dialogue.lua") dofile(path .. "/random_data.lua") -dofile(path .. "/trade.lua") +dofile(path .. "/trade/trade.lua") +dofile(path .. "/trade/prices.lua") print (S("[MOD] Advanced NPC loaded")) diff --git a/npc.lua b/npc.lua index 6bd257f..a349e51 100755 --- a/npc.lua +++ b/npc.lua @@ -1,58 +1,13 @@ +-- Advanced NPC by Zorman2000 +-- Based on original NPC by Tenplus1 local S = mobs.intllib --- Advanced NPC by Zorman2000 --- Based on original NPC by Tenplus1 npc = {} -- Constants npc.FEMALE = "female" npc.MALE = "male" -npc.ITEM_GIFT_EFFECT = 2.5 --- Expected values for these are 720 each respectively -npc.GIFT_TIMER_INTERVAL = 2 -npc.RELATIONSHIP_DECREASE_TIMER_INTERVAL = 60 -npc.RELATIONSHIP_PHASE = {} --- Define phases -npc.RELATIONSHIP_PHASE["phase1"] = {limit = 10} -npc.RELATIONSHIP_PHASE["phase2"] = {limit = 25} -npc.RELATIONSHIP_PHASE["phase3"] = {limit = 45} -npc.RELATIONSHIP_PHASE["phase4"] = {limit = 70} -npc.RELATIONSHIP_PHASE["phase5"] = {limit = 100} - --- Married NPC dialogue definition -npc.MARRIED_NPC_DIALOGUE = { - text = "Hi darling!", - is_married_dialogue = true, - responses = { - [1] = { - text = "Let's talk!", - action_type = "function", - response_id = 1, - action = function(self, player) - npc.start_dialogue(self, player, false) - end - }, - [2] = { - text = "Honey, can you wait for me here?", - action_type = "function", - response_id = 2, - action = function(self, player) - self.order = "stand" - minetest.chat_send_player(player:get_player_name(), S("Ok dear, I will wait here for you.")) - end - }, - [3] = { - text = "Come with me, please!", - action_type = "function", - response_id = 3, - action = function(self, player) - self.order = "follow" - minetest.chat_send_player(player:get_player_name(), S("Ok, let's go!")) - end - } - } -} npc.INVENTORY_ITEM_MAX_STACK = 99 @@ -61,8 +16,9 @@ mobs.npc_drops = { "default:shovel_steel", "farming:bread", "bucket:bucket_water" } - +--------------------------------------------------------------------------------------- -- General functions +--------------------------------------------------------------------------------------- -- Gets name of player or NPC function npc.get_entity_name(entity) if entity:is_player() then @@ -74,13 +30,15 @@ end -- Returns the item "wielded" by player or NPC -- TODO: Implement NPC -local function get_entity_wielded_item(entity) +function npc.get_entity_wielded_item(entity) if entity:is_player() then return entity:get_wielded_item() end end +--------------------------------------------------------------------------------------- -- Inventory functions +--------------------------------------------------------------------------------------- -- NPCs inventories are restrained to 16 slots. -- Each slot can hold one item up to 99 count. @@ -104,6 +62,8 @@ local function initialize_inventory() end -- Add an item to inventory. Returns true if add successful +-- These function can be used to give items to other NPCs +-- given that the "self" variable can be any NPC function npc.add_item_to_inventory(self, item_name, count) -- Check if NPC already has item local existing_item = npc.inventory_contains(self, item_name) @@ -165,7 +125,7 @@ function npc.take_item_from_inventory(self, item_name, count) local new_count = existing_count if existing_count - count < 0 then -- Remove item first - self.inventory[existin_item.slot] = "" + 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) @@ -180,485 +140,40 @@ function npc.take_item_from_inventory(self, item_name, count) end end --- Function to get relationship phase -function npc.get_relationship_phase_by_points(points) - if points > npc.RELATIONSHIP_PHASE["phase5"].limit then - return "phase6" - elseif points > npc.RELATIONSHIP_PHASE["phase4"].limit then - return "phase5" - elseif points > npc.RELATIONSHIP_PHASE["phase3"].limit then - return "phase4" - elseif points > npc.RELATIONSHIP_PHASE["phase2"].limit then - return "phase3" - elseif points > npc.RELATIONSHIP_PHASE["phase1"].limit then - return "phase2" - else - return "phase1" - end +-- Inventory functions for players +function npc.give_item_to_player(player, item_name, count) + local player_name = npc.get_entity_name(player) + local player_inv = minetest.get_inventory({type="player", name=player_name}) + local item = ItemStack(item_name.." "..count) + player_inv:add_item("main", item) end --- Returns the response message for a given item -function npc.get_response_for_favorite_item(item_name, sex, phase) - local items = npc.FAVORITE_ITEMS.female - if sex == npc.MALE then - items = npc.FAVORITE_ITEMS.male - end - - for i = 1, #items[phase] do - if items[phase][i].item == item_name then - return items[phase][i].response - end - end - return nil -end - --- Returns the response message for a disliked item -function npc.get_response_for_disliked_item(item_name, sex) - local items = npc.DISLIKED_ITEMS.female - if sex == npc.MALE then - items = npc.DISLIKED_ITEMS.male - end - - for i = 1, #items do - if items[i].item == item_name then - return items[i].response - end - end - return nil -end - --- Gets the item hint for a favorite item -function npc.get_hint_for_favorite_item(item_name, sex, phase) - for i = 1, #npc.FAVORITE_ITEMS[sex][phase] do - if npc.FAVORITE_ITEMS[sex][phase][i].item == item_name then - return npc.FAVORITE_ITEMS[sex][phase][i].hint - end - end - return nil -end - --- Gets the item hint for a disliked item -function npc.get_hint_for_disliked_item(item_name, sex) - for i = 1, #npc.DISLIKED_ITEMS[sex] do - if npc.DISLIKED_ITEMS[sex][i].item == item_name then - return npc.DISLIKED_ITEMS[sex][i].hint - end - end - return nil -end - - --- Functions on right click ---------------------------------------------------------------------------------------- --- Gift and relationship system ---------------------------------------------------------------------------------------- --- Each NPCs has 2 favorite and 2 disliked items. These items are chosen at spawn --- time and will be re-chosen when the age changes (from child to adult, for example). --- The items are chosen from the npc.FAVORITE_ITEMS table, and depends on sex and age. --- A player, via right-click, or another NPC, can gift an item to a NPC. In the case --- of the player, the player will give one of the currently wielded item. Gifts can be --- given only once per some time period, the NPC will reject the given item if still --- the period isn't over. --- If the NPC is neutral on the item (meanining it's neither favorite or disliked), it --- is possible it will not accept it, and the relationship the giver has with the NPC --- will be unchanged. --- In the other hand, if the item given its a favorite, the relationship points the NPC --- has with giver will increase by a given amount, depending on favoriteness. Favorite 1 --- will increase the relationship by 2 * npc.ITEM_GIFT_EFFECT, and favorite 2 only by --- npc.ITEM_GIFT_EFFECT. Similarly, if the item given is a disliked item, the NPC will --- not take it, and its relationship points with the giver will decrease by 2 or 1 times --- npc.ITEM_GIFT_EFFECT. - --- Relationship functions ---------------------------------------------------------------------------------------- - --- This function selects two random items from the npc.favorite_items table --- It checks for sex and phase for choosing the items -local function 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 - - -- Select the phase - items = items[phase] - - result.fav1 = items[math.random(1, #items)].item - result.fav2 = items[math.random(1, #items)].item - return result -end - --- This function selects two random items from the npc.disliked_items table --- It checks for sex for choosing the items. They stay the same for all --- phases -local function 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 - - result.dis1 = items[math.random(1, #items)].item - result.dis2 = items[math.random(1, #items)].item - return result -end - --- Creates a relationship with a given player or NPC -local function create_relationship(self, clicker_name) - local count = #self.relationships - self.relationships[count + 1] = { - -- Player or NPC name with whom the relationship is with - name = clicker_name, - -- Relationship points - points = 0, - -- Relationship phase, used for items and for phrases - phase = "phase1", - -- How frequent can the NPC receive a gift - gift_interval = npc.GIFT_TIMER_INTERVAL, - -- Current timer count since last gift - gift_timer_value = 0, - -- The amount of time without providing gift or talking that will decrease relationship points - relationship_decrease_interval = npc.RELATIONSHIP_DECREASE_TIMER_INTERVAL, - -- Current timer count for relationship decrease - relationship_decrease_timer_value = 0, - -- Current timer count since last time player talked to NPC - talk_timer_value = 0 - } -end - --- Returns a relationship points -local function get_relationship_points(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - return self.relationships[i].points - end - end - return nil -end - --- Updates relationship with given points -local function update_relationship(self, clicker_name, modifier) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - self.relationships[i].points = self.relationships[i].points + modifier - local current_phase = self.relationships[i].phase - self.relationships[i].phase = npc.get_relationship_phase_by_points(self.relationships[i].points) - if current_phase ~= self.relationships[i].phase then - -- Re-select favorite items per new phase - self.gift_data.favorite_items = - 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) - return true - end - return false - end - end - -- Relationship not found, huge error - return nil -end - --- Checks if a relationship with given player or NPC exists -local function check_relationship_exists(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - return true - end - end - return false -end - --- Returns the relationship phase given the name of the player -function npc.get_relationship_phase(self, clicker_name) - for i = 1, #self.relationships do - if clicker_name == self.relationships[i].name then - return self.relationships[i].phase - end - end - return nil -end - --- Checks if NPC can receive gifts -local function check_npc_can_receive_gift(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - -- Checks avoid married NPC to receive from others - if self.is_married_to == nil - or (self.is_married ~= nil and self.is_married_to == clicker_name) then - return self.relationships[i].gift_timer_value >= self.relationships[i].gift_interval - else - return false - end +function npc.check_item_to_player(player, item_name) + local player_name = npc.get_entity_name(player) + local player_inv = minetest.get_inventory({type="player", name=player_name}) + local main_list = player_inv:get_list("main") + for i = 1, #main_list do + if main_list[i]:get_name() == item_name then + return main_list[i] end end -- Not found return nil end --- Checks if relationship can be updated by talking -local function check_relationship_by_talk_timer_ready(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - return self.relationships[i].talk_timer_value >= self.relationships[i].gift_interval +function npc.take_item_from_player(player, item_name, count) + local player_name = npc.get_entity_name(player) + local player_inv = minetest.get_inventory({type="player", name=player_name}) + local main_list = player_inv:get_list("main") + for i = 1, #main_list do + if main_list[i]:get_name() == item_name then + main_list[i].take_item(count) end end -- Not found return nil end --- Resets the gift timer -local function reset_gift_timer(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - self.relationships[i].gift_timer_value = 0 - self.relationships[i].relationship_decrease_timer_value = 0 - return - end - end -end - --- Resets the talk timer -local function reset_talk_timer(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - self.relationships[i].talk_timer_value = 0 - return - end - end -end - --- Resets the relationshop decrease timer -local function reset_relationship_decrease_timer(self, clicker_name) - for i = 1, #self.relationships do - if self.relationships[i].name == clicker_name then - self.relationships[i].relationship_decrease_timer_value = 0 - return - end - end -end - --- Gifts functions ---------------------------------------------------------------------------------------- - --- Displays message and hearts depending on relationship level -local function show_receive_gift_reaction(self, item_name, modifier, clicker_name, phase_change) - local points = get_relationship_points(self, clicker_name) - - local pos = self.object:getpos() - -- Positive modifier (favorite items) reactions - if modifier >= 0 then - local phase = npc.get_relationship_phase_by_points(points) - if phase == "phase3" then - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 2, "heart.png") - elseif phase == "phase4" then - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 4, "heart.png") - elseif phase == "phase5" then - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 6, "heart.png") - elseif phase == "phase6" then - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png") - end - if phase_change then - local number_code = phase:byte(phase:len()) - 1 - phase = "phase"..string.char(number_code) - end - -- 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.get_response_for_favorite_item(item_name, self.sex, phase) - minetest.chat_send_player(clicker_name, message_to_send) - -- Disliked items reactions - elseif modifier < 0 then - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "smoke.png") - local message_to_send = npc.get_response_for_disliked_item(item_name, self.sex) - minetest.chat_send_player(clicker_name, message_to_send) - end - -end - --- Receive gift function; applies relationship points as explained above --- Also, creates a relationship object if not present -local function receive_gift(self, clicker) - -- Return if clicker is not offering an item - local item = get_entity_wielded_item(clicker) - if item:get_name() == "" then return false end - - -- Get clicker name - local clicker_name = npc.get_entity_name(clicker) - - -- Create relationship if it doesn't exists - if check_relationship_exists(self, clicker_name) == false then - create_relationship(self, clicker_name) - end - - -- If NPC received a gift from this person, then reject any more gifts for now - if check_npc_can_receive_gift(self, clicker_name) == false then - minetest.chat_send_player(clicker_name, "Thanks, but I don't need anything for now") - return false - end - - -- If NPC is ready for marriage, do no accept anything else but the ring, - -- 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.RELATIONSHIP_PHASE["phase5"].limit - and self.owner ~= clicker_name - and item:get_name() ~= "advanced_npc:marriage_ring" then - minetest.chat_send_player(clicker_name, - "Thank you my love, but I think that you have given me") - minetest.chat_send_player(clicker_name, - "enough gifts for now. Maybe we should go a step further") - -- Reset gift timer - reset_gift_timer(self, clicker_name) - return true - elseif get_relationship_points(self, clicker_name) >= npc.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 - local receive_chance = math.random(1, 10) - -- Receive ring and get married - if receive_chance < 6 then - minetest.chat_send_player(clicker_name, - "Oh, oh you make me so happy! Yes! I will marry you!") - -- Get ring - item:take_item() - clicker:set_wielded_item(item) - -- TODO: Implement marriage event - -- Show marriage reaction - local pos = self.object:getpos() - effect({x = pos.x, y = pos.y + 1, z = pos.z}, 20, "heart.png", 4) - -- Give 100 points, so NPC is really happy on marriage - update_relationship(self, clicker_name, 100) - -- This sets the married state, for now. Hehe - self.owner = clicker_name - -- Reject ring for now - else - minetest.chat_send_player(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 - if get_relationship_points(self, clicker_name) >= npc.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 - 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 - -- Favorite item reaction - show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false) - else - -- Neutral item reaction - minetest.chat_send_player(clicker_name, "Thank you honey!") - end - -- Take item - item:take_item() - clicker:set_wielded_item(item) - -- Update relationship - update_relationship(self, clicker_name, modifier) - -- Reset gift timer - reset_gift_timer(self, clicker_name) - return true - end - - -- Modifies relationship depending on given item - local modifier = 0 - local take = true - local show_reaction = false - - if item:get_name() == self.gift_data.favorite_items.fav1 then - modifier = 2 * npc.ITEM_GIFT_EFFECT - show_reaction = true - elseif item:get_name() == self.gift_data.favorite_items.fav2 then - modifier = npc.ITEM_GIFT_EFFECT - show_reaction = true - elseif item:get_name() == self.gift_data.disliked_items.dis1 then - modifier = (-2) * npc.ITEM_GIFT_EFFECT - show_reaction = true - elseif item:get_name() == self.gift_data.disliked_items.dis2 then - modifier = (-1) * npc.ITEM_GIFT_EFFECT - show_reaction = true - else - -- If item is not a favorite or a dislike, then receive chance - -- if 70% - local receive_chance = math.random(1,10) - if receive_chance < 7 then - minetest.chat_send_player(clicker_name, "Thanks. I will find some use for this.") - else - minetest.chat_send_player(clicker_name, "Thank you, but no, I have no use for this.") - take = false - end - show_reaction = false - end - - -- Take item if NPC accepted it - if take == true then - item:take_item() - clicker:set_wielded_item(item) - end - - -- Update relationship status - local is_phase_changed = update_relationship(self, clicker_name, modifier) - - -- Show NPC reaction to gift - if show_reaction == true then - show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, is_phase_changed) - end - - minetest.log(dump(self)) - -- Reset gift timer - reset_gift_timer(self, clicker_name) - return true -end - --- Relationships are slowly increased by talking, increases by +0.2. --- Talking to married NPC increases relationship by +1 --- TODO: This needs a timer as the gift timer. NPC will talk anyways --- but relationship will not increase. -local function dialogue_relationship_update(self, clicker) - -- Get clicker name - local clicker_name = npc.get_entity_name(clicker) - - -- Check if relationship can be updated via talk - if check_relationship_by_talk_timer_ready(self, clicker_name) == false then - return - end - - -- Create relationship if it doesn't exists - if check_relationship_exists(self, clicker_name) == false then - create_relationship(self, clicker_name) - end - - local modifier = 0.2 - if self.is_married_to ~= nil and clicker_name == self.is_married_to then - modifier = 1 - end - -- Update relationship - update_relationship(self, clicker_name, modifier) - - -- Resert timers - reset_talk_timer(self, clicker_name) - reset_relationship_decrease_timer(self, clicker_name) -end - -- Chat functions function npc.start_dialogue(self, clicker, show_married_dialogue) @@ -666,11 +181,13 @@ function npc.start_dialogue(self, clicker, show_married_dialogue) npc.dialogue.start_dialogue(self, clicker, show_married_dialogue) -- Check and update relationship if needed - dialogue_relationship_update(self, clicker) + npc.relationships.dialogue_relationship_update(self, clicker) end - +--------------------------------------------------------------------------------------- +-- Definitions +--------------------------------------------------------------------------------------- mobs:register_mob("advanced_npc:npc", { type = "npc", passive = false, @@ -749,7 +266,7 @@ mobs:register_mob("advanced_npc:npc", { "Do you want to give "..item_name.." to "..self.nametag.."?", npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name, function() - receive_gift(self, clicker) + npc.relationships.receive_gift(self, clicker) end, npc.dialogue.NEGATIVE_ANSWER_LABEL, function() @@ -783,7 +300,8 @@ mobs:register_mob("advanced_npc:npc", { -- Check if married to decrease half if relationship.phase == "phase6" then -- Avoid going below the marriage phase limit - if (relationship.points - 0.5) >= npc.RELATIONSHIP_PHASE["phase5"].limit then + if (relationship.points - 0.5) >= + npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then relationship.points = relationship.points - 0.5 end else @@ -797,6 +315,14 @@ mobs:register_mob("advanced_npc:npc", { end }) +--------------------------------------------------------------------------------------- +-- Spawning functions +--------------------------------------------------------------------------------------- +-- These functions are used at spawn time to determine several +-- random attributes for the NPC in case they are not already +-- defined. On a later phase, pre-defining many of the NPC values +-- will be allowed. + -- This function checks for "female" text on the texture name local function is_female_texture(textures) for i = 1, #textures do @@ -813,6 +339,20 @@ local function can_have_relationships() return chance > 3 end +-- Choose a maximum of two items that the NPC will have at spawn time +-- These items are chosen from the favorite items list. +local function choose_spawn_items(self) + local number_of_items_to_add = math.random(1, 2) + local number_of_items = #npc.FAVORITE_ITEMS[self.sex] + for i = 1, number_of_items_to_add do + npc.add_item_to_inventory( + self, + npc.FAVORITE_ITEMS[self.sex][math.random(1, number_of_items)], + math.random(1,4) + ) + end +end + local function npc_spawn(self, pos) minetest.log("Spawning new NPC:") local ent = self:get_luaentity() @@ -828,9 +368,9 @@ local function npc_spawn(self, pos) -- Initialize all gift data ent.gift_data = { -- Choose favorite items. Choose phase1 per default - favorite_items = select_random_favorite_items(ent.sex, "phase1"), + favorite_items = npc.relationships.select_random_favorite_items(ent.sex, "phase1"), -- Choose disliked items. Choose phase1 per default - disliked_items = select_random_disliked_items(ent.sex), + disliked_items = npc.relationships.select_random_disliked_items(ent.sex), } -- Flag that determines if NPC can have a relationship @@ -851,9 +391,12 @@ local function npc_spawn(self, pos) -- Declare NPC inventory ent.inventory = initialize_inventory() + -- Choose items to spawn with + choose_spawn_items(ent) + ent.trader_data = { -- Type of trader - trader_status = npc.trade.get_random_trade_status() + trader_status = npc.trade.get_random_trade_status(), -- Items to buy items_to_buy = {}, -- Items to sell diff --git a/relationships.lua b/relationships.lua new file mode 100644 index 0000000..842f8e9 --- /dev/null +++ b/relationships.lua @@ -0,0 +1,538 @@ +--------------------------------------------------------------------------------------- +-- Gift and relationship system +--------------------------------------------------------------------------------------- +-- Each NPCs has 2 favorite and 2 disliked items. These items are chosen at spawn +-- time and will be re-chosen when the age changes (from child to adult, for example). +-- The items are chosen from the npc.FAVORITE_ITEMS table, and depends on sex and age. +-- A player, via right-click, or another NPC, can gift an item to a NPC. In the case +-- of the player, the player will give one of the currently wielded item. Gifts can be +-- given only once per some time period, the NPC will reject the given item if still +-- the period isn't over. +-- If the NPC is neutral on the item (meanining it's neither favorite or disliked), it +-- is possible it will not accept it, and the relationship the giver has with the NPC +-- will be unchanged. +-- In the other hand, if the item given its a favorite, the relationship points the NPC +-- has with giver will increase by a given amount, depending on favoriteness. Favorite 1 +-- will increase the relationship by 2 * npc.ITEM_GIFT_EFFECT, and favorite 2 only by +-- npc.ITEM_GIFT_EFFECT. Similarly, if the item given is a disliked item, the NPC will +-- not take it, and its relationship points with the giver will decrease by 2 or 1 times +-- npc.ITEM_GIFT_EFFECT. + +local S = mobs.intllib + +npc.relationships = {} + +-- Constants +npc.relationships.ITEM_GIFT_EFFECT = 2.5 + +-- Expected values for these are 720 each respectively +npc.relationships.GIFT_TIMER_INTERVAL = 2 +npc.relationships.RELATIONSHIP_DECREASE_TIMER_INTERVAL = 60 + +npc.relationships.RELATIONSHIP_PHASE = {} +-- Define phases +npc.relationships.RELATIONSHIP_PHASE["phase1"] = {limit = 10} +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} + +-- Married NPC dialogue definition +npc.relationships.MARRIED_NPC_DIALOGUE = { + text = "Hi darling!", + is_married_dialogue = true, + responses = { + [1] = { + text = "Let's talk!", + action_type = "function", + response_id = 1, + action = function(self, player) + npc.start_dialogue(self, player, false) + end + }, + [2] = { + text = "Honey, can you wait for me here?", + action_type = "function", + response_id = 2, + action = function(self, player) + self.order = "stand" + minetest.chat_send_player(player:get_player_name(), S("Ok dear, I will wait here for you.")) + end + }, + [3] = { + text = "Come with me, please!", + action_type = "function", + response_id = 3, + action = function(self, player) + self.order = "follow" + minetest.chat_send_player(player:get_player_name(), S("Ok, let's go!")) + end + } + } +} + +-- Function to get relationship phase +function npc.relationships.get_relationship_phase_by_points(points) + if points > npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then + return "phase6" + elseif points > npc.relationships.RELATIONSHIP_PHASE["phase4"].limit then + return "phase5" + elseif points > npc.relationships.RELATIONSHIP_PHASE["phase3"].limit then + return "phase4" + elseif points > npc.relationships.RELATIONSHIP_PHASE["phase2"].limit then + return "phase3" + elseif points > npc.relationships.RELATIONSHIP_PHASE["phase1"].limit then + return "phase2" + else + return "phase1" + end +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 + if sex == npc.MALE then + items = npc.FAVORITE_ITEMS.male + end + + for i = 1, #items[phase] do + if items[phase][i].item == item_name then + return items[phase][i].response + end + end + return nil +end + +-- Returns the response message for a disliked item +function npc.relationships.get_response_for_disliked_item(item_name, sex) + local items = npc.DISLIKED_ITEMS.female + if sex == npc.MALE then + items = npc.DISLIKED_ITEMS.male + end + + for i = 1, #items do + minetest.log(dump(items[i])) + if items[i].item == item_name then + minetest.log("Returning: "..dump(items[i].response)) + return items[i].response + end + end + return nil +end + +-- Gets the item hint for a favorite item +function npc.relationships.get_hint_for_favorite_item(item_name, sex, phase) + for i = 1, #npc.FAVORITE_ITEMS[sex][phase] do + if npc.FAVORITE_ITEMS[sex][phase][i].item == item_name then + return npc.FAVORITE_ITEMS[sex][phase][i].hint + end + end + return nil +end + +-- Gets the item hint for a disliked item +function npc.relationships.get_hint_for_disliked_item(item_name, sex) + for i = 1, #npc.DISLIKED_ITEMS[sex] do + if npc.DISLIKED_ITEMS[sex][i].item == item_name then + return npc.DISLIKED_ITEMS[sex][i].hint + end + end + return nil +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 +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 + + -- Select the phase + items = items[phase] + + result.fav1 = items[math.random(1, #items)].item + result.fav2 = items[math.random(1, #items)].item + return result +end + +-- This function selects two random items from the npc.disliked_items table +-- It checks for sex for choosing the items. They stay the same for all +-- phases +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 + + result.dis1 = items[math.random(1, #items)].item + result.dis2 = items[math.random(1, #items)].item + return result +end + +-- Creates a relationship with a given player or NPC +local function create_relationship(self, clicker_name) + local count = #self.relationships + self.relationships[count + 1] = { + -- Player or NPC name with whom the relationship is with + name = clicker_name, + -- Relationship points + points = 0, + -- Relationship phase, used for items and for phrases + phase = "phase1", + -- How frequent can the NPC receive a gift + gift_interval = npc.relationships.GIFT_TIMER_INTERVAL, + -- Current timer count since last gift + gift_timer_value = 0, + -- The amount of time without providing gift or talking that will decrease relationship points + relationship_decrease_interval = npc.relationships.RELATIONSHIP_DECREASE_TIMER_INTERVAL, + -- Current timer count for relationship decrease + relationship_decrease_timer_value = 0, + -- Current timer count since last time player talked to NPC + talk_timer_value = 0 + } +end + +-- Returns a relationship points +local function get_relationship_points(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + return self.relationships[i].points + end + end + return nil +end + +-- Updates relationship with given points +local function update_relationship(self, clicker_name, modifier) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + self.relationships[i].points = self.relationships[i].points + modifier + local current_phase = self.relationships[i].phase + self.relationships[i].phase = + npc.relationships.get_relationship_phase_by_points(self.relationships[i].points) + if current_phase ~= self.relationships[i].phase then + -- Re-select favorite items per new phase + self.gift_data.favorite_items = + 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) + return true + end + return false + end + end + -- Relationship not found, huge error + return nil +end + +-- Checks if a relationship with given player or NPC exists +local function check_relationship_exists(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + return true + end + end + return false +end + +-- Returns the relationship phase given the name of the player +function npc.relationships.get_relationship_phase(self, clicker_name) + for i = 1, #self.relationships do + if clicker_name == self.relationships[i].name then + return self.relationships[i].phase + end + end + return nil +end + +-- Checks if NPC can receive gifts +local function check_npc_can_receive_gift(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + -- Checks avoid married NPC to receive from others + if self.is_married_to == nil + or (self.is_married ~= nil and self.is_married_to == clicker_name) then + return self.relationships[i].gift_timer_value >= self.relationships[i].gift_interval + else + return false + end + end + end + -- Not found + return nil +end + +-- Checks if relationship can be updated by talking +local function check_relationship_by_talk_timer_ready(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + return self.relationships[i].talk_timer_value >= self.relationships[i].gift_interval + end + end + -- Not found + return nil +end + +-- Resets the gift timer +local function reset_gift_timer(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + self.relationships[i].gift_timer_value = 0 + self.relationships[i].relationship_decrease_timer_value = 0 + return + end + end +end + +-- Resets the talk timer +local function reset_talk_timer(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + self.relationships[i].talk_timer_value = 0 + return + end + end +end + +-- Resets the relationshop decrease timer +local function reset_relationship_decrease_timer(self, clicker_name) + for i = 1, #self.relationships do + if self.relationships[i].name == clicker_name then + self.relationships[i].relationship_decrease_timer_value = 0 + return + end + end +end + +-- Gifts functions +--------------------------------------------------------------------------------------- + +-- Displays message and hearts depending on relationship level +local function show_receive_gift_reaction(self, item_name, modifier, clicker_name, phase_change) + local points = get_relationship_points(self, clicker_name) + + local pos = self.object:getpos() + -- Positive modifier (favorite items) reactions + if modifier >= 0 then + local phase = npc.relationships.get_relationship_phase_by_points(points) + if phase == "phase3" then + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 2, "heart.png") + elseif phase == "phase4" then + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 4, "heart.png") + elseif phase == "phase5" then + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 6, "heart.png") + elseif phase == "phase6" then + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png") + end + if phase_change then + local number_code = phase:byte(phase:len()) - 1 + phase = "phase"..string.char(number_code) + end + -- 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) + minetest.chat_send_player(clicker_name, message_to_send) + -- Disliked items reactions + elseif modifier < 0 then + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "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) + minetest.chat_send_player(clicker_name, message_to_send) + end + +end + +-- Receive gift function; applies relationship points as explained above +-- Also, creates a relationship object if not present +function npc.relationships.receive_gift(self, clicker) + -- Return if clicker is not offering an item + local item = npc.get_entity_wielded_item(clicker) + if item:get_name() == "" then return false end + + -- Get clicker name + local clicker_name = npc.get_entity_name(clicker) + + -- Create relationship if it doesn't exists + if check_relationship_exists(self, clicker_name) == false then + create_relationship(self, clicker_name) + end + + -- If NPC received a gift from this person, then reject any more gifts for now + if check_npc_can_receive_gift(self, clicker_name) == false then + minetest.chat_send_player(clicker_name, "Thanks, but I don't need anything for now") + return false + end + + -- If NPC is ready for marriage, do no accept anything else but the ring, + -- 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 + and self.owner ~= clicker_name + and item:get_name() ~= "advanced_npc:marriage_ring" then + minetest.chat_send_player(clicker_name, + "Thank you my love, but I think that you have given me") + minetest.chat_send_player(clicker_name, + "enough gifts for now. Maybe we should go a step further") + -- Reset gift timer + reset_gift_timer(self, clicker_name) + return true + elseif get_relationship_points(self, clicker_name) >= + 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 + local receive_chance = math.random(1, 10) + -- Receive ring and get married + if receive_chance < 6 then + minetest.chat_send_player(clicker_name, + "Oh, oh you make me so happy! Yes! I will marry you!") + -- Get ring + item:take_item() + clicker:set_wielded_item(item) + -- TODO: Implement marriage event + -- Show marriage reaction + local pos = self.object:getpos() + effect({x = pos.x, y = pos.y + 1, z = pos.z}, 20, "heart.png", 4) + -- Give 100 points, so NPC is really happy on marriage + update_relationship(self, clicker_name, 100) + -- This sets the married state, for now. Hehe + self.owner = clicker_name + -- Reject ring for now + else + minetest.chat_send_player(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 + 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 + 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 + -- Favorite item reaction + show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, false) + else + -- Neutral item reaction + minetest.chat_send_player(clicker_name, "Thank you honey!") + end + -- Take item + item:take_item() + clicker:set_wielded_item(item) + -- Update relationship + update_relationship(self, clicker_name, modifier) + -- Reset gift timer + reset_gift_timer(self, clicker_name) + return true + end + + -- Modifies relationship depending on given item + local modifier = 0 + local take = true + local show_reaction = false + + if item:get_name() == self.gift_data.favorite_items.fav1 then + modifier = 2 * npc.relationships.ITEM_GIFT_EFFECT + show_reaction = true + elseif item:get_name() == self.gift_data.favorite_items.fav2 then + modifier = npc.relationships.ITEM_GIFT_EFFECT + show_reaction = true + elseif item:get_name() == self.gift_data.disliked_items.dis1 then + modifier = (-2) * npc.relationships.ITEM_GIFT_EFFECT + show_reaction = true + elseif item:get_name() == self.gift_data.disliked_items.dis2 then + modifier = (-1) * npc.relationships.ITEM_GIFT_EFFECT + show_reaction = true + else + -- If item is not a favorite or a dislike, then receive chance + -- if 70% + local receive_chance = math.random(1,10) + if receive_chance < 7 then + minetest.chat_send_player(clicker_name, "Thanks. I will find some use for this.") + else + minetest.chat_send_player(clicker_name, "Thank you, but no, I have no use for this.") + take = false + end + show_reaction = false + end + + -- Update relationship status + local is_phase_changed = update_relationship(self, clicker_name, modifier) + + -- Show NPC reaction to gift + if show_reaction == true then + show_receive_gift_reaction(self, item:get_name(), modifier, clicker_name, is_phase_changed) + end + + -- Take item if NPC accepted it + if take == true then + item:take_item() + clicker:set_wielded_item(item) + end + + minetest.log(dump(self)) + -- Reset gift timer + reset_gift_timer(self, clicker_name) + return true +end + +-- Relationships are slowly increased by talking, increases by +0.2. +-- Talking to married NPC increases relationship by +1 +-- TODO: This needs a timer as the gift timer. NPC will talk anyways +-- but relationship will not increase. +function npc.relationships.dialogue_relationship_update(self, clicker) + -- Get clicker name + local clicker_name = npc.get_entity_name(clicker) + + -- Check if relationship can be updated via talk + if check_relationship_by_talk_timer_ready(self, clicker_name) == false then + return + end + + -- Create relationship if it doesn't exists + if check_relationship_exists(self, clicker_name) == false then + create_relationship(self, clicker_name) + end + + local modifier = 0.2 + if self.is_married_to ~= nil and clicker_name == self.is_married_to then + modifier = 1 + end + -- Update relationship + update_relationship(self, clicker_name, modifier) + + -- Resert timers + reset_talk_timer(self, clicker_name) + reset_relationship_decrease_timer(self, clicker_name) +end diff --git a/trade/prices.lua b/trade/prices.lua new file mode 100644 index 0000000..483376f --- /dev/null +++ b/trade/prices.lua @@ -0,0 +1,53 @@ +-- Price table for items bought/sold by NPC traders by Zorman2000 +-- This table should be globally accessible so that other mods can set +-- prices as they see fit. + +npc.trade.prices = {} + +-- Table that contains the prices +npc.trade.prices.table = {} + +-- Default definitions for in-game items +npc.trade.prices.table["default:apple"] = {item = "default:iron_ingot", count = 1} +npc.trade.prices.table["default:stone"] = {item = "default:wood_planks", count = 1} +npc.trade.prices.table["default:cobble"] = {item = "default:iron_ingot", count = 1} +npc.trade.prices.table["farming:cotton"] = {item = "default:iron_ingot", count = 1} +npc.trade.prices.table["farming:bread"] = {item = "default:gold_ingot", count = 1} +npc.trade.prices.table["default:sword_stone"] = {item = "default:iron_ingot", count = 2} +npc.trade.prices.table["default:pick_stone"] = {item = "default:iron_ingot", count = 1} +npc.trade.prices.table["default:shovel_stone"] = {item = "default:iron_ingot", count = 2} +npc.trade.prices.table["default:axe_stone"] = {item = "default:iron_ingot", count = 1} +npc.trade.prices.table["default:hoe_stone"] = {item = "default:iron_ingot", count = 1} + + +-- Functions +function npc.trade.prices.update(item_name, price) + for key,value in pairs(npc.trade.prices.table) do + if key == item_name then + value = price + return + end + end + return nil +end + +function npc.trade.prices.get(item_name) + for key,value in pairs(npc.trade.prices.table) do + if key == item_name then + return {item_name = key, price = value} + end + end + return nil +end + +function npc.trade.prices.add(item_name, price) + if npc.trade.prices.get(item_name) == nil then + npc.trade.prices.table[item_name] = price + else + npc.trade.prices.update(item_name, price) + end +end + +function npc.trade.prices.remove(item_name) + npc.trade.prices.table[item_name] = nil +end \ No newline at end of file diff --git a/trade.lua b/trade/trade.lua similarity index 100% rename from trade.lua rename to trade/trade.lua