-- buy-button-based trading from trade list: one item(stack) for another item(stack) -- helper function yl_speak_up.get_trade_item_desc = function(item) local stack = ItemStack(item) local def = minetest.registered_items[stack:get_name()] if(def and def.description) then return minetest.formspec_escape(tostring(stack:get_count()).."x "..def.description) end return minetest.formspec_escape(tostring(stack:get_count()).."x "..stack:get_name()) end -- helper function; also used by trade_list.lua yl_speak_up.get_sorted_trade_id_list = function(dialog) -- make sure all fields exist yl_speak_up.setup_trade_limits(dialog) local keys = {} for k, v in pairs(dialog.trades) do if(k ~= "limits" and k ~= "") then -- structure of the indices: sell name amount for name amount local parts = string.split(k, " ") if(parts and #parts == 6 and parts[4] == "for" and v.pay and v.pay[1] ~= "" and v.pay[1] == parts[5].." "..parts[6] and v.buy and v.buy[1] ~= "" and v.buy[1] == parts[2].." "..parts[3] and minetest.registered_items[parts[5]] and minetest.registered_items[parts[2]] and tonumber(parts[6]) > 0 and tonumber(parts[3]) > 0) then table.insert(keys, k) end end end table.sort(keys) return keys end yl_speak_up.input_trade_via_buy_button = function(player, formname, fields) local pname = player:get_player_name() if(fields.buy_directly) then local trade_id = yl_speak_up.speak_to[pname].trade_id local error_msg = yl_speak_up.check_trade_via_buy_button(player, trade_id, true) if(error_msg ~= "OK") then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:trade_via_buy_button", formspec = "size[6,2.5]".. "label[0.2,-0.2;"..error_msg.."\nTrade aborted.]".. "button[2,1.5;1,0.9;back_from_error_msg;Back]"}) return end yl_speak_up.show_fs(player, "trade_via_buy_button", trade_id) return end -- scroll through the trades with prev/next buttons if(fields.prev_trade or fields.next_trade) then local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id local dialog = yl_speak_up.speak_to[pname].dialog local keys = yl_speak_up.speak_to[pname].trade_id_list or {} local trade_id = yl_speak_up.speak_to[pname].trade_id local idx = math.max(1, table.indexof(keys, trade_id)) if(fields.prev_trade) then idx = idx - 1 elseif(fields.next_trade) then idx = idx + 1 end if(idx > #keys) then idx = 1 elseif(idx < 1) then idx = #keys end yl_speak_up.speak_to[pname].trade_id = keys[idx] -- this is another trade; count from 0 again yl_speak_up.speak_to[pname].trade_done = nil end -- TODO: delete button if(fields.delete_trade_via_buy_button) then -- show the trade list if(fields.back_to_trade_list or fields.quit or not(yl_speak_up.speak_to[pname].trade_id)) then yl_speak_up.show_fs(player, "trade_list") return end -- show the trade formspec again yl_speak_up.show_fs(player, "trade_via_buy_button", yl_speak_up.speak_to[pname].trade_id) end -- helper function -- if do_trade is false: check only if the trade would be possible and return -- error message if not; return "OK" when trade is possible -- if do_trade is true: if possible, execute the trade; return the same as above yl_speak_up.check_trade_via_buy_button = function(player, trade_id, do_trade) local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id local dialog = yl_speak_up.speak_to[pname].dialog -- make sure all necessary table entries exist yl_speak_up.setup_trade_limits(dialog) local this_trade = dialog.trades[trade_id] -- the players' inventory local player_inv = player:get_inventory() -- the NPCs' inventory local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) if(not(this_trade)) then return "Trade not found." elseif(not(player_inv)) then return "Couldn't find player's inventory." elseif(not(npc_inv)) then return "Couldn't find the NPC's inventory." end -- store which trade we're doing yl_speak_up.speak_to[pname].trade_id = trade_id -- what the player pays to the npc: local pay_stack = ItemStack(dialog.trades[ trade_id ].pay[1]) -- what the npc sells and the player buys: local buy_stack = ItemStack(dialog.trades[ trade_id ].buy[1]) local npc_name = minetest.formspec_escape(dialog.n_npc) -- can the NPC provide his part? if(not(npc_inv:contains_item("npc_main", buy_stack))) then return "Out of stock" --"Sorry. "..npc_name.." ran out of stock.\nPlease come back later." -- has the NPC room for the payment? elseif(not(npc_inv:room_for_item("npc_main", pay_stack))) then return npc_name.." has no room left!" -- return "Sorry. "..npc_name.." ran out of inventory space.\n".. -- "There is no room to store your payment!" end -- are there any limits which we have to take into account? local min_storage = dialog.trades.limits.sell_if_more[ buy_stack:get_name() ] local max_storage = dialog.trades.limits.buy_if_less[ pay_stack:get_name() ] if((min_storage and min_storage > 0) or (max_storage and max_storage < 10000)) then local counted_npc_inv = yl_speak_up.count_npc_inv(n_id) local stock_pay = counted_npc_inv[ pay_stack:get_name() ] or 0 local stock_buy = counted_npc_inv[ buy_stack:get_name() ] or 0 -- trade limit: is enough left after the player buys the item? if( min_storage and min_storage > stock_buy - buy_stack:get_count()) then return "Sorry. "..npc_name.." currently does not want to\nsell that much.".. " Current stock: "..tostring(stock_buy).. " (min: "..tostring(min_storage).."). Perhaps later?" -- trade limit: make sure the bought amount does not exceed the desired maximum elseif(max_storage and max_storage < stock_pay + pay_stack:get_count()) then return "Sorry. "..npc_name.." currently does not want to\n".. "buy that much.".. " Current stock: "..tostring(stock_pay).. " (max: "..tostring(max_storage).."). Perhaps later?" end end -- can the player pay? if(not(player_inv:contains_item("main", pay_stack))) then -- both slots will remain empty return "You can't pay the price." elseif(not(player_inv:room_for_item("main", buy_stack))) then -- the player has no room for the sold item; give a warning return "You don't have enough free inventory\nspace to store your purchase." end -- was it a dry run to check if the trade is possible? if(not(do_trade)) then return "OK" end -- actually do the trade local payment = player_inv:remove_item("main", pay_stack) local sold = npc_inv:remove_item("npc_main", buy_stack) -- used items cannot be sold as there is no fair way to indicate how -- much they are used if(payment:get_wear() > 0 or sold:get_wear() > 0) then -- revert the trade player_inv:add_item("main", payment) npc_inv:add_item("npc_main", sold) return "Sorry. "..npc_name.." accepts only undammaged items." end player_inv:add_item("main", sold) npc_inv:add_item("npc_main", payment) -- save the inventory of the npc so that the payment does not get lost yl_speak_up.save_npc_inventory( n_id ) -- store for statistics how many times the player has executed this trade -- (this is also necessary to switch to the right target dialog when -- dealing with dialog options trades) if(not(yl_speak_up.speak_to[pname].trade_done)) then yl_speak_up.speak_to[pname].trade_done = 0 end yl_speak_up.speak_to[pname].trade_done = yl_speak_up.speak_to[pname].trade_done + 1 -- log the trade yl_speak_up.log_change(pname, n_id, "bought "..tostring(pay_stack:to_string()).. " for "..tostring(buy_stack:to_string())) return "OK" end -- trade for a player (the owner of the NPC): one item(stack) for another -- trade by clicking on the "buy" button instead of moving inventory items around -- checks if payment and buying is possible yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id) local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id local dialog = yl_speak_up.speak_to[pname].dialog -- make sure all necessary table entries exist yl_speak_up.setup_trade_limits(dialog) local this_trade = dialog.trades[trade_id] -- the players' inventory local player_inv = player:get_inventory() -- the NPCs' inventory local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) if(not(this_trade) or not(player_inv) or not(npc_inv)) then return yl_speak_up.trade_fail_fs end -- what the player pays to the npc: local pay = dialog.trades[ trade_id ].pay[1] -- what the npc sells and the player buys: local buy = dialog.trades[ trade_id ].buy[1] local pay_name = yl_speak_up.get_trade_item_desc(pay) local buy_name = yl_speak_up.get_trade_item_desc(buy) local npc_name = minetest.formspec_escape(dialog.n_npc) -- get the number of the trade local keys = yl_speak_up.speak_to[pname].trade_id_list or {} local idx = math.max(1, table.indexof(keys, trade_id)) -- the common formspec, shared by actual trade and configuration -- no listring here as that would make things more complicated local formspec = { -- "size[8.5,8]".. yl_speak_up.show_fs_simple_deco(10, 8.8).. "container[0.75,0]".. "label[4.35,1.4;", npc_name, " sells:]", "list[current_player;main;0.2,4.55;8,1;]", "list[current_player;main;0.2,5.78;8,3;8]", -- "label[7.0,0.2;Offer ", tostring(idx), "/", tostring(#keys), "]", -- "label[2.5,0.7;Trading with ", npc_name, "]", "label[1.5,0.7;Offer no. ", tostring(idx), "/", tostring(#keys), " from ", npc_name, "]", "label[1.5,1.4;You pay:]", -- show images of price and what is sold so that the player knows what -- it costs and what he will get even if the trade is not possible at -- that moment -- "item_image[2.1,2.0;1,1;", "item_image_button[2.1,1.9;1.2,1.2;", tostring(pay), ";pay_item_img;", "]", -- "item_image[5.1,2.0;1,1;", -- tostring(buy), "item_image_button[5.1,1.9;1.2,1.2;", tostring(buy), ";buy_item_img;", "]", "label[1.5,3.0;", pay_name, "]", "label[4.35,3.0;", buy_name, "]", "image[3.5,2.0;1,1;gui_furnace_arrow_bg.png^[transformR270]", -- go back to the trade list "button[0.2,0.0;8.0,1.0;back_to_trade_list;Back to list]".. "tooltip[back_to_trade_list;Click here once you've traded enough with this ".. "NPC and want to get back to the trade list.]" } -- show edit button for the owner if in edit_mode if(yl_speak_up.may_edit_npc(player, n_id)) then -- for trades in trade list: allow delete (new trades can easily be added) -- allow delete for trades in trade list even if not in edit mode -- (entering edit mode for that would be too much work) table.insert(formspec, "button[0.2,2.0;1.2,0.9;delete_trade_via_buy_button;Delete]".. "tooltip[delete_trade_via_buy_button;".. "Delete this trade. You can do so only if\n".. "you can edit the NPC as such (i.e. own it).]") end -- dry-run: test if the trade can be done local trade_possible = yl_speak_up.check_trade_via_buy_button(player, trade_id, false) if(trade_possible == "OK") then local buy_str = "Buy" local trade_done = yl_speak_up.speak_to[pname].trade_done if(trade_done and trade_done > 0) then buy_str = "Buy again. Bought: "..tostring(trade_done).."x" end table.insert(formspec, "button[0.2,3.5;8.0,1.0;buy_directly;") -- "button[6.5,2.0;1.7,0.9;buy_directly;Buy]".. table.insert(formspec, buy_str) table.insert(formspec, "]") table.insert(formspec, "tooltip[buy_directly;Click here in order to buy.]") else -- set a red background color in order to alert thep layer to the error table.insert(formspec, "style_type[button;bgcolor=#FF4444]".. "button[0.2,3.5;8.0,1.0;back_from_error_msg;") -- table.insert(formspec, "label[0.5,3.5;") table.insert(formspec, trade_possible) table.insert(formspec, ']') -- set the background color for the next buttons back to our normal one table.insert(formspec, 'style_type[button;bgcolor=#a37e45]') -- table.insert(formspec, "label[6.5,2.0;Trade not\npossible.]") end table.insert(formspec, "container_end[]".. "real_coordinates[true]".. "button[0.5,1.9;0.8,2.0;prev_trade;<]".. "button[11.7,1.9;0.8,2.0;next_trade;>]".. "tooltip[prev_trade;Show previous trade offer]".. "tooltip[next_trade;Show next trade offer]") return table.concat(formspec, '') end