diff --git a/README.md b/README.md index 2ae7702..3a9bfb2 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ Current progress and roadmap __Version 1.0__ +NPCs has the following abilities: +- [x] Inventory: a 16 slot custom inventory, with methods to add, check and remove items. +- [x] Flags: a simple table that can hold different variables. They can be used to drive events. +- [x] Actions: a queue that can contain a set of specific actions that execute at fixed timer interval. + __Phase 1__: Gifts and relationships: In progress - [x] NPCs should be able to receive items - [x] NPCs will have favorite and disliked items @@ -33,7 +38,7 @@ __Phase 2__: Dialogues: In progress - [x] NPCs should be able to perform complex dialogues: - [x] Use yes/no or multiple option dialogue boxes to interact with player - [x] Answers and responses by player -- [ ] Specific dialogues on certain flags (so that events can change what an NPC says) +- [ ] Specific dialogues on certain flags (so that events can change what an NPC says) (in progress) __Phase 3__: Trading: In progress - [ ] NPCs should be able to trade, either buy or sell items to/from player and other NPCs @@ -45,7 +50,7 @@ __Phase 3__: Trading: In progress __Phase 4__: Actions: Complete - [x] NPCs should be able to use chests, furnaces, doors, beds and sit on "sittable" nodes - [x] NPCs should be able to walk to specific places. Should also be able to open doors, fence gates and any other type of openable node while going to a place. -- [x] NPCs should have the ability to identify nodes that belong to him/her, and recall them/ +- [x] NPCs should have the ability to identify nodes that belong to him/her, and recall them __Phase 5__: Schedules and fundamental jobs - [ ] NPCs should be able to perform different activities on depending on the time of the day. For instance, a NPC could be farming during the morning, selling its product on the afternoon, and comfortable sitting at home during the night. diff --git a/dialogue.lua b/dialogue.lua index 1d33a6f..bfccdef 100644 --- a/dialogue.lua +++ b/dialogue.lua @@ -1,4 +1,20 @@ -- 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 = {} @@ -14,10 +30,11 @@ npc.dialogue.dialogue_results = { yes_no_dialogue = {} } +--------------------------------------------------------------------------------------- -- 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, @@ -80,7 +97,35 @@ 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 +local function set_response_ids_recursively(dialogue, depth, dialogue_id) + -- Base case: dialogue object with no responses and no r,esponses below it + if dialogue.responses == nil + and (dialogue.action_type == "dialogue" and dialogue.action.responses == nil) then + return + elseif dialogue.responses ~= nil then + -- Assign a response ID to each response + local response_id_prefix = tostring(depth)..":" + for key,value in ipairs(dialogue.responses) do + if value.action_type == "function" then + value.response_id = response_id_prefix..key + value.dialogue_id = dialogue_id + else + -- We have a dialogue action type. Need to check if dialogue has further responses + if value.action.responses ~= nil then + set_response_ids_recursively(value.action, depth + 1, dialogue_id) + end + end + end + end +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) @@ -102,19 +147,7 @@ function npc.dialogue.select_random_dialogues_for_npc(sex, phase, favorite_items local dialogue_id = math.random(1, #dialogues) result.normal[i] = dialogues[dialogue_id] - -- Check if this particular dialogue has responses - if result.normal[i].responses then - -- Check each response to see if they have action_type == "function". - -- This way the indices for this particular response will be stored - -- and the function can be retrieved for execution later. - for key,value in ipairs(result.normal[i].responses) do - if value.action_type == "function" then - result.normal[i].responses[key].dialogue_id = dialogue_id - result.normal[i].responses[key].response_id = key - end - end - - end + set_response_ids_recursively(result.normal[i], 0, dialogue_id) end -- Add item hints. @@ -139,7 +172,6 @@ end -- and process it. function npc.dialogue.start_dialogue(self, player, show_married_dialogue) -- Choose a dialogue randomly - -- TODO: Add support for flags local dialogue = {} -- Construct dialogue for marriage @@ -167,14 +199,20 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue) end end elseif chance >= 30 and chance < 90 then + -- 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 dialogue = self.dialogues.hints[math.random(1, 4)] end minetest.log("Chosen dialogue: "..dump(dialogue)) - npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) + 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 end -- This function processes a dialogue object and performs @@ -184,18 +222,37 @@ function npc.dialogue.process_dialogue(self, dialogue, player_name) -- Freeze NPC actions npc.lock_actions(self) + -- Check if this dialogue has a flag definition + if dialogue.flag then + -- Check if the NPC has this flag + local flag_value = npc.get_flag(self, dialogue.flag.name) + if flag_value ~= nil then + -- Check if value of the flag is equal to the expected value + if flag_value ~= dialogue.flag.value then + -- Do not process this dialogue + return false + end + else + + if (type(dialogue.flag.value) == "boolean" and dialogue.flag.value ~= false) + or (type(dialogue.flag.value) == "number" and dialogue.flag.value > 0) then + -- Do not process this dialogue + return false + end + end + end + -- Send dialogue line if dialogue.text then minetest.chat_send_player(player_name, self.nametag..": "..dialogue.text) - -- Check if dialogue has responses. If it doesn't, unlock the actions - -- queue and reset actions timer.' - minetest.log("Responses: "..dump(dialogue.responses)) - minetest.log("Condition: "..dump(not dialogue.responses)) - if not dialogue.responses then - npc.unlock_actions(self) - end end + -- Check if dialogue has responses. If it doesn't, unlock the actions + -- queue and reset actions timer.' + if not dialogue.responses then + npc.unlock_actions(self) + end + -- Check if there are responses, then show multi-option dialogue if there are if dialogue.responses then npc.dialogue.show_options_dialogue( @@ -208,6 +265,9 @@ function npc.dialogue.process_dialogue(self, dialogue, player_name) player_name ) end + + -- Dialogue object processed successfully + return true end ----------------------------------------------------------------------------- @@ -250,6 +310,45 @@ function npc.dialogue.rotate_npc_to_player(self) self.object:setyaw(yaw) end +--------------------------------------------------------------------------------------- +-- Answer processing functions +--------------------------------------------------------------------------------------- +-- This function locates a response object that has function on the dialogue tree. +local function get_response_object_by_id_recursive(dialogue, current_depth, response_id) + if dialogue.responses == nil + and (dialogue.action_type == "dialogue" and dialoge.action.responses == nil) then + return nil + elseif dialogue.responses ~= nil then + -- Get current depth and response ID + local d_i1, d_i2 = string.find(response_id, ":") + minetest.log("N1: "..dump(string.sub(response_id, 0, d_i1))..", N2: "..dump(string.sub(response_id, 1, d_i1-1))) + local depth = tonumber(string.sub(response_id, 0, d_i1-1)) + local id = tonumber(string.sub(response_id, d_i2 + 1)) + minetest.log("Depth: "..dump(depth)..", id: "..dump(id)) + -- Check each response + for key,value in ipairs(dialogue.responses) do + minetest.log("Key: "..dump(key)..", value: "..dump(value)..", comp1: "..dump(current_depth == depth)) + if value.action_type == "function" then + -- Check if we are on correct response and correct depth + if current_depth == depth then + if key == id then + return value + end + end + else + minetest.log("Entering again...") + -- We have a dialogue action type. Need to check if dialogue has further responses + if value.action.responses ~= nil then + local response = get_response_object_by_id_recursive(value.action, current_depth + 1, response_id) + if response ~= nil then + return response + end + end + end + end + end +end + -- Handler for dialogue formspec minetest.register_on_player_receive_fields(function (player, formname, fields) -- Additional checks for other forms should be handled here @@ -290,7 +389,6 @@ minetest.register_on_player_receive_fields(function (player, formname, fields) if fields[button_label] then if player_response.options[i].action_type == "dialogue" then -- Process dialogue object - minetest.log("Action: "..dump(player_response.options[i])) npc.dialogue.process_dialogue(player_response.npc, player_response.options[i].action, player_name) @@ -324,10 +422,17 @@ minetest.register_on_player_receive_fields(function (player, formname, fields) -- Get dialogues for sex and phase local dialogues = npc.data.DIALOGUES[player_response.npc.sex][phase] + minetest.log("Object: "..dump(dialogues[player_response.options[i].dialogue_id])) + local response = get_response_object_by_id_recursive(dialogues[player_response.options[i].dialogue_id], 0, player_response.options[i].response_id) + minetest.log("Found: "..dump(response)) + + -- Execute function + response.action(player_response.npc, player) + -- Execute function - dialogues[player_response.options[i].dialogue_id] - .responses[player_response.options[i].response_id] - .action(player_response.npc, player) + -- dialogues[player_response.options[i].dialogue_id] + -- .responses[player_response.options[i].response_id] + -- .action(player_response.npc, player) -- Unlock queue, reset action timer and unfreeze NPC. npc.unlock_actions(player_response.npc) diff --git a/init.lua b/init.lua index 5f95add..8464f06 100755 --- a/init.lua +++ b/init.lua @@ -27,12 +27,12 @@ mobs.intllib = S dofile(path .. "/npc.lua") dofile(path .. "/relationships.lua") dofile(path .. "/dialogue.lua") -dofile(path .. "/random_data.lua") dofile(path .. "/trade/trade.lua") dofile(path .. "/trade/prices.lua") dofile(path .. "/actions/actions.lua") dofile(path .. "/actions/places.lua") dofile(path .. "/actions/pathfinder.lua") +dofile(path .. "/random_data.lua") dofile(path .. "/actions/node_registry.lua") print (S("[Mod] Advanced NPC loaded")) diff --git a/npc.lua b/npc.lua index 667101b..30a1ef7 100755 --- a/npc.lua +++ b/npc.lua @@ -175,7 +175,26 @@ function npc.take_item_from_inventory_itemstring(self, item_string) npc.take_item_from_inventory(self, item_name, item_count) end --- Dialogue functions +--------------------------------------------------------------------------------------- +-- Flag functionality +--------------------------------------------------------------------------------------- +-- TODO: Consider removing them as they are pretty simple and straight forward. +-- Generic variables or function that help drive some functionality for the NPC. +function npc.add_flag(self, flag_name, value) + self.flags[flag_name] = value +end + +function npc.update_flag(self, flag_name, value) + self.flags[flag_name] = value +end + +function npc.get_flag(self, flag_name) + return self.flags[flag_name] +end + +--------------------------------------------------------------------------------------- +-- Dialogue functionality +--------------------------------------------------------------------------------------- function npc.start_dialogue(self, clicker, show_married_dialogue) -- Call dialogue function as normal @@ -192,10 +211,7 @@ end -- This function adds a function to the action queue. -- Actions should be added in strict order for tasks to work as expected. function npc.add_action(self, action, arguments) - --self.freeze = true - --minetest.log("Current Pos: "..dump(self.object:getpos())) local action_entry = {action=action, args=arguments, is_task=false} - --minetest.log(dump(action_entry)) table.insert(self.actions.queue, action_entry) end @@ -356,7 +372,7 @@ local function choose_spawn_items(self) -- Add currency to the items spawned with. Will add 5-10 tier 3 -- currency items local currency_item_count = math.random(5, 10) - npc.add_item_to_inventory(self, npc.trade.prices.currency.tier3, currency_item_count) + 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) @@ -422,6 +438,16 @@ local function npc_spawn(self, pos) "phase1", ent.gift_data.favorite_items, ent.gift_data.disliked_items) + + -- Declare NPC inventory + ent.inventory = initialize_inventory() + + -- Choose items to spawn with + choose_spawn_items(ent) + + -- Flags: generic booleans or functions that help drive functionality + ent.flags = {} + -- Declare trade data ent.trader_data = { -- Type of trader @@ -433,15 +459,14 @@ local function npc_spawn(self, pos) -- Items to buy change timer change_offers_timer = 0, -- Items to buy change timer interval - change_offers_timer_interval = 60 + 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 = {} } - -- Declare NPC inventory - ent.inventory = initialize_inventory() - - -- Choose items to spawn with - choose_spawn_items(ent) - -- Initialize trading offers if NPC is casual trader if ent.trader_data.trader_status == npc.trade.CASUAL then select_casual_trade_offers(ent) @@ -598,7 +623,7 @@ mobs:register_mob("advanced_npc:npc", { local item = clicker:get_wielded_item() local name = clicker:get_player_name() - --minetest.log(dump(self)) + minetest.log(dump(self)) -- Receive gift or start chat. If player has no item in hand -- then it is going to start chat directly diff --git a/random_data.lua b/random_data.lua index 7ec3600..74084b4 100644 --- a/random_data.lua +++ b/random_data.lua @@ -52,7 +52,67 @@ npc.data.DIALOGUES.female["phase1"] = { } } } - } + }, + [7] = { + text = "Hello there, could you help me?", + flag = {name="received_money_help", value=false}, + responses = { + [1] = { + text = "Yes, how can I help?", + action_type = "dialogue", + action = { + text = "Could you please give me 3 "..npc.trade.prices.currency.tier3.name.."?", + responses = { + [1] = { + text = "Yes, ok, here", + action_type = "function", + action = function(self, player) + -- Take item + local args = { + player=player, + pos=nil, + inv_list="main", + item_name=npc.trade.prices.currency.tier3.string, + count=3 + } + npc.actions.take_item_from_external_inventory(self, { + player=player:get_player_name(), + pos=nil, + inv_list="main", + item_name=npc.trade.prices.currency.tier3.string, + count=3 + }) + -- Send message + minetest.chat_send_player(player:get_player_name(), "Thank you, thank you so much!") + -- Set flag + npc.add_flag(self, "received_money_help", true) + -- Add chat line + table.insert(self.dialogues.normal, npc.data.DIALOGUES.female["phase1"][8]) + end + }, + [2] = { + text = "No, I'm sorry", + action_type = "dialogue", + action = { + text = "Oh..." + } + } + } + } + }, + [2] = { + text = "No, I'm sorry, can't now", + action_type = "function", + action = function(self, player) + minetest.chat_send_player(player:get_player_name(), "Oh, ok...") + end + } + } + }, + [8] = { + text = "Thank you so much for your help, thank you!", + flag = {name="received_money_help", value=true} + } } -- Phase 2 diff --git a/trade/prices.lua b/trade/prices.lua index ea3b555..99e94a3 100644 --- a/trade/prices.lua +++ b/trade/prices.lua @@ -6,9 +6,9 @@ npc.trade.prices = {} -- Define default currency (based on lumps from default) npc.trade.prices.currency = { - tier1 = "default:gold_lump", - tier2 = "default:copper_lump", - tier3 = "default:iron_lump" + tier1 = {string = "default:gold_lump", name = "Gold lump"}, + tier2 = {string = "default:copper_lump", name = "Copper lump"}, + tier3 = {string = "default:iron_lump", name = "Iron lump"} } -- TODO: Set the currency depending on available mods @@ -18,33 +18,33 @@ npc.trade.prices.table = {} -- Default definitions for in-game items -- Tier 3 items: cheap items -npc.trade.prices.table["default:cobble"] = {tier = npc.trade.prices.currency.tier3, count = 0.1} -npc.trade.prices.table["flowers:geranium"] = {tier = npc.trade.prices.currency.tier3, count = 0.5} -npc.trade.prices.table["default:apple"] = {tier = npc.trade.prices.currency.tier3, count = 1} -npc.trade.prices.table["default:tree"] = {tier = npc.trade.prices.currency.tier3, count = 2} -npc.trade.prices.table["flowers:rose"] = {tier = npc.trade.prices.currency.tier3, count = 2} -npc.trade.prices.table["default:stone"] = {tier = npc.trade.prices.currency.tier3, count = 2} -npc.trade.prices.table["farming:seed_cotton"] = {tier = npc.trade.prices.currency.tier3, count = 3} -npc.trade.prices.table["farming:seed_wheat"] = {tier = npc.trade.prices.currency.tier3, count = 3} -npc.trade.prices.table["default:clay_lump"] = {tier = npc.trade.prices.currency.tier3, count = 3} -npc.trade.prices.table["default:wood"] = {tier = npc.trade.prices.currency.tier3, count = 3} -npc.trade.prices.table["mobs:meat_raw"] = {tier = npc.trade.prices.currency.tier3, count = 4} -npc.trade.prices.table["default:sapling"] = {tier = npc.trade.prices.currency.tier3, count = 5} -npc.trade.prices.table["mobs:meat"] = {tier = npc.trade.prices.currency.tier3, count = 5} -npc.trade.prices.table["mobs:leather"] = {tier = npc.trade.prices.currency.tier3, count = 6} -npc.trade.prices.table["default:sword_stone"] = {tier = npc.trade.prices.currency.tier3, count = 6} -npc.trade.prices.table["default:shovel_stone"] = {tier = npc.trade.prices.currency.tier3, count = 6} -npc.trade.prices.table["default:axe_stone"] = {tier = npc.trade.prices.currency.tier3, count = 6} -npc.trade.prices.table["default:hoe_stone"] = {tier = npc.trade.prices.currency.tier3, count = 6} -npc.trade.prices.table["default:pick_stone"] = {tier = npc.trade.prices.currency.tier3, count = 7} -npc.trade.prices.table["farming:cotton"] = {tier = npc.trade.prices.currency.tier3, count = 15} -npc.trade.prices.table["farming:bread"] = {tier = npc.trade.prices.currency.tier3, count = 20} +npc.trade.prices.table["default:cobble"] = {tier = npc.trade.prices.currency.tier3.string, count = 0.1} +npc.trade.prices.table["flowers:geranium"] = {tier = npc.trade.prices.currency.tier3.string, count = 0.5} +npc.trade.prices.table["default:apple"] = {tier = npc.trade.prices.currency.tier3.string, count = 1} +npc.trade.prices.table["default:tree"] = {tier = npc.trade.prices.currency.tier3.string, count = 2} +npc.trade.prices.table["flowers:rose"] = {tier = npc.trade.prices.currency.tier3.string, count = 2} +npc.trade.prices.table["default:stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 2} +npc.trade.prices.table["farming:seed_cotton"] = {tier = npc.trade.prices.currency.tier3.string, count = 3} +npc.trade.prices.table["farming:seed_wheat"] = {tier = npc.trade.prices.currency.tier3.string, count = 3} +npc.trade.prices.table["default:clay_lump"] = {tier = npc.trade.prices.currency.tier3.string, count = 3} +npc.trade.prices.table["default:wood"] = {tier = npc.trade.prices.currency.tier3.string, count = 3} +npc.trade.prices.table["mobs:meat_raw"] = {tier = npc.trade.prices.currency.tier3.string, count = 4} +npc.trade.prices.table["default:sapling"] = {tier = npc.trade.prices.currency.tier3.string, count = 5} +npc.trade.prices.table["mobs:meat"] = {tier = npc.trade.prices.currency.tier3.string, count = 5} +npc.trade.prices.table["mobs:leather"] = {tier = npc.trade.prices.currency.tier3.string, count = 6} +npc.trade.prices.table["default:sword_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6} +npc.trade.prices.table["default:shovel_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6} +npc.trade.prices.table["default:axe_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6} +npc.trade.prices.table["default:hoe_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 6} +npc.trade.prices.table["default:pick_stone"] = {tier = npc.trade.prices.currency.tier3.string, count = 7} +npc.trade.prices.table["farming:cotton"] = {tier = npc.trade.prices.currency.tier3.string, count = 15} +npc.trade.prices.table["farming:bread"] = {tier = npc.trade.prices.currency.tier3.string, count = 20} -- Tier 2 items: medium priced items -- Tier 1 items: expensive items -npc.trade.prices.table["default:diamond"] = {tier = npc.trade.prices.currency.tier1, count = 90} -npc.trade.prices.table["advanced_npc:marriage_ring"] = {tier = npc.trade.prices.currency.tier1, count = 100} +npc.trade.prices.table["default:diamond"] = {tier = npc.trade.prices.currency.tier1.string, count = 90} +npc.trade.prices.table["advanced_npc:marriage_ring"] = {tier = npc.trade.prices.currency.tier1.string, count = 100} -- Functions function npc.trade.prices.update(item_name, price) @@ -79,14 +79,43 @@ function npc.trade.prices.remove(item_name) end -- Gets all the item for a specified budget -function npc.trade.prices.get_items_for_currency_count(tier, count) +function npc.trade.prices.get_items_for_currency_count(tier, count, price_factor) local result = {} + minetest.log("Currency quantity: "..dump(count)) for item_name, price in pairs(npc.trade.prices.table) do -- Check price currency is of the same tier if price.tier == tier and price.count <= count then - result[item_name] = price + result[item_name] = {price = price} + + minetest.log("Item name: "..dump(item_name)..", Price: "..dump(price)) + + local min_buying_item_count = 1 + -- Calculate price NPC is going to buy for + local buying_price_count = price.count * price_factor + -- Check if the buying price is not an integer + if buying_price_count % 1 ~= 0 then + -- If not, increase the buying item count until we get an integer + local adjust = 1 / price_factor + if price.count < 1 then + adjust = 1 / (price.count * price_factor) + end + min_buying_item_count = min_buying_item_count * adjust + end + --minetest.log("Minimum item buy quantity: "..dump(min_buying_item_count)) + --minetest.log("Minimum item price quantity: "..dump(buying_price_count)) + -- Calculate maximum buyable quantity + local max_buying_item_count = min_buying_item_count + while ((max_buying_item_count + min_buying_item_count) * buying_price_count <= count) do + max_buying_item_count = max_buying_item_count + min_buying_item_count + end + --minetest.log("Maximum item buy quantity: "..dump(max_buying_item_count)) + + result[item_name].min_buyable_item_count = min_buying_item_count + result[item_name].min_buyable_item_price = min_buying_item_count * buying_price_count + result[item_name].max_buyable_item_count = max_buying_item_count end end + --minetest.log("Final result: "..dump(result)) return result end @@ -94,10 +123,10 @@ end -- currencies set in the currencies table. Returns true if -- itemstring is a currency. function npc.trade.prices.is_item_currency(itemstring) - if npc.get_item_name(itemstring) == npc.trade.prices.currency.tier3 - or npc.get_item_name(itemstring) == npc.trade.prices.currency.tier2 - or npc.get_item_name(itemstring) == npc.trade.prices.currency.tier1 then + if npc.get_item_name(itemstring) == npc.trade.prices.currency.tier3.string + or npc.get_item_name(itemstring) == npc.trade.prices.currency.tier2.string + or npc.get_item_name(itemstring) == npc.trade.prices.currency.tier1.string then return true end return false -end \ No newline at end of file +end diff --git a/trade/trade.lua b/trade/trade.lua index 6216522..0b13b9c 100644 --- a/trade/trade.lua +++ b/trade/trade.lua @@ -103,9 +103,9 @@ end -- items that a NPC has on his/her inventory function npc.trade.get_currencies_in_inventory(self) local result = {} - local tier3 = npc.inventory_contains(self, npc.trade.prices.currency.tier3) - local tier2 = npc.inventory_contains(self, npc.trade.prices.currency.tier2) - local tier1 = npc.inventory_contains(self, npc.trade.prices.currency.tier1) + local tier3 = npc.inventory_contains(self, npc.trade.prices.currency.tier3.string) + local tier2 = npc.inventory_contains(self, npc.trade.prices.currency.tier2.string) + local tier1 = npc.inventory_contains(self, npc.trade.prices.currency.tier1.string) if tier3 ~= nil then table.insert(result, {name = npc.get_item_name(tier3.item_string), count = npc.get_item_count(tier3.item_string)} ) @@ -134,34 +134,20 @@ function npc.trade.get_casual_trade_offer(self, offer_type) -- Choose a random currency local chosen_tier = currencies[math.random(#currencies)] -- Get items for this currency - local buyable_items = - npc.trade.prices.get_items_for_currency_count(chosen_tier.name, chosen_tier.count) + local buyable_items = + npc.trade.prices.get_items_for_currency_count(chosen_tier.name, chosen_tier.count, 0.5) -- Select a random item from the buyable items local item_set = {} - for item,price in pairs(buyable_items) do + for item,price_info in pairs(buyable_items) do table.insert(item_set, item) end local item = item_set[math.random(#item_set)] -- Choose buying quantity. Since this is a buy offer, NPC will buy items -- at half the price. Therefore, NPC will always ask for even quantities -- so that the price count is always an integer number - local amount_to_buy = math.random(1,5) * 2 - local price_item_count = buyable_items[item].count * ((amount_to_buy) / 2) - -- Increase the amount to buy if the result of the price is a decimal number - while price_item_count % 1 ~= 0 do - amount_to_buy = amount_to_buy + 1 - price_item_count = buyable_items[item].count * ((amount_to_buy) / 2) - end - -- Create price itemstring - local price_string = buyable_items[item].tier.." " - ..tostring( buyable_items[item].count * (amount_to_buy / 2) ) - - -- Build the return object - result = { - offer_type = offer_type, - item = item.." "..amount_to_buy, - price = price_string - } + local amount_to_buy = math.random(buyable_items[item].min_buyable_item_count, buyable_items[item].max_buyable_item_count) + -- Create trade offer + result = npc.trade.create_offer(npc.trade.OFFER_BUY, item, price, amount_to_buy) else -- Make sell offer, NPC will sell items to player at regular price -- NPC will also offer items from their inventory @@ -177,9 +163,61 @@ function npc.trade.get_casual_trade_offer(self, offer_type) local item = sellable_items[math.random(#sellable_items)] -- Choose how many of this item will be sold to player local count = math.random(npc.get_item_count(item)) + -- Create trade offer + result = npc.trade.create_offer(npc.trade.OFFER_SELL, npc.get_item_name(item), nil, count) + end + + return result +end + +-- Creates a trade offer based on the offer type, given item and count. If +-- the offer is a "buy" offer, it is required to provide currency items for +-- payment. The currencies should come from either the NPC's inventory or a +-- chest belonging to it. +function npc.trade.create_offer(offer_type, item, price_item_count, count) + + minetest.log("Item:"..dump(item)) + local result = {} + -- Check offer type + if offer_type == npc.trade.OFFER_BUY then + -- Get price for the given item + local item_price = npc.trade.prices.table[item] + minetest.log("Item price: "..dump(item_price)) + -- Choose buying quantity. Since this is a buy offer, NPC will buy items + -- at half the price. Therefore, NPC will always ask for even quantities + -- so that the price count is always an integer number + minetest.log("Count: "..dump(count)..", price item count: "..dump(item_price.count)) + local price_item_count = item_price.count * ((count) / 2) + minetest.log("Price item: "..dump(price_item_count)) + -- Increase the amount to buy if the result of the price is a decimal number + while price_item_count % 1 ~= 0 do + minetest.log("Count: "..dump(count)) + count = count + 1 + price_item_count = item_price.count * ((count) / 2) + minetest.log("Price item: "..dump(price_item_count)) + end + -- Check if the currency items can pay for the selected amount to buy + for i = 1, #currency_items do + minetest.log("Currency item: "..dump(currency_items[i])) + minetest.log("Name: "..dump(item_price.tier)..", Count: "..dump(price_item_count)) + if currency_items[i].name == item_price.tier and currency_items[i].count >= item_price.count then + -- Create price itemstring + local price_string = item_price.tier.." " + ..tostring( item_price.count * (count / 2) ) + + -- Build the return object + result = { + offer_type = offer_type, + item = item.." "..count, + price = price_string + } + end + end + else + -- Make sell offer, NPC will sell items to player at regular price -- Get and calculate price for this object minetest.log("Item: "..dump(item)..", name: "..dump(npc.get_item_name(item))) - local price_object = npc.trade.prices.table[npc.get_item_name(item)] + local price_object = npc.trade.prices.table[item] -- Check price object, if price < 1 then offer to sell for 1 if price_object.count < 1 then price_object.count = 1 @@ -194,8 +232,12 @@ function npc.trade.get_casual_trade_offer(self, offer_type) end return result + end + +-- TODO: THis method needs to be refactored to be able to manage +-- both NPC inventories and chest inventories function npc.trade.perform_trade(self, player_name, offer) local item_stack = ItemStack(offer.item) @@ -219,7 +261,7 @@ function npc.trade.perform_trade(self, player_name, offer) minetest.chat_send_player(player_name, "Thank you!") else minetest.chat_send_player(player_name, - "Looks like you can't what I'm giving you for payment!") + "Looks like you can't get what I'm giving you for payment!") end else minetest.chat_send_player(player_name, "Looks like you don't have what I want to buy...")