diff --git a/fs_trade_via_buy_button.lua b/fs_trade_via_buy_button.lua index 6f29d0f..31fac46 100644 --- a/fs_trade_via_buy_button.lua +++ b/fs_trade_via_buy_button.lua @@ -12,25 +12,57 @@ yl_speak_up.get_trade_item_desc = function(item) end +-- helper function; returns how often a trade can be done +-- stock_buy how much of the buy stack does the NPC have in storage? +-- stock_pay how much of the price stack does the NPC have in storage? +-- buy_stack stack containing the item the NPC sells +-- pay_stack stack containing the price for said item +-- min_storage how many items of the buy stack items shall the NPC keep? +-- max_storage how many items of the pay stack items can the NPC accept? +yl_speak_up.get_trade_amount_available = function(stock_buy, stock_pay, buy_stack, pay_stack, min_storage, max_storage) + local stock = 0 + -- the NPC shall not sell more than this + if(min_storage and min_storage > 0) then + stock_buy = math.max(0, stock_buy - min_storage) + end + stock = math.floor(stock_buy / buy_stack:get_count()) + -- the NPC shall not buy more than this + if(max_storage and max_storage < 10000) then + stock_pay = math.min(max_storage - stock_pay, 10000) + stock = math.min(stock, math.floor(stock_pay / pay_stack:get_count())) + end + return stock +end + + + -- helper function; also used by trade_list.lua -yl_speak_up.get_sorted_trade_id_list = function(dialog) +yl_speak_up.get_sorted_trade_id_list = function(dialog, show_dialog_option_trades) -- 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 + if(show_dialog_option_trades) then + for k, v in pairs(dialog.trades) do + if(k ~= "limits" and k ~= "" and v.d_id) then table.insert(keys, k) end end + else + 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 end table.sort(keys) return keys @@ -42,13 +74,13 @@ yl_speak_up.input_trade_via_buy_button = function(player, formname, fields) 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) + local res = yl_speak_up.check_trade_via_buy_button(player, trade_id, true) - if(error_msg ~= "OK") then + if(res.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.]".. + "label[0.2,-0.2;"..res.msg.."\nTrade aborted.]".. "button[2,1.5;1,0.9;back_from_error_msg;Back]"}) return end @@ -80,8 +112,23 @@ yl_speak_up.input_trade_via_buy_button = function(player, formname, fields) end -- TODO: delete button if(fields.delete_trade_via_buy_button) then + -- the owner wants to go back to the trade list from a dialog trade (action) view + if(fields.back_to_trade_list_dialog_trade) then + yl_speak_up.show_fs(player, "trade_list", true) + return + -- a dialog trade (action) was displayed; go back to the corresponding dialog + elseif(fields.back_to_dialog) 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 trade_id = yl_speak_up.speak_to[pname].trade_id + local new_d_id = dialog.trades[ trade_id ].d_id + yl_speak_up.speak_to[pname].d_id = new_d_id + yl_speak_up.speak_to[pname].trade_list = {} + yl_speak_up.show_fs(player, "talk") -- TODO parameters + return -- show the trade list - if(fields.back_to_trade_list or fields.quit + elseif(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 @@ -96,6 +143,7 @@ end -- 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 +-- also returns how many times the trade could be done (stock= ..) 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 @@ -108,11 +156,11 @@ yl_speak_up.check_trade_via_buy_button = function(player, trade_id, do_trade) -- 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." + return {msg = "Trade not found.", stock=0} elseif(not(player_inv)) then - return "Couldn't find player's inventory." + return {msg = "Couldn't find player's inventory.", stock=0} elseif(not(npc_inv)) then - return "Couldn't find the NPC's inventory." + return {msg = "Couldn't find the NPC's inventory.", stock=0} end -- store which trade we're doing yl_speak_up.speak_to[pname].trade_id = trade_id @@ -125,47 +173,62 @@ yl_speak_up.check_trade_via_buy_button = function(player, trade_id, do_trade) -- 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." + return {msg = "Out of stock", stock = 0} + -- return {msg = "Sorry. "..npc_name.." ran out of stock.\nPlease come back later.", stock=0} -- 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 {npc_name.." has no room left!", stock = 0} -- return "Sorry. "..npc_name.." ran out of inventory space.\n".. -- "There is no room to store your payment!" end + 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 -- 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.".. +-- return "Stock too low. Only "..tostring(stock_buy).. +-- " left, want to keep "..tostring(min_storage).."." + return {msg = "Sorry. "..npc_name.." currently does not want to\nsell that much.".. " Current stock: "..tostring(stock_buy).. - " (min: "..tostring(min_storage).."). Perhaps later?" + " (min: "..tostring(min_storage).."). Perhaps later?", + stock = 0} -- 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".. + return {msg = "Sorry. "..npc_name.." currently does not want to\n".. "buy that much.".. " Current stock: "..tostring(stock_pay).. - " (max: "..tostring(max_storage).."). Perhaps later?" + " (max: "..tostring(max_storage).."). Perhaps later?", + stock = 0} + end + -- the NPC shall not sell more than this + if(min_storage and min_storage > 0) then + stock_buy = math.max(0, stock_buy - min_storage) end end + -- how often can this trade be done? + local stock = yl_speak_up.get_trade_amount_available( + stock_buy, stock_pay, + buy_stack, pay_stack, + min_storage, max_storage) -- 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." + return {msg = "You can't pay the price.", stock = stock} 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." + return {msg = "You don't have enough free inventory\nspace to store your purchase.", + stock = stock} end -- was it a dry run to check if the trade is possible? if(not(do_trade)) then - return "OK" + return {msg = "OK", stock = stock} end -- actually do the trade @@ -177,7 +240,7 @@ yl_speak_up.check_trade_via_buy_button = function(player, trade_id, do_trade) -- revert the trade player_inv:add_item("main", payment) npc_inv:add_item("npc_main", sold) - return "Sorry. "..npc_name.." accepts only undammaged items." + return {msg = "Sorry. "..npc_name.." accepts only undammaged items.", stock = stock} end player_inv:add_item("main", sold) npc_inv:add_item("npc_main", payment) @@ -194,7 +257,7 @@ yl_speak_up.check_trade_via_buy_button = function(player, trade_id, do_trade) yl_speak_up.log_change(pname, n_id, "bought "..tostring(pay_stack:to_string()).. " for "..tostring(buy_stack:to_string())) - return "OK" + return {msg = "OK", stock = stock} end @@ -261,12 +324,37 @@ yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id) 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.]" } + if(not(dialog.trades[ trade_id ].d_id)) then + -- go back to the trade list + table.insert(formspec, "button[0.2,0.0;8.0,1.0;back_to_trade_list;Back to trade list]") + table.insert(formspec, "tooltip[back_to_trade_list;".. + "Click here once you've traded enough with this ".. + "NPC and want to get back to the trade list.]") + elseif(true) then + -- go back to the trade list + table.insert(formspec, "button[0.2,0.0;3.8,1.0;back_to_trade_list_dialog_trade;Back to trade list]") + table.insert(formspec, "tooltip[back_to_trade_list_dialog_trade;".. + "Click here once you've traded enough with this ".. + "NPC and want to get back to the trade list.]") + -- go back to dialog + table.insert(formspec, "button[4.2,0.0;3.8,1.0;back_to_dialog;Back to dialog ") + table.insert(formspec, minetest.formspec_escape(dialog.trades[ trade_id ].d_id)) + table.insert(formspec, "]") + table.insert(formspec, "tooltip[back_to_dialog;".. + "Click here once you've traded enough with this ".. + "NPC and want to get back to talking with the NPC.]") + else + -- go back to dialog + table.insert(formspec, "button[0.2,0.0;8.0,1.0;back_to_dialog;Back to dialog ") + table.insert(formspec, minetest.formspec_escape(dialog.trades[ trade_id ].d_id)) + table.insert(formspec, "]") + table.insert(formspec, "tooltip[back_to_dialog;".. + "Click here once you've traded enough with this ".. + "NPC and want to get back to talking with the NPC.]") + end + -- 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) @@ -280,8 +368,8 @@ yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id) 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 res = yl_speak_up.check_trade_via_buy_button(player, trade_id, false) + if(res.msg == "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 @@ -297,12 +385,18 @@ yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id) 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, res.msg) 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 + -- how often can this trade be repeated? + if(res.stock and res.stock > 0) then + table.insert(formspec, "label[6.5,2.0;Trade ") + table.insert(formspec, tostring(res.stock)) + table.insert(formspec, " x\navailable]") + end table.insert(formspec, "container_end[]".. "real_coordinates[true]".. diff --git a/trade_list.lua b/trade_list.lua index 0234619..565ab82 100644 --- a/trade_list.lua +++ b/trade_list.lua @@ -109,7 +109,7 @@ yl_speak_up.get_fs_trade_list = function(player, show_dialog_option_trades) -- the order in which the trades appear shall not change each time; -- but lua cannot sort the keys of a table by itself... -- this function can be found in fs_trade_via_button.lua - local sorted_trades = yl_speak_up.get_sorted_trade_id_list(dialog) + local sorted_trades = yl_speak_up.get_sorted_trade_id_list(dialog, show_dialog_option_trades) yl_speak_up.speak_to[pname].trade_id_list = sorted_trades for i, k in ipairs(sorted_trades) do