-- inventory management, trading and handling of quest items for talking to NPCs -- cache the inventory of NPCs for easier access yl_speak_up.npc_inventory = {} -- where are they stored on the disk? yl_speak_up.get_inventory_save_path = function(n_id) return yl_speak_up.worldpath .. yl_speak_up.inventory_path .. DIR_DELIM .. "inv_" .. n_id .. ".json" end -- check if stack contains any metadata; return true if it does -- (trade_simple does not allow used items or those with metadata) yl_speak_up.check_stack_has_meta = function(player, stack) local meta = stack:get_meta() for k, v in pairs(meta:to_table()) do -- the name "fields" is allowed - as long as it is empty if(k ~= "fields") then return true end for k2, v2 in pairs(v) do if(k2) then return true end end end return false end -- the player has closed the inventory formspec of the NPC - save it yl_speak_up.input_inventory = 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 -- after closing the inventory formspec: -- ..save the (very probably) modified inventory yl_speak_up.save_npc_inventory(n_id) -- show inventory again? if(fields.back_from_error_msg) then yl_speak_up.show_fs(player, "inventory") return end -- show the trade list? if(fields.inventory_show_tradelist) then yl_speak_up.show_fs(player, "trade_list") return end -- ..and go back to the normal talk formspec yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id}) end -- access the inventory of the NPC (only possible for players with the right priv) yl_speak_up.get_fs_inventory = 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 -- only players which can edit this npc can see its inventory if(not(yl_speak_up.may_edit_npc(player, n_id))) then return "size[6,2]".. "label[0.2,0.5;Sorry. You lack the privileges.]".. "button_exit[2,1.5;1,0.9;exit;Exit]" end return "size[12,11]" .. "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,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[3.5,6.35;5,0.6;inventory_show_tradelist;Show trade list trades (player view)]".. "button[10.0,10.4;2,0.9;back_from_inventory;Back]" end -- save the inventory of the NPC with the id n_id yl_speak_up.save_npc_inventory = function( n_id ) -- only save something if we actually can if(not(n_id) or not(yl_speak_up.npc_inventory[ n_id ])) then return end -- convert the inventory data to something we can actually store local inv = yl_speak_up.npc_inventory[ n_id ] local inv_as_table = {} for i=1, inv:get_size("npc_main") do local stack = inv:get_stack("npc_main", i) -- only save those slots that are not empty if(not(stack:is_empty())) then inv_as_table[ i ] = stack:to_table() end end -- convert the table into json local json = minetest.write_json( inv_as_table ) -- get a file name for storing the data local file_name = yl_speak_up.get_inventory_save_path(n_id) -- actually store it on disk minetest.safe_file_write(file_name, json) end -- helper function for yl_speak_up.load_npc_inventory and -- minetest.register_on_joinplayer yl_speak_up.inventory_allow_item = function(player, stack, input_to) if(not(player) or not(stack) or not(input_to)) then return 0 end local pname = player:get_player_name() local n_id = yl_speak_up.speak_to[pname].n_id if(not(n_id) or not(yl_speak_up.may_edit_npc(player, n_id))) then return 0 end -- 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 = "yl_speak_up:edit_actions" end local error_msg = nil if(stack:get_wear() > 0) then error_msg = "Your NPC accepts only undammaged items.\n".. "Trading dammaged items would be unfair." -- items with metadata cannot be traded elseif(yl_speak_up.check_stack_has_meta(player, stack)) then error_msg = "Your NPC cannot sell items that contain\n".. "additional (meta-) data." end if(error_msg) then yl_speak_up.show_fs(player, "msg", { input_to = input_to, formspec = "size[6,2]".. "label[0.2,-0.2;"..tostring(error_msg).."]".. "button[2,1.5;1,0.9;back_from_error_msg;".. "OK]"}) return 0 end return stack:get_count() end -- create and load the detached inventory in yl_speak_up.after_activate; -- direct access to this inventory is only possible for players with the right privs -- (this is an inventory for the *NPC*, which is stored to disk sometimes) yl_speak_up.load_npc_inventory = function(n_id) if(not(n_id)) then return end -- the inventory is already loaded if( yl_speak_up.npc_inventory[ n_id ]) then return end -- create the detached inventory (it is empty for now) local npc_inv = minetest.create_detached_inventory("yl_speak_up_npc_"..tostring(n_id), { allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) if(not(yl_speak_up.may_edit_npc(player, n_id))) then return 0 end return count end, -- Called when a player wants to move items inside the inventory. -- Return value: number of items allowed to move. allow_put = function(inv, listname, index, stack, player) -- 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:inventory") end, -- Called when a player wants to put something into the inventory. -- Return value: number of items allowed to put. -- Return value -1: Allow and don't modify item count in inventory. allow_take = function(inv, listname, index, stack, player) if(not(yl_speak_up.may_edit_npc(player, n_id))) then return 0 end return stack:get_count() end, -- Called when a player wants to take something out of the inventory. -- Return value: number of items allowed to take. -- Return value -1: Allow and don't modify item count in inventory. -- log inventory changes (same way as modifications to chest inventories) on_move = function(inv, from_list, from_index, to_list, to_index, count, player) minetest.log("action", "(yl_speak_up) "..player:get_player_name() .. " moves stuff in inventory of NPC " .. tostring(n_id)) end, on_put = function(inv, listname, index, stack, player) minetest.log("action", "(yl_speak_up) "..player:get_player_name() .. " moves " .. stack:get_name() .. " to inventory of NPC " .. tostring(n_id)) end, on_take = function(inv, listname, index, stack, player) minetest.log("action", "(yl_speak_up) "..player:get_player_name() .. " takes " .. stack:get_name() .. " from inventory of NPC " .. tostring(n_id)) end, }) -- the NPC needs enough room for trade items, payment and questitems npc_inv:set_size("npc_main", 6*12) -- cache a pointer to this inventory for easier access yl_speak_up.npc_inventory[ n_id ] = npc_inv -- find out where the inventory of the NPC is stored local file_name = yl_speak_up.get_inventory_save_path(n_id) -- load the data from the file local file, err = io.open(file_name, "r") if err then return end io.input(file) local text = io.read() local inv_as_table = minetest.parse_json(text) io.close(file) if(type(inv_as_table) ~= "table") then return end -- restore the inventory for i=1, npc_inv:get_size("npc_main") do npc_inv:set_stack("npc_main", i, ItemStack( inv_as_table[ i ])) end end