1
0
mirror of https://github.com/Uberi/Minetest-WorldEdit.git synced 2025-10-15 23:25:39 +02:00
Files
Minetest-WorldEdit/worldedit_commands/init.lua
2025-09-24 21:29:39 +02:00

408 lines
12 KiB
Lua

local S = minetest.get_translator("worldedit_commands")
minetest.register_privilege("worldedit", S("Can use WorldEdit commands"))
worldedit.pos1 = {}
worldedit.pos2 = {}
local safe_region, reset_pending = dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua")
worldedit.registered_commands = {}
local function copy_state(which, name)
if which == 0 then
return {}
elseif which == 1 then
return {
worldedit.pos1[name] and vector.copy(worldedit.pos1[name])
}
else
return {
worldedit.pos1[name] and vector.copy(worldedit.pos1[name]),
worldedit.pos2[name] and vector.copy(worldedit.pos2[name])
}
end
end
local function compare_state(state, old_state)
for i, v in ipairs(state) do
if not (v == nil and old_state[i] == nil) and not vector.equals(v, old_state[i]) then
return false
end
end
return true
end
local function chatcommand_handler(cmd_name, name, param)
local def = assert(worldedit.registered_commands[cmd_name])
if def.require_pos == 2 then
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, S("no region selected"), "error")
return
end
elseif def.require_pos == 1 then
local pos1 = worldedit.pos1[name]
if pos1 == nil then
worldedit.player_notify(name, S("no position 1 selected"), "error")
return
end
end
param = param:trim()
local parsed = {def.parse(param)}
local success = table.remove(parsed, 1)
if not success then
worldedit.player_notify(name, parsed[1] or S("invalid usage"), "error")
return
end
local run = function()
local ok, msg = def.func(name, unpack(parsed))
if msg then
worldedit.player_notify(name, msg, ok and "ok" or "error")
end
end
if not def.nodes_needed then
-- no safe region check
run()
return
end
local count = def.nodes_needed(name, unpack(parsed))
local old_state = copy_state(def.require_pos, name)
safe_region(name, count, function()
local state = copy_state(def.require_pos, name)
if not compare_state(state, old_state) then
worldedit.player_notify(name, S("ERROR: the operation was cancelled because the region has changed."), "error")
return
end
run()
end)
end
-- Registers a chatcommand for WorldEdit
-- name = "about" -- Name of the chat command (without any /)
-- def = {
-- privs = {}, -- Privileges needed
-- params = "", -- Human readable parameter list (optional)
-- -- if params = "" then a parse() implementation will automatically be provided
-- description = "", -- Description
-- category = "", -- Category of the command (optional)
-- require_pos = 0, -- Number of positions required to be set (optional)
-- parse = function(param)
-- return true, foo, bar, ...
-- -- or
-- return false
-- -- or
-- return false, "error message"
-- end,
-- nodes_needed = function(name, foo, bar, ...), -- (optional)
-- return n
-- end,
-- func = function(name, foo, bar, ...)
-- return success, "message"
-- end,
-- }
function worldedit.register_command(name, def)
local def = table.copy(def)
assert(name and #name > 0)
def.name = name
assert(def.privs)
def.category = def.category or ""
def.require_pos = def.require_pos or 0
assert(def.require_pos >= 0 and def.require_pos < 3)
if def.params == "" and not def.parse then
def.parse = function(param)
return param == ""
end
else
assert(def.parse)
end
assert(def.nodes_needed == nil or type(def.nodes_needed) == "function")
assert(def.func)
-- for development
--[[if def.require_pos == 2 and not def.nodes_needed then
minetest.log("warning", "//" .. name .. " might be missing nodes_needed")
end--]]
-- disable further modification
setmetatable(def, {__newindex = function() end})
minetest.register_chatcommand("/" .. name, {
privs = def.privs,
params = def.params,
description = def.description,
func = function(player_name, param)
return chatcommand_handler(name, player_name, param)
end,
})
worldedit.registered_commands[name] = def
end
-- Notifies a player of something related to WorldEdit.
-- Message types:
-- "error" = An operation did not work as expected.
-- "ok" = An operation completed successfully. Because notifications of this type
-- can be filtered, use this ONLY for generic messages like "1234 nodes set".
-- "info" = Other informational messages
-- @param name Name of player
-- @param message Message text
-- @param typ Type of message (optional but strongly recommend)
function worldedit.player_notify(name, message, typ)
local t = {
"WorldEdit",
"-!-",
tostring(message)
}
if typ == "error" then
t[2] = minetest.colorize("#f22", t[2])
elseif typ == "ok" then
t[2] = minetest.colorize("#2f2", t[2])
end
minetest.chat_send_player(name, table.concat(t, " "))
end
-- Determines the axis in which a player is facing
-- @return axis ("x", "y", or "z") and the sign (1 or -1)
-- @note Not part of API
function worldedit.player_axis(name)
local player = minetest.get_player_by_name(name)
if not player then
-- we promised to return something valid...
return "y", -1
end
local dir = player:get_look_dir()
local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z)
if x > y then
if x > z then
return "x", dir.x > 0 and 1 or -1
end
elseif y > z then
return "y", dir.y > 0 and 1 or -1
end
return "z", dir.z > 0 and 1 or -1
end
-- Look-up table of valid directions (for worldedit.player_direction)
-- Can be stringified for usage in help texts
-- @note Not part of API
worldedit.valid_directions = setmetatable({
x = true, y = true, z = true,
["?"] = true,
up = true, down = true,
front = true, back = true,
left = true, right = true,
}, {
__tostring = function()
return "x/y/z/?/up/down/left/right/front/back"
end
})
-- Accepts a valid directions as above
-- @return axis ("x", "y", or "z") and the sign (1 or -1) *or* nil for invalid combinations
-- @note Not part of API
worldedit.player_direction = function(name, str)
if str == "x" or str == "y" or str == "z" then
return str, 1
elseif str == "up" then
return "y", 1
elseif str == "down" then
return "y", -1
end
local axis, dir = worldedit.player_axis(name)
if str == "?" then
return axis, dir
elseif str == "front" then
if axis ~= "y" then
return axis, dir
end
elseif str == "back" then
if axis ~= "y" then
return axis, -dir
end
elseif str == "left" then
if axis == "x" then
return "z", dir
elseif axis == "z" then
return "x", -dir
end
elseif str == "right" then
if axis == "x" then
return "z", -dir
elseif axis == "z" then
return "x", dir
end
end
return nil, nil
end
-- Wrapper for the engine"s parse_coordinates
-- @return vector or nil
-- @note Not part of API
function worldedit.parse_coordinates(x, y, z, player_name)
local relpos
local player = minetest.get_player_by_name(player_name or "")
if player then
relpos = player:get_pos()
end
-- we don't bother to support ~ in the fallback path here
if not minetest.parse_coordinates then
x, y, z = tonumber(x), tonumber(y), tonumber(z)
return x and y and z and vector.new(x, y, z)
end
return minetest.parse_coordinates(x, y, z, relpos)
end
worldedit.register_command("about", {
privs = {},
params = "",
description = S("Get information about the WorldEdit mod"),
func = function(name)
worldedit.player_notify(name, S("WorldEdit @1"..
" is available on this server. Type @2 to get a list of "..
"commands, or find more information at @3",
worldedit.version_string, minetest.colorize("#0ff", "//help"),
"https://github.com/Uberi/Minetest-WorldEdit"
), "info")
end,
})
-- initially copied from builtin/chatcommands.lua
local function help_command(name, param)
local function format_help_line(cmd, def, follow_alias)
local msg = minetest.colorize("#0ff", "//"..cmd)
if def.name ~= cmd then
msg = msg .. ": " .. S("alias to @1",
minetest.colorize("#0ff", "//"..def.name))
if follow_alias then
msg = msg .. "\n" .. format_help_line(def.name, def)
end
else
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
end
if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
end
return msg
end
-- @param cmds list of {cmd, def}
local function sort_cmds(cmds)
table.sort(cmds, function(c1, c2)
local cmd1, cmd2 = c1[1], c2[1]
local def1, def2 = c1[2], c2[2]
-- by category (this puts the empty category first)
if def1.category ~= def2.category then
return def1.category < def2.category
end
-- put aliases last
if (cmd1 ~= def1.name) ~= (cmd2 ~= def2.name) then
return cmd2 ~= def2.name
end
-- then by name
return c1[1] < c2[1]
end)
end
if not minetest.check_player_privs(name, "worldedit") then
return false, S("You are not allowed to use any WorldEdit commands.")
end
if param == "" then
local list = {}
for cmd, def in pairs(worldedit.registered_commands) do
if minetest.check_player_privs(name, def.privs) then
list[#list + 1] = cmd
end
end
table.sort(list)
local help = minetest.colorize("#0ff", "//help")
return true, S("Available commands: @1@n"
.. "Use '@2' to get more information,"
.. " or '@3' to list everything.",
table.concat(list, " "), help .. " <cmd>", help .. " all")
elseif param == "all" then
local cmds = {}
for cmd, def in pairs(worldedit.registered_commands) do
if minetest.check_player_privs(name, def.privs) then
cmds[#cmds + 1] = {cmd, def}
end
end
sort_cmds(cmds)
local list = {}
local last_cat = ""
for _, e in ipairs(cmds) do
if e[2].category ~= last_cat then
last_cat = e[2].category
list[#list + 1] = "---- " .. last_cat
end
list[#list + 1] = format_help_line(e[1], e[2])
end
return true, S("Available commands:@n") .. table.concat(list, "\n")
else
local def = worldedit.registered_commands[param]
if not def then
return false, S("Command not available: ") .. param
else
return true, format_help_line(param, def, true)
end
end
end
worldedit.register_command("help", {
privs = {},
params = "[all/<cmd>]",
description = S("Get help for WorldEdit commands"),
parse = function(param)
return true, param
end,
func = function(name, param)
local ok, msg = help_command(name, param)
if msg then
worldedit.player_notify(name, msg, ok and "info" or "error")
end
end,
})
-- needs to be here due to reset_pending()
worldedit.register_command("reset", {
params = "",
description = S("Reset the region so that it is empty"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name)
worldedit.pos1[name] = nil
worldedit.pos2[name] = nil
worldedit.marker_update(name)
worldedit.set_pos[name] = nil
--make sure the user does not try to confirm an operation after resetting pos:
reset_pending(name)
return true, S("region reset")
end,
})
-- Load the other parts
do
local modpath = minetest.get_modpath("worldedit_commands")
for _, name in ipairs({
"code", "cuboid", "manipulations", "marker", "nodename", "primitives",
"region", "schematics", "transform", "wand"
}) do
dofile(modpath .. "/" .. name .. ".lua")
end
if worldedit.register_test then
dofile(modpath .. "/test/init.lua")
end
end