added trade interface via buy button

This commit is contained in:
Sokomine 2022-09-08 23:30:25 +02:00
parent 0d88093a45
commit d5fe15a7c7
4 changed files with 334 additions and 7 deletions

314
fs_trade_via_buy_button.lua Normal file
View File

@ -0,0 +1,314 @@
-- 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

View File

@ -87,6 +87,8 @@ dofile(modpath .. "fs_trade_limit.lua")
dofile(modpath .. "fs_edit_trade_limit.lua")
-- trade one item(stack) against one other item(stack)
dofile(modpath .. "trade_simple.lua")
-- just click on a button to buy items from the trade list
dofile(modpath .. "fs_trade_via_buy_button.lua")
-- easily accessible list of all trades the NPC offers
dofile(modpath .. "trade_list.lua")
-- as the name says: list which npc acesses a variable how and in which context

View File

@ -45,6 +45,10 @@ minetest.register_on_player_receive_fields( function(player, formname, fields)
elseif formname == "yl_speak_up:do_trade_simple" then
yl_speak_up.input_do_trade_simple(player, formname, fields)
return true
-- handled in fs_trade_via_buy_button.lua
elseif formname == "yl_speak_up:trade_via_buy_button" then
yl_speak_up.input_trade_via_buy_button(player, formname, fields)
return true
-- (as above - also handled in trade_simple.lua)
elseif formname == "yl_speak_up:add_trade_simple" then
yl_speak_up.input_add_trade_simple(player, formname, fields)
@ -296,6 +300,14 @@ yl_speak_up.show_fs = function(player, fs_name, param)
yl_speak_up.show_fs_ver(pname, "yl_speak_up:do_trade_simple",
yl_speak_up.get_fs_trade_simple(player, param), 1)
elseif(fs_name == "trade_via_buy_button") then
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:trade_via_buy_button",
yl_speak_up.get_fs_trade_via_buy_button(player, param), 1)
elseif(fs_name == "add_trade_simple") then
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then

View File

@ -43,7 +43,7 @@ yl_speak_up.input_trade_list = function(player, formname, fields)
-- the "_" is necessary for the price button so that offer and price
-- button can each have their tooltip *and* lead to the same dialog
if(fields[ k ] or fields[ k.."_" ]) then
yl_speak_up.show_fs(player, "trade_simple", k)
yl_speak_up.show_fs(player, "trade_via_buy_button", k)
return
end
end
@ -108,11 +108,9 @@ 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...
local sorted_trades = {}
for k in pairs(dialog.trades) do
table.insert(sorted_trades, k)
end
table.sort(sorted_trades)
-- this function can be found in fs_trade_via_button.lua
local sorted_trades = yl_speak_up.get_sorted_trade_id_list(dialog)
yl_speak_up.speak_to[pname].trade_id_list = sorted_trades
for i, k in ipairs(sorted_trades) do
local v = dialog.trades[ k ]
@ -157,6 +155,7 @@ yl_speak_up.get_fs_trade_list = function(player, show_dialog_option_trades)
-- show the price label only when the offer is in stock
table.insert(formspec, "label[0,1.9;->]"..
"label[0,4.4;Price:]\ncontainer_end[]")
end
col = col + 1
if(col >= yl_speak_up.trade_max_cols) then
@ -201,7 +200,7 @@ yl_speak_up.get_fs_trade_list = function(player, show_dialog_option_trades)
text, text,
true, nil, nil, pname_for_old_fs)
-- show a list of how much the NPC will buy and sell
text = "Do not buy or sell more than what I will tell you."
text = "Limits: Do not buy or sell more than what I will tell you."
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"trade_limit",
text, text,