From cff85689d7fd9b27cb061550c84e366059817990 Mon Sep 17 00:00:00 2001 From: LeMagnesium Date: Mon, 19 Sep 2016 18:54:13 +0200 Subject: [PATCH] Rewrite calls of meta_exec with new structure - Pass a table to meta_exec containing parameters, function to be called, scope string and requirements to check - Add basic ownership and contextid requirement checks in meta_exec when needed - Update README.md - Move assertion tools to assertions.lua --- README.md | 18 +- assertions.lua | 38 ++++ init.lua | 534 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 399 insertions(+), 191 deletions(-) create mode 100644 assertions.lua diff --git a/README.md b/README.md index fed44bf..5d074c9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Minetest mod metatools ###################### A mod inspired by mgl512's itemframe issue -Version : 1.2 +Version : 1.2.1 # Authors - LeMagnesium / Mg / ElectronLibre : Source code writer @@ -29,21 +29,7 @@ allowing to see/edit metadatas - palige who agreed to test the mod for its first release, and contributed to the last version # Command tutorial - - - help => Get help - - version => Get version - - open (x,y,z) _mode_ => Open the node to manipulate at pos (x,y,z) with mode _mode_ (default is 'fields') - - show => Show fields/path list at actual position - - enter _path_ => Enter next stratum through _path_ - - leave => Leave current field and go back to previous stratum - - set _name_ _value_ => Set metadata _name_ to _value_ (create it if it doesn't exist) - - itemstack => Manipulate itemstacks in Node/inventory/\*/ - - erase _name_ => Erase itemstack at field name - - write _name_ _itemstring_ [_count_] => Set itemstack in field _name_ with item _itemstring_ and count _count_. Default count is one, 0 not handled. - - list - - init _name_ _size_ => Create a list of size _size_ named _name_ - - delete _name_ => Delete any list that could be called _name_ - - close => Close node + - Soon to come, please refer to /meta help until then Node metadatas look like this : diff --git a/assertions.lua b/assertions.lua new file mode 100644 index 0000000..ced87fb --- /dev/null +++ b/assertions.lua @@ -0,0 +1,38 @@ +-- Assertion tools for Metatools + +function assert_contextid(ctid) + return contexts[ctid] ~= nil +end + +function assert_ownership(ctid, name) + return playerlocks[name] == ctid +end + +function assert_pos(pos) + if type(pos) ~= "string" then + return pos and pos.x and pos.y and pos.z and minetest.pos_to_string(pos) + else + return minetest.string_to_pos(pos) ~= nil + end +end + +function assert_mode(mode) + return mode and (mode == "fields" or mode == "inventory") +end + +function assert_poslock(pos) + return nodelock[minetest.pos_to_string(pos)] == nil +end + +function assert_specific_mode(contextid, mode) + return assert_contextid(contextid) and contexts[contextid].mode == mode +end + +function assert_field_type(ftype) + return ftype and type(ftype) == "string" and (ftype == "int" or ftype == "float" or ftype == "string") +end + +function assert_integer(int) + return int and tonumber(int) and tonumber(int) % 1 == 0 +end + diff --git a/init.lua b/init.lua index 32b5c54..e62541b 100644 --- a/init.lua +++ b/init.lua @@ -8,16 +8,19 @@ -- - Lymkwi/LeMagnesium -- - Paly2 -- --- Version: 1.2 +-- Version: 1.2.1 -- ]]-- metatools = {} -- Public namespace -local playerdata = {} -- Will hold the positions of all players currently using metatools +local playerlocks = {} -- Selection locks of the players local contexts = {} -local version = 1.3 +local version = "1.2.1" local nodelock = {} +local modpath = minetest.get_modpath("metatools") +dofile(modpath .. "/assertions.lua") + minetest.register_craftitem("metatools:stick",{ description = "Meta stick", inventory_image = "metatools_stick.png", @@ -48,6 +51,39 @@ function metatools.build_param_str(table, index, separator) return str end +function metatools.get_player_selection(name) + return playerlocks[name] +end + +function metatools.player_select(name, ctxid) + playerlocks[name] = ctxid + return true, ("context %d selected"):format(ctxid) +end + +function metatools.player_unselect(name) + playerlocks[name] = nil + return true, "context unselected" +end + +function metatools.switch(contextid) + local ctx = contexts[contextid] + if ctx.mode == "inventory" then + ctx.mode = "fields" + else + ctx.mode = "inventory" + end + ctx.list = "" + return true, "switched to mode " .. ctx.mode +end + +function metatools.get_context_owner(ctxid) + for name, id in pairs(playerlocks) do + if id == ctxid then + return name + end + end +end + function assign_context(pos, mode, owner) local i = 1 while contexts[i] do i = i + 1 end @@ -70,97 +106,124 @@ function free_context(contextid) return true end -function assert_contextid(ctid) - return contexts[ctid] ~= nil -end - -function assert_ownership(ctid, name) - return contexts[ctid].owner == "" or (name and contexts[ctid].owner == name) -end - -function assert_pos(pos) - return pos and pos.x and pos.y and pos.z and minetest.pos_to_string(pos) -end - -function assert_mode(mode) - return mode and (mode == "fields" or mode == "inventory") -end - -function assert_poslock(pos) - return nodelock[minetest.pos_to_string(pos)] == nil -end - -function assert_specific_mode(contextid, mode) - return assert_contextid(contextid) and contexts[contextid].mode == mode -end - -function assert_field_type(ftype) - return ftype and type(ftype) == "string" and (ftype == "int" or ftype == "float" or ftype == "string") -end - -function assert_integer(int) - return int and tonumber(int) and tonumber(int) % 1 == 0 -end - function dump_normalize(dmp) return dump(dmp):gsub('\n', ''):gsub('\t', ' ') end -function meta_exec(scope, func, ...) - local ret, msg = func(...) +--function meta_assertion(assert_type, params) + +function meta_exec(struct) + if not struct.scope then + struct.scope = "meta" + end + + if not struct.func then + return + end + + if struct.required then + -- will call meta_assertion from here + for category, req in pairs(struct.required) do + minetest.log("Matching " .. category) + if category == "position" and not assert_pos(req) then + return false, ("- %s - Failure : Invalid position : %s"):format(struct.scope, dump_normalize(req)) + + elseif category == "contextid" and not assert_contextid(req) then + return false, ("- %s - Failutre : Invalid contextid : %s"):format(struct.scope, dump_normalize(req)) + + elseif category == "no_nodelock" then + if not assert_pos(req) then + return false, ("- %s - Failure : Invalid pos : %s"):format(struct.scope, dump_normalize(req)) + end + local npos = req + if type(npos) == "table" then + npos = minetest.pos_to_string(npos) + end + if nodelock[npos] then + return false, ("- %s - Failure : Nodelock on %s"):format(struct.scope, dump_normalize(req)) + end + + elseif category == "open_mode" and not assert_mode(req) then + return false, ("- %s - Failure : Invalid mode %s"):format(struct.scope, dump_normalize(req)) + + elseif category == "ownership" then + if type(req) ~= "table" or not req.name then + return false, ("- %s - Failure : Requirement of ownership invalid or missing a 'name' field"):format(struct.scope) + end + + if req.contextid then + if not assert_contextid(req.contextid) then + return false, ("- %s - Failure : Invalid context id %s"):format(struct.scope, req.contextid) + end + if not assert_ownership(req.contextid, req.name) then + return false, ("- %s - Failure : Context %d is not owner by %s"):format(struct.scope, req.contextid, req.name) + end + else + return false, ("- %s - Failure : No context selected"):format(struct.scope) + end + + elseif category == "no_ownership" then + if not assert_contextid(req) then + return false, ("- %s - Failure : Invalid context id %s"):format(struct.scope, dump_normalize(req)) + elseif metatools.get_context_owner(req) then + return false, ("- %s - Failure : Node already owned"):format(struct.scope) + end + + elseif category == "some_ownership" and (not req or not playerlocks[req]) then + return false, ("- %s - Failure : No context owned at the moment"):format(struct.scope) + + elseif category == "specific_open_mode" then + if not req or not req.mode or not req.contextid then + return false, ("- %s - Failure : Invalid specific open mode requirement"):format(struct.scope) + end + + if not assert_contextid(req.contextid) then + return false, ("- %s - Failure : Invalid context id : %s"):format(struct.scope, dump_normalize(req.contextid)) + end + + if not contexts[req.contextid].mode == req.mode then + return false, ("- %s - Failure : Invalid mode, %s is required"):format(struct.scope, dump_normalize(req.mode)) + end + end + minetest.log(("Match %s succeeded"):format(category)) + end + end + + local ret, msg = struct.func(unpack(struct.params)) if ret then - return true, ("- %s - Success : %s"):format(scope, msg) + return true, ("- %s - Success : %s"):format(struct.scope, msg) else - return false, ("- %s - Failure : %s"):format(scope, msg) + return false, ("- %s - Failure : %s"):format(struct.scope, msg) end end function metatools.contexts_summary() local ctxs = {} for ctxid, ctx in pairs(contexts) do - table.insert(ctxs, 1, {id=ctxid, pos=ctx.position, owner=ctx.owner}) + table.insert(ctxs, 1, { + id = ctxid, + pos = ctx.position, + owner = metatools.get_context_owner(ctxid) or "nobody", + mode = ctx.mode, + }) end return true, ctxs end function metatools.open_node(pos, mode, owner) - if not assert_pos(pos) then - return false, "invalid pos " .. dump_normalize(pos) + local id = assign_context(pos, mode) + if owner then + playerlocks[owner] = id end - - if not assert_mode(mode) then - return false, "invalid mode " .. dump_normalize(mode) - end - - if not assert_poslock(pos) then - if nodelock[minetest.pos_to_string(pos)] ~= "" then - return false, "node already opened by " .. nodelock[minetest.pos_to_string(pos)] - else - return false, "node already opened" - end - end - - return true, "opened node " .. minetest.get_node(pos).name .. " at " .. minetest.pos_to_string(pos) .. " in context ID " .. assign_context(pos, mode, owner) + return true, "opened node " .. minetest.get_node(pos).name .. " at " .. minetest.pos_to_string(pos) .. " in context ID " .. id end function metatools.close_node(contextid)--, closer) - if not assert_contextid(contextid) then - return false, "invalid contextid " .. dump_normalize(contextid) - end - --- if closer and not assert_ownership(contextid, closer) then --- return false, "you do not have permission to close that node" --- end - free_context(contextid) return true, "node closed" end function metatools.show(contextid) - if not assert_contextid(contextid) then - return false, "invalid contextid " .. dump_normalize(contextid) - end - local ctx = contexts[contextid] local metabase = minetest.get_meta(ctx.position):to_table()[ctx.mode] if assert_specific_mode(contextid, "inventory") and ctx.list ~= "" then @@ -215,19 +278,7 @@ function metatools.list_leave(contextid) return true, "left list" end -function metatools.set(contextid, ftype, varname, varval) - if not assert_contextid(contextid) then - return false, "invalid contextid " .. dump_normalize(contextid) - end - - if not assert_specific_mode(contextid, "fields") then - return false, "invalid mode, fields mode required" - end - - if not assert_field_type(ftype) then - return false, "invalid field type " .. dump_normalize(ftype) - end - +function metatools.set(contextid, varname, varval) if not varname or varname == "" then return false, "invalid or empty variable name" end @@ -239,31 +290,11 @@ function metatools.set(contextid, ftype, varname, varval) local ctx = contexts[contextid] local meta = minetest.get_meta(ctx.position) - if ftype == "string" then - meta:set_string(varname, ("%s"):format(varval)) - elseif ftype == "int" then - if not tonumber(varval) then - return false, "invalid integer value " .. dump_normalize(varval) - end - meta:set_int(varname, tonumber(varval)) - else - if not tonumber(varval) then - return false, "invalid float value " .. dump_normalize(varval) - end - meta:set_float(varname, tonumber(varval)) - end + meta:set_string(varname, ("%s"):format(varval)) return true, "value of field " .. varname .. " set to " .. varval end function metatools.unset(contextid, varname) - if not assert_contextid(contextid) then - return false, "invalid contextid " .. dump_normalize(contextid) - end - - if not assert_specific_mode(contextid, "fields") then - return false, "invalid mode, fields mode required" - end - if not varname or varname == "" then return false, "invalid or empty variable name" end @@ -273,15 +304,11 @@ function metatools.unset(contextid, varname) end function metatools.purge(contextid) - if not assert_contextid(contextid) then - return false, "invalid context id " .. dump_normalize(contextid) - end - local ctx = contexts[contextid] local meta = minetest.get_meta(ctx.position) if ctx.mode == "inventory" then local inv = meta:get_inventory() - inv:set_lists(nil) + inv:set_lists({}) return true, "inventory purged" else @@ -291,20 +318,12 @@ function metatools.purge(contextid) end function metatools.list_init(contextid, listname, size) - if not assert_contextid(contextid) then - return false, "invalid context id " .. dump_normalize(contextid) - end - - if not assert_specific_mode(contextid, "inventory") then - return false, "invalid mode, inventory mode is required" - end - if not listname or listname == "" then return false, "missing or empty list name" end if not size or not assert_integer(size) or tonumber(size) < 0 then - return false, "invalid size " .. dump_normalize(contextid) + return false, "invalid size " .. dump_normalize(size) end local inv = minetest.get_meta(contexts[contextid].position):get_inventory() @@ -434,13 +453,6 @@ minetest.register_chatcommand("meta", { end local params = paramstr:split(' ') - --[[ - -- Param map - -- [1] = Action - -- [2] = Position (meta open), Gateway (meta open), Variable Name (meta unset, meta set), ItemStack Action (meta itemstack) - -- [3] = Open mode (meta open), Value (meta set), Inventory Index (meta itemstack ) - -- - ]]-- -- meta version if params[1] == "version" then @@ -452,20 +464,22 @@ minetest.register_chatcommand("meta", { "- meta::help - /meta version : Prints out the version\n" .. "- meta::help - /meta help : This very command\n" .. "- meta::help - /meta open <(x,y,z) [mode] : Open not at (x,y,z) with mode 'mode' (either 'fields' or 'inventory'; default is 'fields')\n" .. - "- meta::help - /meta close : Close the node which context id is 'contextid'\n" .. - "- meta::help - /meta show : Show you the fields/lists available\n" .. - "- meta::help - /meta set : Set variable 'name' to 'value', overriding any existing data using data type 'ftype'\n" .. - "- meta::help - /meta unset : Set variable 'name' to nil, ignoring whether it exists or not\n" .. - "- meta::help - /meta purge : Purge all metadata variables\n" .. + "- meta::help - /meta select : Select the node with context for operations\n".. + "- meta::help - /meta switch : Switch open mode in the current context\n" .. + "- meta::help - /meta close : Close the currently selected node\n" .. + "- meta::help - /meta show : Show you the fields/lists available\n" .. + "- meta::help - /meta set : Set variable 'name' to 'value', overriding any existing data\n" .. + "- meta::help - /meta unset : Set variable 'name' to nil, ignoring whether it exists or not\n" .. + "- meta::help - /meta purge : Purge all metadata variables or inventory lists (depending on the open mode)\n" .. "- meta::help - /meta list : List manipulation :\n" .. - "- meta::help - /meta list enter : Enter in list \n" .. - "- meta::help - /meta list leave : Go back to the top level of inventory data\n" .. - "- meta::help - /meta list init : Initialize list 'name' of size 'size', overriding any existing data\n" .. - "- meta::help - /meta list delete : Delete list 'name', ignoring whether it exists or not\n" .. + "- meta::help - /meta list enter : Enter in list \n" .. + "- meta::help - /meta list leave : Go back to the top level of inventory data\n" .. + "- meta::help - /meta list init : Initialize list 'name' of size 'size', overriding any existing data\n" .. + "- meta::help - /meta list delete : Delete list 'name', ignoring whether it exists or not\n" .. "- meta::help - /meta itemstack : ItemStack manipulation :\n" .. - "- meta::help - /meta itemstack write : Write an itemstack represented by 'data' at index 'index' of the list you are in\n" .. - "- meta::help - /meta itemstack add : Add items of an itemstack represented by 'data' in the list you are in\n" .. - "- meta::help - /meta itemstack erase : Remove itemstack at index 'index' in the current inventory, regardless of whether it exists or not\n" .. + "- meta::help - /meta itemstack write : Write an itemstack represented by 'data' at index 'index' of the list you are in\n" .. + "- meta::help - /meta itemstack add : Add items of an itemstack represented by 'data' in the list you are in\n" .. + "- meta::help - /meta itemstack erase : Remove itemstack at index 'index' in the current inventory, regardless of whether it exists or not\n" .. "- meta::help - End of Help" -- meta context @@ -473,8 +487,8 @@ minetest.register_chatcommand("meta", { local _, ctxs = metatools.contexts_summary() local retstr = "" for _, summ in pairs(ctxs) do - retstr = retstr .. ("- meta::contexts : %d: Node at %s owner by %s\n"): - format(summ.id, minetest.pos_to_string(summ.pos), summ.owner) + retstr = retstr .. ("- meta::contexts : %d: [%s] Node at %s owner by %s\n"): + format(summ.id, summ.mode, minetest.pos_to_string(summ.pos), summ.owner) end return true, retstr .. ("- meta::contexts - %d contexts"):format(#ctxs) @@ -482,52 +496,145 @@ minetest.register_chatcommand("meta", { elseif params[1] == "open" then -- Call the API function - return meta_exec("meta::open", metatools.open_node, minetest.string_to_pos(params[2]), params[3] or "fields", name) + if not params[3] then + params[3] = "fields" + end + return meta_exec({ + scope = "meta::open", + func = metatools.open_node, + params = {minetest.string_to_pos(params[2]), params[3], name}, + required = { + mode = params[3], + no_nodelock = params[2], + } + }) -- meta close elseif params[1] == "close" then -- Call the API function - return meta_exec("meta::close", metatools.close_node, tonumber(params[2]) or "invalid or missing id", name) + return meta_exec({ + scope = "meta::close", + func = metatools.close_node, + params = {metatools.get_player_selection(name)}, + required = { + ownership = { + contextid = metatools.get_player_selection(name), + name = name + } + } + }) + + -- meta select + elseif params[1] == "select" then + return meta_exec({ + scope = "meta::select", + func = metatools.player_select, + params = {name, tonumber(params[2])}, + required = { + no_ownership = tonumber(params[2]), + } + }) + + -- meta unselect + elseif params[1] == "unselect" then + return meta_exec({ + scope = "meta::unselect", + func = metatools.player_unselect, + params = {name}, + required = { + some_ownership = name, + } + }) + + -- meta switch + elseif params[1] == "switch" then + return meta_exec({ + scope = "meta::switch", + func = metatools.switch, + params = {metatools.get_player_selection(name)}, + required = { + some_ownership = name, + } + }) -- meta show elseif params[1] == "show" then - local status, fieldlist = metatools.show(tonumber(params[2]) or "invalid or missing id") - if not status then - return status, fieldlist - else - local retstr = "- meta::show - Output :\n" - for name, field in pairs(fieldlist) do - local rpr - if type(field) == "table" then - rpr = ("-> {...} (size %s)"):format(#field) - elseif type(field) == "string" then - rpr = ("= %s"):format(dump_normalize(field)) - elseif type(field) == "userdata" then - if field.get_name and field.get_count then - rpr = ("= ItemStack({name='%s', count=%d, metadata='%s'})"): - format(field:get_name(), field:get_count(), field:get_metadata()) - else - rpr = ("= %s"):format(dump_normalize(field)) - end + return meta_exec({ + scope = "meta::show", + func = function() + local status, fieldlist = metatools.show(metatools.get_player_selection(name)) + if not status then + return status, fieldlist else - rpr = ("= %s"):format(field) + local retstr = "Output :\n" + for name, field in pairs(fieldlist) do + local rpr + if type(field) == "table" then + rpr = ("-> {...} (size %s)"):format(#field) + elseif type(field) == "string" then + rpr = ("= %s"):format(dump_normalize(field)) + elseif type(field) == "userdata" then + if field.get_name and field.get_count then + rpr = ("= ItemStack({name='%s', count=%d, metadata='%s'})"): + format(field:get_name(), field:get_count(), field:get_metadata()) + else + rpr = ("= %s"):format(dump_normalize(field)) + end + else + rpr = ("= %s"):format(field) + end + retstr = retstr .. ("- meta::show - %s %s\n"):format(name, rpr) + end + return true, retstr .. "- meta::show - End of output" end - retstr = retstr .. ("- meta::show - %s %s\n"):format(name, rpr) - end - return true, retstr .. "- meta::show - End of output" - end + end, + params = {}, + required = { + some_ownership = name + } + }) - -- meta set + -- meta set elseif params[1] == "set" then - return meta_exec("meta::set", metatools.set, tonumber(params[2]) or "invalid or missing contextid", params[3], params[4], metatools.build_param_str(params, 5, ' ')) + return meta_exec({ + scope = "meta::set", + func = metatools.set, + params = {metatools.get_player_selection(name), params[2], metatools.build_param_str(params, 3, ' ')}, + required = { + some_ownership = name, + specific_open_mode = { + mode = "fields", + contextid = metatools.get_player_selection(name) + } + } + }) + -- meta unset elseif params[1] == "unset" then - return meta_exec("meta::unset", metatools.unset, tonumber(params[2]) or "invalid or missing contextid", params[3]) - + return meta_exec({ + scope = "meta::unset", + func = metatools.unset, + params = {metatools.get_player_selection(name), params[2]}, + required = { + some_ownership = name, + specific_open_mode = { + mode = "fields", + contextid = metatools.get_player_selection(name) + } + } + }) + -- meta purge elseif params[1] == "purge" then - return meta_exec("meta::purge", metatools.purge, tonumber(params[2]) or "missing or invalid id") + return meta_exec({ + scope = "meta::purge", + func = metatools.purge, + params = {metatools.get_player_selection(name)}, + required = { + some_ownership = name, + } + }) -- meta list... elseif params[1] == "list" then @@ -537,20 +644,64 @@ minetest.register_chatcommand("meta", { -- meta list enter if params[2] == "enter" then - return meta_exec("meta::list::enter", metatools.list_enter, tonumber(params[3]) or "invalid or missing contextid", params[4]) + return meta_exec({ + scope = "meta::list::enter", + func = metatools.list_enter, + params = {metatools.get_player_selection(name), params[3]}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + }, + } + }) -- meta list leave elseif params[2] == "leave" then - return meta_exec("meta::list::leave", metatools.list_leave, tonumber(params[3]) or "invalid or missing contextid") + return meta_exec({ + scope = "meta::list::leave", + func = metatools.list_leave, + params = {metatools.get_player_selection(name)}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + } + } + }) -- meta list init elseif params[2] == "init" then - return meta_exec("meta::list::init", metatools.list_init, tonumber(params[3]) or "invalid or missing contextid", params[4], params[5]) + return meta_exec({ + scope = "meta::list::init", + func = metatools.list_init, + params = {metatools.get_player_selection(name), params[3], params[4]}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + } + } + }) -- meta list delete elseif params[2] == "delete" then - return meta_exec("meta::list::delete", metatools.list_delete, tonumber(params[3]) or "invalid or missing contextid", params[4]) - + return meta_exec({ + scope = "meta::list::delete", + func = metatools.list_delete, + params = {metatools.get_player_selection(name), params[3]}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + } + } + }) + else return false, "- meta::list - Unknown subcommand '" .. params[2] .. "', please consult '/meta help' for help" end @@ -563,15 +714,48 @@ minetest.register_chatcommand("meta", { -- meta itemstack erase if params[2] == "erase" then - return meta_exec("meta::itemstack::erase", metatools.itemstack_erase, tonumber(params[3]) or "invalid or missing contextid", params[4]) + return meta_exec({ + scope = "meta::itemstack::erase", + func = metatools.itemstack_erase, + params = {metatools.get_player_selection(name), params[3]}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + } + } + }) -- meta itemstack write elseif params[2] == "write" then - return meta_exec("meta::itemstack::write", metatools.itemstack_write, tonumber(params[3]) or "invalid or missing contextid", params[4], metatools.build_param_str(params, 5, ' ')) + return meta_exec({ + scope = "meta::itemstack::write", + func = metatools.itemstack_write, + params = {metatools.get_player_selection(name), params[3], metatools.build_param_str(params, 4, ' ')}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + } + } + }) -- meta itemstack add elseif params[2] == "add" then - return meta_exec("meta::itemstack::add", metatools.itemstack_add, tonumber(params[3]) or "invalid or missing contextid", metatools.build_param_str(params, 4, ' ')) + return meta_exec({ + scope = "meta::itemstack::write", + func = metatools.itemstack_add, + params = {metatools.get_player_selection(name), metatools.build_param_str(params, 3, ' ')}, + required = { + some_ownership = name, + specific_open_mode = { + contextid = metatools.get_player_selection(name), + mode = "inventory", + } + } + }) else return false, "- meta::itemstack - Unknown subcommand " .. params[2]