1 minetest.register_privilege("worldedit", "Can use WorldEdit commands")
\r
6 worldedit.set_pos = {}
\r
7 worldedit.inspect = {}
\r
8 worldedit.prob_pos = {}
\r
9 worldedit.prob_list = {}
\r
13 local safe_region, reset_pending = dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua")
\r
15 function worldedit.player_notify(name, message)
\r
16 minetest.chat_send_player(name, "WorldEdit -!- " .. message, false)
\r
19 worldedit.registered_commands = {}
\r
21 local function chatcommand_handler(cmd_name, name, param)
\r
22 local def = assert(worldedit.registered_commands[cmd_name])
\r
24 if def.require_pos == 2 then
\r
25 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
26 if pos1 == nil or pos2 == nil then
\r
27 worldedit.player_notify(name, "no region selected")
\r
30 elseif def.require_pos == 1 then
\r
31 local pos1 = worldedit.pos1[name]
\r
33 worldedit.player_notify(name, "no position 1 selected")
\r
38 local parsed = {def.parse(param)}
\r
39 local success = table.remove(parsed, 1)
\r
41 worldedit.player_notify(name, parsed[1] or "invalid usage")
\r
45 if def.nodes_needed then
\r
46 local count = def.nodes_needed(name, unpack(parsed))
\r
47 safe_region(name, count, function()
\r
48 local success, msg = def.func(name, unpack(parsed))
\r
50 minetest.chat_send_player(name, msg)
\r
54 -- no "safe region" check
\r
55 local success, msg = def.func(name, unpack(parsed))
\r
57 minetest.chat_send_player(name, msg)
\r
62 -- Registers a chatcommand for WorldEdit
\r
63 -- name = "about" -- Name of the chat command (without any /)
\r
65 -- privs = {}, -- Privileges needed
\r
66 -- params = "", -- Human readable parameter list (optional)
\r
67 -- -- setting params = "" will automatically provide a parse() if not given
\r
68 -- description = "", -- Description
\r
69 -- require_pos = 0, -- Number of positions required to be set (optional)
\r
70 -- parse = function(param)
\r
71 -- return true, foo, bar, ...
\r
75 -- return false, "error message"
\r
77 -- nodes_needed = function(name, foo, bar, ...), -- (optional)
\r
80 -- func = function(name, foo, bar, ...)
\r
81 -- return success, "message"
\r
84 function worldedit.register_command(name, def)
\r
85 local def = table.copy(def)
\r
86 assert(name and #name > 0)
\r
88 def.require_pos = def.require_pos or 0
\r
89 assert(def.require_pos >= 0 and def.require_pos < 3)
\r
90 if def.params == "" and not def.parse then
\r
91 def.parse = function(param) return true end
\r
95 assert(def.nodes_needed == nil or type(def.nodes_needed) == "function")
\r
99 --[[if def.require_pos == 2 and not def.nodes_needed then
\r
100 minetest.log("warning", "//" .. name .. " might be missing nodes_needed")
\r
103 minetest.register_chatcommand("/" .. name, {
\r
105 params = def.params,
\r
106 description = def.description,
\r
107 func = function(player_name, param)
\r
108 return chatcommand_handler(name, player_name, param)
\r
111 worldedit.registered_commands[name] = def
\r
116 dofile(minetest.get_modpath("worldedit_commands") .. "/cuboid.lua")
\r
117 dofile(minetest.get_modpath("worldedit_commands") .. "/mark.lua")
\r
118 dofile(minetest.get_modpath("worldedit_commands") .. "/wand.lua")
\r
121 local function check_region(name)
\r
122 return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
\r
125 -- Strips any kind of escape codes (translation, colors) from a string
\r
126 -- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777
\r
127 local function strip_escapes(input)
\r
128 local s = function(idx) return input:sub(idx, idx) end
\r
131 while i <= #input do
\r
132 if s(i) == "\027" then -- escape sequence
\r
134 if s(i) == "(" then -- enclosed
\r
136 while i <= #input and s(i) ~= ")" do
\r
137 if s(i) == "\\" then
\r
149 --print(("%q -> %q"):format(input, out))
\r
153 local function string_endswith(full, part)
\r
154 return full:find(part, 1, true) == #full - #part + 1
\r
157 -- normalizes node "description" `nodename`, returning a string (or nil)
\r
158 worldedit.normalize_nodename = function(nodename)
\r
159 nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces
\r
160 if nodename == "" then return nil end
\r
162 local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
\r
163 if minetest.registered_nodes[fullname] or fullname == "air" then -- full name
\r
166 nodename = nodename:lower()
\r
167 for key, value in pairs(minetest.registered_nodes) do
\r
168 if string_endswith(key, ":" .. nodename) then -- matches name (w/o mod part)
\r
172 for key, value in pairs(minetest.registered_nodes) do
\r
173 local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower()
\r
174 if desc == nodename then -- matches description
\r
177 if desc == nodename .. " block" then
\r
178 -- fuzzy description match (e.g. "Steel" == "Steel Block")
\r
184 for key, value in pairs(minetest.registered_nodes) do
\r
185 if value.description:lower():find(nodename, 1, true) ~= nil then
\r
186 if match ~= nil then
\r
189 match = key -- substring description match (only if no ambiguities)
\r
195 -- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1)
\r
196 function worldedit.player_axis(name)
\r
197 local dir = minetest.get_player_by_name(name):get_look_dir()
\r
198 local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z)
\r
201 return "x", dir.x > 0 and 1 or -1
\r
204 return "y", dir.y > 0 and 1 or -1
\r
206 return "z", dir.z > 0 and 1 or -1
\r
209 local function check_filename(name)
\r
210 return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil
\r
214 worldedit.register_command("about", {
\r
217 description = "Get information about the WorldEdit mod",
\r
218 func = function(name)
\r
219 worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string..
\r
220 " is available on this server. Type //help to get a list of "..
\r
221 "commands, or get more information at "..
\r
222 "https://github.com/Uberi/Minetest-WorldEdit")
\r
226 -- mostly copied from builtin/chatcommands.lua with minor modifications
\r
227 worldedit.register_command("help", {
\r
229 params = "[all/<cmd>]",
\r
230 description = "Get help for WorldEdit commands",
\r
231 parse = function(param)
\r
234 func = function(name, param)
\r
235 local function format_help_line(cmd, def)
\r
236 local msg = minetest.colorize("#00ffff", "//"..cmd)
\r
237 if def.params and def.params ~= "" then
\r
238 msg = msg .. " " .. def.params
\r
240 if def.description and def.description ~= "" then
\r
241 msg = msg .. ": " .. def.description
\r
246 if not minetest.check_player_privs(name, "worldedit") then
\r
247 return false, "You are not allowed to use any WorldEdit commands."
\r
249 if param == "" then
\r
252 for cmd, def in pairs(worldedit.registered_commands) do
\r
253 if minetest.check_player_privs(name, def.privs) then
\r
254 cmds[#cmds + 1] = cmd
\r
258 return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"
\r
259 .. "Use '//help <cmd>' to get more information,"
\r
260 .. " or '//help all' to list everything."
\r
261 elseif param == "all" then
\r
263 for cmd, def in pairs(worldedit.registered_commands) do
\r
264 if minetest.check_player_privs(name, def.privs) then
\r
265 cmds[#cmds + 1] = format_help_line(cmd, def)
\r
269 return true, "Available commands:\n"..table.concat(cmds, "\n")
\r
271 local def = worldedit.registered_commands[param]
\r
273 return false, "Command not available: " .. param
\r
275 return true, format_help_line(param, def)
\r
281 worldedit.register_command("inspect", {
\r
282 params = "[on/off/1/0/true/false/yes/no/enable/disable]",
\r
283 description = "Enable or disable node inspection",
\r
284 privs = {worldedit=true},
\r
285 parse = function(param)
\r
286 if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
\r
288 elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
\r
293 func = function(name, enable)
\r
295 worldedit.inspect[name] = true
\r
296 local axis, sign = worldedit.player_axis(name)
\r
297 worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis",
\r
298 name, axis .. (sign > 0 and "+" or "-")))
\r
300 worldedit.inspect[name] = nil
\r
301 worldedit.player_notify(name, "inspector: inspection disabled")
\r
306 local function get_node_rlight(pos)
\r
307 local vecs = { -- neighboring nodes
\r
308 {x= 1, y= 0, z= 0},
\r
309 {x=-1, y= 0, z= 0},
\r
310 {x= 0, y= 1, z= 0},
\r
311 {x= 0, y=-1, z= 0},
\r
312 {x= 0, y= 0, z= 1},
\r
313 {x= 0, y= 0, z=-1},
\r
316 for _, v in ipairs(vecs) do
\r
317 ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
\r
322 minetest.register_on_punchnode(function(pos, node, puncher)
\r
323 local name = puncher:get_player_name()
\r
324 if worldedit.inspect[name] then
\r
325 local axis, sign = worldedit.player_axis(name)
\r
326 local message = string.format("inspector: %s at %s (param1=%d, param2=%d, received light=%d) punched facing the %s axis",
\r
327 node.name, minetest.pos_to_string(pos), node.param1, node.param2, get_node_rlight(pos), axis .. (sign > 0 and "+" or "-"))
\r
328 worldedit.player_notify(name, message)
\r
332 worldedit.register_command("reset", {
\r
334 description = "Reset the region so that it is empty",
\r
335 privs = {worldedit=true},
\r
336 func = function(name)
\r
337 worldedit.pos1[name] = nil
\r
338 worldedit.pos2[name] = nil
\r
339 worldedit.marker_update(name)
\r
340 worldedit.set_pos[name] = nil
\r
341 --make sure the user does not try to confirm an operation after resetting pos:
\r
342 reset_pending(name)
\r
343 worldedit.player_notify(name, "region reset")
\r
347 worldedit.register_command("mark", {
\r
349 description = "Show markers at the region positions",
\r
350 privs = {worldedit=true},
\r
351 func = function(name)
\r
352 worldedit.marker_update(name)
\r
353 worldedit.player_notify(name, "region marked")
\r
357 worldedit.register_command("unmark", {
\r
359 description = "Hide markers if currently shown",
\r
360 privs = {worldedit=true},
\r
361 func = function(name)
\r
362 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
363 worldedit.pos1[name] = nil
\r
364 worldedit.pos2[name] = nil
\r
365 worldedit.marker_update(name)
\r
366 worldedit.pos1[name] = pos1
\r
367 worldedit.pos2[name] = pos2
\r
368 worldedit.player_notify(name, "region unmarked")
\r
372 worldedit.register_command("pos1", {
\r
374 description = "Set WorldEdit region position 1 to the player's location",
\r
375 privs = {worldedit=true},
\r
376 func = function(name)
\r
377 local pos = minetest.get_player_by_name(name):get_pos()
\r
378 pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5)
\r
379 worldedit.pos1[name] = pos
\r
380 worldedit.mark_pos1(name)
\r
381 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
385 worldedit.register_command("pos2", {
\r
387 description = "Set WorldEdit region position 2 to the player's location",
\r
388 privs = {worldedit=true},
\r
389 func = function(name)
\r
390 local pos = minetest.get_player_by_name(name):get_pos()
\r
391 pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5)
\r
392 worldedit.pos2[name] = pos
\r
393 worldedit.mark_pos2(name)
\r
394 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
398 worldedit.register_command("p", {
\r
399 params = "set/set1/set2/get",
\r
400 description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region",
\r
401 privs = {worldedit=true},
\r
402 parse = function(param)
\r
403 if param == "set" or param == "set1" or param == "set2" or param == "get" then
\r
406 return false, "unknown subcommand: " .. param
\r
408 func = function(name, param)
\r
409 if param == "set" then --set both WorldEdit positions
\r
410 worldedit.set_pos[name] = "pos1"
\r
411 worldedit.player_notify(name, "select positions by punching two nodes")
\r
412 elseif param == "set1" then --set WorldEdit position 1
\r
413 worldedit.set_pos[name] = "pos1only"
\r
414 worldedit.player_notify(name, "select position 1 by punching a node")
\r
415 elseif param == "set2" then --set WorldEdit position 2
\r
416 worldedit.set_pos[name] = "pos2"
\r
417 worldedit.player_notify(name, "select position 2 by punching a node")
\r
418 elseif param == "get" then --display current WorldEdit positions
\r
419 if worldedit.pos1[name] ~= nil then
\r
420 worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name]))
\r
422 worldedit.player_notify(name, "position 1 not set")
\r
424 if worldedit.pos2[name] ~= nil then
\r
425 worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name]))
\r
427 worldedit.player_notify(name, "position 2 not set")
\r
433 worldedit.register_command("fixedpos", {
\r
434 params = "set1/set2 <x> <y> <z>",
\r
435 description = "Set a WorldEdit region position to the position at (<x>, <y>, <z>)",
\r
436 privs = {worldedit=true},
\r
437 parse = function(param)
\r
438 local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
\r
439 if found == nil then
\r
442 return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
444 func = function(name, flag, pos)
\r
445 if flag == "set1" then
\r
446 worldedit.pos1[name] = pos
\r
447 worldedit.mark_pos1(name)
\r
448 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
449 else --flag == "set2"
\r
450 worldedit.pos2[name] = pos
\r
451 worldedit.mark_pos2(name)
\r
452 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
457 minetest.register_on_punchnode(function(pos, node, puncher)
\r
458 local name = puncher:get_player_name()
\r
459 if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
\r
460 if worldedit.set_pos[name] == "pos1" then --setting position 1
\r
461 worldedit.pos1[name] = pos
\r
462 worldedit.mark_pos1(name)
\r
463 worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
\r
464 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
465 elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
\r
466 worldedit.pos1[name] = pos
\r
467 worldedit.mark_pos1(name)
\r
468 worldedit.set_pos[name] = nil --finished setting positions
\r
469 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
470 elseif worldedit.set_pos[name] == "pos2" then --setting position 2
\r
471 worldedit.pos2[name] = pos
\r
472 worldedit.mark_pos2(name)
\r
473 worldedit.set_pos[name] = nil --finished setting positions
\r
474 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
475 elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
\r
476 worldedit.prob_pos[name] = pos
\r
477 minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]")
\r
482 worldedit.register_command("volume", {
\r
484 description = "Display the volume of the current WorldEdit region",
\r
485 privs = {worldedit=true},
\r
487 func = function(name)
\r
488 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
490 local volume = worldedit.volume(pos1, pos2)
\r
491 local abs = math.abs
\r
492 worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes ("
\r
493 .. abs(pos2.x - pos1.x) + 1 .. "*"
\r
494 .. abs(pos2.y - pos1.y) + 1 .. "*"
\r
495 .. abs(pos2.z - pos1.z) + 1 .. ")")
\r
499 worldedit.register_command("deleteblocks", {
\r
501 description = "remove all MapBlocks (16x16x16) containing the selected area from the map",
\r
502 privs = {worldedit=true},
\r
504 nodes_needed = check_region,
\r
505 func = function(name)
\r
506 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
507 local success = minetest.delete_area(pos1, pos2)
\r
509 worldedit.player_notify(name, "Area deleted.")
\r
511 worldedit.player_notify(name, "There was an error during deletion of the area.")
\r
516 worldedit.register_command("set", {
\r
518 description = "Set the current WorldEdit region to <node>",
\r
519 privs = {worldedit=true},
\r
521 parse = function(param)
\r
522 local node = worldedit.normalize_nodename(param)
\r
524 return false, "invalid node name: " .. param
\r
528 nodes_needed = check_region,
\r
529 func = function(name, node)
\r
530 local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
\r
531 worldedit.player_notify(name, count .. " nodes set")
\r
535 worldedit.register_command("param2", {
\r
536 params = "<param2>",
\r
537 description = "Set param2 of all nodes in the current WorldEdit region to <param2>",
\r
538 privs = {worldedit=true},
\r
540 parse = function(param)
\r
541 local param2 = tonumber(param)
\r
544 elseif param2 < 0 or param2 > 255 then
\r
545 return false, "Param2 is out of range (must be between 0 and 255 inclusive!)"
\r
547 return true, param2
\r
549 nodes_needed = check_region,
\r
550 func = function(name, param2)
\r
551 local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
\r
552 worldedit.player_notify(name, count .. " nodes altered")
\r
556 worldedit.register_command("mix", {
\r
557 params = "<node1> [count1] <node2> [count2] ...",
\r
558 description = "Fill the current WorldEdit region with a random mix of <node1>, ...",
\r
559 privs = {worldedit=true},
\r
561 parse = function(param)
\r
563 for nodename in param:gmatch("[^%s]+") do
\r
564 if tonumber(nodename) ~= nil and #nodes > 0 then
\r
565 local last_node = nodes[#nodes]
\r
566 for i = 1, tonumber(nodename) do
\r
567 nodes[#nodes + 1] = last_node
\r
570 local node = worldedit.normalize_nodename(nodename)
\r
572 return false, "invalid node name: " .. nodename
\r
574 nodes[#nodes + 1] = node
\r
577 if #nodes == 0 then
\r
582 nodes_needed = check_region,
\r
583 func = function(name, nodes)
\r
584 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
585 local count = worldedit.set(pos1, pos2, nodes)
\r
586 worldedit.player_notify(name, count .. " nodes set")
\r
590 local check_replace = function(param)
\r
591 local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
\r
592 if found == nil then
\r
595 local newsearchnode = worldedit.normalize_nodename(searchnode)
\r
596 if not newsearchnode then
\r
597 return false, "invalid search node name: " .. searchnode
\r
599 local newreplacenode = worldedit.normalize_nodename(replacenode)
\r
600 if not newreplacenode then
\r
601 return false, "invalid replace node name: " .. replacenode
\r
603 return true, newsearchnode, newreplacenode
\r
606 worldedit.register_command("replace", {
\r
607 params = "<search node> <replace node>",
\r
608 description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region",
\r
609 privs = {worldedit=true},
\r
611 parse = check_replace,
\r
612 nodes_needed = check_region,
\r
613 func = function(name, search_node, replace_node)
\r
614 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
615 search_node, replace_node)
\r
616 worldedit.player_notify(name, count .. " nodes replaced")
\r
620 worldedit.register_command("replaceinverse", {
\r
621 params = "<search node> <replace node>",
\r
622 description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region",
\r
623 privs = {worldedit=true},
\r
625 parse = check_replace,
\r
626 nodes_needed = check_region,
\r
627 func = function(name, search_node, replace_node)
\r
628 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
629 search_node, replace_node, true)
\r
630 worldedit.player_notify(name, count .. " nodes replaced")
\r
634 local check_cube = function(param)
\r
635 local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
636 if found == nil then
\r
639 local node = worldedit.normalize_nodename(nodename)
\r
641 return false, "invalid node name: " .. nodename
\r
643 return true, tonumber(w), tonumber(h), tonumber(l), node
\r
646 worldedit.register_command("hollowcube", {
\r
647 params = "<width> <height> <length> <node>",
\r
648 description = "Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.",
\r
649 privs = {worldedit=true},
\r
651 parse = check_cube,
\r
652 nodes_needed = function(name, w, h, l, node)
\r
655 func = function(name, w, h, l, node)
\r
656 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
\r
657 worldedit.player_notify(name, count .. " nodes added")
\r
661 worldedit.register_command("cube", {
\r
662 params = "<width> <height> <length> <node>",
\r
663 description = "Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.",
\r
664 privs = {worldedit=true},
\r
666 parse = check_cube,
\r
667 nodes_needed = function(name, w, h, l, node)
\r
670 func = function(name, w, h, l, node)
\r
671 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
\r
672 worldedit.player_notify(name, count .. " nodes added")
\r
676 local check_sphere = function(param)
\r
677 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
678 if found == nil then
\r
681 local node = worldedit.normalize_nodename(nodename)
\r
683 return false, "invalid node name: " .. nodename
\r
685 return true, tonumber(radius), node
\r
688 worldedit.register_command("hollowsphere", {
\r
689 params = "<radius> <node>",
\r
690 description = "Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
691 privs = {worldedit=true},
\r
693 parse = check_sphere,
\r
694 nodes_needed = function(name, radius, node)
\r
695 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
697 func = function(name, radius, node)
\r
698 local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
\r
699 worldedit.player_notify(name, count .. " nodes added")
\r
703 worldedit.register_command("sphere", {
\r
704 params = "<radius> <node>",
\r
705 description = "Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
706 privs = {worldedit=true},
\r
708 parse = check_sphere,
\r
709 nodes_needed = function(name, radius, node)
\r
710 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
712 func = function(name, radius, node)
\r
713 local count = worldedit.sphere(worldedit.pos1[name], radius, node)
\r
714 worldedit.player_notify(name, count .. " nodes added")
\r
718 local check_dome = function(param)
\r
719 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
720 if found == nil then
\r
723 local node = worldedit.normalize_nodename(nodename)
\r
725 return false, "invalid node name: " .. nodename
\r
727 return true, tonumber(radius), node
\r
730 worldedit.register_command("hollowdome", {
\r
731 params = "<radius> <node>",
\r
732 description = "Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
733 privs = {worldedit=true},
\r
735 parse = check_dome,
\r
736 nodes_needed = function(name, radius, node)
\r
737 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
739 func = function(name, radius, node)
\r
740 local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
\r
741 worldedit.player_notify(name, count .. " nodes added")
\r
745 worldedit.register_command("dome", {
\r
746 params = "<radius> <node>",
\r
747 description = "Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
748 privs = {worldedit=true},
\r
750 parse = check_dome,
\r
751 nodes_needed = function(name, radius, node)
\r
752 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
754 func = function(name, radius, node)
\r
755 local count = worldedit.dome(worldedit.pos1[name], radius, node)
\r
756 worldedit.player_notify(name, count .. " nodes added")
\r
760 local check_cylinder = function(param)
\r
762 local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
763 if found == nil then
\r
765 found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
\r
768 if found == nil then
\r
771 local node = worldedit.normalize_nodename(nodename)
\r
773 return false, "invalid node name: " .. nodename
\r
775 return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
\r
778 worldedit.register_command("hollowcylinder", {
\r
779 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
780 description = "Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>",
\r
781 privs = {worldedit=true},
\r
783 parse = check_cylinder,
\r
784 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
785 local radius = math.max(radius1, radius2)
\r
786 return math.ceil(math.pi * (radius ^ 2) * length)
\r
788 func = function(name, axis, length, radius1, radius2, node)
\r
789 if axis == "?" then
\r
791 axis, sign = worldedit.player_axis(name)
\r
792 length = length * sign
\r
794 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
\r
795 worldedit.player_notify(name, count .. " nodes added")
\r
799 worldedit.register_command("cylinder", {
\r
800 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
801 description = "Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>",
\r
802 privs = {worldedit=true},
\r
804 parse = check_cylinder,
\r
805 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
806 local radius = math.max(radius1, radius2)
\r
807 return math.ceil(math.pi * (radius ^ 2) * length)
\r
809 func = function(name, axis, length, radius1, radius2, node)
\r
810 if axis == "?" then
\r
812 axis, sign = worldedit.player_axis(name)
\r
813 length = length * sign
\r
815 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
\r
816 worldedit.player_notify(name, count .. " nodes added")
\r
820 local check_pyramid = function(param)
\r
821 local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
\r
822 if found == nil then
\r
825 local node = worldedit.normalize_nodename(nodename)
\r
827 return false, "invalid node name: " .. nodename
\r
829 return true, axis, tonumber(height), node
\r
832 worldedit.register_command("hollowpyramid", {
\r
833 params = "x/y/z/? <height> <node>",
\r
834 description = "Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>",
\r
835 privs = {worldedit=true},
\r
837 parse = check_pyramid,
\r
838 nodes_needed = function(name, axis, height, node)
\r
839 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
841 func = function(name, axis, height, node)
\r
842 if axis == "?" then
\r
844 axis, sign = worldedit.player_axis(name)
\r
845 height = height * sign
\r
847 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
\r
848 worldedit.player_notify(name, count .. " nodes added")
\r
852 worldedit.register_command("pyramid", {
\r
853 params = "x/y/z/? <height> <node>",
\r
854 description = "Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>",
\r
855 privs = {worldedit=true},
\r
857 parse = check_pyramid,
\r
858 nodes_needed = function(name, axis, height, node)
\r
859 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
861 func = function(name, axis, height, node)
\r
862 if axis == "?" then
\r
864 axis, sign = worldedit.player_axis(name)
\r
865 height = height * sign
\r
867 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
\r
868 worldedit.player_notify(name, count .. " nodes added")
\r
872 worldedit.register_command("spiral", {
\r
873 params = "<length> <height> <space> <node>",
\r
874 description = "Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>",
\r
875 privs = {worldedit=true},
\r
877 parse = function(param)
\r
878 local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
879 if found == nil then
\r
882 local node = worldedit.normalize_nodename(nodename)
\r
884 return false, "invalid node name: " .. nodename
\r
886 return true, tonumber(length), tonumber(height), tonumber(space), node
\r
888 nodes_needed = function(name, length, height, space, node)
\r
889 return (length + space) * height -- TODO: this is not the upper bound
\r
891 func = function(name, length, height, space, node)
\r
892 local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
\r
893 worldedit.player_notify(name, count .. " nodes added")
\r
897 worldedit.register_command("copy", {
\r
898 params = "x/y/z/? <amount>",
\r
899 description = "Copy the current WorldEdit region along the given axis by <amount> nodes",
\r
900 privs = {worldedit=true},
\r
902 parse = function(param)
\r
903 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
904 if found == nil then
\r
907 return true, axis, tonumber(amount)
\r
909 nodes_needed = function(name, axis, amount)
\r
910 return check_region(name) * 2
\r
912 func = function(name, axis, amount)
\r
913 if axis == "?" then
\r
915 axis, sign = worldedit.player_axis(name)
\r
916 amount = amount * sign
\r
919 local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
\r
920 worldedit.player_notify(name, count .. " nodes copied")
\r
924 worldedit.register_command("move", {
\r
925 params = "x/y/z/? <amount>",
\r
926 description = "Move the current WorldEdit region along the given axis by <amount> nodes",
\r
927 privs = {worldedit=true},
\r
929 parse = function(param)
\r
930 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
931 if found == nil then
\r
934 return true, axis, tonumber(amount)
\r
936 nodes_needed = function(name, axis, amount)
\r
937 return check_region(name) * 2
\r
939 func = function(name, axis, amount)
\r
940 if axis == "?" then
\r
942 axis, sign = worldedit.player_axis(name)
\r
943 amount = amount * sign
\r
946 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
947 local count = worldedit.move(pos1, pos2, axis, amount)
\r
949 pos1[axis] = pos1[axis] + amount
\r
950 pos2[axis] = pos2[axis] + amount
\r
951 worldedit.marker_update(name)
\r
952 worldedit.player_notify(name, count .. " nodes moved")
\r
956 worldedit.register_command("stack", {
\r
957 params = "x/y/z/? <count>",
\r
958 description = "Stack the current WorldEdit region along the given axis <count> times",
\r
959 privs = {worldedit=true},
\r
961 parse = function(param)
\r
962 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
963 if found == nil then
\r
966 return true, axis, tonumber(repetitions)
\r
968 nodes_needed = function(name, axis, repetitions)
\r
969 return check_region(name) * math.abs(repetitions)
\r
971 func = function(name, axis, repetitions)
\r
972 if axis == "?" then
\r
974 axis, sign = worldedit.player_axis(name)
\r
975 repetitions = repetitions * sign
\r
978 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
979 local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
\r
980 worldedit.stack(pos1, pos2, axis, repetitions, function()
\r
981 worldedit.player_notify(name, count .. " nodes stacked")
\r
986 worldedit.register_command("stack2", {
\r
987 params = "<count> <x> <y> <z>",
\r
988 description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",
\r
989 privs = {worldedit=true},
\r
991 parse = function(param)
\r
992 local repetitions, incs = param:match("(%d+)%s*(.+)")
\r
993 if repetitions == nil then
\r
994 return false, "invalid count: " .. param
\r
996 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
\r
998 return false, "invalid increments: " .. param
\r
1001 return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
1003 nodes_needed = function(name, repetitions, offset)
\r
1004 return check_region(name) * repetitions
\r
1006 func = function(name, repetitions, offset)
\r
1007 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1008 local count = worldedit.volume(pos1, pos2) * repetitions
\r
1009 worldedit.stack2(pos1, pos2, offset, repetitions, function()
\r
1010 worldedit.player_notify(name, count .. " nodes stacked")
\r
1016 worldedit.register_command("stretch", {
\r
1017 params = "<stretchx> <stretchy> <stretchz>",
\r
1018 description = "Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin",
\r
1019 privs = {worldedit=true},
\r
1021 parse = function(param)
\r
1022 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
\r
1023 if found == nil then
\r
1026 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
\r
1027 if stretchx == 0 or stretchy == 0 or stretchz == 0 then
\r
1028 return false, "invalid scaling factors: " .. param
\r
1030 return true, stretchx, stretchy, stretchz
\r
1032 nodes_needed = function(name, stretchx, stretchy, stretchz)
\r
1033 return check_region(name) * stretchx * stretchy * stretchz
\r
1035 func = function(name, stretchx, stretchy, stretchz)
\r
1036 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1037 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
\r
1039 --reset markers to scaled positions
\r
1040 worldedit.pos1[name] = pos1
\r
1041 worldedit.pos2[name] = pos2
\r
1042 worldedit.marker_update(name)
\r
1044 worldedit.player_notify(name, count .. " nodes stretched")
\r
1048 worldedit.register_command("transpose", {
\r
1049 params = "x/y/z/? x/y/z/?",
\r
1050 description = "Transpose the current WorldEdit region along the given axes",
\r
1051 privs = {worldedit=true},
\r
1053 parse = function(param)
\r
1054 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
\r
1055 if found == nil then
\r
1057 elseif axis1 == axis2 then
\r
1058 return false, "invalid usage: axes must be different"
\r
1060 return true, axis1, axis2
\r
1062 nodes_needed = check_region,
\r
1063 func = function(name, axis1, axis2)
\r
1064 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1065 if axis1 == "?" then axis1 = worldedit.player_axis(name) end
\r
1066 if axis2 == "?" then axis2 = worldedit.player_axis(name) end
\r
1067 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
1069 --reset markers to transposed positions
\r
1070 worldedit.pos1[name] = pos1
\r
1071 worldedit.pos2[name] = pos2
\r
1072 worldedit.marker_update(name)
\r
1074 worldedit.player_notify(name, count .. " nodes transposed")
\r
1078 worldedit.register_command("flip", {
\r
1079 params = "x/y/z/?",
\r
1080 description = "Flip the current WorldEdit region along the given axis",
\r
1081 privs = {worldedit=true},
\r
1083 parse = function(param)
\r
1084 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
\r
1087 return true, param
\r
1089 nodes_needed = check_region,
\r
1090 func = function(name, param)
\r
1091 if param == "?" then param = worldedit.player_axis(name) end
\r
1092 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1093 worldedit.player_notify(name, count .. " nodes flipped")
\r
1097 worldedit.register_command("rotate", {
\r
1098 params = "x/y/z/? <angle>",
\r
1099 description = "Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)",
\r
1100 privs = {worldedit=true},
\r
1102 parse = function(param)
\r
1103 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
1104 if found == nil then
\r
1107 angle = tonumber(angle)
\r
1108 if angle % 90 ~= 0 or angle % 360 == 0 then
\r
1109 return false, "invalid usage: angle must be multiple of 90"
\r
1111 return true, axis, angle
\r
1113 nodes_needed = check_region,
\r
1114 func = function(name, axis, angle)
\r
1115 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1116 if axis == "?" then axis = worldedit.player_axis(name) end
\r
1117 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
\r
1119 --reset markers to rotated positions
\r
1120 worldedit.pos1[name] = pos1
\r
1121 worldedit.pos2[name] = pos2
\r
1122 worldedit.marker_update(name)
\r
1124 worldedit.player_notify(name, count .. " nodes rotated")
\r
1128 worldedit.register_command("orient", {
\r
1129 params = "<angle>",
\r
1130 description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",
\r
1131 privs = {worldedit=true},
\r
1133 parse = function(param)
\r
1134 local found, _, angle = param:find("^([+-]?%d+)$")
\r
1135 if found == nil then
\r
1138 angle = tonumber(angle)
\r
1139 if angle % 90 ~= 0 then
\r
1140 return false, "invalid usage: angle must be multiple of 90"
\r
1142 return true, angle
\r
1144 nodes_needed = check_region,
\r
1145 func = function(name, angle)
\r
1146 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
\r
1147 worldedit.player_notify(name, count .. " nodes oriented")
\r
1151 worldedit.register_command("fixlight", {
\r
1153 description = "Fix the lighting in the current WorldEdit region",
\r
1154 privs = {worldedit=true},
\r
1156 nodes_needed = check_region,
\r
1157 func = function(name)
\r
1158 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
\r
1159 worldedit.player_notify(name, count .. " nodes updated")
\r
1163 worldedit.register_command("drain", {
\r
1165 description = "Remove any fluid node within the current WorldEdit region",
\r
1166 privs = {worldedit=true},
\r
1168 nodes_needed = check_region,
\r
1169 func = function(name)
\r
1170 -- TODO: make an API function for this
\r
1172 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1173 for x = pos1.x, pos2.x do
\r
1174 for y = pos1.y, pos2.y do
\r
1175 for z = pos1.z, pos2.z do
\r
1176 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1177 local d = minetest.registered_nodes[n]
\r
1178 if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then
\r
1179 minetest.remove_node({x=x, y=y, z=z})
\r
1185 worldedit.player_notify(name, count .. " nodes updated")
\r
1189 local clearcut_cache
\r
1191 local function clearcut(pos1, pos2)
\r
1192 -- decide which nodes we consider plants
\r
1193 if clearcut_cache == nil then
\r
1194 clearcut_cache = {}
\r
1195 for name, def in pairs(minetest.registered_nodes) do
\r
1196 local groups = def.groups or {}
\r
1198 -- the groups say so
\r
1199 groups.flower or groups.grass or groups.flora or groups.plant or
\r
1200 groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
\r
1201 -- drawtype heuristic
\r
1202 (def.is_ground_content and def.buildable_to and
\r
1203 (def.sunlight_propagates or not def.walkable)
\r
1204 and def.drawtype == "plantlike") or
\r
1205 -- if it's flammable, it probably needs to go too
\r
1206 (def.is_ground_content and not def.walkable and groups.flammable)
\r
1208 clearcut_cache[name] = true
\r
1212 local plants = clearcut_cache
\r
1217 for x = pos1.x, pos2.x do
\r
1218 for z = pos1.z, pos2.z do
\r
1221 -- first pass: remove floating nodes that would be left over
\r
1222 for y = pos1.y, pos2.y do
\r
1223 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1228 local def = minetest.registered_nodes[n] or {}
\r
1229 local groups = def.groups or {}
\r
1230 if groups.attached_node or (def.buildable_to and groups.falling_node) then
\r
1231 minetest.remove_node({x=x, y=y, z=z})
\r
1239 -- second pass: remove plants, top-to-bottom to avoid item drops
\r
1241 for y = pos2.y, pos1.y, -1 do
\r
1242 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1244 minetest.remove_node({x=x, y=y, z=z})
\r
1255 worldedit.register_command("clearcut", {
\r
1257 description = "Remove any plant, tree or foilage-like nodes in the selected region",
\r
1258 privs = {worldedit=true},
\r
1260 nodes_needed = check_region,
\r
1261 func = function(name)
\r
1262 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1263 local count = clearcut(pos1, pos2)
\r
1264 worldedit.player_notify(name, count .. " nodes removed")
\r
1268 worldedit.register_command("hide", {
\r
1270 description = "Hide all nodes in the current WorldEdit region non-destructively",
\r
1271 privs = {worldedit=true},
\r
1273 nodes_needed = check_region,
\r
1274 func = function(name)
\r
1275 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
\r
1276 worldedit.player_notify(name, count .. " nodes hidden")
\r
1280 worldedit.register_command("suppress", {
\r
1281 params = "<node>",
\r
1282 description = "Suppress all <node> in the current WorldEdit region non-destructively",
\r
1283 privs = {worldedit=true},
\r
1285 parse = function(param)
\r
1286 local node = worldedit.normalize_nodename(param)
\r
1288 return false, "invalid node name: " .. param
\r
1292 nodes_needed = check_region,
\r
1293 func = function(name, node)
\r
1294 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1295 worldedit.player_notify(name, count .. " nodes suppressed")
\r
1299 worldedit.register_command("highlight", {
\r
1300 params = "<node>",
\r
1301 description = "Highlight <node> in the current WorldEdit region by hiding everything else non-destructively",
\r
1302 privs = {worldedit=true},
\r
1304 parse = function(param)
\r
1305 local node = worldedit.normalize_nodename(param)
\r
1307 return false, "invalid node name: " .. param
\r
1311 nodes_needed = check_region,
\r
1312 func = function(name, node)
\r
1313 local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1314 worldedit.player_notify(name, count .. " nodes highlighted")
\r
1318 worldedit.register_command("restore", {
\r
1320 description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",
\r
1321 privs = {worldedit=true},
\r
1323 nodes_needed = check_region,
\r
1324 func = function(name)
\r
1325 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
\r
1326 worldedit.player_notify(name, count .. " nodes restored")
\r
1330 local function detect_misaligned_schematic(name, pos1, pos2)
\r
1331 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
1332 -- Check that allocate/save can position the schematic correctly
\r
1333 -- The expected behaviour is that the (0,0,0) corner of the schematic stays
\r
1334 -- sat pos1, this only works when the minimum position is actually present
\r
1335 -- in the schematic.
\r
1336 local node = minetest.get_node(pos1)
\r
1337 local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
\r
1338 if not have_node_at_origin then
\r
1339 worldedit.player_notify(name,
\r
1340 "Warning: The schematic contains excessive free space and WILL be "..
\r
1341 "misaligned when allocated or loaded. To avoid this, shrink your "..
\r
1342 "area to cover exactly the nodes to be saved."
\r
1347 worldedit.register_command("save", {
\r
1348 params = "<file>",
\r
1349 description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
\r
1350 privs = {worldedit=true},
\r
1352 parse = function(param)
\r
1353 if param == "" then
\r
1356 if not check_filename(param) then
\r
1357 return false, "Disallowed file name: " .. param
\r
1359 return true, param
\r
1361 nodes_needed = check_region,
\r
1362 func = function(name, param)
\r
1363 local result, count = worldedit.serialize(worldedit.pos1[name],
\r
1364 worldedit.pos2[name])
\r
1365 detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
\r
1367 local path = minetest.get_worldpath() .. "/schems"
\r
1368 -- Create directory if it does not already exist
\r
1369 minetest.mkdir(path)
\r
1371 local filename = path .. "/" .. param .. ".we"
\r
1372 local file, err = io.open(filename, "wb")
\r
1373 if err ~= nil then
\r
1374 worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")
\r
1377 file:write(result)
\r
1381 worldedit.player_notify(name, count .. " nodes saved")
\r
1385 worldedit.register_command("allocate", {
\r
1386 params = "<file>",
\r
1387 description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",
\r
1388 privs = {worldedit=true},
\r
1390 parse = function(param)
\r
1391 if param == "" then
\r
1394 if not check_filename(param) then
\r
1395 return false, "Disallowed file name: " .. param
\r
1397 return true, param
\r
1399 func = function(name, param)
\r
1400 local pos = worldedit.pos1[name]
\r
1402 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"
\r
1403 local file, err = io.open(filename, "rb")
\r
1404 if err ~= nil then
\r
1405 worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")
\r
1408 local value = file:read("*a")
\r
1411 local version = worldedit.read_header(value)
\r
1412 if version == nil or version == 0 then
\r
1413 worldedit.player_notify(name, "File is invalid!")
\r
1415 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1416 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1419 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
\r
1421 if not nodepos1 then
\r
1422 worldedit.player_notify(name, "Schematic empty, nothing allocated")
\r
1426 worldedit.pos1[name] = nodepos1
\r
1427 worldedit.pos2[name] = nodepos2
\r
1428 worldedit.marker_update(name)
\r
1430 worldedit.player_notify(name, count .. " nodes allocated")
\r
1434 worldedit.register_command("load", {
\r
1435 params = "<file>",
\r
1436 description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",
\r
1437 privs = {worldedit=true},
\r
1439 parse = function(param)
\r
1440 if param == "" then
\r
1443 if not check_filename(param) then
\r
1444 return false, "Disallowed file name: " .. param
\r
1446 return true, param
\r
1448 func = function(name, param)
\r
1449 local pos = worldedit.pos1[name]
\r
1451 if param == "" then
\r
1452 worldedit.player_notify(name, "invalid usage: " .. param)
\r
1455 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then
\r
1456 worldedit.player_notify(name, "invalid file name: " .. param)
\r
1460 --find the file in the world path
\r
1461 local testpaths = {
\r
1462 minetest.get_worldpath() .. "/schems/" .. param,
\r
1463 minetest.get_worldpath() .. "/schems/" .. param .. ".we",
\r
1464 minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
\r
1467 for index, path in ipairs(testpaths) do
\r
1468 file, err = io.open(path, "rb")
\r
1474 worldedit.player_notify(name, "could not open file \"" .. param .. "\"")
\r
1477 local value = file:read("*a")
\r
1480 local version = worldedit.read_header(value)
\r
1481 if version == nil or version == 0 then
\r
1482 worldedit.player_notify(name, "File is invalid!")
\r
1484 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1485 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1489 local count = worldedit.deserialize(pos, value)
\r
1491 worldedit.player_notify(name, count .. " nodes loaded")
\r
1495 worldedit.register_command("lua", {
\r
1496 params = "<code>",
\r
1497 description = "Executes <code> as a Lua chunk in the global namespace",
\r
1498 privs = {worldedit=true, server=true},
\r
1499 parse = function(param)
\r
1500 return true, param
\r
1502 func = function(name, param)
\r
1503 local err = worldedit.lua(param)
\r
1505 worldedit.player_notify(name, "code error: " .. err)
\r
1506 minetest.log("action", name.." tried to execute "..param)
\r
1508 worldedit.player_notify(name, "code successfully executed", false)
\r
1509 minetest.log("action", name.." executed "..param)
\r
1514 worldedit.register_command("luatransform", {
\r
1515 params = "<code>",
\r
1516 description = "Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region",
\r
1517 privs = {worldedit=true, server=true},
\r
1519 parse = function(param)
\r
1520 return true, param
\r
1522 nodes_needed = check_region,
\r
1523 func = function(name, param)
\r
1524 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1526 worldedit.player_notify(name, "code error: " .. err, false)
\r
1527 minetest.log("action", name.." tried to execute luatransform "..param)
\r
1529 worldedit.player_notify(name, "code successfully executed", false)
\r
1530 minetest.log("action", name.." executed luatransform "..param)
\r
1535 worldedit.register_command("mtschemcreate", {
\r
1536 params = "<file>",
\r
1537 description = "Save the current WorldEdit region using the Minetest "..
\r
1538 "Schematic format to \"(world folder)/schems/<filename>.mts\"",
\r
1539 privs = {worldedit=true},
\r
1541 parse = function(param)
\r
1542 if param == "" then
\r
1545 if not check_filename(param) then
\r
1546 return false, "Disallowed file name: " .. param
\r
1548 return true, param
\r
1550 nodes_needed = check_region,
\r
1551 func = function(name, param)
\r
1552 local path = minetest.get_worldpath() .. "/schems"
\r
1553 -- Create directory if it does not already exist
\r
1554 minetest.mkdir(path)
\r
1556 local filename = path .. "/" .. param .. ".mts"
\r
1557 local ret = minetest.create_schematic(worldedit.pos1[name],
\r
1558 worldedit.pos2[name], worldedit.prob_list[name],
\r
1560 if ret == nil then
\r
1561 worldedit.player_notify(name, "Failed to create Minetest schematic")
\r
1563 worldedit.player_notify(name, "Saved Minetest schematic to " .. param)
\r
1565 worldedit.prob_list[name] = {}
\r
1569 worldedit.register_command("mtschemplace", {
\r
1570 params = "<file>",
\r
1571 description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",
\r
1572 privs = {worldedit=true},
\r
1574 parse = function(param)
\r
1575 if param == "" then
\r
1578 if not check_filename(param) then
\r
1579 return false, "Disallowed file name: " .. param
\r
1581 return true, param
\r
1583 func = function(name, param)
\r
1584 local pos = worldedit.pos1[name]
\r
1586 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"
\r
1587 if minetest.place_schematic(pos, path) == nil then
\r
1588 worldedit.player_notify(name, "failed to place Minetest schematic")
\r
1590 worldedit.player_notify(name, "placed Minetest schematic " .. param ..
\r
1591 " at " .. minetest.pos_to_string(pos))
\r
1596 worldedit.register_command("mtschemprob", {
\r
1597 params = "start/finish/get",
\r
1598 description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",
\r
1599 privs = {worldedit=true},
\r
1600 parse = function(param)
\r
1601 if param ~= "start" and param ~= "finish" and param ~= "get" then
\r
1602 return false, "unknown subcommand: " .. param
\r
1604 return true, param
\r
1606 func = function(name, param)
\r
1607 if param == "start" then --start probability setting
\r
1608 worldedit.set_pos[name] = "prob"
\r
1609 worldedit.prob_list[name] = {}
\r
1610 worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")
\r
1611 elseif param == "finish" then --finish probability setting
\r
1612 worldedit.set_pos[name] = nil
\r
1613 worldedit.player_notify(name, "finished Minetest schematic probability selection")
\r
1614 elseif param == "get" then --get all nodes that had probabilities set on them
\r
1616 local problist = worldedit.prob_list[name]
\r
1617 if problist == nil then
\r
1620 for k,v in pairs(problist) do
\r
1621 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
\r
1622 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
\r
1624 worldedit.player_notify(name, "currently set node probabilities:")
\r
1625 worldedit.player_notify(name, text)
\r
1630 minetest.register_on_player_receive_fields(function(player, formname, fields)
\r
1631 if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then
\r
1632 local name = player:get_player_name()
\r
1633 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
\r
1634 local index = table.getn(worldedit.prob_list[name]) + 1
\r
1635 worldedit.prob_list[name][index] = prob_entry
\r
1639 worldedit.register_command("clearobjects", {
\r
1641 description = "Clears all objects within the WorldEdit region",
\r
1642 privs = {worldedit=true},
\r
1644 nodes_needed = check_region,
\r
1645 func = function(name)
\r
1646 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
\r
1647 worldedit.player_notify(name, count .. " objects cleared")
\r