-- 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]" -- helper function for -- yl_speak_up.input_do_trade_simple (here) and -- yl_speak_up.input_trade_via_buy_button (in fs_trade_via_buy_button.lua) -- -- delete a trade; this can be done here only if.. -- * it is a trade from the trade list (not an effect of a dialog option) -- * it is a trade associated with a dialog option and the player is in -- edit mode -- * the player has the necessary privs -- This option is available without having to enter edit mode first. yl_speak_up.delete_trade_simple = function(player, trade_id) local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id if(not(yl_speak_up.may_edit_npc(player, n_id))) then -- not a really helpful message - but then, this should never happen (player probably cheated) return yl_speak_up.trade_fail_msg end -- get the necessary dialog data local dialog = yl_speak_up.speak_to[pname].dialog -- store d_id and o_id in order to be able to return to the right -- edit options dialog local back_to_d_id = nil local back_to_o_id = nil if(dialog and dialog.trades and trade_id and dialog.trades[ trade_id ] and n_id) then if( dialog.trades[ trade_id ].d_id and yl_speak_up.edit_mode[pname] ~= n_id) 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;".. "Trades that are attached to dialog options\n".. "can only be deleted in edit mode. Please tell\n".. "your NPC that you are its owner and have\n".. "new commands!]".. "button[2,1.5;1,0.9;back_from_error_msg;Back]"}) return end if( dialog.trades[ trade_id ].d_id ) then back_to_d_id = dialog.trades[ trade_id ].d_id back_to_o_id = dialog.trades[ trade_id ].o_id end -- log the change yl_speak_up.log_change(pname, n_id, "Trade: Deleted offer "..tostring(trade_id)..".") -- delete this particular trade dialog.trades[ trade_id ] = nil -- actually save the dialog to disk yl_speak_up.save_dialog(n_id, dialog) -- we are done with this trade yl_speak_up.trade[pname] = nil yl_speak_up.speak_to[pname].trade_id = nil yl_speak_up.speak_to[pname].trade_done = nil end -- always return to edit options dialog if deleting a trade that belonged to one if(back_to_d_id and back_to_o_id) then yl_speak_up.show_fs(player, "edit_option_dialog", {n_id = n_id, d_id = back_to_d_id, o_id = back_to_o_id}) return end -- go back showing the trade list (since we deleted this trade) yl_speak_up.show_fs(player, "trade_list") return end -- 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 -- 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 local n_id = yl_speak_up.speak_to[pname].n_id -- if in edit mode: go back to the edit options dialog if(fields.back_to_edit_options and yl_speak_up.edit_mode[pname] == n_id and n_id) then local dialog = yl_speak_up.speak_to[pname].dialog local tr = dialog.trades[ trade.trade_id ] if(tr) then -- done trading yl_speak_up.speak_to[pname].target_d_id = nil yl_speak_up.speak_to[pname].trade_id = nil -- go to the edit options dialog yl_speak_up.show_fs(player, "edit_option_dialog", {n_id = n_id, d_id = tr.d_id, o_id = tr.o_id}) return end end if(fields.delete_trade_simple) then yl_speak_up.delete_trade_simple(player, trade.trade_id) return end -- can the player edit this trade? if(fields.edit_trade_simple and (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id and (yl_speak_up.speak_to[pname].n_id))) then -- force edit mode for this trade trade.edit_trade = true 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 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) return end -- show this formspec again yl_speak_up.show_fs(player, "trade_simple") end -- simple trade: add a new trade or edit existing one (by storing a new one); -- set trade_id to "new" if it shall be a new trade added to the trade list; -- set trade_id to " " if it shall be a result/effect of a dialog option; yl_speak_up.get_fs_add_trade_simple = function(player, trade_id) if(not(player)) then return yl_speak_up.trade_fail_fs end 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 -- is this player allowed to edit the NPC and his trades? If not abort. if(not(yl_speak_up.may_edit_npc(player, n_id)) or not(dialog) or not(dialog.n_npc)) then return "size[9,2]".. "label[2.0,1.8;Ups! Something went wrong.]".. "button[6.2,1.6;2.0,0.9;abort_trade_simple;Back]" end -- store the trade_id (so that it doesn't have to be transfered in a hidden field) yl_speak_up.speak_to[pname].trade_id = trade_id local delete_button = "button[0.2,2.6;1.0,0.9;delete_trade_simple;Delete]".. "tooltip[delete_trade_simple;Delete this trade.]" -- no point in deleting a new trade - it doesn't exist yet if(trade_id and trade_id == "new") then delete_button = "" end return "size[8.5,9]".. "label[4.35,0.8;"..minetest.formspec_escape(dialog.n_npc).." sells:]".. "list[current_player;main;0.2,4.85;8,1;]".. "list[current_player;main;0.2,6.08;8,3;8]".. -- show the second slot of the setup inventory in the detached player's inv "list[detached:yl_speak_up_player_"..pname..";setup;2,1.5;1,1;]".. -- show the second slot of said inventory "list[detached:yl_speak_up_player_"..pname..";setup;5,1.5;1,1;1]".. "label[0.5,0.0;Configure trade with "..minetest.formspec_escape(dialog.n_npc)..":]".. "label[1.5,0.8;The customer pays:]".. "label[1.5,3.8;Put items in the two slots and click on \"Store trade\".]".. "label[1.5,4.2;You will get your items back when storing the trade.]".. -- annoyingly, the height value no longer works :-( "label[0.2,2.5;Item\nname:]".. "field[1.5,3.2;3,0.2;item_name_price;;]".. "label[4.35,2.5;If you don't have the item you\n".. "want to buy, then enter its item\n".. "name (i.e. default:diamond) here.]".. "button[0.2,1.6;1.0,0.9;abort_trade_simple;Abort]".. delete_button.. "button[6.2,1.6;2.0,0.9;store_trade_simple;Store trade]".. "tooltip[store_trade_simple;Click here to store this as a new trade. Your\n".. "items will be returned to you and the trade will\n".. "will be shown the way the customer can see it.]".. "tooltip[abort_trade_simple;Abort setting up this new trade.]" end -- when closing the yl_speak_up.get_fs_add_trade_simple formspec: -- give the items back to the player (he took them from his inventory and -- had no real chance to put them elsewhere - so there really ought to be -- room enough) yl_speak_up.add_trade_simple_return_items = function(player, trade_inv, pay, buy) local player_inv = player:get_inventory() if( pay and player_inv:room_for_item("main", pay)) then player_inv:add_item("main", pay) trade_inv:set_stack("setup", 1, "") end if( buy and player_inv:room_for_item("main", buy)) then player_inv:add_item("main", buy) trade_inv:set_stack("setup", 2, "") end end -- the player wants to add a simple trade; handle formspec input -- possible inputs: -- fields.back_from_error_msg show this formspec here again -- fields.store_trade_simple store this trade as a result and -- go on to showing the do_trade_simple formspec -- fields.delete_trade_simple delete this trade -- go back to edit options dialog -- abort_trade_simple, ESC go back to edit options dialog -- The rest is inventory item movement. yl_speak_up.input_add_trade_simple = function(player, formname, fields) if(not(player)) then return 0 end local pname = player:get_player_name() local input_to = "add_trade_simple" -- are we editing an action of the type trade? if( yl_speak_up.speak_to[pname][ "tmp_action" ] and yl_speak_up.speak_to[pname][ "tmp_action" ].what == 3) then input_to = "edit_actions" end -- we return from showing an error message (the player may not have noticed -- a chat message while viewing a formspec; thus, we showed a formspec message) if(fields.back_from_error_msg) then yl_speak_up.show_fs(player, input_to) return end -- which trade are we talking about? local trade_id = yl_speak_up.speak_to[pname].trade_id -- this also contains the inventory list "setup" where the player placed the items local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) -- fields.abort_trade_simple can be ignored as it is similar to ESC local pay = trade_inv:get_stack("setup", 1) local buy = trade_inv:get_stack("setup", 2) -- clicking on abort here when adding a new trade via the trade list -- goes back to the trade list (does not require special privs) if(fields.abort_trade_simple and trade_id == "new") then -- we are no longer doing a particular trade yl_speak_up.speak_to[pname].trade_id = nil -- return the items (setting up the trade was aborted) yl_speak_up.add_trade_simple_return_items(player, trade_inv, pay, buy) -- ..else go back to the edit options formspec yl_speak_up.show_fs(player, "trade_list") return end -- adding a new trade via the trade list? if(not(trade_id) and fields.store_trade_simple) then trade_id = "new" end local n_id = yl_speak_up.speak_to[pname].n_id local d_id = yl_speak_up.speak_to[pname].d_id local o_id = yl_speak_up.speak_to[pname].o_id -- the trade can only be changed in edit mode if((not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) -- exception: when adding a new trade via the trade list -- (that is allowed without having to be in edit mode) and not(trade_id == "new" and yl_speak_up.may_edit_npc(player, n_id))) then -- return the items (setting up the trade was aborted) yl_speak_up.add_trade_simple_return_items(player, trade_inv, pay, buy) return end -- store the new trade if(fields.store_trade_simple) then local error_msg = "" local simulated_pay = false if(pay:is_empty() and fields.item_name_price and fields.item_name_price ~= "") then pay = ItemStack(fields.item_name_price) simulated_pay = true end -- check for error conditions if(pay:is_empty()) then error_msg = "What shall the customer pay?\nWe don't give away stuff for free here!" elseif(buy:is_empty()) then error_msg = "What shall your NPC sell?\nCustomers won't pay for nothing!" elseif(pay:get_wear() > 0 or buy:get_wear() > 0) then error_msg = "Selling used items is not possible." elseif(not(minetest.registered_items[ pay:get_name() ]) or not(minetest.registered_items[ buy:get_name() ])) then error_msg = "Unkown items cannot be traded." elseif(pay:get_name() == buy:get_name()) then error_msg = "Selling *and* buying the same item\nat the same time makes no sense." else -- get the necessary dialog data local dialog = yl_speak_up.speak_to[pname].dialog -- player_gives (pay stack): local ps = pay:get_name().." "..tostring(pay:get_count()) -- npc_gives (buy stack): local bs = buy:get_name().." "..tostring(buy:get_count()) local r_id = "?" if(not(dialog.trades)) then dialog.trades = {} end -- is this a trade attached to the trade list? -- or do we have to create a new trade ID? if(trade_id == "new") then -- if the player adds the same trade again, the ID is reused; other -- than that, the ID is uniq -- (the ID is formed so that we can later easily sort the offers by -- the name of the buy stack - which is more helpful for the player -- than sorting by the pay stack) trade_id = "sell "..bs.." for "..ps -- log the change yl_speak_up.log_change(pname, n_id, "Trade: Added offer "..tostring(trade_id)..".") -- add this new trade dialog.trades[ trade_id ] = {pay={ps},buy={bs}} -- actually save the dialog to disk yl_speak_up.save_dialog(n_id, dialog) -- store the newly created trade_id yl_speak_up.speak_to[pname].trade_id = trade_id -- all ok so far error_msg = nil -- storing trades that are associated with particular dialogs and options -- requires d_id and o_id to be set elseif(trade_id ~= "new" and (not(d_id) or not(o_id))) then error_msg = "Internal error. o_id was not set." else -- record this as a change, but do not save do disk yet table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Trade "..tostring(trade_id).." added to option ".. tostring(o_id)..".") -- add this new trade - complete with information to which dialog and -- to which option the trade belongs dialog.trades[ trade_id ] = {pay={ps},buy={bs}, d_id = d_id, o_id = o_id} -- all ok so far error_msg = nil end -- do not return yet - the items still need to be given back! end -- make sure we don't create items here out of thin air if(simulated_pay) then pay = ItemStack("") end -- show error message (that leads back to this formspec) if(error_msg) then yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:"..input_to, formspec = "size[6,2]".. "label[0.2,0.5;"..error_msg.."]".. "button[2,1.5;1,0.9;back_from_error_msg;Back]"}) return end -- we need a way of deleting trades as well; -- this affects only trades that are associated with dialogs and options; -- trades from the trade list are deleted more directly elseif(fields.delete_trade_simple) then -- delete this result (if it exists) -- get the necessary dialog data local dialog = yl_speak_up.speak_to[pname].dialog -- record this as a change table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Trade "..tostring(trade_id).." deleted from option ".. tostring(o_id)..".") if(not(dialog.trades)) then dialog.trades = {} end -- delete the trade type result if(trade_id) then dialog.trades[ trade_id ] = nil end -- do not return yet - the items still need to be given back! end -- return the items after successful setup yl_speak_up.add_trade_simple_return_items(player, trade_inv, pay, buy) local dialog = yl_speak_up.speak_to[pname].dialog if(not(dialog.trades)) then dialog.trades = {} end if(dialog.trades[ trade_id ] and dialog.trades[ trade_id ].d_id and input_to ~= "edit_actions") then yl_speak_up.speak_to[pname].trade_id = trade_id -- tell the player that the new trade has been added yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:do_trade_simple", formspec = "size[6,2]".. "label[0.2,0.5;The new trade has been configured successfully.]".. "button[1.5,1.5;2,0.9;trade_simple_stored;Show trade]"}) -- return back to trade list elseif(not(o_id)) then -- we are no longer trading yl_speak_up.speak_to[pname].trade_id = nil -- ..else go back to the edit options formspec yl_speak_up.show_fs(player, "trade_list") else -- we are no longer trading yl_speak_up.speak_to[pname].trade_id = nil -- the trade has been stored or deleted successfully return true -- -- ..else go back to the edit options formspec (obsolete) -- yl_speak_up.show_fs(player, "edit_option_dialog", -- {n_id = n_id, d_id = d_id, o_id = o_id}) end end -- can this trade be made? called in allow_take yl_speak_up.can_trade_simple = function(player, count) 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] -- do we have all the necessary data? if(not(trade) or trade.trade_type ~= "trade_simple") then return 0 end -- the player tries to take *less* items than what his payment is; -- avoid this confusion! if(ItemStack(trade.npc_gives):get_count() ~= count) then return 0 end -- 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)}) -- is the payment in the payment slot? if( not(trade_inv:contains_item("pay", trade.player_gives)) -- is the item to be sold in the buy slot? or not(trade_inv:contains_item("buy", trade.npc_gives)) -- has the NPC room for the payment? or not(npc_inv:room_for_item("npc_main", trade.player_gives)) -- has the player room for the sold item? or not(player_inv:room_for_item("main", trade.npc_gives))) then -- trade not possible return 0 end -- used items cannot be sold as there is no fair way to indicate how -- much they are used if( trade_inv:get_stack("pay", 1):get_wear() > 0) then return 0 end -- all ok; all items that are to be sold can be taken return ItemStack(trade.npc_gives):get_count() end -- actually execute the trade yl_speak_up.do_trade_simple = function(player, count) -- can the trade be made? if(not(yl_speak_up.can_trade_simple(player, count))) then return end local pname = player:get_player_name() -- which trade are we talking about? local trade = yl_speak_up.trade[pname] -- 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 NPCs' inventory local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)}) -- the NPC sells these items right now, and the player is moving it to his inventory npc_inv:remove_item("npc_main", trade.npc_gives) -- move price items to the NPC local stack = trade_inv:remove_item("pay", trade.player_gives) npc_inv:add_item("npc_main", stack) -- 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)) 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_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 = -- "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 in edit_mode 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).]" 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 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 -- functions for handling the detached trade inventory of players -- these functions exist as extra functions so that they can be changed with /npc_talk_reload -- moving of items between diffrent lists is not allowed yl_speak_up.trade_inv_allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) if(not(player)) then return 0 end if(from_list ~= to_list) then return 0 end return count end -- these all require calling special functions, depending on context yl_speak_up.trade_inv_allow_put = function(inv, listname, index, stack, player) if(not(player)) then return 0 end -- the "buy" slot is managed by the NPC; the player only takes from it if(listname == "buy") then return 0 end -- do not allow used items or items with metadata in the setup slots -- (they can't really be traded later on anyway) if(listname == "setup") then -- check if player can edit NPC, item is undammaged and contains no metadata return yl_speak_up.inventory_allow_item(player, stack, "yl_speak_up:add_trade_simple") end -- allow putting something in in edit mode - but not otherwise if(listname == "npc_gives") then local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id -- only in edit mode! else the NPC manages this slot if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then return 0 end end return stack:get_count() end yl_speak_up.trade_inv_allow_take = function(inv, listname, index, stack, player) if(not(player)) then return 0 end -- can the trade be made? if(listname == "buy") then return yl_speak_up.can_trade_simple(player, stack:get_count()) end return stack:get_count() end yl_speak_up.trade_inv_on_move = function(inv, from_list, from_index, to_list, to_index, count, player) end yl_speak_up.trade_inv_on_put = function(inv, listname, index, stack, player) if(listname == "pay") then local pname = player:get_player_name() -- show formspec with updated information (perhaps sale is now possible) yl_speak_up.show_fs(player, "trade_simple") elseif(listname == "npc_gives" or listname == "npc_wants") then -- monitor changes in order to adjust the formspec yl_speak_up.action_inv_changed(inv, listname, index, stack, player, "put") end end yl_speak_up.trade_inv_on_take = function(inv, listname, index, stack, player) -- the player may have put something wrong in the payment slot -- -> show updated formspec if(listname == "pay") then local pname = player:get_player_name() -- show formspec with updated information (perhaps sale is now possible) yl_speak_up.show_fs(player, "trade_simple") elseif(listname == "buy") then -- do the exchange yl_speak_up.do_trade_simple(player, stack:get_count()) local pname = player:get_player_name() -- which trade are we talking about? local trade = yl_speak_up.trade[pname] -- when the player traded once inside an action: that action was a success; -- execute next action -- but only if not in edit mode if(trade and trade.trade_done > 0 and not(trade.trade_is_trade_list) and yl_speak_up.edit_mode[pname] ~= trade.n_id) then local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname}) -- return surplus items from the pay slot local pay = trade_inv:get_stack("pay", 1) local player_inv = player:get_inventory() if( pay and player_inv:room_for_item("main", pay)) then player_inv:add_item("main", pay) trade_inv:set_stack("pay", 1, "") end -- 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, trade.a_id, true) return end -- information may require an update (NPC might now be out of stock), or -- the player can do the trade a second time yl_speak_up.show_fs(player, "trade_simple") elseif(listname == "npc_gives" or listname == "npc_wants") then -- monitor changes in order to adjust the formspec yl_speak_up.action_inv_changed(inv, listname, index, stack, player, "take") end end -- create a detached inventory for the *player* for trading with the npcs -- (called in minetest.register_on_joinplayer) yl_speak_up.player_joined_add_trade_inv = function(player, last_login) local pname = player:get_player_name() -- create the detached inventory; -- the functions for monitoring changes will be important later on -- only the the player owning this detached inventory may access it local trade_inv = minetest.create_detached_inventory("yl_speak_up_player_"..tostring(pname), { allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) return yl_speak_up.trade_inv_allow_move(inv, from_list, from_index, to_list, to_index, count, player) end, allow_put = function(inv, listname, index, stack, player) return yl_speak_up.trade_inv_allow_put(inv, listname, index, stack, player) end, allow_take = function(inv, listname, index, stack, player) return yl_speak_up.trade_inv_allow_take(inv, listname, index, stack, player) end, on_move = function(inv, from_list, from_index, to_list, to_index, count, player) return yl_speak_up.trade_inv_on_move(inv, from_list, from_index, to_list, to_index, count, player) end, on_put = function(inv, listname, index, stack, player) return yl_speak_up.trade_inv_on_put(inv, listname, index, stack, player) end, on_take = function(inv, listname, index, stack, player) return yl_speak_up.trade_inv_on_take(inv, listname, index, stack, player) end, }) -- prepare the actual inventories trade_inv:set_size("pay", 1) trade_inv:set_size("buy", 1) -- for setting up new simple trades trade_inv:set_size("setup", 2*1) -- for setting up actions trade_inv:set_size("npc_gives", 1) trade_inv:set_size("npc_wants", 1) -- for setting wielded items (left and right) trade_inv:set_size("wield", 2) end