diff --git a/mods/ITEMS/REDSTONE/mesecons_commandblock/api.lua b/mods/ITEMS/REDSTONE/mesecons_commandblock/api.lua new file mode 100644 index 000000000..50127a2d2 --- /dev/null +++ b/mods/ITEMS/REDSTONE/mesecons_commandblock/api.lua @@ -0,0 +1,226 @@ +mesecon = mesecon or {} +local mod = {} +mesecon.commandblock = mod + +local S = minetest.get_translator(minetest.get_current_modname()) +local F = minetest.formspec_escape +local color_red = mcl_colors.RED + +mod.initialize = function(meta) + meta:set_string("commands", "") + meta:set_string("commander", "") +end + +mod.place = function(meta, placer) + if not placer then return end + + meta:set_string("commander", placer:get_player_name()) +end + +mod.resolve_commands = function(commands, meta, pos) + local players = minetest.get_connected_players() + local commander = meta:get_string("commander") + + -- A non-printable character used while replacing “@@”. + local SUBSTITUTE_CHARACTER = "\26" -- ASCII SUB + + -- No players online: remove all commands containing + -- problematic placeholders. + if #players == 0 then + commands = commands:gsub("[^\r\n]+", function (line) + line = line:gsub("@@", SUBSTITUTE_CHARACTER) + if line:find("@n") then return "" end + if line:find("@p") then return "" end + if line:find("@f") then return "" end + if line:find("@r") then return "" end + line = line:gsub("@c", commander) + line = line:gsub(SUBSTITUTE_CHARACTER, "@") + return line + end) + return commands + end + + local nearest, farthest = nil, nil + local min_distance, max_distance = math.huge, -1 + for index, player in pairs(players) do + local distance = vector.distance(pos, player:get_pos()) + if distance < min_distance then + min_distance = distance + nearest = player:get_player_name() + end + if distance > max_distance then + max_distance = distance + farthest = player:get_player_name() + end + end + local random = players[math.random(#players)]:get_player_name() + commands = commands:gsub("@@", SUBSTITUTE_CHARACTER) + commands = commands:gsub("@p", nearest) + commands = commands:gsub("@n", nearest) + commands = commands:gsub("@f", farthest) + commands = commands:gsub("@r", random) + commands = commands:gsub("@c", commander) + commands = commands:gsub(SUBSTITUTE_CHARACTER, "@") + return commands +end +local resolve_commands = mod.resolve_commands + +mod.check_commands = function(commands, player_name) + for _, command in pairs(commands:split("\n")) do + local pos = command:find(" ") + local cmd = command + if pos then + cmd = command:sub(1, pos - 1) + end + local cmddef = minetest.chatcommands[cmd] + if not cmddef then + -- Invalid chat command + local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd) + if string.sub(cmd, 1, 1) == "/" then + msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd) + end + return false, minetest.colorize(color_red, msg) + end + if player_name then + local player_privs = minetest.get_player_privs(player_name) + + for cmd_priv, _ in pairs(cmddef.privs) do + if player_privs[cmd_priv] ~= true then + local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv) + return false, minetest.colorize(color_red, msg) + end + end + end + end + return true +end +local check_commands = mod.check_commands + +mod.action_on = function(meta, pos) + local commander = meta:get_string("commander") + local commands = resolve_commands(meta:get_string("commands"), meta, pos) + for _, command in pairs(commands:split("\n")) do + local cpos = command:find(" ") + local cmd, param = command, "" + if cpos then + cmd = command:sub(1, cpos - 1) + param = command:sub(cpos + 1) + end + local cmddef = minetest.chatcommands[cmd] + if not cmddef then + -- Invalid chat command + return + end + -- Execute command in the name of commander + cmddef.func(commander, param) + end +end + +local formspec_metas = {} + +mod.handle_rightclick = function(meta, player) + local can_edit = true + -- Only allow write access in Creative Mode + if not minetest.is_creative_enabled(player:get_player_name()) then + can_edit = false + end + local pname = player:get_player_name() + if minetest.is_protected(pos, pname) then + can_edit = false + end + local privs = minetest.get_player_privs(pname) + if not privs.maphack then + can_edit = false + end + + local commands = meta:get_string("commands") + if not commands then + commands = "" + end + local commander = meta:get_string("commander") + local commanderstr + if commander == "" or commander == nil then + commanderstr = S("Error: No commander! Block must be replaced.") + else + commanderstr = S("Commander: @1", commander) + end + local textarea_name, submit, textarea + -- If editing is not allowed, only allow read-only access. + -- Player can still view the contents of the command block. + if can_edit then + textarea_name = "commands" + submit = "button_exit[3.3,4.4;2,1;submit;"..F(S("Submit")).."]" + else + textarea_name = "" + submit = "" + end + if not can_edit and commands == "" then + textarea = "label[0.5,0.5;"..F(S("No commands.")).."]" + else + textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]" + end + local formspec = "size[9,5;]" .. + textarea .. + submit .. + "image_button[8,4.4;1,1;doc_button_icon_lores.png;doc;]" .. + "tooltip[doc;"..F(S("Help")).."]" .. + "label[0,4;"..F(commanderstr).."]" + + -- Store the metadata object for later use + local fs_id = #formspec_metas + 1 + formspec_metas[fs_id] = meta + print("using fs_id="..tostring(fs_id)..",meta="..tostring(meta)..",formspec_metas[fs_id]="..tostring(formspec_metas[fs_id])) + + minetest.show_formspec(pname, "commandblock_"..tostring(fs_id), formspec) +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if string.sub(formname, 1, 13) == "commandblock_" then + -- Show documentation + if fields.doc and minetest.get_modpath("doc") then + doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true) + return + end + + -- Validate form fields + if (not fields.submit and not fields.key_enter) or (not fields.commands) then + return + end + + -- Check privileges + local privs = minetest.get_player_privs(player:get_player_name()) + if not privs.maphack then + minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks.")) + return + end + + -- Check game mode + if not minetest.is_creative_enabled(player:get_player_name()) then + minetest.chat_send_player(player:get_player_name(), + S("Editing the command block has failed! You can only change the command block in Creative Mode!") + ) + return + end + + -- Retrieve the metadata object this formspec data belongs to + local index, _, fs_id = string.find(formname, "commandblock_(-?%d+)") + fs_id = tonumber(fs_id) + if not index or not fs_id or not formspec_metas[fs_id] then + print("index="..tostring(index)..", fs_id="..tostring(fs_id).."formspec_metas[fs_id]="..tostring(formspec_metas[fs_id])) + minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone.")) + return + end + local meta = formspec_metas[fs_id] + + -- Verify the command + local check, error_message = check_commands(fields.commands, player:get_player_name()) + if check == false then + -- Command block rejected + minetest.chat_send_player(player:get_player_name(), error_message) + return + end + + -- Update the command in the metadata + meta:set_string("commands", fields.commands) + end +end) diff --git a/mods/ITEMS/REDSTONE/mesecons_commandblock/init.lua b/mods/ITEMS/REDSTONE/mesecons_commandblock/init.lua index ec41785df..3c3aa741a 100644 --- a/mods/ITEMS/REDSTONE/mesecons_commandblock/init.lua +++ b/mods/ITEMS/REDSTONE/mesecons_commandblock/init.lua @@ -1,106 +1,27 @@ +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) local S = minetest.get_translator(minetest.get_current_modname()) -local F = minetest.formspec_escape -local tonumber = tonumber - ---mesecon.command_block = {} - -local color_red = mcl_colors.RED +-- Initialize API +dofile(modpath.."/api.lua") +local api = mesecon.commandblock local command_blocks_activated = minetest.settings:get_bool("mcl_enable_commandblocks", true) local msg_not_activated = S("Command blocks are not enabled on this server") local function construct(pos) local meta = minetest.get_meta(pos) - - meta:set_string("commands", "") - meta:set_string("commander", "") + api.initialize(meta) end local function after_place(pos, placer) - if placer then - local meta = minetest.get_meta(pos) - meta:set_string("commander", placer:get_player_name()) - end + local meta = minetest.get_meta(pos) + api.place(meta, placer) end local function resolve_commands(commands, pos) - local players = minetest.get_connected_players() - local meta = minetest.get_meta(pos) - local commander = meta:get_string("commander") - - -- A non-printable character used while replacing “@@”. - local SUBSTITUTE_CHARACTER = "\26" -- ASCII SUB - - -- No players online: remove all commands containing - -- problematic placeholders. - if #players == 0 then - commands = commands:gsub("[^\r\n]+", function (line) - line = line:gsub("@@", SUBSTITUTE_CHARACTER) - if line:find("@n") then return "" end - if line:find("@p") then return "" end - if line:find("@f") then return "" end - if line:find("@r") then return "" end - line = line:gsub("@c", commander) - line = line:gsub(SUBSTITUTE_CHARACTER, "@") - return line - end) - return commands - end - - local nearest, farthest = nil, nil - local min_distance, max_distance = math.huge, -1 - for index, player in pairs(players) do - local distance = vector.distance(pos, player:get_pos()) - if distance < min_distance then - min_distance = distance - nearest = player:get_player_name() - end - if distance > max_distance then - max_distance = distance - farthest = player:get_player_name() - end - end - local random = players[math.random(#players)]:get_player_name() - commands = commands:gsub("@@", SUBSTITUTE_CHARACTER) - commands = commands:gsub("@p", nearest) - commands = commands:gsub("@n", nearest) - commands = commands:gsub("@f", farthest) - commands = commands:gsub("@r", random) - commands = commands:gsub("@c", commander) - commands = commands:gsub(SUBSTITUTE_CHARACTER, "@") - return commands -end - -local function check_commands(commands, player_name) - for _, command in pairs(commands:split("\n")) do - local pos = command:find(" ") - local cmd = command - if pos then - cmd = command:sub(1, pos - 1) - end - local cmddef = minetest.chatcommands[cmd] - if not cmddef then - -- Invalid chat command - local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd) - if string.sub(cmd, 1, 1) == "/" then - msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd) - end - return false, minetest.colorize(color_red, msg) - end - if player_name then - local player_privs = minetest.get_player_privs(player_name) - - for cmd_priv, _ in pairs(cmddef.privs) do - if player_privs[cmd_priv] ~= true then - local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv) - return false, minetest.colorize(color_red, msg) - end - end - end - end - return true + return api.resolve_commands(commands, meta) end local function commandblock_action_on(pos, node) @@ -109,7 +30,6 @@ local function commandblock_action_on(pos, node) end local meta = minetest.get_meta(pos) - local commander = meta:get_string("commander") if not command_blocks_activated then --minetest.chat_send_player(commander, msg_not_activated) @@ -117,22 +37,7 @@ local function commandblock_action_on(pos, node) end minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_on"}) - local commands = resolve_commands(meta:get_string("commands"), pos) - for _, command in pairs(commands:split("\n")) do - local cpos = command:find(" ") - local cmd, param = command, "" - if cpos then - cmd = command:sub(1, cpos - 1) - param = command:sub(cpos + 1) - end - local cmddef = minetest.chatcommands[cmd] - if not cmddef then - -- Invalid chat command - return - end - -- Execute command in the name of commander - cmddef.func(commander, param) - end + api.action_on(meta, pos) end local function commandblock_action_off(pos, node) @@ -146,54 +51,10 @@ local function on_rightclick(pos, node, player, itemstack, pointed_thing) minetest.chat_send_player(player:get_player_name(), msg_not_activated) return end - local can_edit = true - -- Only allow write access in Creative Mode - if not minetest.is_creative_enabled(player:get_player_name()) then - can_edit = false - end - local pname = player:get_player_name() - if minetest.is_protected(pos, pname) then - can_edit = false - end - local privs = minetest.get_player_privs(pname) - if not privs.maphack then - can_edit = false - end local meta = minetest.get_meta(pos) - local commands = meta:get_string("commands") - if not commands then - commands = "" - end - local commander = meta:get_string("commander") - local commanderstr - if commander == "" or commander == nil then - commanderstr = S("Error: No commander! Block must be replaced.") - else - commanderstr = S("Commander: @1", commander) - end - local textarea_name, submit, textarea - -- If editing is not allowed, only allow read-only access. - -- Player can still view the contents of the command block. - if can_edit then - textarea_name = "commands" - submit = "button_exit[3.3,4.4;2,1;submit;"..F(S("Submit")).."]" - else - textarea_name = "" - submit = "" - end - if not can_edit and commands == "" then - textarea = "label[0.5,0.5;"..F(S("No commands.")).."]" - else - textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]" - end - local formspec = "size[9,5;]" .. - textarea .. - submit .. - "image_button[8,4.4;1,1;doc_button_icon_lores.png;doc;]" .. - "tooltip[doc;"..F(S("Help")).."]" .. - "label[0,4;"..F(commanderstr).."]" - minetest.show_formspec(pname, "commandblock_"..pos.x.."_"..pos.y.."_"..pos.z, formspec) + api.handle_rightclick(meta, player) + end local function on_place(itemstack, placer, pointed_thing) @@ -202,12 +63,10 @@ local function on_place(itemstack, placer, pointed_thing) end -- Use pointed node's on_rightclick function first, if present - local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing) - if new_stack then - return new_stack - end - - --local node = minetest.get_node(pointed_thing.under) + local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing) + if new_stack then + return new_stack + end local privs = minetest.get_player_privs(placer:get_player_name()) if not privs.maphack then @@ -282,44 +141,6 @@ minetest.register_node("mesecons_commandblock:commandblock_on", { _mcl_hardness = -1, }) -minetest.register_on_player_receive_fields(function(player, formname, fields) - if string.sub(formname, 1, 13) == "commandblock_" then - if fields.doc and minetest.get_modpath("doc") then - doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true) - return - end - if (not fields.submit and not fields.key_enter) or (not fields.commands) then - return - end - - local privs = minetest.get_player_privs(player:get_player_name()) - if not privs.maphack then - minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks.")) - return - end - - local index, _, x, y, z = string.find(formname, "commandblock_(-?%d+)_(-?%d+)_(-?%d+)") - if index and x and y and z then - local pos = {x = tonumber(x), y = tonumber(y), z = tonumber(z)} - local meta = minetest.get_meta(pos) - if not minetest.is_creative_enabled(player:get_player_name()) then - minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! You can only change the command block in Creative Mode!")) - return - end - local check, error_message = check_commands(fields.commands, player:get_player_name()) - if check == false then - -- Command block rejected - minetest.chat_send_player(player:get_player_name(), error_message) - return - else - meta:set_string("commands", fields.commands) - end - else - minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone.")) - end - end -end) - -- Add entry alias for the Help if minetest.get_modpath("doc") then doc.add_entry_alias("nodes", "mesecons_commandblock:commandblock_off", "nodes", "mesecons_commandblock:commandblock_on")