local S = minetest.get_translator("cmdtool") local F = minetest.formspec_escape local NEWLINE = "\n" local DESCRIPTION = S("Command Tool") local MAX_CMD_TOOLTIP_LEN = 48 local print_result = minetest.settings:get_bool("cmdtool_print_result", true) local split_commands = function(commands_string) return string.split(commands_string, NEWLINE) end local set_commands = function(itemstack, commands_string) local meta = itemstack:get_meta() meta:set_string("cmd", commands_string) local cmds = split_commands(commands_string) local first_cmd if #cmds >= 1 then first_cmd = cmds[1] if string.len(first_cmd) > MAX_CMD_TOOLTIP_LEN then first_cmd = string.sub(first_cmd, 1, MAX_CMD_TOOLTIP_LEN) .. " (…)" end local tooltip = DESCRIPTION .. NEWLINE .. first_cmd if #cmds == 2 then tooltip = tooltip .. NEWLINE .. S("… and 1 more command") elseif #cmds > 2 then tooltip = tooltip .. NEWLINE .. S("… and @1 more command(s)", #cmds - 1) end meta:set_string("description", tooltip) else meta:set_string("description", "") end return itemstack end -- Returns a table of commands in a command tool itemstack local get_commands = function(itemstack) local meta = itemstack:get_meta() local cmd_str = meta:get_string("cmd") local cmds = split_commands(cmd_str) return cmds end --[[ Takes a command and substitutes placeholders like “@playername” with the actual values. This operation might fail. Returns the substituted command on success. Returns false on failure. ]] local substitute_placeholders = function(command, itemstack, player, pointed_thing) local pos_pt, pos_pl pos_pl = player:get_pos() if pointed_thing.type == "node" then pos_pt = pointed_thing.under command = string.gsub(command, "@ptx", pos_pt.x) command = string.gsub(command, "@pty", pos_pt.y) command = string.gsub(command, "@ptz", pos_pt.z) local node = minetest.get_node(pos_pt) command = string.gsub(command, "@nodename", node.name) command = string.gsub(command, "@param2", node.param2) else -- If one of the coordinate parameters is in the command, but -- the pointed thing is not a node, we have to fail as the placeholders -- are meaningless. local coord = {"@ptx", "@pty", "@ptz", "@nodename", "@param2", "@light"} for c=1, #coord do if string.find(command, coord[c]) ~= nil then return false end end end command = string.gsub(command, "@plx", pos_pl.x) command = string.gsub(command, "@ply", pos_pl.y) command = string.gsub(command, "@plz", pos_pl.z) command = string.gsub(command, "@playername", player:get_player_name()) command = string.gsub(command, "@@", "@") return command end local execute_command = function(itemstack, player, pointed_thing) local player_name = player:get_player_name() local cmds = get_commands(itemstack) if not cmds then return end local player_privs = minetest.get_player_privs(player_name) for c=1, #cmds do local cmd = cmds[c] -- Substitution successful? -- Split command string into command name and parameters local cmd_split = string.split(cmd, " ", false, 1) local cmd_name -- Perform some checks: -- 1. Command exists -- 2. Player has all required privileges if cmd_split then -- Get command name cmd_name = cmd_split[1] local cmd_params = "" if cmd_split[2] then cmd_params = cmd_split[2] end cmd_params = substitute_placeholders(cmd_params, itemstack, player, pointed_thing) local def = minetest.registered_chatcommands[cmd_name] -- Command exists? Placeholder substitution successful? if def then if cmd_params then local required_privs = def.privs for priv, _ in pairs(required_privs) do if player_privs[priv] ~= true then minetest.chat_send_player(player_name, minetest.colorize("#FF0000", S("Insufficient privileges for command “@1”! You need the “@2” privilege.", cmd_name, priv))) return end end -- All tests survived! -- Call the command local retval, msg = def.func(player_name, cmd_params) -- Print return value and message if print_result then if retval == true then -- Command successfull minetest.chat_send_player(player_name, minetest.colorize("#00FF00", "["..S("OK").."] ".. cmd)) elseif retval == false then -- Command failed minetest.chat_send_player(player_name, minetest.colorize("#FF0000", "["..S("FAIL").."] ".. cmd)) elseif retval == nil then -- Command result unknown minetest.chat_send_player(player_name, minetest.colorize("#FF8800", "["..S("UNKN").."] ".. cmd)) end end if msg ~= nil and msg ~= "" then local out = msg if print_result then local out = "> " .. msg end minetest.chat_send_player(player_name, out) end else minetest.chat_send_player(player_name, minetest.colorize("#FF0000", S("Nothing pointed!"))) end else minetest.chat_send_player(player_name, minetest.colorize("#FF0000", S("The command “@1” does not exist!", cmd_name))) return end else minetest.chat_send_player(player_name, minetest.colorize("#FF0000", S("Invalid command!"))) return end -- One iteration is done. We continue with the next command. end end local open_command_configuration = function(itemstack, player, pointed_thing) local player_name = player:get_player_name() local commands = get_commands(itemstack) local commands_str = "" if commands then for c=1, #commands do commands_str = commands_str .. commands[c] if c < #commands then commands_str = commands_str .. "\n" end end end local formspec = "size[12,6]".. "textarea[0.25,0.25;12,5;commands;"..F(S("Commands:"))..";"..F(commands_str).."]".. "button_exit[0.5,5;2,1;ok;"..F(S("OK")).."]".. "button_exit[3.5,5;2,1;cancel;"..F(S("Cancel")).."]" minetest.show_formspec(player_name, "cmdtool", formspec) end minetest.register_tool("cmdtool:cmdtool", { description = DESCRIPTION, _doc_items_longdesc = S("This is a programmable tool which can be used to run server commands."), _doc_items_usagehelp = S("This tool is very mighty, so handle with care!").."\n".. S("Use the [Place] key to set the commands. Write a list of server commands you wish to execute (with one command per line), in that order, but without the trailing slash like in the chat. Confirm with the OK button.").."\n".. S("To run the commands, use the attack key. Note that commands might fail if you lack the required privileges or you made a mistake.").."\n\n".. S("Optionally, you can use the following placeholders to insert variable values into your commands:").."\n".. S("• @playername: Your player name").."\n".. S("• @plx, @ply and @plz: Your player coordinates").."\n".. S("• @@: Literal at sign").."\n\n".. S("These placeholders only work when you use the tool on a block.").."\n".. S("• @ptx, @pty and @ptz: Coordinates of the pointed node (i.e. block)").."\n".. S("• @nodename: Itemstring of the pointed node").."\n".. S("• @param2: param2 of the pointed node").."\n\n".. S("Refer to “Advancd usage > Server commands” to learn more about server commands.").."\n\n\n".. S([[Example 1: time 12000 → Sets time to midday.]]).."\n\n".. S([[Example 2: teleport @plx 9 @plz giveme default:apple → Teleports you to Y=9 without changing the X and Z coordinates, then gives you an apple.]]), inventory_image = "cmdtool_cmdtool.png", wield_imagee = "cmdtool_cmdtool.png", groups = { disable_repair = 1 }, on_use = execute_command, on_place = open_command_configuration, on_secondary_use = open_command_configuration, }) -- Set commands minetest.register_on_player_receive_fields(function(player, formname, fields) if formname == "cmdtool" and fields.ok and fields.commands ~= nil then local wield_tool = player:get_wielded_item() if wield_tool:get_name() == "cmdtool:cmdtool" then local updated_tool = set_commands(wield_tool, fields.commands) player:set_wielded_item(updated_tool) end end end)