diff --git a/dialogue.lua b/dialogue.lua index fa86b0f..2bb0f64 100644 --- a/dialogue.lua +++ b/dialogue.lua @@ -24,6 +24,13 @@ npc.dialogue.NEGATIVE_ANSWER_LABEL = "Nevermind" npc.dialogue.MIN_DIALOGUES = 2 npc.dialogue.MAX_DIALOGUES = 4 +npc.dialogue.dialogue_type = { + married = 1, + casual_trade = 2, + dedicated_trade = 3, + custom_trade = 4 +} + -- This table contains the answers of dialogue boxes npc.dialogue.dialogue_results = { options_dialogue = {}, @@ -38,13 +45,10 @@ npc.dialogue.dialogue_results = { -- 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, - responses, - is_married_dialogue, - is_casual_trade_dialogue, - casual_trade_type, - is_dedicated_trade_prompt, + dialogue, dismiss_option_label, player_name) + local responses = dialogue.responses local options_length = table.getn(responses) + 1 local formspec_height = (options_length * 0.7) + 0.4 local formspec = "size[7,"..tostring(formspec_height).."]" @@ -63,10 +67,11 @@ 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 = is_married_dialogue, - is_casual_trade_dialogue = is_casual_trade_dialogue, - casual_trade_type = casual_trade_type, - is_dedicated_trade_prompt = is_dedicated_trade_prompt, + 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 } @@ -170,6 +175,33 @@ function npc.dialogue.select_random_dialogues_for_npc(sex, phase, favorite_items return result end +-- This function creates a multi-option dialogue from the custom trades that the +-- NPC have. +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) + end + -- Default text to be shown for dialogue prompt + local text = npc.trade.CUSTOM_TRADES_PROMPT_TEXT + -- Get the options from each custom trade entry + local options = {} + if #self.trader_data.custom_trades == 1 then + table.insert(options, self.trader_data.custom_trades[1].button_prompt) + text = self.trader_data.custom_trades[1].option_prompt + else + for i = 1, #self.trader_data.custom_trades do + table.insert(options, self.trader_data.custom_trades[i].button_prompt) + end + end + -- Create dialogue object + local dialogue = npc.dialogue.create_option_dialogue(text, options, actions) + dialogue.dialogue_type = npc.dialogue.dialogue_type.custom_trade + + return dialogue +end + -- This function will choose randomly a dialogue from the NPC data -- and process it. function npc.dialogue.start_dialogue(self, player, show_married_dialogue) @@ -196,17 +228,30 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue) 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.CASUAL then - -- Show buy/sell with 50% chance each - local buy_or_sell_chance = math.random(1, 2) - if buy_or_sell_chance == 1 then - -- Show casual buy dialogue - dialogue = npc.trade.CASUAL_TRADE_BUY_DIALOGUE - else - -- Show casual sell dialogue - dialogue = npc.trade.CASUAL_TRADE_SELL_DIALOGUE - end - end + 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 dialogue = self.dialogues.normal[math.random(1, #self.dialogues.normal)] @@ -215,8 +260,6 @@ function npc.dialogue.start_dialogue(self, player, show_married_dialogue) dialogue = self.dialogues.hints[math.random(1, 4)] end - minetest.log("Chosen dialogue: "..dump(dialogue)) - local dialogue_result = npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) if dialogue_result == false then -- Try to find another dialogue line @@ -266,11 +309,7 @@ function npc.dialogue.process_dialogue(self, dialogue, player_name) if dialogue.responses then npc.dialogue.show_options_dialogue( self, - dialogue.responses, - dialogue.is_married_dialogue, - dialogue.casual_trade_type ~= nil, - dialogue.casual_trade_type, - dialogue.is_dedicated_trade_prompt, + dialogue, npc.dialogue.NEGATIVE_ANSWER_LABEL, player_name ) @@ -280,6 +319,16 @@ function npc.dialogue.process_dialogue(self, dialogue, player_name) return true end +function npc.dialogue.create_option_dialogue(prompt, options, actions) + local result = {} + result.text = prompt + result.responses = {} + for i = 1, #options do + table.insert(result.responses, {text = options[i], action_type="function", action=actions[i]}) + end + return result +end + ----------------------------------------------------------------------------- -- Functions for rotating NPC to look at player -- (taken from the mobs_redo API) @@ -428,12 +477,17 @@ minetest.register_on_player_receive_fields(function (player, formname, fields) .action(player_response.npc, player) end return - elseif player_response.is_dedicated_trade_prompt == true then + elseif player_response.is_dedicated_trade_dialogue == true then -- Get the functions for a dedicated trader prompt npc.trade.DEDICATED_TRADER_PROMPT .responses[player_response.options[i].response_id] .action(player_response.npc, player) return + elseif player_response.is_custom_trade_dialogue == true then + -- Functions for a custom trade should be available from the same dialogue + -- object as it is created in memory + minetest.log("Player response: "..dump(player_response.options[i])) + player_response.options[i].action(player_response.npc, player) else -- Get dialogues for sex and phase local dialogues = npc.data.DIALOGUES[player_response.npc.sex][phase] diff --git a/npc.lua b/npc.lua index 0aaab59..dec2847 100755 --- a/npc.lua +++ b/npc.lua @@ -478,7 +478,10 @@ local function npc_spawn(self, pos) 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 @@ -559,6 +562,12 @@ local function npc_spawn(self, pos) npc.trade.generate_trade_offers_by_status(ent) + -- Add a custom trade offer + local offer1 = npc.trade.create_custom_sell_trade_offer("Do you want me to fix your steel sword?", "Fix steel sword", "Fix steel sword", "default:sword_steel", {"default:sword_steel", "default:iron_lump 5"}) + table.insert(ent.trader_data.custom_trades, offer1) + local offer2 = npc.trade.create_custom_sell_trade_offer("Do you want me to fix your mese sword?", "Fix mese sword", "Fix mese sword", "default:sword_mese", {"default:sword_mese", "default:copper_lump 10"}) + table.insert(ent.trader_data.custom_trades, offer2) + -- npc.add_action(ent, npc.action.stand, {self = ent}) -- npc.add_action(ent, npc.action.stand, {self = ent}) -- npc.add_action(ent, npc.action.walk_step, {self = ent, dir = npc.direction.east}) diff --git a/trade/trade.lua b/trade/trade.lua index 0e22a46..31376a3 100644 --- a/trade/trade.lua +++ b/trade/trade.lua @@ -19,11 +19,16 @@ npc.trade.results = { trade_offers = {} } +-- This is the text to be shown each time the NPC has more +-- than one custom trade options to choose from +npc.trade.CUSTOM_TRADES_PROMPT_TEXT = "Hi there, how can I help you today?" + -- Casual trader NPC dialogues definition -- Casual buyer npc.trade.CASUAL_TRADE_BUY_DIALOGUE = { text = "I'm looking to buy some items, are you interested?", casual_trade_type = npc.trade.OFFER_BUY, + dialogue_type = npc.dialogue.dialogue_type.casual_trade, responses = { [1] = { text = "Sell", @@ -39,6 +44,7 @@ npc.trade.CASUAL_TRADE_BUY_DIALOGUE = { -- Casual seller npc.trade.CASUAL_TRADE_SELL_DIALOGUE = { text = "I have some items to sell, are you interested?", + dialogue_type = npc.dialogue.dialogue_type.casual_trade, casual_trade_type = npc.trade.OFFER_SELL, responses = { [1] = { @@ -55,7 +61,7 @@ npc.trade.CASUAL_TRADE_SELL_DIALOGUE = { -- Dedicated trade dialogue prompt npc.trade.DEDICATED_TRADER_PROMPT = { text = "Hello there, would you like to trade?", - is_dedicated_trade_prompt = true, + dialogue_type = npc.dialogue.dialogue_type.dedicated_trade, responses = { [1] = { text = "Buy", @@ -72,6 +78,15 @@ npc.trade.DEDICATED_TRADER_PROMPT = { action = function(self, player) npc.trade.show_dedicated_trade_formspec(self, player, npc.trade.OFFER_BUY) end + }, + [3] = { + text = "Other", + action_type = "function", + response_id = 3, + action = function(self, player) + local dialogue = npc.dialogue.create_custom_trade_options(self, player) + npc.dialogue.process_dialogue(self, dialogue, player:get_player_name()) + end } } } @@ -136,9 +151,14 @@ function npc.trade.show_dedicated_trade_formspec(self, player, offers_type) "label[0.2,0.05;Click on the price button to "..menu_offer_type.." item]" for i = 1, #offers do local price_item_name = minetest.registered_items[npc.get_item_name(offers[i].price)].description + local count_label = "" + if npc.get_item_count(offers[i].item) > 1 then + count_label = "label["..(current_x + 1.35)..","..(current_y + 1)..";"..npc.get_item_count(offers[i].item).."]" + end formspec = formspec.. "box["..current_x..","..current_y..";2,2.3;#212121]".. "item_image["..(current_x + 0.45)..","..(current_y + 0.15)..";1.3,1.3;"..npc.get_item_name(offers[i].item).."]".. + count_label.. "item_image_button["..(current_x + 1.15)..","..(current_y + 1.4)..";1,1;"..offers[i].price..";price"..i..";]".. "label["..(current_x + 0.15)..","..(current_y + 1.7)..";Price]" current_x = current_x + 2.1 @@ -163,6 +183,59 @@ function npc.trade.show_dedicated_trade_formspec(self, player, offers_type) end +-- For the moment, the trade offer for custom trade is always of sell type +function npc.trade.show_custom_trade_offer(self, player, offer) + local for_string = "for" + -- Create payments grid. Try to center it. When there are 4 + -- payment options, a grid is to be displayed. + local price_count = #offer.price + local start_x = 2 + local margin_x = 0 + local start_y = 1.45 + if price_count == 2 then + start_x = 1.5 + margin_x = 0.3 + elseif price_count == 3 then + start_x = 1.15 + margin_x = 0.85 + elseif price_count == 4 then + start_x = 1.5 + start_y = 0.8 + margin_x = 0.3 + end + + -- Create payment grid + local price_grid = "" + for i = 1, #offer.price do + price_grid = price_grid.."item_image_button["..start_x..","..start_y..";1,1;"..offer.price[i]..";price"..i..";]" + if #offer.price == 4 and i == 2 then + start_x = 1.5 + start_y = start_y + 1 + else + start_x = start_x + 1 + end + end + + local formspec = "size[8,4]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "label[2,0.1;"..self.nametag..": "..offer.dialogue_prompt.."]".. + price_grid.. + "label["..(margin_x + 3.75)..",1.75;"..for_string.."]".. + "item_image_button["..(margin_x + 4.8)..",1.3;1.2,1.2;"..offer.item..";item;]".. + "button_exit[1,3.3;2.9,0.5;yes_option;"..offer.button_prompt.."]".. + "button_exit[4.1,3.3;2.9,0.5;no_option;"..npc.dialogue.NEGATIVE_ANSWER_LABEL.."]" + + -- Create entry into results table + npc.trade.results.single_trade_offer[player:get_player_name()] = { + trade_offer = trade_offer, + npc = self + } + -- Show formspec to player + minetest.show_formspec(player:get_player_name(), "advanced_npc:trade_offer", formspec) +end + function npc.trade.get_random_trade_status() local chance = math.random(1,10) @@ -309,7 +382,7 @@ function npc.trade.get_dedicated_trade_offers(self) if item ~= nil and trade_info.last_offer_type ~= npc.trade.OFFER_BUY then -- Create sell offer for this item. Currently, traders will offer to sell only - -- of their items to allow the fine control for players to buy what they want. + -- one of their items to allow the fine control for players to buy what they want. -- This requires, however, that the trade offers are re-generated everytime a -- sell is made. table.insert(offers.sell, npc.trade.create_offer( @@ -409,6 +482,23 @@ function npc.trade.create_offer(offer_type, item, price, min_price_item_count, c end +-- A custom sell trade offer is a special type of trading the NPC can +-- have where a different prompt and multiple payment objects are +-- required from the player. A good example is offering to repair a sword, +-- where the player has to give an amount of currency and the sword to +-- repair in exchange to get a fully repaired sword. +-- For the moment being, only sell is supported. +function npc.trade.create_custom_sell_trade_offer(option_prompt, dialogue_prompt, button_prompt, item, payments) + return { + offer_type = npc.OFFER_SELL, + option_prompt = option_prompt, + dialogue_prompt = dialogue_prompt, + button_prompt = button_prompt, + item = item, + price = payments + } +end + -- TODO: This method needs to be refactored to be able to manage -- both NPC inventories and chest inventories. @@ -475,7 +565,6 @@ function npc.trade.perform_trade(self, player_name, offer) end end - -- Handler for chat formspec minetest.register_on_player_receive_fields(function (player, formname, fields) -- Additional checks for other forms should be handled here