minetest_cmdtool/init.lua

225 lines
7.8 KiB
Lua

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)