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 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 -- FIXME: shouldn't this check for param to be empty? def.parse = function(param) return true 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 .. " ", 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/]", 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