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 .. " is available on this server. Type //help to get a list of commands, or get more information at https://github.com/Uberi/Minetest-WorldEdit")
\r
223 -- mostly copied from builtin/chatcommands.lua with minor modifications
\r
224 worldedit.register_command("help", {
\r
226 params = "[all/<cmd>]",
\r
227 description = "Get help for WorldEdit commands",
\r
228 parse = function(param)
\r
231 func = function(name, param)
\r
232 local function format_help_line(cmd, def)
\r
233 local msg = minetest.colorize("#00ffff", "//"..cmd)
\r
234 if def.params and def.params ~= "" then
\r
235 msg = msg .. " " .. def.params
\r
237 if def.description and def.description ~= "" then
\r
238 msg = msg .. ": " .. def.description
\r
243 if not minetest.check_player_privs(name, "worldedit") then
\r
244 return false, "You are not allowed to use any WorldEdit commands."
\r
246 if param == "" then
\r
249 for cmd, def in pairs(worldedit.registered_commands) do
\r
250 if minetest.check_player_privs(name, def.privs) then
\r
251 cmds[#cmds + 1] = cmd
\r
255 return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"
\r
256 .. "Use '//help <cmd>' to get more information,"
\r
257 .. " or '//help all' to list everything."
\r
258 elseif param == "all" then
\r
260 for cmd, def in pairs(worldedit.registered_commands) do
\r
261 if minetest.check_player_privs(name, def.privs) then
\r
262 cmds[#cmds + 1] = format_help_line(cmd, def)
\r
266 return true, "Available commands:\n"..table.concat(cmds, "\n")
\r
268 local def = worldedit.registered_commands[param]
\r
270 return false, "Command not available: " .. param
\r
272 return true, format_help_line(param, def)
\r
278 worldedit.register_command("inspect", {
\r
279 params = "[on/off/1/0/true/false/yes/no/enable/disable]",
\r
280 description = "Enable or disable node inspection",
\r
281 privs = {worldedit=true},
\r
282 parse = function(param)
\r
283 if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
\r
285 elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
\r
290 func = function(name, enable)
\r
292 worldedit.inspect[name] = true
\r
293 local axis, sign = worldedit.player_axis(name)
\r
294 worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis",
\r
295 name, axis .. (sign > 0 and "+" or "-")))
\r
297 worldedit.inspect[name] = nil
\r
298 worldedit.player_notify(name, "inspector: inspection disabled")
\r
303 local function get_node_rlight(pos)
\r
304 local vecs = { -- neighboring nodes
\r
305 {x= 1, y= 0, z= 0},
\r
306 {x=-1, y= 0, z= 0},
\r
307 {x= 0, y= 1, z= 0},
\r
308 {x= 0, y=-1, z= 0},
\r
309 {x= 0, y= 0, z= 1},
\r
310 {x= 0, y= 0, z=-1},
\r
313 for _, v in ipairs(vecs) do
\r
314 ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
\r
319 minetest.register_on_punchnode(function(pos, node, puncher)
\r
320 local name = puncher:get_player_name()
\r
321 if worldedit.inspect[name] then
\r
322 local axis, sign = worldedit.player_axis(name)
\r
323 local message = string.format("inspector: %s at %s (param1=%d, param2=%d, received light=%d) punched facing the %s axis",
\r
324 node.name, minetest.pos_to_string(pos), node.param1, node.param2, get_node_rlight(pos), axis .. (sign > 0 and "+" or "-"))
\r
325 worldedit.player_notify(name, message)
\r
329 worldedit.register_command("reset", {
\r
331 description = "Reset the region so that it is empty",
\r
332 privs = {worldedit=true},
\r
333 func = function(name)
\r
334 worldedit.pos1[name] = nil
\r
335 worldedit.pos2[name] = nil
\r
336 worldedit.marker_update(name)
\r
337 worldedit.set_pos[name] = nil
\r
338 --make sure the user does not try to confirm an operation after resetting pos:
\r
339 reset_pending(name)
\r
340 worldedit.player_notify(name, "region reset")
\r
344 worldedit.register_command("mark", {
\r
346 description = "Show markers at the region positions",
\r
347 privs = {worldedit=true},
\r
348 func = function(name)
\r
349 worldedit.marker_update(name)
\r
350 worldedit.player_notify(name, "region marked")
\r
354 worldedit.register_command("unmark", {
\r
356 description = "Hide markers if currently shown",
\r
357 privs = {worldedit=true},
\r
358 func = function(name)
\r
359 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
360 worldedit.pos1[name] = nil
\r
361 worldedit.pos2[name] = nil
\r
362 worldedit.marker_update(name)
\r
363 worldedit.pos1[name] = pos1
\r
364 worldedit.pos2[name] = pos2
\r
365 worldedit.player_notify(name, "region unmarked")
\r
369 worldedit.register_command("pos1", {
\r
371 description = "Set WorldEdit region position 1 to the player's location",
\r
372 privs = {worldedit=true},
\r
373 func = function(name)
\r
374 local pos = minetest.get_player_by_name(name):get_pos()
\r
375 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
376 worldedit.pos1[name] = pos
\r
377 worldedit.mark_pos1(name)
\r
378 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
382 worldedit.register_command("pos2", {
\r
384 description = "Set WorldEdit region position 2 to the player's location",
\r
385 privs = {worldedit=true},
\r
386 func = function(name)
\r
387 local pos = minetest.get_player_by_name(name):get_pos()
\r
388 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
389 worldedit.pos2[name] = pos
\r
390 worldedit.mark_pos2(name)
\r
391 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
395 worldedit.register_command("p", {
\r
396 params = "set/set1/set2/get",
\r
397 description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region",
\r
398 privs = {worldedit=true},
\r
399 parse = function(param)
\r
400 if param == "set" or param == "set1" or param == "set2" or param == "get" then
\r
403 return false, "unknown subcommand: " .. param
\r
405 func = function(name, param)
\r
406 if param == "set" then --set both WorldEdit positions
\r
407 worldedit.set_pos[name] = "pos1"
\r
408 worldedit.player_notify(name, "select positions by punching two nodes")
\r
409 elseif param == "set1" then --set WorldEdit position 1
\r
410 worldedit.set_pos[name] = "pos1only"
\r
411 worldedit.player_notify(name, "select position 1 by punching a node")
\r
412 elseif param == "set2" then --set WorldEdit position 2
\r
413 worldedit.set_pos[name] = "pos2"
\r
414 worldedit.player_notify(name, "select position 2 by punching a node")
\r
415 elseif param == "get" then --display current WorldEdit positions
\r
416 if worldedit.pos1[name] ~= nil then
\r
417 worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name]))
\r
419 worldedit.player_notify(name, "position 1 not set")
\r
421 if worldedit.pos2[name] ~= nil then
\r
422 worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name]))
\r
424 worldedit.player_notify(name, "position 2 not set")
\r
430 worldedit.register_command("fixedpos", {
\r
431 params = "set1/set2 x y z",
\r
432 description = "Set a WorldEdit region position to the position at (<x>, <y>, <z>)",
\r
433 privs = {worldedit=true},
\r
434 parse = function(param)
\r
435 local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
\r
436 if found == nil then
\r
439 return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
441 func = function(name, flag, pos)
\r
442 if flag == "set1" then
\r
443 worldedit.pos1[name] = pos
\r
444 worldedit.mark_pos1(name)
\r
445 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
446 else --flag == "set2"
\r
447 worldedit.pos2[name] = pos
\r
448 worldedit.mark_pos2(name)
\r
449 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
454 minetest.register_on_punchnode(function(pos, node, puncher)
\r
455 local name = puncher:get_player_name()
\r
456 if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
\r
457 if worldedit.set_pos[name] == "pos1" then --setting position 1
\r
458 worldedit.pos1[name] = pos
\r
459 worldedit.mark_pos1(name)
\r
460 worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
\r
461 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
462 elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
\r
463 worldedit.pos1[name] = pos
\r
464 worldedit.mark_pos1(name)
\r
465 worldedit.set_pos[name] = nil --finished setting positions
\r
466 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
467 elseif worldedit.set_pos[name] == "pos2" then --setting position 2
\r
468 worldedit.pos2[name] = pos
\r
469 worldedit.mark_pos2(name)
\r
470 worldedit.set_pos[name] = nil --finished setting positions
\r
471 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
472 elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
\r
473 worldedit.prob_pos[name] = pos
\r
474 minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]")
\r
479 worldedit.register_command("volume", {
\r
481 description = "Display the volume of the current WorldEdit region",
\r
482 privs = {worldedit=true},
\r
484 func = function(name)
\r
485 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
487 local volume = worldedit.volume(pos1, pos2)
\r
488 local abs = math.abs
\r
489 worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes ("
\r
490 .. abs(pos2.x - pos1.x) + 1 .. "*"
\r
491 .. abs(pos2.y - pos1.y) + 1 .. "*"
\r
492 .. abs(pos2.z - pos1.z) + 1 .. ")")
\r
496 worldedit.register_command("deleteblocks", {
\r
498 description = "remove all MapBlocks (16x16x16) containing the selected area from the map",
\r
499 privs = {worldedit=true},
\r
501 nodes_needed = check_region,
\r
502 func = function(name)
\r
503 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
504 local success = minetest.delete_area(pos1, pos2)
\r
506 worldedit.player_notify(name, "Area deleted.")
\r
508 worldedit.player_notify(name, "There was an error during deletion of the area.")
\r
513 worldedit.register_command("set", {
\r
515 description = "Set the current WorldEdit region to <node>",
\r
516 privs = {worldedit=true},
\r
518 parse = function(param)
\r
519 local node = worldedit.normalize_nodename(param)
\r
521 return false, "invalid node name: " .. param
\r
525 nodes_needed = check_region,
\r
526 func = function(name, node)
\r
527 local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
\r
528 worldedit.player_notify(name, count .. " nodes set")
\r
532 worldedit.register_command("param2", {
\r
533 params = "<param2>",
\r
534 description = "Set param2 of all nodes in the current WorldEdit region to <param2>",
\r
535 privs = {worldedit=true},
\r
537 parse = function(param)
\r
538 local param2 = tonumber(param)
\r
540 return false, "Invalid or missing param2 argument"
\r
541 elseif param2 < 0 or param2 > 255 then
\r
542 return false, "Param2 is out of range (must be between 0 and 255 inclusive!)"
\r
544 return true, param2
\r
546 nodes_needed = check_region,
\r
547 func = function(name, param2)
\r
548 local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
\r
549 worldedit.player_notify(name, count .. " nodes altered")
\r
553 worldedit.register_command("mix", {
\r
554 params = "<node1> [<weighting1>] [<node2> [<weighting2>]] ...",
\r
555 description = "Fill the current WorldEdit region with a random mix of <node1>, ...",
\r
556 privs = {worldedit=true},
\r
558 parse = function(param)
\r
560 for nodename in param:gmatch("[^%s]+") do
\r
561 if tonumber(nodename) ~= nil and #nodes > 0 then
\r
562 local last_node = nodes[#nodes]
\r
563 for i = 1, tonumber(nodename) do
\r
564 nodes[#nodes + 1] = last_node
\r
567 local node = worldedit.normalize_nodename(nodename)
\r
569 return false, "invalid node name: " .. nodename
\r
571 nodes[#nodes + 1] = node
\r
576 nodes_needed = check_region,
\r
577 func = function(name, nodes)
\r
578 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
579 local count = worldedit.set(pos1, pos2, nodes)
\r
580 worldedit.player_notify(name, count .. " nodes set")
\r
584 local check_replace = function(param)
\r
585 local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
\r
586 if found == nil then
\r
589 local newsearchnode = worldedit.normalize_nodename(searchnode)
\r
590 if not newsearchnode then
\r
591 return false, "invalid search node name: " .. searchnode
\r
593 local newreplacenode = worldedit.normalize_nodename(replacenode)
\r
594 if not newreplacenode then
\r
595 return false, "invalid replace node name: " .. replacenode
\r
597 return true, newsearchnode, newreplacenode
\r
600 worldedit.register_command("replace", {
\r
601 params = "<search node> <replace node>",
\r
602 description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region",
\r
603 privs = {worldedit=true},
\r
605 parse = check_replace,
\r
606 nodes_needed = check_region,
\r
607 func = function(name, search_node, replace_node)
\r
608 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
609 search_node, replace_node)
\r
610 worldedit.player_notify(name, count .. " nodes replaced")
\r
614 worldedit.register_command("replaceinverse", {
\r
615 params = "<search node> <replace node>",
\r
616 description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region",
\r
617 privs = {worldedit=true},
\r
619 parse = check_replace,
\r
620 nodes_needed = check_region,
\r
621 func = function(name, search_node, replace_node)
\r
622 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
623 search_node, replace_node, true)
\r
624 worldedit.player_notify(name, count .. " nodes replaced")
\r
628 local check_cube = function(param)
\r
629 local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
630 if found == nil then
\r
633 local node = worldedit.normalize_nodename(nodename)
\r
635 return false, "invalid node name: " .. nodename
\r
637 return true, tonumber(w), tonumber(h), tonumber(l), node
\r
640 worldedit.register_command("hollowcube", {
\r
641 params = "<width> <height> <length> <node>",
\r
642 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
643 privs = {worldedit=true},
\r
645 parse = check_cube,
\r
646 nodes_needed = function(name, w, h, l, node)
\r
649 func = function(name, w, h, l, node)
\r
650 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
\r
651 worldedit.player_notify(name, count .. " nodes added")
\r
655 worldedit.register_command("cube", {
\r
656 params = "<width> <height> <length> <node>",
\r
657 description = "Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.",
\r
658 privs = {worldedit=true},
\r
660 parse = check_cube,
\r
661 nodes_needed = function(name, w, h, l, node)
\r
664 func = function(name, w, h, l, node)
\r
665 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
\r
666 worldedit.player_notify(name, count .. " nodes added")
\r
670 local check_sphere = function(param)
\r
671 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
672 if found == nil then
\r
675 local node = worldedit.normalize_nodename(nodename)
\r
677 return false, "invalid node name: " .. nodename
\r
679 return true, tonumber(radius), node
\r
682 worldedit.register_command("hollowsphere", {
\r
683 params = "<radius> <node>",
\r
684 description = "Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
685 privs = {worldedit=true},
\r
687 parse = check_sphere,
\r
688 nodes_needed = function(name, radius, node)
\r
689 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
691 func = function(name, radius, node)
\r
692 local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
\r
693 worldedit.player_notify(name, count .. " nodes added")
\r
697 worldedit.register_command("sphere", {
\r
698 params = "<radius> <node>",
\r
699 description = "Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
700 privs = {worldedit=true},
\r
702 parse = check_sphere,
\r
703 nodes_needed = function(name, radius, node)
\r
704 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
706 func = function(name, radius, node)
\r
707 local count = worldedit.sphere(worldedit.pos1[name], radius, node)
\r
708 worldedit.player_notify(name, count .. " nodes added")
\r
712 local check_dome = function(param)
\r
713 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
714 if found == nil then
\r
717 local node = worldedit.normalize_nodename(nodename)
\r
719 return false, "invalid node name: " .. nodename
\r
721 return true, tonumber(radius), node
\r
724 worldedit.register_command("hollowdome", {
\r
725 params = "<radius> <node>",
\r
726 description = "Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
727 privs = {worldedit=true},
\r
729 parse = check_dome,
\r
730 nodes_needed = function(name, radius, node)
\r
731 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
733 func = function(name, radius, node)
\r
734 local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
\r
735 worldedit.player_notify(name, count .. " nodes added")
\r
739 worldedit.register_command("dome", {
\r
740 params = "<radius> <node>",
\r
741 description = "Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
742 privs = {worldedit=true},
\r
744 parse = check_dome,
\r
745 nodes_needed = function(name, radius, node)
\r
746 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
748 func = function(name, radius, node)
\r
749 local count = worldedit.dome(worldedit.pos1[name], radius, node)
\r
750 worldedit.player_notify(name, count .. " nodes added")
\r
754 local check_cylinder = function(param)
\r
756 local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
757 if found == nil then
\r
759 found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
\r
762 if found == nil then
\r
765 local node = worldedit.normalize_nodename(nodename)
\r
767 return false, "invalid node name: " .. nodename
\r
769 return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
\r
772 worldedit.register_command("hollowcylinder", {
\r
773 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
774 description = "Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>",
\r
775 privs = {worldedit=true},
\r
777 parse = check_cylinder,
\r
778 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
779 local radius = math.max(radius1, radius2)
\r
780 return math.ceil(math.pi * (radius ^ 2) * length)
\r
782 func = function(name, axis, length, radius1, radius2, node)
\r
783 if axis == "?" then
\r
785 axis, sign = worldedit.player_axis(name)
\r
786 length = length * sign
\r
788 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
\r
789 worldedit.player_notify(name, count .. " nodes added")
\r
793 worldedit.register_command("cylinder", {
\r
794 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
795 description = "Add cylinder at WorldEdit position 1 along the x/y/z/? axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>",
\r
796 privs = {worldedit=true},
\r
798 parse = check_cylinder,
\r
799 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
800 local radius = math.max(radius1, radius2)
\r
801 return math.ceil(math.pi * (radius ^ 2) * length)
\r
803 func = function(name, axis, length, radius1, radius2, node)
\r
804 if axis == "?" then
\r
806 axis, sign = worldedit.player_axis(name)
\r
807 length = length * sign
\r
809 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
\r
810 worldedit.player_notify(name, count .. " nodes added")
\r
814 local check_pyramid = function(param)
\r
815 local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
\r
816 if found == nil then
\r
819 local node = worldedit.normalize_nodename(nodename)
\r
821 return false, "invalid node name: " .. nodename
\r
823 return true, axis, tonumber(height), node
\r
826 worldedit.register_command("hollowpyramid", {
\r
827 params = "x/y/z/? <height> <node>",
\r
828 description = "Add hollow pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height <height>, composed of <node>",
\r
829 privs = {worldedit=true},
\r
831 parse = check_pyramid,
\r
832 nodes_needed = function(name, axis, height, node)
\r
833 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
835 func = function(name, axis, height, node)
\r
836 if axis == "?" then
\r
838 axis, sign = worldedit.player_axis(name)
\r
839 height = height * sign
\r
841 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
\r
842 worldedit.player_notify(name, count .. " nodes added")
\r
846 worldedit.register_command("pyramid", {
\r
847 params = "x/y/z/? <height> <node>",
\r
848 description = "Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height <height>, composed of <node>",
\r
849 privs = {worldedit=true},
\r
851 parse = check_pyramid,
\r
852 nodes_needed = function(name, axis, height, node)
\r
853 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
855 func = function(name, axis, height, node)
\r
856 if axis == "?" then
\r
858 axis, sign = worldedit.player_axis(name)
\r
859 height = height * sign
\r
861 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
\r
862 worldedit.player_notify(name, count .. " nodes added")
\r
866 worldedit.register_command("spiral", {
\r
867 params = "<length> <height> <space> <node>",
\r
868 description = "Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>",
\r
869 privs = {worldedit=true},
\r
871 parse = function(param)
\r
872 local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
873 if found == nil then
\r
876 local node = worldedit.normalize_nodename(nodename)
\r
878 return false, "invalid node name: " .. nodename
\r
880 return true, tonumber(length), tonumber(height), tonumber(space), node
\r
882 nodes_needed = function(name, length, height, space, node)
\r
883 return (length + space) * height -- TODO: this is not the upper bound
\r
885 func = function(name, length, height, space, node)
\r
886 local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
\r
887 worldedit.player_notify(name, count .. " nodes added")
\r
891 worldedit.register_command("copy", {
\r
892 params = "x/y/z/? <amount>",
\r
893 description = "Copy the current WorldEdit region along the x/y/z/? axis by <amount> nodes",
\r
894 privs = {worldedit=true},
\r
896 parse = function(param)
\r
897 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
898 if found == nil then
\r
901 return true, axis, tonumber(amount)
\r
903 nodes_needed = function(name, axis, amount)
\r
904 return check_region(name) * 2
\r
906 func = function(name, axis, amount)
\r
907 if axis == "?" then
\r
909 axis, sign = worldedit.player_axis(name)
\r
910 amount = amount * sign
\r
913 local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
\r
914 worldedit.player_notify(name, count .. " nodes copied")
\r
918 worldedit.register_command("move", {
\r
919 params = "x/y/z/? <amount>",
\r
920 description = "Move the current WorldEdit region along the x/y/z/? axis by <amount> nodes",
\r
921 privs = {worldedit=true},
\r
923 parse = function(param)
\r
924 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
925 if found == nil then
\r
928 return true, axis, tonumber(amount)
\r
930 nodes_needed = function(name, axis, amount)
\r
931 return check_region(name) * 2
\r
933 func = function(name, axis, amount)
\r
934 if axis == "?" then
\r
936 axis, sign = worldedit.player_axis(name)
\r
937 amount = amount * sign
\r
940 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
941 local count = worldedit.move(pos1, pos2, axis, amount)
\r
943 pos1[axis] = pos1[axis] + amount
\r
944 pos2[axis] = pos2[axis] + amount
\r
945 worldedit.marker_update(name)
\r
946 worldedit.player_notify(name, count .. " nodes moved")
\r
950 worldedit.register_command("stack", {
\r
951 params = "x/y/z/? <count>",
\r
952 description = "Stack the current WorldEdit region along the x/y/z/? axis <count> times",
\r
953 privs = {worldedit=true},
\r
955 parse = function(param)
\r
956 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
957 if found == nil then
\r
960 return true, axis, tonumber(repetitions)
\r
962 nodes_needed = function(name, axis, repetitions)
\r
963 return check_region(name) * math.abs(repetitions)
\r
965 func = function(name, axis, repetitions)
\r
966 if axis == "?" then
\r
968 axis, sign = worldedit.player_axis(name)
\r
969 repetitions = repetitions * sign
\r
972 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
973 local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
\r
974 worldedit.stack(pos1, pos2, axis, repetitions, function()
\r
975 worldedit.player_notify(name, count .. " nodes stacked")
\r
980 worldedit.register_command("stack2", {
\r
981 params = "<count> <x> <y> <z>",
\r
982 description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",
\r
983 privs = {worldedit=true},
\r
985 parse = function(param)
\r
986 local repetitions, incs = param:match("(%d+)%s*(.+)")
\r
987 if repetitions == nil then
\r
988 return false, "invalid count: " .. param
\r
990 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
\r
992 return false, "invalid increments: " .. param
\r
995 return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
997 nodes_needed = function(name, repetitions, offset)
\r
998 return check_region(name) * repetitions
\r
1000 func = function(name, repetitions, offset)
\r
1001 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1002 local count = worldedit.volume(pos1, pos2) * repetitions
\r
1003 worldedit.stack2(pos1, pos2, offset, repetitions, function()
\r
1004 worldedit.player_notify(name, count .. " nodes stacked")
\r
1010 worldedit.register_command("stretch", {
\r
1011 params = "<stretchx> <stretchy> <stretchz>",
\r
1012 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
1013 privs = {worldedit=true},
\r
1015 parse = function(param)
\r
1016 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
\r
1017 if found == nil then
\r
1020 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
\r
1021 if stretchx == 0 or stretchy == 0 or stretchz == 0 then
\r
1022 return false, "invalid scaling factors: " .. param
\r
1024 return true, stretchx, stretchy, stretchz
\r
1026 nodes_needed = function(name, stretchx, stretchy, stretchz)
\r
1027 return check_region(name) * stretchx * stretchy * stretchz
\r
1029 func = function(name, stretchx, stretchy, stretchz)
\r
1030 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1031 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
\r
1033 --reset markers to scaled positions
\r
1034 worldedit.pos1[name] = pos1
\r
1035 worldedit.pos2[name] = pos2
\r
1036 worldedit.marker_update(name)
\r
1038 worldedit.player_notify(name, count .. " nodes stretched")
\r
1042 worldedit.register_command("transpose", {
\r
1043 params = "x/y/z/? x/y/z/?",
\r
1044 description = "Transpose the current WorldEdit region along the x/y/z/? and x/y/z/? axes",
\r
1045 privs = {worldedit=true},
\r
1047 parse = function(param)
\r
1048 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
\r
1049 if found == nil then
\r
1051 elseif axis1 == axis2 then
\r
1052 return false, "invalid usage: axes must be different"
\r
1054 return true, axis1, axis2
\r
1056 nodes_needed = check_region,
\r
1057 func = function(name, axis1, axis2)
\r
1058 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1059 if axis1 == "?" then axis1 = worldedit.player_axis(name) end
\r
1060 if axis2 == "?" then axis2 = worldedit.player_axis(name) end
\r
1061 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
1063 --reset markers to transposed positions
\r
1064 worldedit.pos1[name] = pos1
\r
1065 worldedit.pos2[name] = pos2
\r
1066 worldedit.marker_update(name)
\r
1068 worldedit.player_notify(name, count .. " nodes transposed")
\r
1072 worldedit.register_command("flip", {
\r
1073 params = "x/y/z/?",
\r
1074 description = "Flip the current WorldEdit region along the x/y/z/? axis",
\r
1075 privs = {worldedit=true},
\r
1077 parse = function(param)
\r
1078 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
\r
1081 return true, param
\r
1083 nodes_needed = check_region,
\r
1084 func = function(name, param)
\r
1085 if param == "?" then param = worldedit.player_axis(name) end
\r
1086 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1087 worldedit.player_notify(name, count .. " nodes flipped")
\r
1091 worldedit.register_command("rotate", {
\r
1092 params = "<axis> <angle>",
\r
1093 description = "Rotate the current WorldEdit region around the axis <axis> by angle <angle> (90 degree increment)",
\r
1094 privs = {worldedit=true},
\r
1096 parse = function(param)
\r
1097 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
1098 if found == nil then
\r
1101 angle = tonumber(angle)
\r
1102 if angle % 90 ~= 0 or angle % 360 == 0 then
\r
1103 return false, "invalid usage: angle must be multiple of 90"
\r
1105 return true, axis, angle
\r
1107 nodes_needed = check_region,
\r
1108 func = function(name, axis, angle)
\r
1109 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1110 if axis == "?" then axis = worldedit.player_axis(name) end
\r
1111 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
\r
1113 --reset markers to rotated positions
\r
1114 worldedit.pos1[name] = pos1
\r
1115 worldedit.pos2[name] = pos2
\r
1116 worldedit.marker_update(name)
\r
1118 worldedit.player_notify(name, count .. " nodes rotated")
\r
1122 worldedit.register_command("orient", {
\r
1123 params = "<angle>",
\r
1124 description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",
\r
1125 privs = {worldedit=true},
\r
1127 parse = function(param)
\r
1128 local found, _, angle = param:find("^([+-]?%d+)$")
\r
1129 if found == nil then
\r
1132 angle = tonumber(angle)
\r
1133 if angle % 90 ~= 0 then
\r
1134 return false, "invalid usage: angle must be multiple of 90"
\r
1136 return true, angle
\r
1138 nodes_needed = check_region,
\r
1139 func = function(name, angle)
\r
1140 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
\r
1141 worldedit.player_notify(name, count .. " nodes oriented")
\r
1145 worldedit.register_command("fixlight", {
\r
1147 description = "Fix the lighting in the current WorldEdit region",
\r
1148 privs = {worldedit=true},
\r
1150 nodes_needed = check_region,
\r
1151 func = function(name)
\r
1152 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
\r
1153 worldedit.player_notify(name, count .. " nodes updated")
\r
1157 worldedit.register_command("drain", {
\r
1159 description = "Remove any fluid node within the current WorldEdit region",
\r
1160 privs = {worldedit=true},
\r
1162 nodes_needed = check_region,
\r
1163 func = function(name)
\r
1164 -- TODO: make an API function for this
\r
1166 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1167 for x = pos1.x, pos2.x do
\r
1168 for y = pos1.y, pos2.y do
\r
1169 for z = pos1.z, pos2.z do
\r
1170 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1171 local d = minetest.registered_nodes[n]
\r
1172 if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then
\r
1173 minetest.remove_node({x=x, y=y, z=z})
\r
1179 worldedit.player_notify(name, count .. " nodes updated")
\r
1183 local clearcut_cache
\r
1185 local function clearcut(pos1, pos2)
\r
1186 -- decide which nodes we consider plants
\r
1187 if clearcut_cache == nil then
\r
1188 clearcut_cache = {}
\r
1189 for name, def in pairs(minetest.registered_nodes) do
\r
1190 local groups = def.groups or {}
\r
1192 -- the groups say so
\r
1193 groups.flower or groups.grass or groups.flora or groups.plant or
\r
1194 groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
\r
1195 -- drawtype heuristic
\r
1196 (def.is_ground_content and def.buildable_to and
\r
1197 (def.sunlight_propagates or not def.walkable)
\r
1198 and def.drawtype == "plantlike") or
\r
1199 -- if it's flammable, it probably needs to go too
\r
1200 (def.is_ground_content and not def.walkable and groups.flammable)
\r
1202 clearcut_cache[name] = true
\r
1206 local plants = clearcut_cache
\r
1211 for x = pos1.x, pos2.x do
\r
1212 for z = pos1.z, pos2.z do
\r
1215 -- first pass: remove floating nodes that would be left over
\r
1216 for y = pos1.y, pos2.y do
\r
1217 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1222 local def = minetest.registered_nodes[n] or {}
\r
1223 local groups = def.groups or {}
\r
1224 if groups.attached_node or (def.buildable_to and groups.falling_node) then
\r
1225 minetest.remove_node({x=x, y=y, z=z})
\r
1233 -- second pass: remove plants, top-to-bottom to avoid item drops
\r
1235 for y = pos2.y, pos1.y, -1 do
\r
1236 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1238 minetest.remove_node({x=x, y=y, z=z})
\r
1249 worldedit.register_command("clearcut", {
\r
1251 description = "Remove any plant, tree or foilage-like nodes in the selected region",
\r
1252 privs = {worldedit=true},
\r
1254 nodes_needed = check_region,
\r
1255 func = function(name)
\r
1256 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1257 local count = clearcut(pos1, pos2)
\r
1258 worldedit.player_notify(name, count .. " nodes removed")
\r
1262 worldedit.register_command("hide", {
\r
1264 description = "Hide all nodes in the current WorldEdit region non-destructively",
\r
1265 privs = {worldedit=true},
\r
1267 nodes_needed = check_region,
\r
1268 func = function(name)
\r
1269 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
\r
1270 worldedit.player_notify(name, count .. " nodes hidden")
\r
1274 worldedit.register_command("suppress", {
\r
1275 params = "<node>",
\r
1276 description = "Suppress all <node> in the current WorldEdit region non-destructively",
\r
1277 privs = {worldedit=true},
\r
1279 parse = function(param)
\r
1280 local node = worldedit.normalize_nodename(param)
\r
1282 return false, "invalid node name: " .. param
\r
1286 nodes_needed = check_region,
\r
1287 func = function(name, node)
\r
1288 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1289 worldedit.player_notify(name, count .. " nodes suppressed")
\r
1293 worldedit.register_command("highlight", {
\r
1294 params = "<node>",
\r
1295 description = "Highlight <node> in the current WorldEdit region by hiding everything else non-destructively",
\r
1296 privs = {worldedit=true},
\r
1298 parse = function(param)
\r
1299 local node = worldedit.normalize_nodename(param)
\r
1301 return false, "invalid node name: " .. param
\r
1305 nodes_needed = check_region,
\r
1306 func = function(name, node)
\r
1307 local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1308 worldedit.player_notify(name, count .. " nodes highlighted")
\r
1312 worldedit.register_command("restore", {
\r
1314 description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",
\r
1315 privs = {worldedit=true},
\r
1317 nodes_needed = check_region,
\r
1318 func = function(name)
\r
1319 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
\r
1320 worldedit.player_notify(name, count .. " nodes restored")
\r
1324 local function detect_misaligned_schematic(name, pos1, pos2)
\r
1325 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
1326 -- Check that allocate/save can position the schematic correctly
\r
1327 -- The expected behaviour is that the (0,0,0) corner of the schematic stays
\r
1328 -- sat pos1, this only works when the minimum position is actually present
\r
1329 -- in the schematic.
\r
1330 local node = minetest.get_node(pos1)
\r
1331 local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
\r
1332 if not have_node_at_origin then
\r
1333 worldedit.player_notify(name,
\r
1334 "Warning: The schematic contains excessive free space and WILL be "..
\r
1335 "misaligned when allocated or loaded. To avoid this, shrink your "..
\r
1336 "area to cover exactly the nodes to be saved."
\r
1341 worldedit.register_command("save", {
\r
1342 params = "<file>",
\r
1343 description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
\r
1344 privs = {worldedit=true},
\r
1346 parse = function(param)
\r
1347 if param == "" then
\r
1350 if not check_filename(param) then
\r
1351 return false, "Disallowed file name: " .. param
\r
1353 return true, param
\r
1355 nodes_needed = check_region,
\r
1356 func = function(name, param)
\r
1357 local result, count = worldedit.serialize(worldedit.pos1[name],
\r
1358 worldedit.pos2[name])
\r
1359 detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
\r
1361 local path = minetest.get_worldpath() .. "/schems"
\r
1362 -- Create directory if it does not already exist
\r
1363 minetest.mkdir(path)
\r
1365 local filename = path .. "/" .. param .. ".we"
\r
1366 local file, err = io.open(filename, "wb")
\r
1367 if err ~= nil then
\r
1368 worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")
\r
1371 file:write(result)
\r
1375 worldedit.player_notify(name, count .. " nodes saved")
\r
1379 worldedit.register_command("allocate", {
\r
1380 params = "<file>",
\r
1381 description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",
\r
1382 privs = {worldedit=true},
\r
1384 parse = function(param)
\r
1385 if param == "" then
\r
1388 if not check_filename(param) then
\r
1389 return false, "Disallowed file name: " .. param
\r
1391 return true, param
\r
1393 func = function(name, param)
\r
1394 local pos = worldedit.pos1[name]
\r
1396 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"
\r
1397 local file, err = io.open(filename, "rb")
\r
1398 if err ~= nil then
\r
1399 worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")
\r
1402 local value = file:read("*a")
\r
1405 local version = worldedit.read_header(value)
\r
1406 if version == nil or version == 0 then
\r
1407 worldedit.player_notify(name, "File is invalid!")
\r
1409 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1410 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1413 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
\r
1415 if not nodepos1 then
\r
1416 worldedit.player_notify(name, "Schematic empty, nothing allocated")
\r
1420 worldedit.pos1[name] = nodepos1
\r
1421 worldedit.pos2[name] = nodepos2
\r
1422 worldedit.marker_update(name)
\r
1424 worldedit.player_notify(name, count .. " nodes allocated")
\r
1428 worldedit.register_command("load", {
\r
1429 params = "<file>",
\r
1430 description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",
\r
1431 privs = {worldedit=true},
\r
1433 parse = function(param)
\r
1434 if param == "" then
\r
1437 if not check_filename(param) then
\r
1438 return false, "Disallowed file name: " .. param
\r
1440 return true, param
\r
1442 func = function(name, param)
\r
1443 local pos = worldedit.pos1[name]
\r
1445 if param == "" then
\r
1446 worldedit.player_notify(name, "invalid usage: " .. param)
\r
1449 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then
\r
1450 worldedit.player_notify(name, "invalid file name: " .. param)
\r
1454 --find the file in the world path
\r
1455 local testpaths = {
\r
1456 minetest.get_worldpath() .. "/schems/" .. param,
\r
1457 minetest.get_worldpath() .. "/schems/" .. param .. ".we",
\r
1458 minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
\r
1461 for index, path in ipairs(testpaths) do
\r
1462 file, err = io.open(path, "rb")
\r
1468 worldedit.player_notify(name, "could not open file \"" .. param .. "\"")
\r
1471 local value = file:read("*a")
\r
1474 local version = worldedit.read_header(value)
\r
1475 if version == nil or version == 0 then
\r
1476 worldedit.player_notify(name, "File is invalid!")
\r
1478 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1479 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1483 local count = worldedit.deserialize(pos, value)
\r
1485 worldedit.player_notify(name, count .. " nodes loaded")
\r
1489 worldedit.register_command("lua", {
\r
1490 params = "<code>",
\r
1491 description = "Executes <code> as a Lua chunk in the global namespace",
\r
1492 privs = {worldedit=true, server=true},
\r
1493 parse = function(param)
\r
1494 return true, param
\r
1496 func = function(name, param)
\r
1497 local err = worldedit.lua(param)
\r
1499 worldedit.player_notify(name, "code error: " .. err)
\r
1500 minetest.log("action", name.." tried to execute "..param)
\r
1502 worldedit.player_notify(name, "code successfully executed", false)
\r
1503 minetest.log("action", name.." executed "..param)
\r
1508 worldedit.register_command("luatransform", {
\r
1509 params = "<code>",
\r
1510 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
1511 privs = {worldedit=true, server=true},
\r
1513 parse = function(param)
\r
1514 return true, param
\r
1516 nodes_needed = check_region,
\r
1517 func = function(name, param)
\r
1518 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1520 worldedit.player_notify(name, "code error: " .. err, false)
\r
1521 minetest.log("action", name.." tried to execute luatransform "..param)
\r
1523 worldedit.player_notify(name, "code successfully executed", false)
\r
1524 minetest.log("action", name.." executed luatransform "..param)
\r
1529 worldedit.register_command("mtschemcreate", {
\r
1530 params = "<file>",
\r
1531 description = "Save the current WorldEdit region using the Minetest "..
\r
1532 "Schematic format to \"(world folder)/schems/<filename>.mts\"",
\r
1533 privs = {worldedit=true},
\r
1535 parse = function(param)
\r
1536 if param == "" then
\r
1539 if not check_filename(param) then
\r
1540 return false, "Disallowed file name: " .. param
\r
1542 return true, param
\r
1544 nodes_needed = check_region,
\r
1545 func = function(name, param)
\r
1546 local path = minetest.get_worldpath() .. "/schems"
\r
1547 -- Create directory if it does not already exist
\r
1548 minetest.mkdir(path)
\r
1550 local filename = path .. "/" .. param .. ".mts"
\r
1551 local ret = minetest.create_schematic(worldedit.pos1[name],
\r
1552 worldedit.pos2[name], worldedit.prob_list[name],
\r
1554 if ret == nil then
\r
1555 worldedit.player_notify(name, "Failed to create Minetest schematic")
\r
1557 worldedit.player_notify(name, "Saved Minetest schematic to " .. param)
\r
1559 worldedit.prob_list[name] = {}
\r
1563 worldedit.register_command("mtschemplace", {
\r
1564 params = "<file>",
\r
1565 description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",
\r
1566 privs = {worldedit=true},
\r
1568 parse = function(param)
\r
1569 if param == "" then
\r
1572 if not check_filename(param) then
\r
1573 return false, "Disallowed file name: " .. param
\r
1575 return true, param
\r
1577 func = function(name, param)
\r
1578 local pos = worldedit.pos1[name]
\r
1580 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"
\r
1581 if minetest.place_schematic(pos, path) == nil then
\r
1582 worldedit.player_notify(name, "failed to place Minetest schematic")
\r
1584 worldedit.player_notify(name, "placed Minetest schematic " .. param ..
\r
1585 " at " .. minetest.pos_to_string(pos))
\r
1590 worldedit.register_command("mtschemprob", {
\r
1591 params = "start/finish/get",
\r
1592 description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",
\r
1593 privs = {worldedit=true},
\r
1594 parse = function(param)
\r
1595 if param ~= "start" and param ~= "finish" and param ~= "get" then
\r
1596 return false, "unknown subcommand: " .. param
\r
1598 return true, param
\r
1600 func = function(name, param)
\r
1601 if param == "start" then --start probability setting
\r
1602 worldedit.set_pos[name] = "prob"
\r
1603 worldedit.prob_list[name] = {}
\r
1604 worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")
\r
1605 elseif param == "finish" then --finish probability setting
\r
1606 worldedit.set_pos[name] = nil
\r
1607 worldedit.player_notify(name, "finished Minetest schematic probability selection")
\r
1608 elseif param == "get" then --get all nodes that had probabilities set on them
\r
1610 local problist = worldedit.prob_list[name]
\r
1611 if problist == nil then
\r
1614 for k,v in pairs(problist) do
\r
1615 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
\r
1616 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
\r
1618 worldedit.player_notify(name, "currently set node probabilities:")
\r
1619 worldedit.player_notify(name, text)
\r
1624 minetest.register_on_player_receive_fields(function(player, formname, fields)
\r
1625 if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then
\r
1626 local name = player:get_player_name()
\r
1627 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
\r
1628 local index = table.getn(worldedit.prob_list[name]) + 1
\r
1629 worldedit.prob_list[name][index] = prob_entry
\r
1633 worldedit.register_command("clearobjects", {
\r
1635 description = "Clears all objects within the WorldEdit region",
\r
1636 privs = {worldedit=true},
\r
1638 nodes_needed = check_region,
\r
1639 func = function(name)
\r
1640 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
\r
1641 worldedit.player_notify(name, count .. " objects cleared")
\r