yl_speak_up/fs/fs_do_trade_simple.lua
2025-06-01 05:22:17 +02:00

445 lines
17 KiB
Lua

-- spimple trading: one item(stack) for another item(stack)
-- fallback message if something went wrong
yl_speak_up.trade_fail_fs = "size[6,2]"..
"label[0.2,0.5;Ups! The trade is not possible.\nPlease notify an admin.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
-- possible inputs:
-- fields.edit_trade_simple go on to showing the add_trade_simple formspec
-- fields.abort_trade_simple, ESC, depends on context
-- fields.delete_trade_simple delete this trade
-- fields.finished_trading
-- if in edit_mode: go back to edit options dialog (handled by editor/)
-- if traded at least once: go on to the target dialog
-- if not traded: go back to the original dialog
yl_speak_up.input_do_trade_simple = function(player, formname, fields)
if(not(player)) then
return 0
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- show the trade list
if(fields.back_to_trade_list) then
yl_speak_up.show_fs(player, "trade_list")
return
end
-- get from a dialog option trade back to the list of all these trades
if(fields.show_trade_list_dialog_options) then
yl_speak_up.show_fs(player, "trade_list", true)
return
end
-- a new trade has been stored - show it
if(fields.trade_simple_stored) then
yl_speak_up.show_fs(player, "trade_simple", yl_speak_up.speak_to[pname].trade_id)
return
end
if(fields.buy_directly) then
local error_msg = yl_speak_up.do_trade_direct(player)
if(error_msg ~= "") then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:do_trade_simple",
formspec = "size[6,2]"..
"label[0.2,-0.2;"..error_msg.."]"..
"button[2,1.5;1,0.9;back_from_error_msg;Back]"})
return
end
yl_speak_up.show_fs(player, "trade_simple", yl_speak_up.speak_to[pname].trade_id)
return
end
if(fields.delete_trade_simple) then
yl_speak_up.delete_trade_simple(player, trade.trade_id)
return
end
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
local player_inv = player:get_inventory()
-- give the items from the pay slot back
local pay = trade_inv:get_stack("pay", 1)
if( player_inv:room_for_item("main", pay)) then
player_inv:add_item("main", pay)
trade_inv:set_stack("pay", 1, "")
end
-- clear the buy slot as well
trade_inv:set_stack("buy", 1, "")
-- show the edit trade formspec
if(fields.edit_trade_simple) then
yl_speak_up.show_fs(player, "add_trade_simple", trade.trade_id)
return
end
-- go back to the main dialog
if(fields.abort_trade_simple or fields.quit or fields.finished_trading) then
-- was the action a success?
local success = not(not(trade and trade.trade_done and trade.trade_done > 0))
local a_id = trade.a_id
local o_id = trade.o_id
local n_id = yl_speak_up.speak_to[pname].n_id
yl_speak_up.debug_msg(player, n_id, o_id, "Ending trade.")
-- done trading
yl_speak_up.speak_to[pname].target_d_id = nil
yl_speak_up.speak_to[pname].trade_id = nil
-- execute the next action
yl_speak_up.execute_next_action(player, a_id, success, formname)
return
end
-- show this formspec again
yl_speak_up.show_fs(player, "trade_simple")
end
-- try to do the trade directly - without moving items in the buy/sell inventory slot
-- returns error_msg or "" when successful
yl_speak_up.do_trade_direct = function(player)
if(not(player)) then
return "Player, where are you?"
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- do we have all the necessary data?
if(not(trade) or trade.trade_type ~= "trade_simple") then
return "No trade found!"
end
-- 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(trade.n_id)})
-- has the NPC the item he wants to sell?
if( not(npc_inv:contains_item("npc_main", trade.npc_gives))) then
return "Sorry. This item is sold out!"
-- has the NPC room for the payment?
elseif(not(npc_inv:room_for_item("npc_main", trade.player_gives))) then
return "Sorry. No room to store your payment!\n"..
"Please try again later."
-- can the player pay the price?
elseif(not(player_inv:contains_item("main", trade.player_gives))) then
return "You can't pay the price!"
-- has the player room for the sold item?
elseif(not(player_inv:room_for_item("main", trade.npc_gives))) then
return "You don't have enough free inventory space.\n"..
"Trade aborted."
end
local payment = player_inv:remove_item("main", trade.player_gives)
local sold = npc_inv:remove_item("npc_main", trade.npc_gives)
-- 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 "At least one of the items that shall be traded\n"..
"is dammaged. Trade aborted."
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( trade.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)
yl_speak_up.trade[pname].trade_done = yl_speak_up.trade[pname].trade_done + 1
-- log the trade
yl_speak_up.log_change(pname, trade.n_id,
"bought "..tostring(trade.npc_gives)..
" for "..tostring(trade.player_gives))
return ""
end
-- simple trade: one item(stack) for another
-- handles configuration of new trades and showing the formspec for trades;
-- checks if payment and buying is possible
yl_speak_up.get_fs_do_trade_simple = function(player, trade_id)
if(not(player)) then
return yl_speak_up.trade_fail_fs
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
if(trade and trade.trade_id and trade_id and trade.trade_id == trade_id) then
-- nothing to do; trade is already loaded and stored
elseif(trade_id) then
local d_id = yl_speak_up.speak_to[pname].d_id
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.speak_to[pname].dialog
yl_speak_up.setup_trade_limits(dialog)
trade = {
-- we start with the simple trade
trade_type = "trade_simple",
-- can be determined from other variables, but it is easier to store it here
n_id = n_id,
npc_name = dialog.n_npc,
-- for statistics and in order to determine which dialog to show next
trade_done = 0,
-- we need to know which option this is
target_dialog = d_id,
trade_is_trade_list = true,
trade_id = trade_id
}
if(dialog.trades[ trade_id ]) then
trade.player_gives = dialog.trades[ trade_id ].pay[1]
trade.npc_gives = dialog.trades[ trade_id ].buy[1]
trade.trade_is_trade_list = not(dialog.trades[ trade_id ].d_id)
yl_speak_up.speak_to[pname].trade_id = trade_id
-- copy the limits
local stack = ItemStack(trade.npc_gives)
trade.npc_gives_name = stack:get_name()
trade.npc_gives_amount = stack:get_count()
trade.min_storage = dialog.trades.limits.sell_if_more[ trade.npc_gives_name ]
stack = ItemStack(trade.player_gives)
trade.player_gives_name = stack:get_name()
trade.player_gives_amount = stack:get_count()
trade.max_storage = dialog.trades.limits.buy_if_less[ trade.player_gives_name ]
else
trade.edit_trade = true
end
yl_speak_up.trade[pname] = trade
-- store which action we are working at
trade.a_id = yl_speak_up.speak_to[pname].a_id
else
trade_id = yl_speak_up.speak_to[pname].trade_id
trade.trade_id = trade_id
end
-- do we have all the necessary data?
if(not(trade) or trade.trade_type ~= "trade_simple") then
return yl_speak_up.trade_fail_fs
end
-- the common formspec, shared by actual trade and configuration
-- no listring here as that would make things more complicated
local formspec = table.concat({ -- "size[8.5,8]"..
yl_speak_up.show_fs_simple_deco(8.5, 8),
"label[4.35,0.7;", minetest.formspec_escape(trade.npc_name), " sells:]",
"list[current_player;main;0.2,3.85;8,1;]",
"list[current_player;main;0.2,5.08;8,3;8]"
}, "")
-- configuration of a new trade happens here
if(not(trade.player_gives) or not(trade.npc_gives) or trade.edit_trade) then
return yl_speak_up.get_fs_add_trade_simple(player, trade_id)
end
-- view for the customer when actually trading
-- buy, sell and config items need to be placed somewhere
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- 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(trade.n_id)})
-- show edit button for the owner if the owner can edit the npc
if(yl_speak_up.may_edit_npc(player, trade.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)
formspec = formspec..
"button[0.2,2.0;1.2,0.9;delete_trade_simple;Delete]"..
"tooltip[delete_trade_simple;"..
"Delete this trade. You can do so only if\n"..
"you can edit the NPC as such (i.e. own it).]"
if(not(trade.trade_is_trade_list)) then
-- normal back button will lead to the talk dialog or edit option dialog;
-- add this second back button to go back to the list of all dialog option trades
formspec = formspec..
"button[0.2,1.0;2.0,0.9;show_trade_list_dialog_options;Back to list]"..
"tooltip[show_trade_list_dialog_options;"..
"Click here to get back to the list of all trades\n"..
"associated with dialog options (like this one).\n"..
"This button is only shown if you can edit this NPC.]"
local dialog = yl_speak_up.speak_to[pname].dialog
local tr = dialog.trades[ trade_id ]
if( tr and tr.d_id and tr.o_id) then
formspec = formspec..
"label[0.2,-0.3;This trade belongs to dialog "..
minetest.formspec_escape(tostring(tr.d_id)).." option "..
minetest.formspec_escape(tostring(tr.o_id))..".]"
end
end
end
-- the functionality of the back button depends on context
if(not(trade.trade_is_trade_list)) then
-- go back to the right dialog (or forward to the next one)
formspec = formspec..
-- "button[6.2,1.6;2.0,0.9;finished_trading;Back to talk]"..
"button[0.2,0.0;2.0,0.9;finished_trading;Back to talk]"..
"tooltip[finished_trading;Click here once you've traded enough with this "..
"NPC and want to get back to talking.]"
else
-- go back to the trade list
formspec = formspec.. "button[0.2,0.0;2.0,0.9;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.]"
end
local trade_possible_msg = "Status of trade: Unknown."
local can_trade = false
-- find out how much the npc has stoerd
local stock_pay = 0
local stock_buy = 0
-- only count the inv if there actually are any mins or max
if(trade.min_storage or trade.max_storage) then
local n_id = yl_speak_up.speak_to[pname].n_id
local counted_npc_inv = {}
counted_npc_inv = yl_speak_up.count_npc_inv(n_id)
stock_pay = counted_npc_inv[trade.player_gives_name] or 0
stock_buy = counted_npc_inv[trade.npc_gives_name] or 0
end
-- can the NPC provide his part?
if(not(npc_inv:contains_item("npc_main", trade.npc_gives))) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.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", trade.player_gives))) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" ran out of inventory space.\nThere is no room to store your payment!"
-- trade limit: is enough left after the player buys the item?
elseif(trade.min_storage and trade.min_storage > stock_buy - trade.npc_gives_amount) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" currently does not want to\nsell that much."..
" Current stock: "..tostring(stock_buy)..
" (min: "..tostring(trade.min_storage)..
"). Perhaps later?"
-- trade limit: make sure the bought amount does not exceed the desired maximum
elseif(trade.max_storage and trade.max_storage < stock_pay + trade.player_gives_amount) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" currently does not want to\nbuy that much."..
" Current stock: "..tostring(stock_pay)..
" (max: "..tostring(trade.max_storage)..
"). Perhaps later?"
-- trade as an action
elseif(not(trade.trade_is_trade_list)) then
if(trade_inv:contains_item("pay", trade.player_gives)) then
-- all good so far; move the price stack to the pay slot
-- move price item to the price slot
local stack = player_inv:remove_item("main", trade.player_gives)
trade_inv:add_item("pay", stack)
trade_possible_msg = "Please take your purchase!"
can_trade = true
elseif(trade_inv:is_empty("pay")) then
trade_possible_msg = "Please insert the right payment in the pay slot\n"..
"and then take your purchase."
can_trade = false
else
trade_possible_msg = "This is not what "..minetest.formspec_escape(trade.npc_name)..
" wants.\nPlease insert the right payment!"
can_trade = false
end
-- can the player pay?
elseif(not(player_inv:contains_item("main", trade.player_gives))) then
-- both slots will remain empty
trade_possible_msg = "You cannot pay the price."
-- is the slot for the payment empty?
elseif not(trade_inv:is_empty("pay")) then
-- both slots will remain empty
-- (the slot may already contain the right things; we'll find that out later on)
trade_possible_msg = "This is not what "..minetest.formspec_escape(trade.npc_name)..
" wants.\nPlease insert the right payment!"
else
trade_possible_msg = "Please insert the right payment in the pay slot\n"..
"or click on \"buy\"."..
"]button[6.5,2.0;1.2,0.9;buy_directly;Buy]"..
"tooltip[buy_directly;"..
"Click here in order to buy directly without having to insert\n"..
"your payment manually into the pay slot."
can_trade = true
end
-- make sure the sale slot is empty (we will fill it if the trade is possible)
trade_inv:set_stack("buy", 1, "")
-- after all this: does the payment slot contain the right things?
if(can_trade and trade_inv:contains_item("pay", trade.player_gives)) then
trade_possible_msg = "Take the offered item(s) in order to buy them."
-- only new/undammaged tools, weapons and armor are accepted
if(trade_inv:get_stack("pay", 1):get_wear() > 0) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" accepts only undammaged items."
else
-- put a *copy* of the item(stack) that is to be sold in the sale slot
trade_inv:add_item("buy", trade.npc_gives)
end
end
if(can_trade and not(player_inv:room_for_item("main", trade.npc_gives))) then
-- the player has no room for the sold item; give a warning
trade_possible_msg = "Careful! You do not seem to have enough\n"..
"free inventory space to store your purchase."
end
local trades_done = "Not yet traded."
if(yl_speak_up.trade[pname].trade_done > 0) then
trades_done = "Traded: "..tostring(yl_speak_up.trade[pname].trade_done).." time(s)"
end
return table.concat({formspec,
"label[2.5,0.0;Trading with ",
minetest.formspec_escape(trade.npc_name),
"]",
"label[1.5,0.7;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,1.2;0.8,0.8;",
tostring(trade.player_gives),
"]",
"item_image[5.1,1.2;0.8,0.8;",
tostring(trade.npc_gives),
"]",
"image[3.5,2.0;1,1;gui_furnace_arrow_bg.png^[transformR270]",
-- show the pay slot from the detached player's trade inventory
"list[detached:yl_speak_up_player_",
pname,
";pay;2,2.0;1,1;]",
-- show the buy slot from the same inventory
"list[detached:yl_speak_up_player_",
pname,
";buy;5,2.0;1,1;]",
"label[1.5,3.0;",
trade_possible_msg,
"]",
"label[6.0,1.5;",
trades_done,
"]"
}, "")
end
yl_speak_up.get_fs_do_trade_simple_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_do_trade_simple(player, param)
end
yl_speak_up.register_fs("do_trade_simple",
yl_speak_up.input_do_trade_simple,
yl_speak_up.get_fs_do_trade_simple_wrapper,
-- force formspec version 1:
1
)