diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a803116 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/projects/node_metadata.dia.autosave diff --git a/README.md b/README.md deleted file mode 100644 index a610b0c..0000000 --- a/README.md +++ /dev/null @@ -1,58 +0,0 @@ -Minetest mod metatools -###################### - -A mod inspired by mgl512's itemframe issue -Version : 1.2.2 - -# Authors - - LeMagnesium / Mg / ElectronLibre : Source code writer - - Paly2 / Palige : Contributor for the source code - - Ataron : Texture creater - -# Purpose -This mod's aim is to provide a way for admins to navigate through any (ok, not -ignores) nodes on the map, and see values of its metadatas at any of their -stratum. - -# Media -"metatools_stick.png" by Ataron (CC-BY-NC-SA) - -# Todo - - Rewrite the table stocking : a variable containing a copy of the global - table returned by :to_table(), on which we would work, and a save command to - apply it on the node - -# Special thanks - - mgl512 (Le_Docteur) for its locked itemframe which gave me the idea of a tool -allowing to see/edit metadatas - - Ataron who created the stick's texture - - palige who agreed to test the mod for its first release, and contributed to the last version - -# Command tutorial - - Soon to come, please refer to /meta help until then - - Node metadatas look like this : - - 0 1 2 3 ... - Node/ - | - +- fields - | | - | +- foo - | +- bar - | +- ... - +- inventory - | - +- main - | | - | +- 1 - | +- 2 - | +- 3 - | +- ... - +- craft - | | - | +- 1 - | +- 2 - | +- 3 - | +- ... - +- ... diff --git a/chatcommands.lua b/chatcommands.lua new file mode 100644 index 0000000..27785ff --- /dev/null +++ b/chatcommands.lua @@ -0,0 +1,17 @@ +minetest.register_chatcommand("howlight", { + description = "Show the light level of the ground below you", + func = function(name) + local player = minetest.get_player_by_name(name) + if player then + local player_pos = vector.round(player:get_pos()) + -- local pos = vector.new(player_pos.x, player_pos.y - 1 , player_pos.z) + local pos = player_pos + -- underground, light is always zero, so z-1 doesn't work. + local pos_string = minetest.pos_to_string(pos) + minetest.chat_send_player(name, "Light level at " .. pos_string .. " is " .. minetest.get_node_light(pos) .. ".") + return true + else + return false, "You are not connected to minetestserver." + end + end +}) diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..b8c505e --- /dev/null +++ b/description.txt @@ -0,0 +1 @@ +See metadata for nodes (and Entities in Poikilos' fork!) diff --git a/init.lua b/init.lua index 3417994..b54fd84 100644 --- a/init.lua +++ b/init.lua @@ -1,17 +1,289 @@ --[[ - -- Metadata Tools +-- Metadata Tools -- --- A mod providing write and read access to a nodes' metadata using commands --- ßÿ Lymkwi/LeMagnesium/Mg ; 2015-2016 --- License: WTFPL --- Contributors : --- - Lymkwi/LeMagnesium --- - Paly2 +-- A mod providing write and read access to a nodes' metadata using commands +-- (c) 2015-2016 ßÿ Lymkwi/LeMagnesium/Mg and Paly2; (c) 2017-2022 Poikilos +-- License: [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/) -- --- Version: 1.2.2 +-- Version: Poikilos fork of 1.2.2 -- ]]-- + +local function isArray(t) + -- Check if a table only contains sequential values. + -- by kikito + -- [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) + -- answered May 21, 2011 at 7:22 + -- edited Mar 2, 2014 at 17:13 + -- + local i = 0 + for _ in pairs(t) do + i = i + 1 + if t[i] == nil then return false end + end + return true +end + + +-- function string:endswith(ending) + -- from https://gist.github.com/kgriffs/124aae3ac80eefe57199451b823c24ec +-- return ending == "" or self:sub(-#ending) == ending +--end +function endswith(str, ending) + return ending == "" or str:sub(-#ending) == ending +end + +function yamlSerializeTable(val, name, depth) + -- Make a table into a string. + -- (c) 2011 Henrik Ilgen, 2022 Poikilos + -- [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) + -- answered May 21 '11 at 12:14 Henrik Ilgen + -- edited May 13, 2019 at 9:10 + -- on + -- Only the first argument is required. + -- Get the object back from the string via: + -- a = loadstring(s)() + depth = depth or 0 + + local tmp = string.rep(" ", depth) + + if name then + if name == "METATOOLS_ARRAY_ELEMENT" then + tmp = tmp .. "- " + else + tmp = tmp .. name .. ": " + end + -- else: should only occur for a value that is after a name already + -- given, such as for tables. + end + + if type(val) == "table" then + tmp = tmp .. "\n" -- Newline is after : for tables. + -- tmp = tmp .. " # table" .. "\n" -- for debug only + if isArray(val) then + for k, v in pairs(val) do + tmp = tmp .. yamlSerializeTable(v, "METATOOLS_ARRAY_ELEMENT", depth + 1) .. "\n" + end + -- tmp = tmp .. string.rep(" ", depth) + else + for k, v in pairs(val) do + tmp = tmp .. yamlSerializeTable(v, k, depth + 1) .. "\n" + end + -- tmp = tmp .. string.rep(" ", depth) + end + while endswith(tmp, "\n\n") do + -- Removing repeated '\n' is necessary since any sub-value + -- (and any more deeply nested value) may be a table and + -- append "\n" (Then this depth appends "\n"). + tmp = string.sub(tmp, ( #tmp - 1 )) + end + elseif type(val) == "number" then + tmp = tmp .. tostring(val) + elseif type(val) == "string" then + tmp = tmp .. string.format("%q", val) + -- %q: "surrounds the string with double quotes and properly + -- escapes double quotes, newlines, and some other characters + -- inside the string." + -- - + elseif type(val) == "boolean" then + tmp = tmp .. (val and "true" or "false") + elseif type(val) == nil then + tmp = tmp .. "null" + else + tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" + end + return tmp +end + +function serializeTable(val, name, depth, skipnewlines) + -- Make a table into a string. + -- (c) 2011 Henrik Ilgen, 2022 Poikilos (switch depth & skipnewlines param order) + -- [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) + -- answered May 21 '11 at 12:14 Henrik Ilgen + -- edited May 13, 2019 at 9:10 + -- on + -- Only the first argument is required. + -- Get the object back from the string via: + -- a = loadstring(s)() + skipnewlines = skipnewlines or false + depth = depth or 0 + + local tmp = string.rep(" ", depth) + + if name then tmp = tmp .. name .. " = " end + + if type(val) == "table" then + tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") + + for k, v in pairs(val) do + tmp = tmp .. serializeTable(v, k, depth + 1, skipnewlines) .. "," .. (not skipnewlines and "\n" or "") + end + + tmp = tmp .. string.rep(" ", depth) .. "}" + elseif type(val) == "number" then + tmp = tmp .. tostring(val) + elseif type(val) == "string" then + tmp = tmp .. string.format("%q", val) + elseif type(val) == "boolean" then + tmp = tmp .. (val and "true" or "false") + else + tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" + end + + return tmp +end + +local function token_indices(haystack, needle) + local results = {} + for i = 1, #haystack do + local try = haystack:sub(i,i + needle:len() - 1) + if try == needle then + table.insert(results, i) + end + end + return results +end + +local function split_and_keep_token(s, needle) + local results = {} + local indices = token_indices(s, needle) + local start = 1 + for k, v in pairs(indices) do + table.insert(results, s:sub(start, v)) + start = v + 1 + end + if start < #s then + table.insert(results, s:sub(start)) + end + return results +end + +local function delimit(table, tab, delimiter) + if not tab then + tab = "" + end + if not table then + return tab .. "nil" + end + if not delimiter then + delimiter = " " + end + local ret = "" + if delimiter ~= "\n" then + ret = tab + end + for k, v in pairs(table) do + if delimiter == "\n" then + ret = ret .. tab .. k .. ":" .. v .. "\n" + else + ret = ret .. k .. ":" .. v .. "\n" + end + end +return ret +end + +local function delimit_sequence(table, tab, delimiter) + if not tab then + tab = "" + end + if not table then + return tab .. "nil" + end + if not delimiter then + delimiter = " " + end + local ret = "" + if delimiter ~= "\n" then + ret = tab + end + for k, v in pairs(table) do + if delimiter == "\n" then + ret = ret .. tab .. v .. delimiter + else + ret = ret .. v .. delimiter + end + end + return ret +end + +local function send_messages_sequence(username, table, tab) + if not tab then + tab = "" + end + if not table then + minetest.chat_send_player(username, tab .. "nil") + return + end + for k, v in pairs(table) do + minetest.chat_send_player(username, tab .. v .. ",") + end + +end + +local function inv_to_tables(inv) + -- see bones mod + results = {} + for i = 1, inv:get_size("main") do + local stk = inv:get_stack("main", i) + table.insert(results, stk:to_table()) + -- to_table shows everything: + -- meta: + -- metadata: "" + -- count:1 + -- name:"default:sapling" + -- wear:0 + end + return results +end + +local function inv_to_table(inv, blank) + -- see bones mod + local results = {} + for i = 1, inv:get_size("main") do + local stk = inv:get_stack("main", i) + local stk_s = stk:to_string() + if #stk_s > 0 or blank then + table.insert(results, stk_s) + end + end + return results +end + +local function send_messages(username, table, tab, blank) + if not tab then + tab = "" + end + if not table then + minetest.chat_send_player(username, tab .. "nil") + return + end + for k, v in pairs(table) do + if blank or ((v ~= nil) and (dump(v) ~= "") and (dump(v) ~= "\"\"")) then + if type(v) == "table" then + minetest.chat_send_player(username, tab .. k .. ":") + send_messages(username, v, tab.."\t") + elseif k == "formspec" then + minetest.chat_send_player(username, tab .. k .. ":") + local chunks = split_and_keep_token(v, "]") + send_messages_sequence(username, chunks, tab.."\t") + else + minetest.chat_send_player(username, tab..k..":"..dump(v)) + end + end + end +end + +local function get_nodedef_field(nodename, fieldname) + if not minetest.registered_nodes[nodename] then + -- print("metatools.get_nodedef_field: no registered node named " .. nodename) + return nil + end + -- print("metatools.get_nodedef_field: checking " .. nodename .. " for " .. fieldname .. " in " .. dump(minetest.registered_nodes[nodename])) + -- print("* result:" .. dump(minetest.registered_nodes[nodename][fieldname])) + return minetest.registered_nodes[nodename][fieldname] +end + metatools = {} -- Public namespace metatools.contexts = {} metatools.playerlocks = {} -- Selection locks of the players @@ -20,22 +292,290 @@ local nodelock = {} local modpath = minetest.get_modpath("metatools") dofile(modpath .. "/assertions.lua") +dofile(modpath .. "/chatcommands.lua") minetest.register_craftitem("metatools:stick",{ description = "Meta stick", inventory_image = "metatools_stick.png", + stack_max = 1, on_use = function(itemstack, user, pointed_thing) local username = user:get_player_name() + local userpos = user:get_pos() + if pointed_thing.type == "nothing" then + minetest.chat_send_player( + username, + "[metatools::stick] You pointed at nothing." + ) + return + elseif pointed_thing.type == "object" then + local pointedObjRef = pointed_thing.ref + -- local objAsStr = minetest.serialize(pointedObjRef) + -- ^ if param is pointed_thing or pointed_thing.ref, minetest.serialize causes "2021-11-14 16:45:39: ERROR[Main]: ServerError: AsyncErr: ServerThread::run Lua: Runtime error from mod 'metatools' in callback item_OnUse(): /home/owner/minetest/bin/../builtin/common/serialize.lua:151: Can't serialize data of type userdata" + -- - even yamlSerializeTable returns [inserializeable datatype:userdata] + -- unrelated note: minetest.serialize(nil) returns "return nil" + -- TODO: + -- Show ObjectRef armor groups (See ) + -- documentation for ObjectRef: + local objAsStr = yamlSerializeTable(pointedObjRef) + minetest.chat_send_player( + username, + "[metatools::stick] You pointed at an object (" .. objAsStr .. ")" + ) + -- ^ (always?) says "[inserializeable datatype:userdata]", so: + local pointedObjRef = pointed_thing.ref + -- if pointed_thing.ref.get_hp then + minetest.chat_send_player( + username, + " pointed_thing.ref:get_hp(): " .. pointedObjRef:get_hp() + ) + -- end + -- minetest.log("action", "[metatools] You pointed at an object: " .. objAsStr) + local luaEntity = pointedObjRef:get_luaentity() + -- INFO: For player name, use user:get_player_name() + minetest.chat_send_player( + username, + " LuaEntity.name: " .. luaEntity.name + ) + -- ^ This is the entity name such as namespace:sheep_black where namespace is a mod name. + minetest.chat_send_player( + username, + " LuaEntity: " .. yamlSerializeTable(luaEntity, "", 1) + ) + local animation = pointedObjRef:get_animation() + minetest.chat_send_player( + username, + " pointed_thing.ref:get_animation():" .. yamlSerializeTable(animation) + ) + if luaEntity.state then + minetest.chat_send_player( + username, + " luaEntity.state: " .. yamlSerializeTable(luaEntity.state) + ) + end + -- Hmm, animation.range, animation['range'] are nil + -- (same for other variables), + -- so API documentation is unclear: + -- `get_animation()`: returns `range`, `frame_speed`, `frame_blend` and + -- `frame_loop`. + -- yamlSerializeTable(animation) only gets: + -- y: 65 + -- x: 35 + -- minetest.chat_send_player( + -- username, + -- yamlSerializeTable(animation.range, " range") + -- ) + -- else type is usually "node" + end local nodepos = pointed_thing.under + -- > * `under` refers to the node position behind the pointed face + -- > * `above` refers to the node position in front of the pointed face. + -- - if not nodepos or not minetest.get_node(nodepos) then return end local nodename = minetest.get_node(nodepos).name - local node = minetest.registered_nodes[nodename] - local meta = minetest.get_meta(nodepos) + local node = minetest.registered_nodes[nodename] + local meta = minetest.get_meta(nodepos) local metalist = meta:to_table() - minetest.chat_send_player(username, "- meta::stick - Node located at "..minetest.pos_to_string(nodepos)) - minetest.chat_send_player(username, "- meta::stick - Metadata fields dump : " .. dump(meta:to_table()["fields"]):gsub('\n', "")) - minetest.log("action","[metatools] Player "..username.." saw metadatas of node at "..minetest.pos_to_string(nodepos)) + minetest.chat_send_player( + username, + "[metatools::stick] You pointed at the '" .. nodename .. "':" + ) + minetest.chat_send_player( + username, + "[metatools::stick] pos:" + .. minetest.pos_to_string(nodepos) + ) + -- minetest.chat_send_player( + -- username, + -- "[metatools::stick] drawtype:" + -- .. get_nodedef_field(nodename, "drawtype") + -- ) + -- minetest.chat_send_player( + -- username, + -- "[metatools::stick] sunlight_propagates:" + -- .. (get_nodedef_field(nodename, "sunlight_propagates") and 'true' or 'false') + -- ) + + if #metalist > 0 then + minetest.chat_send_player( + username, + "[metatools::stick] metadata: " + --.. delimit(meta:to_table()["fields"], "", "\n") + ) + send_messages(username, metalist) + -- send_messages(username, meta:to_table()["fields"]) + -- minetest.chat_send_player( + -- username, + -- "[metatools::stick] inventory: " + -- --.. delimit(meta:to_table()["fields"], "", "\n") + -- ) + end + if meta["get_inventory"] then + local inventory = meta:get_inventory() + if inventory then -- this is never true for some reason + local this_inv_table = inv_to_table(inventory, true) + if #this_inv_table > 0 then + minetest.chat_send_player(username, "get_inventory():") + send_messages(username, this_inv_table, " ") + end + -- else + -- minetest.chat_send_player(username, "\tnil") + end + -- else + -- minetest.chat_send_player(username, "get_inventory:nil") + end + -- node is nil at this point if the node is an "unknown node"! + if node and node.frame_contents then + -- frames mod + local frame_contents = node.frame_contents + if frame_contents then + minetest.chat_send_player(username, "frame_contents: "..frame_contents) + -- else + -- minetest.chat_send_player(username, "\tnil") + end + -- else + -- minetest.chat_send_player(username, "get_inventory:nil") + end + if meta:get_string("item") ~= "" then + -- itemframes mod or xdecor:itemframe + local frame_contents = meta:get_string("item") + if frame_contents then + minetest.chat_send_player(username, "meta item: "..frame_contents) + -- else + -- minetest.chat_send_player(username, "\tnil") + end + -- else + -- minetest.chat_send_player(username, "get_inventory:nil") + end + local airname = minetest.get_name_from_content_id(minetest.CONTENT_AIR) + -- local litnode = nil + local litpos = nil + local litdist = nil + local litwhy = "unknown" + local litmsg = "" + local litid = nil + local litwhat = nil + local litindent = "" + local foundPointed = false + local offsets = { + [0] = {["x"] = 0, ["y"] = 0, ["z"] = 0}, + [1] = {["x"] = 0, ["y"] = 1, ["z"] = 0}, + [2] = {["x"] = 0, ["y"] = -1, ["z"] = 0}, + [3] = {["x"] = 1, ["y"] = 0, ["z"] = 0}, + [4] = {["x"] = -1, ["y"] = 0, ["z"] = 0}, + [5] = {["x"] = 0, ["y"] = 0, ["z"] = 1}, + [6] = {["x"] = 0, ["y"] = 0, ["z"] = -1}, + } + -- local touching = {} + for key, value in pairs(offsets) do + local trydist = nil + local trywhy = nil + local trypos = vector.new( + nodepos.x + value.x, + nodepos.y + value.y, + nodepos.z + value.z + ) + -- touching[key] = trypos + local trynode = minetest.get_node(trypos) + local tryid = nil + local tryname = nil + if (trynode) then + tryname = trynode.name + tryid = minetest.get_content_id(tryname) + + -- print("tryname:" .. tryname) + -- print("trynode.name:" .. trynode.name) + -- if (tryid == minetest.CONTENT_AIR) then + if trynode.name == airname then + -- found: + if (userpos) then + trydist = vector.distance(userpos, trypos) + else + -- dummy value for "found" state: + trydist = vector.distance(nodepos, trypos) + end + trywhy = "air" + else + -- local trygroup = minetest.get_item_group(trynode.name, "air") + -- local drawtype = get_nodedef_field(tryname, "drawtype") + if (get_nodedef_field(tryname, "drawtype") == "airlike") then + trywhy = "airlike" + elseif (get_nodedef_field(tryname, "sunlight_propagates") == true) then + trywhy = "sunlight_propagates" + else + trynode = nil + -- print("[metatools::stick] " .. key .. ": "..tryname.." is not airlike, no sunlight_propagates") + end + if (trynode) then + -- found: + if (userpos) then + trydist = vector.distance(userpos, trypos) + else + -- dummy value for "found" state: + trydist = vector.distance(nodepos, trypos) + end + end + -- if trydef.sunlight_propagates + end + else + trywhy = "non-node" + -- (non-node pos should work for the later light check) + -- found: + if (userpos) then + trydist = vector.distance(userpos, trypos) + else + -- dummy value for "found" state: + trydist = vector.distance(nodepos, trypos) + end + end + if (trydist) then + if (litpos == nil) or (trydist < litdist) then + litdist = trydist + litpos = trypos + litid = tryid -- nil if trywhy == "non-node" + litwhy = trywhy + if (key > 0) then + litwhat = "neighbor:" + litindent = " " + else + foundPointed = true + -- is the pointed node + break -- always use pointed node if lightable + end + end + end + end + local nodelightsource = get_nodedef_field(nodename, "light_source") + if (nodelightsource) and (nodelightsource > 0) then + if not foundPointed then + litmsg = " # next to pointed light_source=" .. nodelightsource + end + end + + + -- litnode = minetest.find_node_near(nodepos, 1, minetest.get_name_from_content_id(minetest.CONTENT_AIR)) + if (litpos) then + if (litwhat) then + minetest.chat_send_player( + username, + "[metatools::stick] nearby lit: #"..minetest.pos_to_string(litpos) + ) + end + minetest.chat_send_player( + username, + "[metatools::stick] "..litindent.."why lit:" .. litwhy .. litmsg + ) + minetest.chat_send_player( + username, + "[metatools::stick] "..litindent.."light:" .. minetest.get_node_light(litpos) + ) + else + minetest.chat_send_player( + username, + "[metatools::stick] nearby lit: ~ # no air/propogator for determining lighting" + ) + end + minetest.log("action","[metatools] Player " .. username .. " saw metadatas of node at " .. minetest.pos_to_string(nodepos)) end, }) @@ -162,7 +702,7 @@ function meta_exec(struct) for category, req in pairs(struct.required) do 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)) @@ -215,7 +755,7 @@ function meta_exec(struct) 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 metatools.contexts[req.contextid].mode == req.mode then return false, ("- %s - Failure : Invalid mode, %s is required"):format(struct.scope, dump_normalize(req.mode)) end @@ -350,7 +890,7 @@ function metatools.purge(contextid) local inv = meta:get_inventory() inv:set_lists({}) return true, "inventory purged" - + else meta:from_table(nil) return true, "fields purged" @@ -543,7 +1083,7 @@ minetest.register_chatcommand("meta", { format(summ.id, summ.mode, minetest.pos_to_string(summ.pos), summ.owner) end return true, retstr .. ("- meta::contexts - %d contexts"):format(#ctxs) - + -- meta open (x,y,z) [fields|inventory] elseif params[1] == "open" then @@ -702,7 +1242,7 @@ minetest.register_chatcommand("meta", { } } }) - + -- meta purge elseif params[1] == "purge" then return meta_exec({ @@ -782,7 +1322,7 @@ minetest.register_chatcommand("meta", { } } }) - + else return false, "- meta::list - Unknown subcommand '" .. params[2] .. "', please consult '/meta help' for help" end @@ -822,7 +1362,7 @@ minetest.register_chatcommand("meta", { } } }) - + -- meta itemstack add elseif params[2] == "add" then return meta_exec({ diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..46573ef --- /dev/null +++ b/mod.conf @@ -0,0 +1,3 @@ +name = metatools +depends = +description = See metadata for nodes (and Entities in Poikilos' fork!) diff --git a/projects/node_metadata.dia b/projects/node_metadata.dia new file mode 100644 index 0000000..9b16b55 Binary files /dev/null and b/projects/node_metadata.dia differ diff --git a/projects/node_metadata.png b/projects/node_metadata.png new file mode 100644 index 0000000..e6f5598 Binary files /dev/null and b/projects/node_metadata.png differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..7454f40 --- /dev/null +++ b/readme.md @@ -0,0 +1,91 @@ +# Minetest mod metatools + +Get everything possible* about a node just by clicking it with metatools:stick! + +## Differences in Poikilos' fork: +This is a hard fork oriented around getting information without typing any commands. +- All known* metadata is shown on click! There is no need for various commands to traverse the tree, though the original chat command code is intact. +- Click a node and get its inventory. +- Click an entity and list the entire LuaEntitySOA tree! + - See the "[Minetest API Notes](minetest-api-notes)" section below. +- The texture is redone so that doesn't any longer have an invasive and inappropriate CC-BY-NC-SA license from the upstream version of metatools. + +`*` All metadata where how to obtain it is known by the maintainer of this repo (except where there is an open issue). + + +## Authors +Code: +- 2015-2016 LeMagnesium/Mg/ElectronLibre and Paly2/Palige (mod inspired by mgl512's itemframe issue) +- 2017-2022 Poikilos (Poikilos' fork of Version 1.2.2) + +Textures: +- 2022 Poikilos (redone "metatools_stick.png" *replaces one with invasive and inappropriate CC-BY-NC-SA license [old texture was by Ataron]*) + + +## Purpose +This mod's aim is to provide a way for admins to navigate through any (ok, not +ignores) nodes on the map, and see values of its metadatas at any of their +stratum. + + +## Install +- Copy the repo folder containing init.lua to your Minetest mods folder (The resulting folder should be mods/metatools/). +- Enable the mod for the world. +- You can remove the "projects" folder to lighten the "game" if necessary (Keeping the png is useful but only for documentation--Nothing in "projects" is used by the mod code). + + +## Special thanks +- mgl512 (Le_Docteur) for its locked itemframe which gave me the idea of a tool + allowing to see/edit metadatas +- Ataron who created the stick's texture +- palige who agreed to test the mod for its first release, and contributed to the last version + + +## Use +- After following the "Install" steps above, open the world in Minetest. +- Type `/grantme all` +- Type `/giveme metatools:stick` + +The chat commands from https://github.com/Lymkwi/minetest-mod-metatools are still present but usually not necessary: +- Type `/meta help` to see instructions on chat commands. + + +## Development + +### Minetest API Notes + +#### Entity Metadata +The magic sauce to get the entire LuaEntitySOA of a pointed_thing was finally discovered by accident (when researching unrelated API feature(s)) after no one would/could answer my question: +```Lua +local pointedObjRef = pointed_thing.ref; +-- . . . some other code is here, then ... +local luaEntity = pointedObjRef:get_luaentity(); +``` + +#### Node metadata +![Node has fields and inventory; there are main and craft inventories, where each is a sequential table where each entry is an itemstack](projects/node_metadata.png) + +``` +Node +| ++-fields +| | +| +-foo +| +-bar +| +-... ++-inventory + | + +-main + | | + | +-1 + | +-2 + | +-3 + | +-... + +-craft + | | + | +-1 + | +-2 + | +-3 + | +-... + +-... +``` diff --git a/textures/metatools_stick.png b/textures/metatools_stick.png index 42302e6..16f7c1f 100644 Binary files a/textures/metatools_stick.png and b/textures/metatools_stick.png differ diff --git a/update local.bat b/update local.bat new file mode 100644 index 0000000..92b946e --- /dev/null +++ b/update local.bat @@ -0,0 +1,6 @@ +SET MT_PROGRAM_DIR=C:\games\Minetest +IF EXIST "C:\Games\ENLIVEN" SET MT_PROGRAM_DIR=C:\Games\ENLIVEN +copy /y *.lua "%MT_PROGRAM_DIR%\games\ENLIVEN\mods\metatools\" +copy /y *.md "%MT_PROGRAM_DIR%\games\ENLIVEN\mods\metatools\" +copy /y textures\*.png "%MT_PROGRAM_DIR%\games\ENLIVEN\mods\metatools\textures\" +if NOT ["%errorlevel%"]==["0"] pause \ No newline at end of file