diff --git a/api/api_trade.lua b/api/api_trade.lua index 37fca5c..cfcf3de 100644 --- a/api/api_trade.lua +++ b/api/api_trade.lua @@ -135,3 +135,72 @@ yl_speak_up.get_sorted_trade_id_list = function(dialog, show_dialog_option_trade table.sort(keys) return keys end + + +-- taken from trade_simple.lua: + +-- 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 diff --git a/api/api_trade_inv.lua b/api/api_trade_inv.lua new file mode 100644 index 0000000..5e86907 --- /dev/null +++ b/api/api_trade_inv.lua @@ -0,0 +1,240 @@ + +-- 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 + +-- 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 + + +-- 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, "yl_speak_up:trade_simple") + 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 diff --git a/fs/fs_add_trade_simple.lua b/fs/fs_add_trade_simple.lua new file mode 100644 index 0000000..355f404 --- /dev/null +++ b/fs/fs_add_trade_simple.lua @@ -0,0 +1,282 @@ +-- 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 + + + +-- 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 + +-- 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 + diff --git a/fs/fs_do_trade_simple.lua b/fs/fs_do_trade_simple.lua new file mode 100644 index 0000000..540ed5c --- /dev/null +++ b/fs/fs_do_trade_simple.lua @@ -0,0 +1,433 @@ +-- 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 +-- 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, 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 = -- "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 + diff --git a/init.lua b/init.lua index 242537c..612b45a 100644 --- a/init.lua +++ b/init.lua @@ -208,9 +208,11 @@ yl_speak_up.reload = function(modpath, log_entry) -- limit how much the NPC shall buy and sell dofile(modpath .. "api/api_trade.lua") dofile(modpath .. "fs/fs_trade_limit.lua") - dofile(modpath .. "fs_edit_trade_limit.lua") + dofile(modpath .. "fs/fs_edit_trade_limit.lua") -- trade one item(stack) against one other item(stack) - dofile(modpath .. "trade_simple.lua") + dofile(modpath .. "api/api_trade_inv.lua") + dofile(modpath .. "fs/fs_do_trade_simple.lua") + dofile(modpath .. "fs/fs_add_trade_simple.lua") -- just click on a button to buy items from the trade list dofile(modpath .. "fs/fs_trade_via_buy_button.lua") -- easily accessible list of all trades the NPC offers diff --git a/show_fs.lua b/show_fs.lua index e6b3651..2e6f4db 100644 --- a/show_fs.lua +++ b/show_fs.lua @@ -42,7 +42,7 @@ yl_speak_up.input_handler = function(player, formname, fields) elseif formname == "yl_speak_up:player_offers_item" then yl_speak_up.input_player_offers_item(player, formname, fields) return true - -- handled in trade_simple.lua + -- handled in fs/fs_do_trade_simple.lua elseif formname == "yl_speak_up:do_trade_simple" then yl_speak_up.input_do_trade_simple(player, formname, fields) return true @@ -333,7 +333,7 @@ yl_speak_up.show_fs = function(player, fs_name, param) param = yl_speak_up.speak_to[pname].trade_id end yl_speak_up.show_fs_ver(pname, "yl_speak_up:do_trade_simple", - yl_speak_up.get_fs_trade_simple(player, param), 1) + yl_speak_up.get_fs_do_trade_simple(player, param), 1) elseif(fs_name == "trade_via_buy_button") then -- the optional parameter param is the trade_id diff --git a/trade_simple.lua b/trade_simple.lua deleted file mode 100644 index 38e9770..0000000 --- a/trade_simple.lua +++ /dev/null @@ -1,1018 +0,0 @@ --- 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, formname) - 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, "yl_speak_up:trade_simple") - 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