Create mesecons command API and modify commandblock to use it

This commit is contained in:
teknomunk 2024-03-28 05:49:46 +00:00
parent dbaa1c1373
commit e0d3953e75
2 changed files with 242 additions and 195 deletions

View File

@ -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)

View File

@ -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")