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 local description_cache = nil
\r
159 -- normalizes node "description" `nodename`, returning a string (or nil)
\r
160 worldedit.normalize_nodename = function(nodename)
\r
161 nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces
\r
162 if nodename == "" then return nil end
\r
164 local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
\r
165 if minetest.registered_nodes[fullname] or fullname == "air" then -- full name
\r
168 nodename = nodename:lower()
\r
170 for key, _ in pairs(minetest.registered_nodes) do
\r
171 if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part)
\r
176 if description_cache == nil then
\r
177 -- cache stripped descriptions
\r
178 description_cache = {}
\r
179 for key, value in pairs(minetest.registered_nodes) do
\r
180 local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower()
\r
182 description_cache[key] = desc
\r
187 for key, desc in pairs(description_cache) do
\r
188 if desc == nodename then -- matches description
\r
192 for key, desc in pairs(description_cache) do
\r
193 if desc == nodename .. " block" then
\r
194 -- fuzzy description match (e.g. "Steel" == "Steel Block")
\r
200 for key, value in pairs(description_cache) do
\r
201 if value:find(nodename, 1, true) ~= nil then
\r
202 if match ~= nil then
\r
205 match = key -- substring description match (only if no ambiguities)
\r
211 -- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1)
\r
212 function worldedit.player_axis(name)
\r
213 local dir = minetest.get_player_by_name(name):get_look_dir()
\r
214 local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z)
\r
217 return "x", dir.x > 0 and 1 or -1
\r
220 return "y", dir.y > 0 and 1 or -1
\r
222 return "z", dir.z > 0 and 1 or -1
\r
225 local function check_filename(name)
\r
226 return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil
\r
230 worldedit.register_command("about", {
\r
233 description = "Get information about the WorldEdit mod",
\r
234 func = function(name)
\r
235 worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string..
\r
236 " is available on this server. Type //help to get a list of "..
\r
237 "commands, or get more information at "..
\r
238 "https://github.com/Uberi/Minetest-WorldEdit")
\r
242 -- mostly copied from builtin/chatcommands.lua with minor modifications
\r
243 worldedit.register_command("help", {
\r
245 params = "[all/<cmd>]",
\r
246 description = "Get help for WorldEdit commands",
\r
247 parse = function(param)
\r
250 func = function(name, param)
\r
251 local function format_help_line(cmd, def)
\r
252 local msg = minetest.colorize("#00ffff", "//"..cmd)
\r
253 if def.params and def.params ~= "" then
\r
254 msg = msg .. " " .. def.params
\r
256 if def.description and def.description ~= "" then
\r
257 msg = msg .. ": " .. def.description
\r
262 if not minetest.check_player_privs(name, "worldedit") then
\r
263 return false, "You are not allowed to use any WorldEdit commands."
\r
265 if param == "" then
\r
268 for cmd, def in pairs(worldedit.registered_commands) do
\r
269 if minetest.check_player_privs(name, def.privs) then
\r
270 cmds[#cmds + 1] = cmd
\r
274 return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"
\r
275 .. "Use '//help <cmd>' to get more information,"
\r
276 .. " or '//help all' to list everything."
\r
277 elseif param == "all" then
\r
279 for cmd, def in pairs(worldedit.registered_commands) do
\r
280 if minetest.check_player_privs(name, def.privs) then
\r
281 cmds[#cmds + 1] = format_help_line(cmd, def)
\r
285 return true, "Available commands:\n"..table.concat(cmds, "\n")
\r
287 local def = worldedit.registered_commands[param]
\r
289 return false, "Command not available: " .. param
\r
291 return true, format_help_line(param, def)
\r
297 worldedit.register_command("inspect", {
\r
298 params = "[on/off/1/0/true/false/yes/no/enable/disable]",
\r
299 description = "Enable or disable node inspection",
\r
300 privs = {worldedit=true},
\r
301 parse = function(param)
\r
302 if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
\r
304 elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
\r
309 func = function(name, enable)
\r
311 worldedit.inspect[name] = true
\r
312 local axis, sign = worldedit.player_axis(name)
\r
313 worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis",
\r
314 name, axis .. (sign > 0 and "+" or "-")))
\r
316 worldedit.inspect[name] = nil
\r
317 worldedit.player_notify(name, "inspector: inspection disabled")
\r
322 local function get_node_rlight(pos)
\r
323 local vecs = { -- neighboring nodes
\r
324 {x= 1, y= 0, z= 0},
\r
325 {x=-1, y= 0, z= 0},
\r
326 {x= 0, y= 1, z= 0},
\r
327 {x= 0, y=-1, z= 0},
\r
328 {x= 0, y= 0, z= 1},
\r
329 {x= 0, y= 0, z=-1},
\r
332 for _, v in ipairs(vecs) do
\r
333 ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
\r
338 minetest.register_on_punchnode(function(pos, node, puncher)
\r
339 local name = puncher:get_player_name()
\r
340 if worldedit.inspect[name] then
\r
341 local axis, sign = worldedit.player_axis(name)
\r
342 local message = string.format("inspector: %s at %s (param1=%d, param2=%d, received light=%d) punched facing the %s axis",
\r
343 node.name, minetest.pos_to_string(pos), node.param1, node.param2, get_node_rlight(pos), axis .. (sign > 0 and "+" or "-"))
\r
344 worldedit.player_notify(name, message)
\r
348 worldedit.register_command("reset", {
\r
350 description = "Reset the region so that it is empty",
\r
351 privs = {worldedit=true},
\r
352 func = function(name)
\r
353 worldedit.pos1[name] = nil
\r
354 worldedit.pos2[name] = nil
\r
355 worldedit.marker_update(name)
\r
356 worldedit.set_pos[name] = nil
\r
357 --make sure the user does not try to confirm an operation after resetting pos:
\r
358 reset_pending(name)
\r
359 worldedit.player_notify(name, "region reset")
\r
363 worldedit.register_command("mark", {
\r
365 description = "Show markers at the region positions",
\r
366 privs = {worldedit=true},
\r
367 func = function(name)
\r
368 worldedit.marker_update(name)
\r
369 worldedit.player_notify(name, "region marked")
\r
373 worldedit.register_command("unmark", {
\r
375 description = "Hide markers if currently shown",
\r
376 privs = {worldedit=true},
\r
377 func = function(name)
\r
378 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
379 worldedit.pos1[name] = nil
\r
380 worldedit.pos2[name] = nil
\r
381 worldedit.marker_update(name)
\r
382 worldedit.pos1[name] = pos1
\r
383 worldedit.pos2[name] = pos2
\r
384 worldedit.player_notify(name, "region unmarked")
\r
388 worldedit.register_command("pos1", {
\r
390 description = "Set WorldEdit region position 1 to the player's location",
\r
391 privs = {worldedit=true},
\r
392 func = function(name)
\r
393 local pos = minetest.get_player_by_name(name):get_pos()
\r
394 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
395 worldedit.pos1[name] = pos
\r
396 worldedit.mark_pos1(name)
\r
397 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
401 worldedit.register_command("pos2", {
\r
403 description = "Set WorldEdit region position 2 to the player's location",
\r
404 privs = {worldedit=true},
\r
405 func = function(name)
\r
406 local pos = minetest.get_player_by_name(name):get_pos()
\r
407 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
408 worldedit.pos2[name] = pos
\r
409 worldedit.mark_pos2(name)
\r
410 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
414 worldedit.register_command("p", {
\r
415 params = "set/set1/set2/get",
\r
416 description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region",
\r
417 privs = {worldedit=true},
\r
418 parse = function(param)
\r
419 if param == "set" or param == "set1" or param == "set2" or param == "get" then
\r
422 return false, "unknown subcommand: " .. param
\r
424 func = function(name, param)
\r
425 if param == "set" then --set both WorldEdit positions
\r
426 worldedit.set_pos[name] = "pos1"
\r
427 worldedit.player_notify(name, "select positions by punching two nodes")
\r
428 elseif param == "set1" then --set WorldEdit position 1
\r
429 worldedit.set_pos[name] = "pos1only"
\r
430 worldedit.player_notify(name, "select position 1 by punching a node")
\r
431 elseif param == "set2" then --set WorldEdit position 2
\r
432 worldedit.set_pos[name] = "pos2"
\r
433 worldedit.player_notify(name, "select position 2 by punching a node")
\r
434 elseif param == "get" then --display current WorldEdit positions
\r
435 if worldedit.pos1[name] ~= nil then
\r
436 worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name]))
\r
438 worldedit.player_notify(name, "position 1 not set")
\r
440 if worldedit.pos2[name] ~= nil then
\r
441 worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name]))
\r
443 worldedit.player_notify(name, "position 2 not set")
\r
449 worldedit.register_command("fixedpos", {
\r
450 params = "set1/set2 <x> <y> <z>",
\r
451 description = "Set a WorldEdit region position to the position at (<x>, <y>, <z>)",
\r
452 privs = {worldedit=true},
\r
453 parse = function(param)
\r
454 local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
\r
455 if found == nil then
\r
458 return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
460 func = function(name, flag, pos)
\r
461 if flag == "set1" then
\r
462 worldedit.pos1[name] = pos
\r
463 worldedit.mark_pos1(name)
\r
464 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
465 else --flag == "set2"
\r
466 worldedit.pos2[name] = pos
\r
467 worldedit.mark_pos2(name)
\r
468 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
473 minetest.register_on_punchnode(function(pos, node, puncher)
\r
474 local name = puncher:get_player_name()
\r
475 if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
\r
476 if worldedit.set_pos[name] == "pos1" then --setting position 1
\r
477 worldedit.pos1[name] = pos
\r
478 worldedit.mark_pos1(name)
\r
479 worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
\r
480 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
481 elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
\r
482 worldedit.pos1[name] = pos
\r
483 worldedit.mark_pos1(name)
\r
484 worldedit.set_pos[name] = nil --finished setting positions
\r
485 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
486 elseif worldedit.set_pos[name] == "pos2" then --setting position 2
\r
487 worldedit.pos2[name] = pos
\r
488 worldedit.mark_pos2(name)
\r
489 worldedit.set_pos[name] = nil --finished setting positions
\r
490 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
491 elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
\r
492 worldedit.prob_pos[name] = pos
\r
493 minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]")
\r
498 worldedit.register_command("volume", {
\r
500 description = "Display the volume of the current WorldEdit region",
\r
501 privs = {worldedit=true},
\r
503 func = function(name)
\r
504 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
506 local volume = worldedit.volume(pos1, pos2)
\r
507 local abs = math.abs
\r
508 worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes ("
\r
509 .. abs(pos2.x - pos1.x) + 1 .. "*"
\r
510 .. abs(pos2.y - pos1.y) + 1 .. "*"
\r
511 .. abs(pos2.z - pos1.z) + 1 .. ")")
\r
515 worldedit.register_command("deleteblocks", {
\r
517 description = "remove all MapBlocks (16x16x16) containing the selected area from the map",
\r
518 privs = {worldedit=true},
\r
520 nodes_needed = check_region,
\r
521 func = function(name)
\r
522 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
523 local success = minetest.delete_area(pos1, pos2)
\r
525 worldedit.player_notify(name, "Area deleted.")
\r
527 worldedit.player_notify(name, "There was an error during deletion of the area.")
\r
532 worldedit.register_command("set", {
\r
534 description = "Set the current WorldEdit region to <node>",
\r
535 privs = {worldedit=true},
\r
537 parse = function(param)
\r
538 local node = worldedit.normalize_nodename(param)
\r
540 return false, "invalid node name: " .. param
\r
544 nodes_needed = check_region,
\r
545 func = function(name, node)
\r
546 local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
\r
547 worldedit.player_notify(name, count .. " nodes set")
\r
551 worldedit.register_command("param2", {
\r
552 params = "<param2>",
\r
553 description = "Set param2 of all nodes in the current WorldEdit region to <param2>",
\r
554 privs = {worldedit=true},
\r
556 parse = function(param)
\r
557 local param2 = tonumber(param)
\r
560 elseif param2 < 0 or param2 > 255 then
\r
561 return false, "Param2 is out of range (must be between 0 and 255 inclusive!)"
\r
563 return true, param2
\r
565 nodes_needed = check_region,
\r
566 func = function(name, param2)
\r
567 local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
\r
568 worldedit.player_notify(name, count .. " nodes altered")
\r
572 worldedit.register_command("mix", {
\r
573 params = "<node1> [count1] <node2> [count2] ...",
\r
574 description = "Fill the current WorldEdit region with a random mix of <node1>, ...",
\r
575 privs = {worldedit=true},
\r
577 parse = function(param)
\r
579 for nodename in param:gmatch("[^%s]+") do
\r
580 if tonumber(nodename) ~= nil and #nodes > 0 then
\r
581 local last_node = nodes[#nodes]
\r
582 for i = 1, tonumber(nodename) do
\r
583 nodes[#nodes + 1] = last_node
\r
586 local node = worldedit.normalize_nodename(nodename)
\r
588 return false, "invalid node name: " .. nodename
\r
590 nodes[#nodes + 1] = node
\r
593 if #nodes == 0 then
\r
598 nodes_needed = check_region,
\r
599 func = function(name, nodes)
\r
600 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
601 local count = worldedit.set(pos1, pos2, nodes)
\r
602 worldedit.player_notify(name, count .. " nodes set")
\r
606 local check_replace = function(param)
\r
607 local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
\r
608 if found == nil then
\r
611 local newsearchnode = worldedit.normalize_nodename(searchnode)
\r
612 if not newsearchnode then
\r
613 return false, "invalid search node name: " .. searchnode
\r
615 local newreplacenode = worldedit.normalize_nodename(replacenode)
\r
616 if not newreplacenode then
\r
617 return false, "invalid replace node name: " .. replacenode
\r
619 return true, newsearchnode, newreplacenode
\r
622 worldedit.register_command("replace", {
\r
623 params = "<search node> <replace node>",
\r
624 description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region",
\r
625 privs = {worldedit=true},
\r
627 parse = check_replace,
\r
628 nodes_needed = check_region,
\r
629 func = function(name, search_node, replace_node)
\r
630 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
631 search_node, replace_node)
\r
632 worldedit.player_notify(name, count .. " nodes replaced")
\r
636 worldedit.register_command("replaceinverse", {
\r
637 params = "<search node> <replace node>",
\r
638 description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region",
\r
639 privs = {worldedit=true},
\r
641 parse = check_replace,
\r
642 nodes_needed = check_region,
\r
643 func = function(name, search_node, replace_node)
\r
644 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
645 search_node, replace_node, true)
\r
646 worldedit.player_notify(name, count .. " nodes replaced")
\r
650 local check_cube = function(param)
\r
651 local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
652 if found == nil then
\r
655 local node = worldedit.normalize_nodename(nodename)
\r
657 return false, "invalid node name: " .. nodename
\r
659 return true, tonumber(w), tonumber(h), tonumber(l), node
\r
662 worldedit.register_command("hollowcube", {
\r
663 params = "<width> <height> <length> <node>",
\r
664 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
665 privs = {worldedit=true},
\r
667 parse = check_cube,
\r
668 nodes_needed = function(name, w, h, l, node)
\r
671 func = function(name, w, h, l, node)
\r
672 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
\r
673 worldedit.player_notify(name, count .. " nodes added")
\r
677 worldedit.register_command("cube", {
\r
678 params = "<width> <height> <length> <node>",
\r
679 description = "Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.",
\r
680 privs = {worldedit=true},
\r
682 parse = check_cube,
\r
683 nodes_needed = function(name, w, h, l, node)
\r
686 func = function(name, w, h, l, node)
\r
687 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
\r
688 worldedit.player_notify(name, count .. " nodes added")
\r
692 local check_sphere = function(param)
\r
693 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
694 if found == nil then
\r
697 local node = worldedit.normalize_nodename(nodename)
\r
699 return false, "invalid node name: " .. nodename
\r
701 return true, tonumber(radius), node
\r
704 worldedit.register_command("hollowsphere", {
\r
705 params = "<radius> <node>",
\r
706 description = "Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
707 privs = {worldedit=true},
\r
709 parse = check_sphere,
\r
710 nodes_needed = function(name, radius, node)
\r
711 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
713 func = function(name, radius, node)
\r
714 local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
\r
715 worldedit.player_notify(name, count .. " nodes added")
\r
719 worldedit.register_command("sphere", {
\r
720 params = "<radius> <node>",
\r
721 description = "Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
722 privs = {worldedit=true},
\r
724 parse = check_sphere,
\r
725 nodes_needed = function(name, radius, node)
\r
726 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
728 func = function(name, radius, node)
\r
729 local count = worldedit.sphere(worldedit.pos1[name], radius, node)
\r
730 worldedit.player_notify(name, count .. " nodes added")
\r
734 local check_dome = function(param)
\r
735 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
736 if found == nil then
\r
739 local node = worldedit.normalize_nodename(nodename)
\r
741 return false, "invalid node name: " .. nodename
\r
743 return true, tonumber(radius), node
\r
746 worldedit.register_command("hollowdome", {
\r
747 params = "<radius> <node>",
\r
748 description = "Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
749 privs = {worldedit=true},
\r
751 parse = check_dome,
\r
752 nodes_needed = function(name, radius, node)
\r
753 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
755 func = function(name, radius, node)
\r
756 local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
\r
757 worldedit.player_notify(name, count .. " nodes added")
\r
761 worldedit.register_command("dome", {
\r
762 params = "<radius> <node>",
\r
763 description = "Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
764 privs = {worldedit=true},
\r
766 parse = check_dome,
\r
767 nodes_needed = function(name, radius, node)
\r
768 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
770 func = function(name, radius, node)
\r
771 local count = worldedit.dome(worldedit.pos1[name], radius, node)
\r
772 worldedit.player_notify(name, count .. " nodes added")
\r
776 local check_cylinder = function(param)
\r
778 local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
779 if found == nil then
\r
781 found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
\r
784 if found == nil then
\r
787 local node = worldedit.normalize_nodename(nodename)
\r
789 return false, "invalid node name: " .. nodename
\r
791 return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
\r
794 worldedit.register_command("hollowcylinder", {
\r
795 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
796 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
797 privs = {worldedit=true},
\r
799 parse = check_cylinder,
\r
800 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
801 local radius = math.max(radius1, radius2)
\r
802 return math.ceil(math.pi * (radius ^ 2) * length)
\r
804 func = function(name, axis, length, radius1, radius2, node)
\r
805 if axis == "?" then
\r
807 axis, sign = worldedit.player_axis(name)
\r
808 length = length * sign
\r
810 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
\r
811 worldedit.player_notify(name, count .. " nodes added")
\r
815 worldedit.register_command("cylinder", {
\r
816 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
817 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
818 privs = {worldedit=true},
\r
820 parse = check_cylinder,
\r
821 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
822 local radius = math.max(radius1, radius2)
\r
823 return math.ceil(math.pi * (radius ^ 2) * length)
\r
825 func = function(name, axis, length, radius1, radius2, node)
\r
826 if axis == "?" then
\r
828 axis, sign = worldedit.player_axis(name)
\r
829 length = length * sign
\r
831 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
\r
832 worldedit.player_notify(name, count .. " nodes added")
\r
836 local check_pyramid = function(param)
\r
837 local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
\r
838 if found == nil then
\r
841 local node = worldedit.normalize_nodename(nodename)
\r
843 return false, "invalid node name: " .. nodename
\r
845 return true, axis, tonumber(height), node
\r
848 worldedit.register_command("hollowpyramid", {
\r
849 params = "x/y/z/? <height> <node>",
\r
850 description = "Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>",
\r
851 privs = {worldedit=true},
\r
853 parse = check_pyramid,
\r
854 nodes_needed = function(name, axis, height, node)
\r
855 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
857 func = function(name, axis, height, node)
\r
858 if axis == "?" then
\r
860 axis, sign = worldedit.player_axis(name)
\r
861 height = height * sign
\r
863 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
\r
864 worldedit.player_notify(name, count .. " nodes added")
\r
868 worldedit.register_command("pyramid", {
\r
869 params = "x/y/z/? <height> <node>",
\r
870 description = "Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>",
\r
871 privs = {worldedit=true},
\r
873 parse = check_pyramid,
\r
874 nodes_needed = function(name, axis, height, node)
\r
875 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
877 func = function(name, axis, height, node)
\r
878 if axis == "?" then
\r
880 axis, sign = worldedit.player_axis(name)
\r
881 height = height * sign
\r
883 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
\r
884 worldedit.player_notify(name, count .. " nodes added")
\r
888 worldedit.register_command("spiral", {
\r
889 params = "<length> <height> <space> <node>",
\r
890 description = "Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>",
\r
891 privs = {worldedit=true},
\r
893 parse = function(param)
\r
894 local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
895 if found == nil then
\r
898 local node = worldedit.normalize_nodename(nodename)
\r
900 return false, "invalid node name: " .. nodename
\r
902 return true, tonumber(length), tonumber(height), tonumber(space), node
\r
904 nodes_needed = function(name, length, height, space, node)
\r
905 return (length + space) * height -- TODO: this is not the upper bound
\r
907 func = function(name, length, height, space, node)
\r
908 local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
\r
909 worldedit.player_notify(name, count .. " nodes added")
\r
913 worldedit.register_command("copy", {
\r
914 params = "x/y/z/? <amount>",
\r
915 description = "Copy the current WorldEdit region along the given axis by <amount> nodes",
\r
916 privs = {worldedit=true},
\r
918 parse = function(param)
\r
919 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
920 if found == nil then
\r
923 return true, axis, tonumber(amount)
\r
925 nodes_needed = function(name, axis, amount)
\r
926 return check_region(name) * 2
\r
928 func = function(name, axis, amount)
\r
929 if axis == "?" then
\r
931 axis, sign = worldedit.player_axis(name)
\r
932 amount = amount * sign
\r
935 local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
\r
936 worldedit.player_notify(name, count .. " nodes copied")
\r
940 worldedit.register_command("move", {
\r
941 params = "x/y/z/? <amount>",
\r
942 description = "Move the current WorldEdit region along the given axis by <amount> nodes",
\r
943 privs = {worldedit=true},
\r
945 parse = function(param)
\r
946 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
947 if found == nil then
\r
950 return true, axis, tonumber(amount)
\r
952 nodes_needed = function(name, axis, amount)
\r
953 return check_region(name) * 2
\r
955 func = function(name, axis, amount)
\r
956 if axis == "?" then
\r
958 axis, sign = worldedit.player_axis(name)
\r
959 amount = amount * sign
\r
962 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
963 local count = worldedit.move(pos1, pos2, axis, amount)
\r
965 pos1[axis] = pos1[axis] + amount
\r
966 pos2[axis] = pos2[axis] + amount
\r
967 worldedit.marker_update(name)
\r
968 worldedit.player_notify(name, count .. " nodes moved")
\r
972 worldedit.register_command("stack", {
\r
973 params = "x/y/z/? <count>",
\r
974 description = "Stack the current WorldEdit region along the given axis <count> times",
\r
975 privs = {worldedit=true},
\r
977 parse = function(param)
\r
978 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
979 if found == nil then
\r
982 return true, axis, tonumber(repetitions)
\r
984 nodes_needed = function(name, axis, repetitions)
\r
985 return check_region(name) * math.abs(repetitions)
\r
987 func = function(name, axis, repetitions)
\r
988 if axis == "?" then
\r
990 axis, sign = worldedit.player_axis(name)
\r
991 repetitions = repetitions * sign
\r
994 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
995 local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
\r
996 worldedit.stack(pos1, pos2, axis, repetitions, function()
\r
997 worldedit.player_notify(name, count .. " nodes stacked")
\r
1002 worldedit.register_command("stack2", {
\r
1003 params = "<count> <x> <y> <z>",
\r
1004 description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",
\r
1005 privs = {worldedit=true},
\r
1007 parse = function(param)
\r
1008 local repetitions, incs = param:match("(%d+)%s*(.+)")
\r
1009 if repetitions == nil then
\r
1010 return false, "invalid count: " .. param
\r
1012 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
\r
1014 return false, "invalid increments: " .. param
\r
1017 return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
1019 nodes_needed = function(name, repetitions, offset)
\r
1020 return check_region(name) * repetitions
\r
1022 func = function(name, repetitions, offset)
\r
1023 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1024 local count = worldedit.volume(pos1, pos2) * repetitions
\r
1025 worldedit.stack2(pos1, pos2, offset, repetitions, function()
\r
1026 worldedit.player_notify(name, count .. " nodes stacked")
\r
1032 worldedit.register_command("stretch", {
\r
1033 params = "<stretchx> <stretchy> <stretchz>",
\r
1034 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
1035 privs = {worldedit=true},
\r
1037 parse = function(param)
\r
1038 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
\r
1039 if found == nil then
\r
1042 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
\r
1043 if stretchx == 0 or stretchy == 0 or stretchz == 0 then
\r
1044 return false, "invalid scaling factors: " .. param
\r
1046 return true, stretchx, stretchy, stretchz
\r
1048 nodes_needed = function(name, stretchx, stretchy, stretchz)
\r
1049 return check_region(name) * stretchx * stretchy * stretchz
\r
1051 func = function(name, stretchx, stretchy, stretchz)
\r
1052 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1053 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
\r
1055 --reset markers to scaled positions
\r
1056 worldedit.pos1[name] = pos1
\r
1057 worldedit.pos2[name] = pos2
\r
1058 worldedit.marker_update(name)
\r
1060 worldedit.player_notify(name, count .. " nodes stretched")
\r
1064 worldedit.register_command("transpose", {
\r
1065 params = "x/y/z/? x/y/z/?",
\r
1066 description = "Transpose the current WorldEdit region along the given axes",
\r
1067 privs = {worldedit=true},
\r
1069 parse = function(param)
\r
1070 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
\r
1071 if found == nil then
\r
1073 elseif axis1 == axis2 then
\r
1074 return false, "invalid usage: axes must be different"
\r
1076 return true, axis1, axis2
\r
1078 nodes_needed = check_region,
\r
1079 func = function(name, axis1, axis2)
\r
1080 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1081 if axis1 == "?" then axis1 = worldedit.player_axis(name) end
\r
1082 if axis2 == "?" then axis2 = worldedit.player_axis(name) end
\r
1083 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
1085 --reset markers to transposed positions
\r
1086 worldedit.pos1[name] = pos1
\r
1087 worldedit.pos2[name] = pos2
\r
1088 worldedit.marker_update(name)
\r
1090 worldedit.player_notify(name, count .. " nodes transposed")
\r
1094 worldedit.register_command("flip", {
\r
1095 params = "x/y/z/?",
\r
1096 description = "Flip the current WorldEdit region along the given axis",
\r
1097 privs = {worldedit=true},
\r
1099 parse = function(param)
\r
1100 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
\r
1103 return true, param
\r
1105 nodes_needed = check_region,
\r
1106 func = function(name, param)
\r
1107 if param == "?" then param = worldedit.player_axis(name) end
\r
1108 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1109 worldedit.player_notify(name, count .. " nodes flipped")
\r
1113 worldedit.register_command("rotate", {
\r
1114 params = "x/y/z/? <angle>",
\r
1115 description = "Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)",
\r
1116 privs = {worldedit=true},
\r
1118 parse = function(param)
\r
1119 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
1120 if found == nil then
\r
1123 angle = tonumber(angle)
\r
1124 if angle % 90 ~= 0 or angle % 360 == 0 then
\r
1125 return false, "invalid usage: angle must be multiple of 90"
\r
1127 return true, axis, angle
\r
1129 nodes_needed = check_region,
\r
1130 func = function(name, axis, angle)
\r
1131 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1132 if axis == "?" then axis = worldedit.player_axis(name) end
\r
1133 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
\r
1135 --reset markers to rotated positions
\r
1136 worldedit.pos1[name] = pos1
\r
1137 worldedit.pos2[name] = pos2
\r
1138 worldedit.marker_update(name)
\r
1140 worldedit.player_notify(name, count .. " nodes rotated")
\r
1144 worldedit.register_command("orient", {
\r
1145 params = "<angle>",
\r
1146 description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",
\r
1147 privs = {worldedit=true},
\r
1149 parse = function(param)
\r
1150 local found, _, angle = param:find("^([+-]?%d+)$")
\r
1151 if found == nil then
\r
1154 angle = tonumber(angle)
\r
1155 if angle % 90 ~= 0 then
\r
1156 return false, "invalid usage: angle must be multiple of 90"
\r
1158 return true, angle
\r
1160 nodes_needed = check_region,
\r
1161 func = function(name, angle)
\r
1162 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
\r
1163 worldedit.player_notify(name, count .. " nodes oriented")
\r
1167 worldedit.register_command("fixlight", {
\r
1169 description = "Fix the lighting in the current WorldEdit region",
\r
1170 privs = {worldedit=true},
\r
1172 nodes_needed = check_region,
\r
1173 func = function(name)
\r
1174 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
\r
1175 worldedit.player_notify(name, count .. " nodes updated")
\r
1179 worldedit.register_command("drain", {
\r
1181 description = "Remove any fluid node within the current WorldEdit region",
\r
1182 privs = {worldedit=true},
\r
1184 nodes_needed = check_region,
\r
1185 func = function(name)
\r
1186 -- TODO: make an API function for this
\r
1188 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1189 for x = pos1.x, pos2.x do
\r
1190 for y = pos1.y, pos2.y do
\r
1191 for z = pos1.z, pos2.z do
\r
1192 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1193 local d = minetest.registered_nodes[n]
\r
1194 if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then
\r
1195 minetest.remove_node({x=x, y=y, z=z})
\r
1201 worldedit.player_notify(name, count .. " nodes updated")
\r
1205 local clearcut_cache
\r
1207 local function clearcut(pos1, pos2)
\r
1208 -- decide which nodes we consider plants
\r
1209 if clearcut_cache == nil then
\r
1210 clearcut_cache = {}
\r
1211 for name, def in pairs(minetest.registered_nodes) do
\r
1212 local groups = def.groups or {}
\r
1214 -- the groups say so
\r
1215 groups.flower or groups.grass or groups.flora or groups.plant or
\r
1216 groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
\r
1217 -- drawtype heuristic
\r
1218 (def.is_ground_content and def.buildable_to and
\r
1219 (def.sunlight_propagates or not def.walkable)
\r
1220 and def.drawtype == "plantlike") or
\r
1221 -- if it's flammable, it probably needs to go too
\r
1222 (def.is_ground_content and not def.walkable and groups.flammable)
\r
1224 clearcut_cache[name] = true
\r
1228 local plants = clearcut_cache
\r
1233 for x = pos1.x, pos2.x do
\r
1234 for z = pos1.z, pos2.z do
\r
1237 -- first pass: remove floating nodes that would be left over
\r
1238 for y = pos1.y, pos2.y do
\r
1239 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1244 local def = minetest.registered_nodes[n] or {}
\r
1245 local groups = def.groups or {}
\r
1246 if groups.attached_node or (def.buildable_to and groups.falling_node) then
\r
1247 minetest.remove_node({x=x, y=y, z=z})
\r
1255 -- second pass: remove plants, top-to-bottom to avoid item drops
\r
1257 for y = pos2.y, pos1.y, -1 do
\r
1258 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1260 minetest.remove_node({x=x, y=y, z=z})
\r
1271 worldedit.register_command("clearcut", {
\r
1273 description = "Remove any plant, tree or foilage-like nodes in the selected region",
\r
1274 privs = {worldedit=true},
\r
1276 nodes_needed = check_region,
\r
1277 func = function(name)
\r
1278 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1279 local count = clearcut(pos1, pos2)
\r
1280 worldedit.player_notify(name, count .. " nodes removed")
\r
1284 worldedit.register_command("hide", {
\r
1286 description = "Hide all nodes in the current WorldEdit region non-destructively",
\r
1287 privs = {worldedit=true},
\r
1289 nodes_needed = check_region,
\r
1290 func = function(name)
\r
1291 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
\r
1292 worldedit.player_notify(name, count .. " nodes hidden")
\r
1296 worldedit.register_command("suppress", {
\r
1297 params = "<node>",
\r
1298 description = "Suppress all <node> in the current WorldEdit region non-destructively",
\r
1299 privs = {worldedit=true},
\r
1301 parse = function(param)
\r
1302 local node = worldedit.normalize_nodename(param)
\r
1304 return false, "invalid node name: " .. param
\r
1308 nodes_needed = check_region,
\r
1309 func = function(name, node)
\r
1310 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1311 worldedit.player_notify(name, count .. " nodes suppressed")
\r
1315 worldedit.register_command("highlight", {
\r
1316 params = "<node>",
\r
1317 description = "Highlight <node> in the current WorldEdit region by hiding everything else non-destructively",
\r
1318 privs = {worldedit=true},
\r
1320 parse = function(param)
\r
1321 local node = worldedit.normalize_nodename(param)
\r
1323 return false, "invalid node name: " .. param
\r
1327 nodes_needed = check_region,
\r
1328 func = function(name, node)
\r
1329 local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1330 worldedit.player_notify(name, count .. " nodes highlighted")
\r
1334 worldedit.register_command("restore", {
\r
1336 description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",
\r
1337 privs = {worldedit=true},
\r
1339 nodes_needed = check_region,
\r
1340 func = function(name)
\r
1341 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
\r
1342 worldedit.player_notify(name, count .. " nodes restored")
\r
1346 local function detect_misaligned_schematic(name, pos1, pos2)
\r
1347 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
1348 -- Check that allocate/save can position the schematic correctly
\r
1349 -- The expected behaviour is that the (0,0,0) corner of the schematic stays
\r
1350 -- sat pos1, this only works when the minimum position is actually present
\r
1351 -- in the schematic.
\r
1352 local node = minetest.get_node(pos1)
\r
1353 local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
\r
1354 if not have_node_at_origin then
\r
1355 worldedit.player_notify(name,
\r
1356 "Warning: The schematic contains excessive free space and WILL be "..
\r
1357 "misaligned when allocated or loaded. To avoid this, shrink your "..
\r
1358 "area to cover exactly the nodes to be saved."
\r
1363 worldedit.register_command("save", {
\r
1364 params = "<file>",
\r
1365 description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
\r
1366 privs = {worldedit=true},
\r
1368 parse = function(param)
\r
1369 if param == "" then
\r
1372 if not check_filename(param) then
\r
1373 return false, "Disallowed file name: " .. param
\r
1375 return true, param
\r
1377 nodes_needed = check_region,
\r
1378 func = function(name, param)
\r
1379 local result, count = worldedit.serialize(worldedit.pos1[name],
\r
1380 worldedit.pos2[name])
\r
1381 detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
\r
1383 local path = minetest.get_worldpath() .. "/schems"
\r
1384 -- Create directory if it does not already exist
\r
1385 minetest.mkdir(path)
\r
1387 local filename = path .. "/" .. param .. ".we"
\r
1388 local file, err = io.open(filename, "wb")
\r
1389 if err ~= nil then
\r
1390 worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")
\r
1393 file:write(result)
\r
1397 worldedit.player_notify(name, count .. " nodes saved")
\r
1401 worldedit.register_command("allocate", {
\r
1402 params = "<file>",
\r
1403 description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",
\r
1404 privs = {worldedit=true},
\r
1406 parse = function(param)
\r
1407 if param == "" then
\r
1410 if not check_filename(param) then
\r
1411 return false, "Disallowed file name: " .. param
\r
1413 return true, param
\r
1415 func = function(name, param)
\r
1416 local pos = worldedit.pos1[name]
\r
1418 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"
\r
1419 local file, err = io.open(filename, "rb")
\r
1420 if err ~= nil then
\r
1421 worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")
\r
1424 local value = file:read("*a")
\r
1427 local version = worldedit.read_header(value)
\r
1428 if version == nil or version == 0 then
\r
1429 worldedit.player_notify(name, "File is invalid!")
\r
1431 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1432 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1435 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
\r
1437 if not nodepos1 then
\r
1438 worldedit.player_notify(name, "Schematic empty, nothing allocated")
\r
1442 worldedit.pos1[name] = nodepos1
\r
1443 worldedit.pos2[name] = nodepos2
\r
1444 worldedit.marker_update(name)
\r
1446 worldedit.player_notify(name, count .. " nodes allocated")
\r
1450 worldedit.register_command("load", {
\r
1451 params = "<file>",
\r
1452 description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",
\r
1453 privs = {worldedit=true},
\r
1455 parse = function(param)
\r
1456 if param == "" then
\r
1459 if not check_filename(param) then
\r
1460 return false, "Disallowed file name: " .. param
\r
1462 return true, param
\r
1464 func = function(name, param)
\r
1465 local pos = worldedit.pos1[name]
\r
1467 if param == "" then
\r
1468 worldedit.player_notify(name, "invalid usage: " .. param)
\r
1471 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then
\r
1472 worldedit.player_notify(name, "invalid file name: " .. param)
\r
1476 --find the file in the world path
\r
1477 local testpaths = {
\r
1478 minetest.get_worldpath() .. "/schems/" .. param,
\r
1479 minetest.get_worldpath() .. "/schems/" .. param .. ".we",
\r
1480 minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
\r
1483 for index, path in ipairs(testpaths) do
\r
1484 file, err = io.open(path, "rb")
\r
1490 worldedit.player_notify(name, "could not open file \"" .. param .. "\"")
\r
1493 local value = file:read("*a")
\r
1496 local version = worldedit.read_header(value)
\r
1497 if version == nil or version == 0 then
\r
1498 worldedit.player_notify(name, "File is invalid!")
\r
1500 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1501 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1505 local count = worldedit.deserialize(pos, value)
\r
1507 worldedit.player_notify(name, count .. " nodes loaded")
\r
1511 worldedit.register_command("lua", {
\r
1512 params = "<code>",
\r
1513 description = "Executes <code> as a Lua chunk in the global namespace",
\r
1514 privs = {worldedit=true, server=true},
\r
1515 parse = function(param)
\r
1516 return true, param
\r
1518 func = function(name, param)
\r
1519 local err = worldedit.lua(param)
\r
1521 worldedit.player_notify(name, "code error: " .. err)
\r
1522 minetest.log("action", name.." tried to execute "..param)
\r
1524 worldedit.player_notify(name, "code successfully executed", false)
\r
1525 minetest.log("action", name.." executed "..param)
\r
1530 worldedit.register_command("luatransform", {
\r
1531 params = "<code>",
\r
1532 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
1533 privs = {worldedit=true, server=true},
\r
1535 parse = function(param)
\r
1536 return true, param
\r
1538 nodes_needed = check_region,
\r
1539 func = function(name, param)
\r
1540 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1542 worldedit.player_notify(name, "code error: " .. err, false)
\r
1543 minetest.log("action", name.." tried to execute luatransform "..param)
\r
1545 worldedit.player_notify(name, "code successfully executed", false)
\r
1546 minetest.log("action", name.." executed luatransform "..param)
\r
1551 worldedit.register_command("mtschemcreate", {
\r
1552 params = "<file>",
\r
1553 description = "Save the current WorldEdit region using the Minetest "..
\r
1554 "Schematic format to \"(world folder)/schems/<filename>.mts\"",
\r
1555 privs = {worldedit=true},
\r
1557 parse = function(param)
\r
1558 if param == "" then
\r
1561 if not check_filename(param) then
\r
1562 return false, "Disallowed file name: " .. param
\r
1564 return true, param
\r
1566 nodes_needed = check_region,
\r
1567 func = function(name, param)
\r
1568 local path = minetest.get_worldpath() .. "/schems"
\r
1569 -- Create directory if it does not already exist
\r
1570 minetest.mkdir(path)
\r
1572 local filename = path .. "/" .. param .. ".mts"
\r
1573 local ret = minetest.create_schematic(worldedit.pos1[name],
\r
1574 worldedit.pos2[name], worldedit.prob_list[name],
\r
1576 if ret == nil then
\r
1577 worldedit.player_notify(name, "Failed to create Minetest schematic")
\r
1579 worldedit.player_notify(name, "Saved Minetest schematic to " .. param)
\r
1581 worldedit.prob_list[name] = {}
\r
1585 worldedit.register_command("mtschemplace", {
\r
1586 params = "<file>",
\r
1587 description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",
\r
1588 privs = {worldedit=true},
\r
1590 parse = function(param)
\r
1591 if param == "" then
\r
1594 if not check_filename(param) then
\r
1595 return false, "Disallowed file name: " .. param
\r
1597 return true, param
\r
1599 func = function(name, param)
\r
1600 local pos = worldedit.pos1[name]
\r
1602 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"
\r
1603 if minetest.place_schematic(pos, path) == nil then
\r
1604 worldedit.player_notify(name, "failed to place Minetest schematic")
\r
1606 worldedit.player_notify(name, "placed Minetest schematic " .. param ..
\r
1607 " at " .. minetest.pos_to_string(pos))
\r
1612 worldedit.register_command("mtschemprob", {
\r
1613 params = "start/finish/get",
\r
1614 description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",
\r
1615 privs = {worldedit=true},
\r
1616 parse = function(param)
\r
1617 if param ~= "start" and param ~= "finish" and param ~= "get" then
\r
1618 return false, "unknown subcommand: " .. param
\r
1620 return true, param
\r
1622 func = function(name, param)
\r
1623 if param == "start" then --start probability setting
\r
1624 worldedit.set_pos[name] = "prob"
\r
1625 worldedit.prob_list[name] = {}
\r
1626 worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")
\r
1627 elseif param == "finish" then --finish probability setting
\r
1628 worldedit.set_pos[name] = nil
\r
1629 worldedit.player_notify(name, "finished Minetest schematic probability selection")
\r
1630 elseif param == "get" then --get all nodes that had probabilities set on them
\r
1632 local problist = worldedit.prob_list[name]
\r
1633 if problist == nil then
\r
1636 for k,v in pairs(problist) do
\r
1637 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
\r
1638 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
\r
1640 worldedit.player_notify(name, "currently set node probabilities:")
\r
1641 worldedit.player_notify(name, text)
\r
1646 minetest.register_on_player_receive_fields(function(player, formname, fields)
\r
1647 if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then
\r
1648 local name = player:get_player_name()
\r
1649 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
\r
1650 local index = table.getn(worldedit.prob_list[name]) + 1
\r
1651 worldedit.prob_list[name][index] = prob_entry
\r
1655 worldedit.register_command("clearobjects", {
\r
1657 description = "Clears all objects within the WorldEdit region",
\r
1658 privs = {worldedit=true},
\r
1660 nodes_needed = check_region,
\r
1661 func = function(name)
\r
1662 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
\r
1663 worldedit.player_notify(name, count .. " objects cleared")
\r