diff --git a/fs_edit_trade_limit.lua b/fs_edit_trade_limit.lua new file mode 100644 index 0000000..4a8beab --- /dev/null +++ b/fs_edit_trade_limit.lua @@ -0,0 +1,88 @@ +-- add or edit a trade limit + +yl_speak_up.input_edit_trade_limit = function(player, formname, fields) + -- store the new limits? + if(fields and fields["store_limit"]) then + if(not(fields["item_name"]) + or fields["item_name"] == "" + or not(minetest.registered_items[fields["item_name"]])) then + -- TODO: show error message and back + return + 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 + + -- make sure all necessary entries in the trades table exist + yl_speak_up.setup_trade_limits(dialog) + + local anz = tonumber(fields['SellIfMoreThan'] or "0") + if( anz and anz > 0 and anz < 10000 ) then + dialog.trades.limits.sell_if_more[ fields["item_name"] ] = anz; + yl_speak_up.log_change(pname, n_id, "sell_if_more set to "..tostring(anz).. + " for "..tostring(fields["item_name"])) + end + + anz = tonumber(fields['BuyIfLessThan'] or "0") + if( anz and anz > 0 and anz < 10000 ) then + dialog.trades.limits.buy_if_less[ fields["item_name"] ] = anz; + yl_speak_up.log_change(pname, n_id, "buy_if_less set to "..tostring(anz).. + " for "..tostring(fields["item_name"])) + end + -- save these values + yl_speak_up.save_dialog(n_id, dialog) + yl_speak_up.show_fs(player, "trade_limit", {selected = fields.item_name}) + return + end + -- TODO: implement delete button + + -- back to the normal trade list + yl_speak_up.show_fs(player, "trade_limit", {selected = fields.item_name}) +end + + +-- edit a trade limit or add a new one +yl_speak_up.get_fs_edit_trade_limit = function(player, selected_row) + local pname = player:get_player_name() + local items = yl_speak_up.speak_to[pname].trade_limit_items + local item_list = yl_speak_up.speak_to[pname].trade_limit_item_list + if(not(selected_row) or selected_row < 1 + or not(items) or not(item_list) + or selected_row > #item_list + 1) then + -- TODO show more helpful error message + return "Error in get_fs_edit_trade_limit." + end + local selected = item_list[ selected_row - 1] + local item_data = items[ selected ] + -- items is a table (list) with these entries: + -- [1] 0 in stock; + -- [2] sell if more than 0; + -- [3] buy if less than 10000; + -- [4] item is part of a trade offer + local def = minetest.registered_items[ selected ] + if(not(def)) then + def = {description = '- unknown item -'} + end + + local formspec = {'size[8,7]', + 'item_image[-0.25,2.5;2.0,2.0;', selected, ']', + 'label[1.0,0.0;Set limits for buy and sell]', + 'label[1.5,1.0;Description:]', + 'label[4.0,1.0;', (def.description or '?'), ']', + 'label[1.5,2.0;Item name:]', + --'label[3.5,1.0;', tostring( selected ), ']', + 'field[4.0,2.0;4,1;item_name;;', tostring( selected ), ']', + 'label[1.5,3.0;In stock:]', + 'label[4.0,3.0;', tostring( item_data[1] ), ']', + 'label[1.5,4.0;Sell if more than]', + 'field[4.0,4.0;1.2,1.0;SellIfMoreThan;;', tostring( item_data[2] ), ']', + 'label[5.0,4.0;will remain in stock.]', + 'label[1.5,5.0;Buy if less than]', + 'field[4.0,5.0;1.2,1.0;BuyIfLessThan;;', tostring( item_data[3] ), ']', + 'label[5.0,5.0;will end up in stock.]', + 'button[1.5,6.0;2,1.0;store_limit;Save]', + 'button[4.5,6.0;2,1.0;back_to_limit_list;Back]' + -- TODO: delete button + } + return table.concat(formspec, '') +end diff --git a/fs_trade_limit.lua b/fs_trade_limit.lua new file mode 100644 index 0000000..e7aab12 --- /dev/null +++ b/fs_trade_limit.lua @@ -0,0 +1,225 @@ +----------------------------------------------------------------------------- +-- limits for trading: maximum and minimum stock to keep +----------------------------------------------------------------------------- +-- sometimes players may not want the NPC to sell *all* of their stock, +-- or not let the NPC buy endless amounts of something when only a limited +-- amount is needed +----------------------------------------------------------------------------- + +-- helper function: make sure all necessary entries in the trades table exist +yl_speak_up.setup_trade_limits = function(dialog) + if(not(dialog)) then + dialog = {} + end + if(not(dialog.trades)) then + dialog.trades = {} + end + if(not(dialog.trades.limits)) then + dialog.trades.limits = {} + end + if(not(dialog.trades.limits.sell_if_more)) then + dialog.trades.limits.sell_if_more = {} + end + if(not(dialog.trades.limits.buy_if_less)) then + dialog.trades.limits.buy_if_less = {} + end + return dialog +end + + +-- helper function: count how many items the NPC has in his inventory +-- empty stacks are counted under the key ""; +-- for other items, the amount of items of each type is counted +yl_speak_up.count_npc_inv = function(n_id) + if(not(n_id)) then + return {} + end + -- the NPC's inventory + local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)}) + + if(not(npc_inv)) then + return {} + end + local anz = npc_inv:get_size('npc_main') + local stored = {} + for i=1, anz do + local stack = npc_inv:get_stack('npc_main', i ) + local name = stack:get_name() + local count = stack:get_count() + -- count empty stacks + if(name=="") then + count = 1 + end + -- count how much of each item is there + if(not(stored[ name ])) then + stored[ name ] = count + else + stored[ name ] = stored[ name ] + count + end + end + return stored +end + + +-- helper function: update the items table so that it reflects a limitation +-- items is a table (list) with these entries: +-- [1] 0 in stock; +-- [2] sell if more than 0; +-- [3] buy if less than 10000; +-- [4] item is part of a trade offer +yl_speak_up.insert_trade_item_limitation = function( items, k, i, v ) + if( i<1 or i>4) then + return; + end + if( not( items[ k ] )) then + -- 0 in stock; sell if more than 0; buy if less than 10000; item is part of a trade offer + items[ k ] = { 0, 0, 10000, false, #items } + end + items[ k ][ i ] = v +end + + + +-- handle input to the formspec +yl_speak_up.input_trade_limit = function(player, formname, fields) + -- the player has selected an entry - edit it + if(fields and fields["edit_trade_limit"]) then + local selection = minetest.explode_table_event( fields[ "edit_trade_limit" ]) + if( selection and selection['row'] + and (selection['type'] == 'DCL' or selection['type'] == 'CHG')) then + -- show edit trade limit formspec + yl_speak_up.show_fs(player, "edit_trade_limit", {selected_row = selection['row']}) + return + end + end + + if(fields and (fields.back or fields.quit)) then + -- back to the normal trade list + yl_speak_up.show_fs(player, "trade_list") + return + end + -- else show this formspec again + yl_speak_up.show_fs(player, "trade_limit") +end + + +-- show the formspec +yl_speak_up.get_fs_trade_limit = function(player, selected) + 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 + -- in items, existing amount and limit are collected for display + local items = {} + + if(not(dialog) or not(n_id)) then + return "Error. Missing dialog when accessing trade limits." + end + -- make sure all necessary entries in the trades table exist + yl_speak_up.setup_trade_limits(dialog) + + -- how many items does the NPC have alltogether? + -- there may be more than one stack of the same type; store amount + local counted_inv = yl_speak_up.count_npc_inv(n_id) + for k,v in pairs(counted_inv) do + yl_speak_up.insert_trade_item_limitation(items, k, 1, v) + end + + -- items that are part of any of the trades may also be subject to limits; store item names + for trade_id, trade_data in ipairs(dialog.trades) do + if(trade_id ~= "limits") then + -- what the NPC sells may be subject to limits + local stack = ItemStack(trade_data.buy[1]) + yl_speak_up.insert_trade_item_limitation(items, stack:get_name(), 4, true ) + -- what the customer pays may be subject to limits as well + stack = ItemStack(trade_data.pay[1]) + yl_speak_up.insert_trade_item_limitation(items, stack:get_name(), 4, true ) + end + end + + -- everything for which there's already a sell_if_more limit + for k,v in pairs(dialog.trades.limits.sell_if_more) do + mob_trading.insert_item_limitation( items, k, 2, v ) + end + + -- everything for which there's already a buy_if_less limit + for k,v in pairs(dialog.trades.limits.buy_if_less ) do + mob_trading.insert_item_limitation( items, k, 3, v ) + end + + -- all items for which limitations might possibly be needed have been collected; + -- now display them + local formspec = {'size[18,12]'.. + 'button[0.5,11.1;17,0.8;back;Back]'.. + 'label[7.0,0.5;List of trade limits]'.. + 'label[0.5,1.0;If you do not set any limits, your NPC will buy and sell as many '.. + 'items as his inventory allows.\n'.. + 'If you set \'Will sell if more than this\', your NPC '.. + 'will only sell if he will have enough left after the trade,\n'.. + 'and if you set \'Will buy if less than this\', he will '.. + 'only buy items as long as he will not end up with more than '.. + 'this.]'.. + 'tablecolumns[' .. + 'text,align=left;'.. + 'color;text,align=right;'.. + 'color;text,align=center;'.. + 'text,align=right;'.. + 'color;text,align=center;'.. + 'text,align=right;'.. + 'color;text,align=left]'.. + 'table[0.1,2.3;17.8,8.5;edit_trade_limit;'.. + 'Description:,'.. + '#FFFFFF,NPC has:,'.. + '#FFFFFF,Will sell if more than this:,,'.. + '#FFFFFF,Will buy if less than this:,,'.. + '#EEEEEE,Item string:,' + } + + -- the table event selection returns a row index - we need to translate that to our table + local item_list = {} + local row = 2 + local selected_row = 1 + -- TODO: sort this list in some way? alphabeticly? by limit? + for k,v in pairs( items ) do + table.insert(item_list, k) + if(selected and k == selected) then + selected_row = #item_list + 1 + end + local c1 = '#FF0000' + if( v[1] > 0 ) then + c1 = '#BBBBBB' + end + local t1 = 'sell always' + local c2 = '#44EE44' + if( v[2] > 0 ) then + c2 = '#00FF00' + t1 = 'sell if more than:' + end + local t2 = 'buy always' + local c3 = '#EEEE44' + if( v[3] ~= 10000 ) then + c3 = '#FFFF00' + t2 = 'buy if less than:' + end + + local desc = '' + if( k =="" ) then + desc = '' + k = '' + elseif( minetest.registered_items[ k ] + and minetest.registered_items[ k ].description ) then + desc = minetest.registered_items[ k ].description + end + + table.insert(formspec, + desc..','.. + c1..','.. tostring( v[1] )..','.. + c2..','..t1..','..tostring( v[2] )..','.. + c3..','..t2..','..tostring( v[3] )..',#EEEEEE,'..k..',') + end + -- we need to store the table somewhere so that we know which limit is edited + yl_speak_up.speak_to[pname].trade_limit_items = items + yl_speak_up.speak_to[pname].trade_limit_item_list = item_list + + table.insert(formspec, ";"..selected_row) + return table.concat(formspec, '') +end diff --git a/init.lua b/init.lua index 607a482..49cf2b8 100644 --- a/init.lua +++ b/init.lua @@ -82,6 +82,9 @@ dofile(modpath .. "fs_initial_config.lua") dofile(modpath .. "fs_player_offers_item.lua") -- inventory management, trading and handling of quest items: dofile(modpath .. "inventory.lua") +-- limit how much the NPC shall buy and sell +dofile(modpath .. "fs_trade_limit.lua") +dofile(modpath .. "fs_edit_trade_limit.lua") -- trade one item(stack) against one other item(stack) dofile(modpath .. "trade_simple.lua") -- easily accessible list of all trades the NPC offers diff --git a/show_fs.lua b/show_fs.lua index d15fe43..dd5ff8b 100644 --- a/show_fs.lua +++ b/show_fs.lua @@ -49,6 +49,12 @@ minetest.register_on_player_receive_fields( function(player, formname, fields) elseif formname == "yl_speak_up:add_trade_simple" then yl_speak_up.input_add_trade_simple(player, formname, fields) return true + elseif formname == "yl_speak_up:trade_limit" then + yl_speak_up.input_trade_limit(player, formname, fields) + return true + elseif formname == "yl_speak_up:edit_trade_limit" then + yl_speak_up.input_edit_trade_limit(player, formname, fields) + return true -- handled in fs_initial_config.lua elseif formname == "yl_speak_up:initial_config" then yl_speak_up.input_fs_initial_config(player, formname, fields) @@ -298,6 +304,20 @@ yl_speak_up.show_fs = function(player, fs_name, param) yl_speak_up.show_fs_ver(pname, "yl_speak_up:add_trade_simple", yl_speak_up.get_fs_add_trade_simple(player, param), 1) + elseif(fs_name == "trade_limit") then + if(not(param)) then + param = {} + end + yl_speak_up.show_fs_ver(pname, "yl_speak_up:trade_limit", + yl_speak_up.get_fs_trade_limit(player, param.selected)) + + elseif(fs_name == "edit_trade_limit") then + if(not(param)) then + param = {} + end + yl_speak_up.show_fs_ver(pname, "yl_speak_up:edit_trade_limit", + yl_speak_up.get_fs_edit_trade_limit(player, param.selected_row), 1) + elseif(fs_name == "initial_config") then if(not(param)) then param = {} diff --git a/trade_list.lua b/trade_list.lua index e4c85db..fac5e86 100644 --- a/trade_list.lua +++ b/trade_list.lua @@ -19,6 +19,12 @@ yl_speak_up.input_trade_list = function(player, formname, fields) return end + if(fields.trade_limit) then + -- show a list of how much the NPC can buy and sell + yl_speak_up.show_fs(player, "trade_limit") + return + end + -- toggle between view of dialog option trades and trade list trades if(fields.show_dialog_option_trades or fields.show_trade_list) then @@ -186,7 +192,7 @@ yl_speak_up.get_fs_trade_list = function(player, show_dialog_option_trades) h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, "show_trade_list", text, text, - true, nil, nil, pname_for_old_fs) + true, nil, nil, pname_for_old_fs) end -- button "add trade" for those who can edit the NPC (entering edit mode is not required) local text = "Add a new trade." @@ -194,6 +200,12 @@ yl_speak_up.get_fs_trade_list = function(player, show_dialog_option_trades) "trade_list_add_trade", text, text, true, nil, nil, pname_for_old_fs) + -- show a list of how much the NPC will buy and sell + text = "Do not buy or sell more than what I will tell you." + h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h, + "trade_limit", + text, text, + true, nil, nil, pname_for_old_fs) end local text = "That was all. Let's continue talking."