diff --git a/config.lua b/config.lua index e725f93..1a4fa67 100644 --- a/config.lua +++ b/config.lua @@ -25,3 +25,7 @@ yl_speak_up.infotext = "Rightclick to talk" yl_speak_up.max_number_of_buttons = 7 -- how many buttons can be added to one dialog? yl_speak_up.max_number_of_options_per_dialog = 15 + +-- how many rows and cols shall be used for the trade overview list? +yl_speak_up.trade_max_rows = 10 +yl_speak_up.trade_max_cols = 4 diff --git a/functions.lua b/functions.lua index 47cfd6e..ff6653e 100644 --- a/functions.lua +++ b/functions.lua @@ -1744,6 +1744,9 @@ minetest.register_on_player_receive_fields( function(player, formname, fields) elseif formname == "yl_speak_up:inventory" then yl_speak_up.input_inventory(player, formname, fields) return true + elseif formname == "yl_speak_up:trade_list" then + yl_speak_up.input_trade_list(player, formname, fields) + return true elseif formname == "yl_speak_up:do_trade_simple" then yl_speak_up.input_do_trade_simple(player, formname, fields) return true @@ -1762,6 +1765,12 @@ yl_speak_up.input_inventory = function(player, formname, fields) -- after closing the inventory formspec: -- ..save the (very probably) modified inventory yl_speak_up.save_npc_inventory(n_id) + -- show the trade list? + if(fields.inventory_show_tradelist) then + minetest.show_formspec(pname, "yl_speak_up:trade_list", + yl_speak_up.get_fs_trade_list(player)) + return + end -- ..and go back to the normal talk formspec minetest.show_formspec(pname, "yl_speak_up:talk", yl_speak_up.get_fs_talkdialog(player, n_id, d_id)) diff --git a/init.lua b/init.lua index ae6c950..2f66b2c 100644 --- a/init.lua +++ b/init.lua @@ -21,6 +21,8 @@ dofile(modpath .. "privs.lua") dofile(modpath .. "inventory.lua") -- trade one item(stack) against one other item(stack) dofile(modpath .. "trade_simple.lua") +-- easily accessible list of all trades the NPC offers +dofile(modpath .. "trade_list.lua") -- the main functionality of the mod dofile(modpath .. "functions.lua") -- the staffs (requires npc_master priv) diff --git a/inventory.lua b/inventory.lua index 1fc6aae..86687b5 100644 --- a/inventory.lua +++ b/inventory.lua @@ -48,11 +48,12 @@ yl_speak_up.get_fs_inventory = function(player) "label[2,-0.2;Inventory of "..minetest.formspec_escape(dialog.n_npc).. " (ID: "..tostring(n_id).."):]".. "list[detached:yl_speak_up_npc_"..tostring(n_id)..";npc_main;0,0.3;12,6;]" .. - "list[current_player;main;2,6.85;8,1;]" .. - "list[current_player;main;2,8.08;8,3;8]" .. + "list[current_player;main;2,7.05;8,1;]" .. + "list[current_player;main;2,8.28;8,3;8]" .. "listring[detached:yl_speak_up_npc_"..tostring(n_id)..";npc_main]" .. "listring[current_player;main]" .. - "button[10.0,10.2;2,0.9;back_from_inventory;Back]" + "button[4.5,6.35;3,0.6;inventory_show_tradelist;Show all trades]".. + "button[10.0,10.4;2,0.9;back_from_inventory;Back]" end diff --git a/trade_list.lua b/trade_list.lua new file mode 100644 index 0000000..4f396ea --- /dev/null +++ b/trade_list.lua @@ -0,0 +1,129 @@ +-- show a list of all trades + + +-- the player is accessing the trade list +yl_speak_up.input_trade_list = function(player, formname, fields) + local pname = player:get_player_name() + 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 + + if(not(dialog.trades)) then + dialog.trades = {} + end + + -- the player wants to add a new trade + if(fields.trade_list_add_trade) then + -- show the trade config dialog for a new trade + minetest.show_formspec(pname, "yl_speak_up:add_trade_simple", + yl_speak_up.get_fs_trade_simple(player, "new")) + return + end + + -- go back to the main dialog + if(fields.finished_trading) then + minetest.show_formspec(pname, "yl_speak_up:talk", + yl_speak_up.get_fs_talkdialog(player, n_id, d_id)) + return + end + + -- normal mode: the player wants to see a particular trade + for k,v in pairs(dialog.trades) do + if(fields[ k ]) then + minetest.show_formspec(pname, "yl_speak_up:do_trade_simple", + yl_speak_up.get_fs_trade_simple(player, k)) + return + end + end + -- TODO: and otherwise? +end + + +-- show a list of all trades the NPC has to offer +yl_speak_up.get_fs_trade_list = function(player) + if(not(player)) then + return "" + end + local pname = player:get_player_name() + -- which NPC is the player talking to? + local n_id = yl_speak_up.speak_to[pname].n_id + local dialog = yl_speak_up.speak_to[pname].dialog + -- do we have all the necessary data? + if(not(n_id) or not(dialog.n_npc)) then + return "size[6,2]".. + "label[0.2,0.5;Ups! This NPC lacks ID or name.]".. + "button_exit[2,1.5;1,0.9;exit;Exit]" + + end + + -- make sure the NPC has that table defined + if(not(dialog.trades)) then + dialog.trades = {} + end + + yl_speak_up.load_npc_inventory(n_id) + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) + + local formspec = {} + -- arrange the offers in yl_speak_up.trade_max_cols columns horizontally + -- and yl_speak_up.trade_max_rows row vertically + local row = 0 + local col = 0 + local anz_trades = 0 + + -- TODO: handle multiple pages? + for k, v in pairs(dialog.trades) do + if(col < yl_speak_up.trade_max_cols + and v.pay and v.pay[1] and v.pay[1] ~= "" and v.buy and v.buy[1] and v.buy[1] ~= "") then + local pay_stack = ItemStack(v.pay[1]) + local buy_stack = ItemStack(v.buy[1]) + -- do not show trades with nonexistant items + if( not(minetest.registered_items[ pay_stack:get_name() ]) + or not(minetest.registered_items[ buy_stack:get_name() ])) then + break + end + + anz_trades = anz_trades + 1 + local kstr = tostring(minetest.formspec_escape(k)) + table.insert(formspec, + "item_image_button["..tostring(1+(col*4))..","..tostring(1+row)..";1,1;".. + tostring(v.pay[1])..";"..kstr..";]".. + "image_button["..tostring(1.8+(col*4))..","..tostring(1+row)..";1,1;".. + "gui_furnace_arrow_bg.png^[transformR270;"..kstr..";]".. + "item_image_button["..tostring(2.6+(col*4))..","..tostring(1+row)..";1,1;".. + tostring(v.buy[1])..";"..kstr..";]") + row = row + 1 + if(row > yl_speak_up.trade_max_rows) then + row = 0 + col = col + 1 + end + if(not(npc_inv:contains_item("npc_main", buy_stack))) then + table.insert(formspec, + "label["..tostring(1.7+(col*4))..","..tostring(0.2+row)..";Sold out]") + end + end + end + + -- if there are no trades, at least print a hint that there could be some here + -- (a mostly empty formspec looks too boring and could irritate players) + if(anz_trades == 0) then + table.insert(formspec, + "label[1,1;Sorry. There are currently no offers available.]") + end + + -- button "add trade" for those who can edit the NPC + if((yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) + and (yl_speak_up.speak_to[pname].n_id)) then + table.insert(formspec, + "button["..tostring(yl_speak_up.trade_max_cols * 4 - 2.2).. + ",0.2;2.0,0.9;trade_list_add_trade;Add trade]") + end + + return "size[".. + tostring(yl_speak_up.trade_max_cols * 4)..",".. + tostring(yl_speak_up.trade_max_rows + 2).."]".. + "button[0.2,0.0;2.0,0.9;finished_trading;Back to talk]".. + "label[3.0,0.2;"..tostring(dialog.n_npc).." offers you these trades:]".. + table.concat(formspec, "") +end diff --git a/trade_simple.lua b/trade_simple.lua index e4a3c97..6d2f77f 100644 --- a/trade_simple.lua +++ b/trade_simple.lua @@ -22,6 +22,13 @@ yl_speak_up.input_do_trade_simple = function(player, formname, fields) -- which trade are we talking about? local trade = yl_speak_up.trade[pname] + -- show the trade list + if(fields.back_to_trade_list) then + minetest.show_formspec(pname, "yl_speak_up:trade_list", + yl_speak_up.get_fs_trade_list(player)) + 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 @@ -52,6 +59,11 @@ yl_speak_up.input_do_trade_simple = function(player, formname, fields) if(fields.abort_trade_simple or fields.quit or fields.finished_trading) then local d_id = yl_speak_up.speak_to[pname].d_id local n_id = yl_speak_up.speak_to[pname].n_id + if(trade.trade_is_trade_list) then + minetest.show_formspec(pname, "yl_speak_up:talk", + yl_speak_up.get_fs_talkdialog(player, n_id, d_id)) + return + end -- if in edit mode: go back to the edit options dialog if(yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id and (yl_speak_up.speak_to[pname].n_id)) then @@ -138,39 +150,51 @@ yl_speak_up.input_add_trade_simple = function(player, formname, fields) else -- get the necessary dialog data local dialog = yl_speak_up.speak_to[pname].dialog - -- does this option have a trade result already? - local r_id = yl_speak_up.get_result_id_by_type(dialog, d_id, o_id, "trade") - -- no trade stored for that option yet? -> create new result - if(not(r_id)) then - r_id = yl_speak_up.add_new_result(dialog, d_id, o_id) - end - -- construct the text information for r_value - local trade_text = "npc_gives: \"".. - buy:get_name().." "..tostring(buy:get_count()).. - "\" player_gives: \"".. - pay:get_name().." "..tostring(pay:get_count()).. - "\"" + -- 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 = "?" + -- is this a trade attached to the trade list? + if(trade.trade_is_trade_list) then + -- if the player adds the same trade again, the ID is reused; other + -- than that, the ID is uniq + local trade_id = "trade "..ps.." for "..bs + table.insert(yl_speak_up.npc_was_changed[ n_id ], + "Trade: Added offer "..tostring(trade_id)..".") + dialog.trades[ trade_id ] = {pay={ps},buy={bs}} + -- is this a trade stored as a result of an option? + else + -- does this option have a trade result already? + r_id = yl_speak_up.get_result_id_by_type(dialog, d_id, o_id, "trade") + -- no trade stored for that option yet? -> create new result + if(not(r_id)) then + r_id = yl_speak_up.add_new_result(dialog, d_id, o_id) + end + -- construct the text information for r_value + local trade_text = "npc_gives: \""..bs.."\" player_gives: \""..ps.."\"" - -- store the new result - dialog.n_dialogs[d_id].d_options[o_id].o_results[r_id] = { - r_id = r_id, - r_type = "trade", - r_value = trade_text, - -- additional data for easier parsing of the trade - r_player_gives = pay:get_name().." "..tostring(pay:get_count()), - r_npc_gives = buy:get_name().." "..tostring(buy:get_count()), - r_trade_type = "trade_simple", - } + -- store the new result + dialog.n_dialogs[d_id].d_options[o_id].o_results[r_id] = { + r_id = r_id, + r_type = "trade", + r_value = trade_text, + -- additional data for easier parsing of the trade + r_player_gives = ps, + r_npc_gives = bs, + r_trade_type = "trade_simple", + } + -- record this as a change + table.insert(yl_speak_up.npc_was_changed[ n_id ], + "Dialog "..d_id..": Trade "..tostring(r_id).." added to option ".. + tostring(o_id)..".") + end -- update temporal trade information trade.trade_type = "trade_simple" - trade.player_gives = pay:get_name().." "..tostring(pay:get_count()) - trade.npc_gives = buy:get_name().." "..tostring(buy:get_count()) + trade.player_gives = ps + trade.npc_gives = bs -- finished editing trade.edit_trade = nil - -- record this as a change - table.insert(yl_speak_up.npc_was_changed[ n_id ], - "Dialog "..d_id..": Trade "..tostring(r_id).." added to option ".. - tostring(o_id)..".") -- do not return yet - the items still need to be given back! error_msg = nil end @@ -186,9 +210,15 @@ yl_speak_up.input_add_trade_simple = function(player, formname, fields) -- we need a way of deleting trades as well elseif(fields.delete_trade_simple) then -- delete this result (if it exists) - if(trade.r_id) then - -- get the necessary dialog data - local dialog = yl_speak_up.speak_to[pname].dialog + -- get the necessary dialog data + local dialog = yl_speak_up.speak_to[pname].dialog + if(trade.trade_type == "trade_list" and trade.trade_id) then + -- record this as a change + table.insert(yl_speak_up.npc_was_changed[ n_id ], + "Trade: Deleted offer "..tostring(trade.trade_id)..".") + -- delete this particular trade and update the IDs in the array + dialog.trades[ trade.trade_id ] = nil + elseif(trade.r_id) then -- record this as a change table.insert(yl_speak_up.npc_was_changed[ n_id ], "Dialog "..d_id..": Trade "..tostring(r_id).." deleted from option ".. @@ -217,6 +247,14 @@ yl_speak_up.input_add_trade_simple = function(player, formname, fields) "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(trade.o_id)) then + -- we are no longer trading + yl_speak_up.trade[pname] = nil + -- ..else go back to the edit options formspec + minetest.show_formspec(pname, "yl_speak_up:trade_list", + yl_speak_up.get_fs_trade_list(player)) + return else -- we are no longer trading yl_speak_up.trade[pname] = nil @@ -313,13 +351,40 @@ 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) +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_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 + + 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 + } + if(dialog.trades[ trade_id ]) then + trade.player_gives = dialog.trades[ trade_id ].pay[1] + trade.npc_gives = dialog.trades[ trade_id ].buy[1] + else + trade.edit_trade = true + end + yl_speak_up.trade[pname] = trade + 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 @@ -370,10 +435,20 @@ yl_speak_up.get_fs_trade_simple = function(player) -- show edit button for the owner if in edit_mode if(yl_speak_up.may_edit_npc(player, trade.n_id) and (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)) then - formspec = formspec.. - "button[0.2,1.6;0.8,0.9;edit_trade_simple;Edit]".. - "tooltip[edit_trade_simple;Edit this trade. You can do so only ".. - "if you can edit the NPC as such (i.e. own it).]" + -- for trades as part of the results/effects: allow edit + if(not(trade.trade_is_trade_list)) then + formspec = formspec.. + "button[0.2,1.6;1.0,0.9;edit_trade_simple;Edit]".. + "tooltip[edit_trade_simple;Edit this trade. You can do so only ".. + "if you can edit the NPC as such (i.e. own it).]" + -- for trades in trade list: allow delete (new trades can easily be added) + else + -- TODO: make this button work + formspec = formspec.. + "button[0.2,1.6;1.2,0.9;delete_trade_simple;Delete]".. + "tooltip[delete_trade_simple;Delete this trade. You can do so only ".. + "if you can edit the NPC as such (i.e. own it).]" + end end local trade_possible_msg = "Status of trade: Unknown." @@ -425,6 +500,23 @@ yl_speak_up.get_fs_trade_simple = function(player) "free inventory space to store your purchase.]" 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 + -- TODO: make this button work + 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 + return formspec.. "label[2.5,0.0;Trading with "..minetest.formspec_escape(trade.npc_name).."]".. "label[1.5,0.7;You pay:]".. @@ -438,11 +530,7 @@ yl_speak_up.get_fs_trade_simple = function(player) "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.."]".. - "button[0.2,0.0;2.0,0.9;finished_trading;Back to talk]".. --- "button[6.2,1.6;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.]" + "label[1.5,3.0;"..trade_possible_msg.."]" end