yl_speak_up/fs/fs_trade_via_buy_button.lua
2024-02-10 18:55:11 +01:00

378 lines
15 KiB
Lua

-- 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
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 res = yl_speak_up.check_trade_via_buy_button(player, trade_id, true)
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;"..res.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
if(fields.delete_trade_via_buy_button) then
local trade_id = yl_speak_up.speak_to[pname].trade_id
yl_speak_up.delete_trade_simple(player, trade_id)
return
end
-- 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
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
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
-- 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
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 {msg = "Trade not found.", stock=0}
elseif(not(player_inv)) then
return {msg = "Couldn't find player's inventory.", stock=0}
elseif(not(npc_inv)) then
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
-- 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 {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!", 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
-- 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 "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?",
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 {msg = "Sorry. "..npc_name.." currently does not want to\n"..
"buy that much."..
" Current stock: "..tostring(stock_pay)..
" (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 {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 {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 {msg = "OK", stock = stock}
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 {msg = "Sorry. "..npc_name.." accepts only undammaged items.", stock = stock}
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(buy_stack:to_string())..
" for "..tostring(pay_stack:to_string()))
return {msg = "OK", stock = stock}
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]",
}
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)..
" (option "..
minetest.formspec_escape(dialog.trades[ trade_id ].o_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 the player can edit the NPC
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 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
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, 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]"..
"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
yl_speak_up.get_fs_trade_via_buy_button_wrapper = function(player, param)
local pname = player:get_player_name()
-- 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
return yl_speak_up.get_fs_trade_via_buy_button(player, param)
end
yl_speak_up.register_fs("trade_via_buy_button",
yl_speak_up.input_trade_via_buy_button,
yl_speak_up.get_fs_trade_via_buy_button_wrapper,
-- force formspec version 1:
1
)