1 minetest.register_privilege("worldedit", "Can use WorldEdit commands")
\r
6 worldedit.set_pos = {}
\r
7 worldedit.inspect = {}
\r
8 worldedit.prob_pos = {}
\r
9 worldedit.prob_list = {}
\r
13 local safe_region, reset_pending = dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua")
\r
15 function worldedit.player_notify(name, message)
\r
16 minetest.chat_send_player(name, "WorldEdit -!- " .. message, false)
\r
19 worldedit.registered_commands = {}
\r
21 local function chatcommand_handler(cmd_name, name, param)
\r
22 local def = assert(worldedit.registered_commands[cmd_name])
\r
24 if def.require_pos == 2 then
\r
25 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
26 if pos1 == nil or pos2 == nil then
\r
27 worldedit.player_notify(name, "no region selected")
\r
30 elseif def.require_pos == 1 then
\r
31 local pos1 = worldedit.pos1[name]
\r
33 worldedit.player_notify(name, "no position 1 selected")
\r
38 local parsed = {def.parse(param)}
\r
39 local success = table.remove(parsed, 1)
\r
41 worldedit.player_notify(name, parsed[1] or "invalid usage")
\r
45 if def.nodes_needed then
\r
46 local count = def.nodes_needed(name, unpack(parsed))
\r
47 safe_region(name, count, function()
\r
48 local success, msg = def.func(name, unpack(parsed))
\r
50 minetest.chat_send_player(name, msg)
\r
54 -- no "safe region" check
\r
55 local success, msg = def.func(name, unpack(parsed))
\r
57 minetest.chat_send_player(name, msg)
\r
62 -- Registers a chatcommand for WorldEdit
\r
63 -- name = "about" -- Name of the chat command (without any /)
\r
65 -- privs = {}, -- Privileges needed
\r
66 -- params = "", -- Human readable parameter list (optional)
\r
67 -- -- setting params = "" will automatically provide a parse() if not given
\r
68 -- description = "", -- Description
\r
69 -- require_pos = 0, -- Number of positions required to be set (optional)
\r
70 -- parse = function(param)
\r
71 -- return true, foo, bar, ...
\r
75 -- return false, "error message"
\r
77 -- nodes_needed = function(name, foo, bar, ...), -- (optional)
\r
80 -- func = function(name, foo, bar, ...)
\r
81 -- return success, "message"
\r
84 function worldedit.register_command(name, def)
\r
85 local def = table.copy(def)
\r
86 assert(name and #name > 0)
\r
88 def.require_pos = def.require_pos or 0
\r
89 assert(def.require_pos >= 0 and def.require_pos < 3)
\r
90 if def.params == "" and not def.parse then
\r
91 def.parse = function(param) return true end
\r
95 assert(def.nodes_needed == nil or type(def.nodes_needed) == "function")
\r
99 --[[if def.require_pos == 2 and not def.nodes_needed then
\r
100 minetest.log("warning", "//" .. name .. " might be missing nodes_needed")
\r
103 minetest.register_chatcommand("/" .. name, {
\r
105 params = def.params,
\r
106 description = def.description,
\r
107 func = function(player_name, param)
\r
108 return chatcommand_handler(name, player_name, param)
\r
111 worldedit.registered_commands[name] = def
\r
116 dofile(minetest.get_modpath("worldedit_commands") .. "/cuboid.lua")
\r
117 dofile(minetest.get_modpath("worldedit_commands") .. "/mark.lua")
\r
118 dofile(minetest.get_modpath("worldedit_commands") .. "/wand.lua")
\r
121 local function check_region(name)
\r
122 return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
\r
125 -- Strips any kind of escape codes (translation, colors) from a string
\r
126 -- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777
\r
127 local function strip_escapes(input)
\r
128 local s = function(idx) return input:sub(idx, idx) end
\r
131 while i <= #input do
\r
132 if s(i) == "\027" then -- escape sequence
\r
134 if s(i) == "(" then -- enclosed
\r
136 while i <= #input and s(i) ~= ")" do
\r
137 if s(i) == "\\" then
\r
149 --print(("%q -> %q"):format(input, out))
\r
153 local function string_endswith(full, part)
\r
154 return full:find(part, 1, true) == #full - #part + 1
\r
157 -- normalizes node "description" `nodename`, returning a string (or nil)
\r
158 worldedit.normalize_nodename = function(nodename)
\r
159 nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces
\r
160 if nodename == "" then return nil end
\r
162 local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
\r
163 if minetest.registered_nodes[fullname] or fullname == "air" then -- full name
\r
166 nodename = nodename:lower()
\r
167 for key, value in pairs(minetest.registered_nodes) do
\r
168 if string_endswith(key, ":" .. nodename) then -- matches name (w/o mod part)
\r
172 for key, value in pairs(minetest.registered_nodes) do
\r
173 local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower()
\r
174 if desc == nodename then -- matches description
\r
177 if desc == nodename .. " block" then
\r
178 -- fuzzy description match (e.g. "Steel" == "Steel Block")
\r
184 for key, value in pairs(minetest.registered_nodes) do
\r
185 if value.description:lower():find(nodename, 1, true) ~= nil then
\r
186 if match ~= nil then
\r
189 match = key -- substring description match (only if no ambiguities)
\r
195 -- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1)
\r
196 function worldedit.player_axis(name)
\r
197 local dir = minetest.get_player_by_name(name):get_look_dir()
\r
198 local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z)
\r
201 return "x", dir.x > 0 and 1 or -1
\r
204 return "y", dir.y > 0 and 1 or -1
\r
206 return "z", dir.z > 0 and 1 or -1
\r
209 local function check_filename(name)
\r
210 return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil
\r
214 worldedit.register_command("about", {
\r
217 description = "Get information about the WorldEdit mod",
\r
218 func = function(name)
\r
219 worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string..
\r
220 " is available on this server. Type //help to get a list of "..
\r
221 "commands, or get more information at "..
\r
222 "https://github.com/Uberi/Minetest-WorldEdit")
\r
226 -- mostly copied from builtin/chatcommands.lua with minor modifications
\r
227 worldedit.register_command("help", {
\r
229 params = "[all/<cmd>]",
\r
230 description = "Get help for WorldEdit commands",
\r
231 parse = function(param)
\r
234 func = function(name, param)
\r
235 local function format_help_line(cmd, def)
\r
236 local msg = minetest.colorize("#00ffff", "//"..cmd)
\r
237 if def.params and def.params ~= "" then
\r
238 msg = msg .. " " .. def.params
\r
240 if def.description and def.description ~= "" then
\r
241 msg = msg .. ": " .. def.description
\r
246 if not minetest.check_player_privs(name, "worldedit") then
\r
247 return false, "You are not allowed to use any WorldEdit commands."
\r
249 if param == "" then
\r
252 for cmd, def in pairs(worldedit.registered_commands) do
\r
253 if minetest.check_player_privs(name, def.privs) then
\r
254 cmds[#cmds + 1] = cmd
\r
258 return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"
\r
259 .. "Use '//help <cmd>' to get more information,"
\r
260 .. " or '//help all' to list everything."
\r
261 elseif param == "all" then
\r
263 for cmd, def in pairs(worldedit.registered_commands) do
\r
264 if minetest.check_player_privs(name, def.privs) then
\r
265 cmds[#cmds + 1] = format_help_line(cmd, def)
\r
269 return true, "Available commands:\n"..table.concat(cmds, "\n")
\r
271 local def = worldedit.registered_commands[param]
\r
273 return false, "Command not available: " .. param
\r
275 return true, format_help_line(param, def)
\r
281 worldedit.register_command("inspect", {
\r
282 params = "[on/off/1/0/true/false/yes/no/enable/disable]",
\r
283 description = "Enable or disable node inspection",
\r
284 privs = {worldedit=true},
\r
285 parse = function(param)
\r
286 if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
\r
288 elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
\r
293 func = function(name, enable)
\r
295 worldedit.inspect[name] = true
\r
296 local axis, sign = worldedit.player_axis(name)
\r
297 worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis",
\r
298 name, axis .. (sign > 0 and "+" or "-")))
\r
300 worldedit.inspect[name] = nil
\r
301 worldedit.player_notify(name, "inspector: inspection disabled")
\r
306 local function get_node_rlight(pos)
\r
307 local vecs = { -- neighboring nodes
\r
308 {x= 1, y= 0, z= 0},
\r
309 {x=-1, y= 0, z= 0},
\r
310 {x= 0, y= 1, z= 0},
\r
311 {x= 0, y=-1, z= 0},
\r
312 {x= 0, y= 0, z= 1},
\r
313 {x= 0, y= 0, z=-1},
\r
316 for _, v in ipairs(vecs) do
\r
317 ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
\r
322 minetest.register_on_punchnode(function(pos, node, puncher)
\r
323 local name = puncher:get_player_name()
\r
324 if worldedit.inspect[name] then
\r
325 local axis, sign = worldedit.player_axis(name)
\r
326 local message = string.format("inspector: %s at %s (param1=%d, param2=%d, received light=%d) punched facing the %s axis",
\r
327 node.name, minetest.pos_to_string(pos), node.param1, node.param2, get_node_rlight(pos), axis .. (sign > 0 and "+" or "-"))
\r
328 worldedit.player_notify(name, message)
\r
332 worldedit.register_command("reset", {
\r
334 description = "Reset the region so that it is empty",
\r
335 privs = {worldedit=true},
\r
336 func = function(name)
\r
337 worldedit.pos1[name] = nil
\r
338 worldedit.pos2[name] = nil
\r
339 worldedit.marker_update(name)
\r
340 worldedit.set_pos[name] = nil
\r
341 --make sure the user does not try to confirm an operation after resetting pos:
\r
342 reset_pending(name)
\r
343 worldedit.player_notify(name, "region reset")
\r
347 worldedit.register_command("mark", {
\r
349 description = "Show markers at the region positions",
\r
350 privs = {worldedit=true},
\r
351 func = function(name)
\r
352 worldedit.marker_update(name)
\r
353 worldedit.player_notify(name, "region marked")
\r
357 worldedit.register_command("unmark", {
\r
359 description = "Hide markers if currently shown",
\r
360 privs = {worldedit=true},
\r
361 func = function(name)
\r
362 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
363 worldedit.pos1[name] = nil
\r
364 worldedit.pos2[name] = nil
\r
365 worldedit.marker_update(name)
\r
366 worldedit.pos1[name] = pos1
\r
367 worldedit.pos2[name] = pos2
\r
368 worldedit.player_notify(name, "region unmarked")
\r
372 worldedit.register_command("pos1", {
\r
374 description = "Set WorldEdit region position 1 to the player's location",
\r
375 privs = {worldedit=true},
\r
376 func = function(name)
\r
377 local pos = minetest.get_player_by_name(name):get_pos()
\r
378 pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5)
\r
379 worldedit.pos1[name] = pos
\r
380 worldedit.mark_pos1(name)
\r
381 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
385 worldedit.register_command("pos2", {
\r
387 description = "Set WorldEdit region position 2 to the player's location",
\r
388 privs = {worldedit=true},
\r
389 func = function(name)
\r
390 local pos = minetest.get_player_by_name(name):get_pos()
\r
391 pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5)
\r
392 worldedit.pos2[name] = pos
\r
393 worldedit.mark_pos2(name)
\r
394 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
398 worldedit.register_command("p", {
\r
399 params = "set/set1/set2/get",
\r
400 description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region",
\r
401 privs = {worldedit=true},
\r
402 parse = function(param)
\r
403 if param == "set" or param == "set1" or param == "set2" or param == "get" then
\r
406 return false, "unknown subcommand: " .. param
\r
408 func = function(name, param)
\r
409 if param == "set" then --set both WorldEdit positions
\r
410 worldedit.set_pos[name] = "pos1"
\r
411 worldedit.player_notify(name, "select positions by punching two nodes")
\r
412 elseif param == "set1" then --set WorldEdit position 1
\r
413 worldedit.set_pos[name] = "pos1only"
\r
414 worldedit.player_notify(name, "select position 1 by punching a node")
\r
415 elseif param == "set2" then --set WorldEdit position 2
\r
416 worldedit.set_pos[name] = "pos2"
\r
417 worldedit.player_notify(name, "select position 2 by punching a node")
\r
418 elseif param == "get" then --display current WorldEdit positions
\r
419 if worldedit.pos1[name] ~= nil then
\r
420 worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name]))
\r
422 worldedit.player_notify(name, "position 1 not set")
\r
424 if worldedit.pos2[name] ~= nil then
\r
425 worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name]))
\r
427 worldedit.player_notify(name, "position 2 not set")
\r
433 worldedit.register_command("fixedpos", {
\r
434 params = "set1/set2 <x> <y> <z>",
\r
435 description = "Set a WorldEdit region position to the position at (<x>, <y>, <z>)",
\r
436 privs = {worldedit=true},
\r
437 parse = function(param)
\r
438 local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
\r
439 if found == nil then
\r
442 return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
444 func = function(name, flag, pos)
\r
445 if flag == "set1" then
\r
446 worldedit.pos1[name] = pos
\r
447 worldedit.mark_pos1(name)
\r
448 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
449 else --flag == "set2"
\r
450 worldedit.pos2[name] = pos
\r
451 worldedit.mark_pos2(name)
\r
452 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
457 minetest.register_on_punchnode(function(pos, node, puncher)
\r
458 local name = puncher:get_player_name()
\r
459 if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
\r
460 if worldedit.set_pos[name] == "pos1" then --setting position 1
\r
461 worldedit.pos1[name] = pos
\r
462 worldedit.mark_pos1(name)
\r
463 worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
\r
464 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
465 elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
\r
466 worldedit.pos1[name] = pos
\r
467 worldedit.mark_pos1(name)
\r
468 worldedit.set_pos[name] = nil --finished setting positions
\r
469 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))
\r
470 elseif worldedit.set_pos[name] == "pos2" then --setting position 2
\r
471 worldedit.pos2[name] = pos
\r
472 worldedit.mark_pos2(name)
\r
473 worldedit.set_pos[name] = nil --finished setting positions
\r
474 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))
\r
475 elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
\r
476 worldedit.prob_pos[name] = pos
\r
477 minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]")
\r
482 worldedit.register_command("volume", {
\r
484 description = "Display the volume of the current WorldEdit region",
\r
485 privs = {worldedit=true},
\r
487 func = function(name)
\r
488 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
490 local volume = worldedit.volume(pos1, pos2)
\r
491 local abs = math.abs
\r
492 worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes ("
\r
493 .. abs(pos2.x - pos1.x) + 1 .. "*"
\r
494 .. abs(pos2.y - pos1.y) + 1 .. "*"
\r
495 .. abs(pos2.z - pos1.z) + 1 .. ")")
\r
499 worldedit.register_command("deleteblocks", {
\r
501 description = "remove all MapBlocks (16x16x16) containing the selected area from the map",
\r
502 privs = {worldedit=true},
\r
504 nodes_needed = check_region,
\r
505 func = function(name)
\r
506 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
507 local success = minetest.delete_area(pos1, pos2)
\r
509 worldedit.player_notify(name, "Area deleted.")
\r
511 worldedit.player_notify(name, "There was an error during deletion of the area.")
\r
516 worldedit.register_command("set", {
\r
518 description = "Set the current WorldEdit region to <node>",
\r
519 privs = {worldedit=true},
\r
521 parse = function(param)
\r
522 local node = worldedit.normalize_nodename(param)
\r
524 return false, "invalid node name: " .. param
\r
528 nodes_needed = check_region,
\r
529 func = function(name, node)
\r
530 local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
\r
531 worldedit.player_notify(name, count .. " nodes set")
\r
535 worldedit.register_command("param2", {
\r
536 params = "<param2>",
\r
537 description = "Set param2 of all nodes in the current WorldEdit region to <param2>",
\r
538 privs = {worldedit=true},
\r
540 parse = function(param)
\r
541 local param2 = tonumber(param)
\r
544 elseif param2 < 0 or param2 > 255 then
\r
545 return false, "Param2 is out of range (must be between 0 and 255 inclusive!)"
\r
547 return true, param2
\r
549 nodes_needed = check_region,
\r
550 func = function(name, param2)
\r
551 local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
\r
552 worldedit.player_notify(name, count .. " nodes altered")
\r
556 worldedit.register_command("mix", {
\r
557 params = "<node1> [count1] <node2> [count2] ...",
\r
558 description = "Fill the current WorldEdit region with a random mix of <node1>, ...",
\r
559 privs = {worldedit=true},
\r
561 parse = function(param)
\r
563 for nodename in param:gmatch("[^%s]+") do
\r
564 if tonumber(nodename) ~= nil and #nodes > 0 then
\r
565 local last_node = nodes[#nodes]
\r
566 for i = 1, tonumber(nodename) do
\r
567 nodes[#nodes + 1] = last_node
\r
570 local node = worldedit.normalize_nodename(nodename)
\r
572 return false, "invalid node name: " .. nodename
\r
574 nodes[#nodes + 1] = node
\r
579 nodes_needed = check_region,
\r
580 func = function(name, nodes)
\r
581 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
582 local count = worldedit.set(pos1, pos2, nodes)
\r
583 worldedit.player_notify(name, count .. " nodes set")
\r
587 local check_replace = function(param)
\r
588 local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
\r
589 if found == nil then
\r
592 local newsearchnode = worldedit.normalize_nodename(searchnode)
\r
593 if not newsearchnode then
\r
594 return false, "invalid search node name: " .. searchnode
\r
596 local newreplacenode = worldedit.normalize_nodename(replacenode)
\r
597 if not newreplacenode then
\r
598 return false, "invalid replace node name: " .. replacenode
\r
600 return true, newsearchnode, newreplacenode
\r
603 worldedit.register_command("replace", {
\r
604 params = "<search node> <replace node>",
\r
605 description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region",
\r
606 privs = {worldedit=true},
\r
608 parse = check_replace,
\r
609 nodes_needed = check_region,
\r
610 func = function(name, search_node, replace_node)
\r
611 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
612 search_node, replace_node)
\r
613 worldedit.player_notify(name, count .. " nodes replaced")
\r
617 worldedit.register_command("replaceinverse", {
\r
618 params = "<search node> <replace node>",
\r
619 description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region",
\r
620 privs = {worldedit=true},
\r
622 parse = check_replace,
\r
623 nodes_needed = check_region,
\r
624 func = function(name, search_node, replace_node)
\r
625 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
\r
626 search_node, replace_node, true)
\r
627 worldedit.player_notify(name, count .. " nodes replaced")
\r
631 local check_cube = function(param)
\r
632 local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
633 if found == nil then
\r
636 local node = worldedit.normalize_nodename(nodename)
\r
638 return false, "invalid node name: " .. nodename
\r
640 return true, tonumber(w), tonumber(h), tonumber(l), node
\r
643 worldedit.register_command("hollowcube", {
\r
644 params = "<width> <height> <length> <node>",
\r
645 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
646 privs = {worldedit=true},
\r
648 parse = check_cube,
\r
649 nodes_needed = function(name, w, h, l, node)
\r
652 func = function(name, w, h, l, node)
\r
653 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
\r
654 worldedit.player_notify(name, count .. " nodes added")
\r
658 worldedit.register_command("cube", {
\r
659 params = "<width> <height> <length> <node>",
\r
660 description = "Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.",
\r
661 privs = {worldedit=true},
\r
663 parse = check_cube,
\r
664 nodes_needed = function(name, w, h, l, node)
\r
667 func = function(name, w, h, l, node)
\r
668 local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
\r
669 worldedit.player_notify(name, count .. " nodes added")
\r
673 local check_sphere = function(param)
\r
674 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
675 if found == nil then
\r
678 local node = worldedit.normalize_nodename(nodename)
\r
680 return false, "invalid node name: " .. nodename
\r
682 return true, tonumber(radius), node
\r
685 worldedit.register_command("hollowsphere", {
\r
686 params = "<radius> <node>",
\r
687 description = "Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
688 privs = {worldedit=true},
\r
690 parse = check_sphere,
\r
691 nodes_needed = function(name, radius, node)
\r
692 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
694 func = function(name, radius, node)
\r
695 local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
\r
696 worldedit.player_notify(name, count .. " nodes added")
\r
700 worldedit.register_command("sphere", {
\r
701 params = "<radius> <node>",
\r
702 description = "Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
703 privs = {worldedit=true},
\r
705 parse = check_sphere,
\r
706 nodes_needed = function(name, radius, node)
\r
707 return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
\r
709 func = function(name, radius, node)
\r
710 local count = worldedit.sphere(worldedit.pos1[name], radius, node)
\r
711 worldedit.player_notify(name, count .. " nodes added")
\r
715 local check_dome = function(param)
\r
716 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
\r
717 if found == nil then
\r
720 local node = worldedit.normalize_nodename(nodename)
\r
722 return false, "invalid node name: " .. nodename
\r
724 return true, tonumber(radius), node
\r
727 worldedit.register_command("hollowdome", {
\r
728 params = "<radius> <node>",
\r
729 description = "Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
730 privs = {worldedit=true},
\r
732 parse = check_dome,
\r
733 nodes_needed = function(name, radius, node)
\r
734 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
736 func = function(name, radius, node)
\r
737 local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
\r
738 worldedit.player_notify(name, count .. " nodes added")
\r
742 worldedit.register_command("dome", {
\r
743 params = "<radius> <node>",
\r
744 description = "Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",
\r
745 privs = {worldedit=true},
\r
747 parse = check_dome,
\r
748 nodes_needed = function(name, radius, node)
\r
749 return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
\r
751 func = function(name, radius, node)
\r
752 local count = worldedit.dome(worldedit.pos1[name], radius, node)
\r
753 worldedit.player_notify(name, count .. " nodes added")
\r
757 local check_cylinder = function(param)
\r
759 local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
760 if found == nil then
\r
762 found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
\r
765 if found == nil then
\r
768 local node = worldedit.normalize_nodename(nodename)
\r
770 return false, "invalid node name: " .. nodename
\r
772 return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
\r
775 worldedit.register_command("hollowcylinder", {
\r
776 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
777 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
778 privs = {worldedit=true},
\r
780 parse = check_cylinder,
\r
781 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
782 local radius = math.max(radius1, radius2)
\r
783 return math.ceil(math.pi * (radius ^ 2) * length)
\r
785 func = function(name, axis, length, radius1, radius2, node)
\r
786 if axis == "?" then
\r
788 axis, sign = worldedit.player_axis(name)
\r
789 length = length * sign
\r
791 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
\r
792 worldedit.player_notify(name, count .. " nodes added")
\r
796 worldedit.register_command("cylinder", {
\r
797 params = "x/y/z/? <length> <radius1> [radius2] <node>",
\r
798 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
799 privs = {worldedit=true},
\r
801 parse = check_cylinder,
\r
802 nodes_needed = function(name, axis, length, radius1, radius2, node)
\r
803 local radius = math.max(radius1, radius2)
\r
804 return math.ceil(math.pi * (radius ^ 2) * length)
\r
806 func = function(name, axis, length, radius1, radius2, node)
\r
807 if axis == "?" then
\r
809 axis, sign = worldedit.player_axis(name)
\r
810 length = length * sign
\r
812 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
\r
813 worldedit.player_notify(name, count .. " nodes added")
\r
817 local check_pyramid = function(param)
\r
818 local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
\r
819 if found == nil then
\r
822 local node = worldedit.normalize_nodename(nodename)
\r
824 return false, "invalid node name: " .. nodename
\r
826 return true, axis, tonumber(height), node
\r
829 worldedit.register_command("hollowpyramid", {
\r
830 params = "x/y/z/? <height> <node>",
\r
831 description = "Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>",
\r
832 privs = {worldedit=true},
\r
834 parse = check_pyramid,
\r
835 nodes_needed = function(name, axis, height, node)
\r
836 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
838 func = function(name, axis, height, node)
\r
839 if axis == "?" then
\r
841 axis, sign = worldedit.player_axis(name)
\r
842 height = height * sign
\r
844 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
\r
845 worldedit.player_notify(name, count .. " nodes added")
\r
849 worldedit.register_command("pyramid", {
\r
850 params = "x/y/z/? <height> <node>",
\r
851 description = "Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>",
\r
852 privs = {worldedit=true},
\r
854 parse = check_pyramid,
\r
855 nodes_needed = function(name, axis, height, node)
\r
856 return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
\r
858 func = function(name, axis, height, node)
\r
859 if axis == "?" then
\r
861 axis, sign = worldedit.player_axis(name)
\r
862 height = height * sign
\r
864 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
\r
865 worldedit.player_notify(name, count .. " nodes added")
\r
869 worldedit.register_command("spiral", {
\r
870 params = "<length> <height> <space> <node>",
\r
871 description = "Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>",
\r
872 privs = {worldedit=true},
\r
874 parse = function(param)
\r
875 local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
\r
876 if found == nil then
\r
879 local node = worldedit.normalize_nodename(nodename)
\r
881 return false, "invalid node name: " .. nodename
\r
883 return true, tonumber(length), tonumber(height), tonumber(space), node
\r
885 nodes_needed = function(name, length, height, space, node)
\r
886 return (length + space) * height -- TODO: this is not the upper bound
\r
888 func = function(name, length, height, space, node)
\r
889 local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
\r
890 worldedit.player_notify(name, count .. " nodes added")
\r
894 worldedit.register_command("copy", {
\r
895 params = "x/y/z/? <amount>",
\r
896 description = "Copy the current WorldEdit region along the given axis by <amount> nodes",
\r
897 privs = {worldedit=true},
\r
899 parse = function(param)
\r
900 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
901 if found == nil then
\r
904 return true, axis, tonumber(amount)
\r
906 nodes_needed = function(name, axis, amount)
\r
907 return check_region(name) * 2
\r
909 func = function(name, axis, amount)
\r
910 if axis == "?" then
\r
912 axis, sign = worldedit.player_axis(name)
\r
913 amount = amount * sign
\r
916 local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
\r
917 worldedit.player_notify(name, count .. " nodes copied")
\r
921 worldedit.register_command("move", {
\r
922 params = "x/y/z/? <amount>",
\r
923 description = "Move the current WorldEdit region along the given axis by <amount> nodes",
\r
924 privs = {worldedit=true},
\r
926 parse = function(param)
\r
927 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
928 if found == nil then
\r
931 return true, axis, tonumber(amount)
\r
933 nodes_needed = function(name, axis, amount)
\r
934 return check_region(name) * 2
\r
936 func = function(name, axis, amount)
\r
937 if axis == "?" then
\r
939 axis, sign = worldedit.player_axis(name)
\r
940 amount = amount * sign
\r
943 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
944 local count = worldedit.move(pos1, pos2, axis, amount)
\r
946 pos1[axis] = pos1[axis] + amount
\r
947 pos2[axis] = pos2[axis] + amount
\r
948 worldedit.marker_update(name)
\r
949 worldedit.player_notify(name, count .. " nodes moved")
\r
953 worldedit.register_command("stack", {
\r
954 params = "x/y/z/? <count>",
\r
955 description = "Stack the current WorldEdit region along the given axis <count> times",
\r
956 privs = {worldedit=true},
\r
958 parse = function(param)
\r
959 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
960 if found == nil then
\r
963 return true, axis, tonumber(repetitions)
\r
965 nodes_needed = function(name, axis, repetitions)
\r
966 return check_region(name) * math.abs(repetitions)
\r
968 func = function(name, axis, repetitions)
\r
969 if axis == "?" then
\r
971 axis, sign = worldedit.player_axis(name)
\r
972 repetitions = repetitions * sign
\r
975 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
976 local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
\r
977 worldedit.stack(pos1, pos2, axis, repetitions, function()
\r
978 worldedit.player_notify(name, count .. " nodes stacked")
\r
983 worldedit.register_command("stack2", {
\r
984 params = "<count> <x> <y> <z>",
\r
985 description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",
\r
986 privs = {worldedit=true},
\r
988 parse = function(param)
\r
989 local repetitions, incs = param:match("(%d+)%s*(.+)")
\r
990 if repetitions == nil then
\r
991 return false, "invalid count: " .. param
\r
993 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
\r
995 return false, "invalid increments: " .. param
\r
998 return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
\r
1000 nodes_needed = function(name, repetitions, offset)
\r
1001 return check_region(name) * repetitions
\r
1003 func = function(name, repetitions, offset)
\r
1004 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1005 local count = worldedit.volume(pos1, pos2) * repetitions
\r
1006 worldedit.stack2(pos1, pos2, offset, repetitions, function()
\r
1007 worldedit.player_notify(name, count .. " nodes stacked")
\r
1013 worldedit.register_command("stretch", {
\r
1014 params = "<stretchx> <stretchy> <stretchz>",
\r
1015 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
1016 privs = {worldedit=true},
\r
1018 parse = function(param)
\r
1019 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
\r
1020 if found == nil then
\r
1023 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
\r
1024 if stretchx == 0 or stretchy == 0 or stretchz == 0 then
\r
1025 return false, "invalid scaling factors: " .. param
\r
1027 return true, stretchx, stretchy, stretchz
\r
1029 nodes_needed = function(name, stretchx, stretchy, stretchz)
\r
1030 return check_region(name) * stretchx * stretchy * stretchz
\r
1032 func = function(name, stretchx, stretchy, stretchz)
\r
1033 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1034 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
\r
1036 --reset markers to scaled positions
\r
1037 worldedit.pos1[name] = pos1
\r
1038 worldedit.pos2[name] = pos2
\r
1039 worldedit.marker_update(name)
\r
1041 worldedit.player_notify(name, count .. " nodes stretched")
\r
1045 worldedit.register_command("transpose", {
\r
1046 params = "x/y/z/? x/y/z/?",
\r
1047 description = "Transpose the current WorldEdit region along the given axes",
\r
1048 privs = {worldedit=true},
\r
1050 parse = function(param)
\r
1051 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
\r
1052 if found == nil then
\r
1054 elseif axis1 == axis2 then
\r
1055 return false, "invalid usage: axes must be different"
\r
1057 return true, axis1, axis2
\r
1059 nodes_needed = check_region,
\r
1060 func = function(name, axis1, axis2)
\r
1061 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1062 if axis1 == "?" then axis1 = worldedit.player_axis(name) end
\r
1063 if axis2 == "?" then axis2 = worldedit.player_axis(name) end
\r
1064 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
1066 --reset markers to transposed positions
\r
1067 worldedit.pos1[name] = pos1
\r
1068 worldedit.pos2[name] = pos2
\r
1069 worldedit.marker_update(name)
\r
1071 worldedit.player_notify(name, count .. " nodes transposed")
\r
1075 worldedit.register_command("flip", {
\r
1076 params = "x/y/z/?",
\r
1077 description = "Flip the current WorldEdit region along the given axis",
\r
1078 privs = {worldedit=true},
\r
1080 parse = function(param)
\r
1081 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
\r
1084 return true, param
\r
1086 nodes_needed = check_region,
\r
1087 func = function(name, param)
\r
1088 if param == "?" then param = worldedit.player_axis(name) end
\r
1089 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1090 worldedit.player_notify(name, count .. " nodes flipped")
\r
1094 worldedit.register_command("rotate", {
\r
1095 params = "x/y/z/? <angle>",
\r
1096 description = "Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)",
\r
1097 privs = {worldedit=true},
\r
1099 parse = function(param)
\r
1100 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
\r
1101 if found == nil then
\r
1104 angle = tonumber(angle)
\r
1105 if angle % 90 ~= 0 or angle % 360 == 0 then
\r
1106 return false, "invalid usage: angle must be multiple of 90"
\r
1108 return true, axis, angle
\r
1110 nodes_needed = check_region,
\r
1111 func = function(name, axis, angle)
\r
1112 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
\r
1113 if axis == "?" then axis = worldedit.player_axis(name) end
\r
1114 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
\r
1116 --reset markers to rotated positions
\r
1117 worldedit.pos1[name] = pos1
\r
1118 worldedit.pos2[name] = pos2
\r
1119 worldedit.marker_update(name)
\r
1121 worldedit.player_notify(name, count .. " nodes rotated")
\r
1125 worldedit.register_command("orient", {
\r
1126 params = "<angle>",
\r
1127 description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",
\r
1128 privs = {worldedit=true},
\r
1130 parse = function(param)
\r
1131 local found, _, angle = param:find("^([+-]?%d+)$")
\r
1132 if found == nil then
\r
1135 angle = tonumber(angle)
\r
1136 if angle % 90 ~= 0 then
\r
1137 return false, "invalid usage: angle must be multiple of 90"
\r
1139 return true, angle
\r
1141 nodes_needed = check_region,
\r
1142 func = function(name, angle)
\r
1143 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
\r
1144 worldedit.player_notify(name, count .. " nodes oriented")
\r
1148 worldedit.register_command("fixlight", {
\r
1150 description = "Fix the lighting in the current WorldEdit region",
\r
1151 privs = {worldedit=true},
\r
1153 nodes_needed = check_region,
\r
1154 func = function(name)
\r
1155 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
\r
1156 worldedit.player_notify(name, count .. " nodes updated")
\r
1160 worldedit.register_command("drain", {
\r
1162 description = "Remove any fluid node within the current WorldEdit region",
\r
1163 privs = {worldedit=true},
\r
1165 nodes_needed = check_region,
\r
1166 func = function(name)
\r
1167 -- TODO: make an API function for this
\r
1169 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1170 for x = pos1.x, pos2.x do
\r
1171 for y = pos1.y, pos2.y do
\r
1172 for z = pos1.z, pos2.z do
\r
1173 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1174 local d = minetest.registered_nodes[n]
\r
1175 if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then
\r
1176 minetest.remove_node({x=x, y=y, z=z})
\r
1182 worldedit.player_notify(name, count .. " nodes updated")
\r
1186 local clearcut_cache
\r
1188 local function clearcut(pos1, pos2)
\r
1189 -- decide which nodes we consider plants
\r
1190 if clearcut_cache == nil then
\r
1191 clearcut_cache = {}
\r
1192 for name, def in pairs(minetest.registered_nodes) do
\r
1193 local groups = def.groups or {}
\r
1195 -- the groups say so
\r
1196 groups.flower or groups.grass or groups.flora or groups.plant or
\r
1197 groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
\r
1198 -- drawtype heuristic
\r
1199 (def.is_ground_content and def.buildable_to and
\r
1200 (def.sunlight_propagates or not def.walkable)
\r
1201 and def.drawtype == "plantlike") or
\r
1202 -- if it's flammable, it probably needs to go too
\r
1203 (def.is_ground_content and not def.walkable and groups.flammable)
\r
1205 clearcut_cache[name] = true
\r
1209 local plants = clearcut_cache
\r
1214 for x = pos1.x, pos2.x do
\r
1215 for z = pos1.z, pos2.z do
\r
1218 -- first pass: remove floating nodes that would be left over
\r
1219 for y = pos1.y, pos2.y do
\r
1220 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1225 local def = minetest.registered_nodes[n] or {}
\r
1226 local groups = def.groups or {}
\r
1227 if groups.attached_node or (def.buildable_to and groups.falling_node) then
\r
1228 minetest.remove_node({x=x, y=y, z=z})
\r
1236 -- second pass: remove plants, top-to-bottom to avoid item drops
\r
1238 for y = pos2.y, pos1.y, -1 do
\r
1239 local n = minetest.get_node({x=x, y=y, z=z}).name
\r
1241 minetest.remove_node({x=x, y=y, z=z})
\r
1252 worldedit.register_command("clearcut", {
\r
1254 description = "Remove any plant, tree or foilage-like nodes in the selected region",
\r
1255 privs = {worldedit=true},
\r
1257 nodes_needed = check_region,
\r
1258 func = function(name)
\r
1259 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
\r
1260 local count = clearcut(pos1, pos2)
\r
1261 worldedit.player_notify(name, count .. " nodes removed")
\r
1265 worldedit.register_command("hide", {
\r
1267 description = "Hide all nodes in the current WorldEdit region non-destructively",
\r
1268 privs = {worldedit=true},
\r
1270 nodes_needed = check_region,
\r
1271 func = function(name)
\r
1272 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
\r
1273 worldedit.player_notify(name, count .. " nodes hidden")
\r
1277 worldedit.register_command("suppress", {
\r
1278 params = "<node>",
\r
1279 description = "Suppress all <node> in the current WorldEdit region non-destructively",
\r
1280 privs = {worldedit=true},
\r
1282 parse = function(param)
\r
1283 local node = worldedit.normalize_nodename(param)
\r
1285 return false, "invalid node name: " .. param
\r
1289 nodes_needed = check_region,
\r
1290 func = function(name, node)
\r
1291 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1292 worldedit.player_notify(name, count .. " nodes suppressed")
\r
1296 worldedit.register_command("highlight", {
\r
1297 params = "<node>",
\r
1298 description = "Highlight <node> in the current WorldEdit region by hiding everything else 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.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
\r
1311 worldedit.player_notify(name, count .. " nodes highlighted")
\r
1315 worldedit.register_command("restore", {
\r
1317 description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",
\r
1318 privs = {worldedit=true},
\r
1320 nodes_needed = check_region,
\r
1321 func = function(name)
\r
1322 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
\r
1323 worldedit.player_notify(name, count .. " nodes restored")
\r
1327 local function detect_misaligned_schematic(name, pos1, pos2)
\r
1328 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
1329 -- Check that allocate/save can position the schematic correctly
\r
1330 -- The expected behaviour is that the (0,0,0) corner of the schematic stays
\r
1331 -- sat pos1, this only works when the minimum position is actually present
\r
1332 -- in the schematic.
\r
1333 local node = minetest.get_node(pos1)
\r
1334 local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
\r
1335 if not have_node_at_origin then
\r
1336 worldedit.player_notify(name,
\r
1337 "Warning: The schematic contains excessive free space and WILL be "..
\r
1338 "misaligned when allocated or loaded. To avoid this, shrink your "..
\r
1339 "area to cover exactly the nodes to be saved."
\r
1344 worldedit.register_command("save", {
\r
1345 params = "<file>",
\r
1346 description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
\r
1347 privs = {worldedit=true},
\r
1349 parse = function(param)
\r
1350 if param == "" then
\r
1353 if not check_filename(param) then
\r
1354 return false, "Disallowed file name: " .. param
\r
1356 return true, param
\r
1358 nodes_needed = check_region,
\r
1359 func = function(name, param)
\r
1360 local result, count = worldedit.serialize(worldedit.pos1[name],
\r
1361 worldedit.pos2[name])
\r
1362 detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
\r
1364 local path = minetest.get_worldpath() .. "/schems"
\r
1365 -- Create directory if it does not already exist
\r
1366 minetest.mkdir(path)
\r
1368 local filename = path .. "/" .. param .. ".we"
\r
1369 local file, err = io.open(filename, "wb")
\r
1370 if err ~= nil then
\r
1371 worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")
\r
1374 file:write(result)
\r
1378 worldedit.player_notify(name, count .. " nodes saved")
\r
1382 worldedit.register_command("allocate", {
\r
1383 params = "<file>",
\r
1384 description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",
\r
1385 privs = {worldedit=true},
\r
1387 parse = function(param)
\r
1388 if param == "" then
\r
1391 if not check_filename(param) then
\r
1392 return false, "Disallowed file name: " .. param
\r
1394 return true, param
\r
1396 func = function(name, param)
\r
1397 local pos = worldedit.pos1[name]
\r
1399 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"
\r
1400 local file, err = io.open(filename, "rb")
\r
1401 if err ~= nil then
\r
1402 worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")
\r
1405 local value = file:read("*a")
\r
1408 local version = worldedit.read_header(value)
\r
1409 if version == nil or version == 0 then
\r
1410 worldedit.player_notify(name, "File is invalid!")
\r
1412 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1413 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1416 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
\r
1418 if not nodepos1 then
\r
1419 worldedit.player_notify(name, "Schematic empty, nothing allocated")
\r
1423 worldedit.pos1[name] = nodepos1
\r
1424 worldedit.pos2[name] = nodepos2
\r
1425 worldedit.marker_update(name)
\r
1427 worldedit.player_notify(name, count .. " nodes allocated")
\r
1431 worldedit.register_command("load", {
\r
1432 params = "<file>",
\r
1433 description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",
\r
1434 privs = {worldedit=true},
\r
1436 parse = function(param)
\r
1437 if param == "" then
\r
1440 if not check_filename(param) then
\r
1441 return false, "Disallowed file name: " .. param
\r
1443 return true, param
\r
1445 func = function(name, param)
\r
1446 local pos = worldedit.pos1[name]
\r
1448 if param == "" then
\r
1449 worldedit.player_notify(name, "invalid usage: " .. param)
\r
1452 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then
\r
1453 worldedit.player_notify(name, "invalid file name: " .. param)
\r
1457 --find the file in the world path
\r
1458 local testpaths = {
\r
1459 minetest.get_worldpath() .. "/schems/" .. param,
\r
1460 minetest.get_worldpath() .. "/schems/" .. param .. ".we",
\r
1461 minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
\r
1464 for index, path in ipairs(testpaths) do
\r
1465 file, err = io.open(path, "rb")
\r
1471 worldedit.player_notify(name, "could not open file \"" .. param .. "\"")
\r
1474 local value = file:read("*a")
\r
1477 local version = worldedit.read_header(value)
\r
1478 if version == nil or version == 0 then
\r
1479 worldedit.player_notify(name, "File is invalid!")
\r
1481 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
\r
1482 worldedit.player_notify(name, "File was created with newer version of WorldEdit!")
\r
1486 local count = worldedit.deserialize(pos, value)
\r
1488 worldedit.player_notify(name, count .. " nodes loaded")
\r
1492 worldedit.register_command("lua", {
\r
1493 params = "<code>",
\r
1494 description = "Executes <code> as a Lua chunk in the global namespace",
\r
1495 privs = {worldedit=true, server=true},
\r
1496 parse = function(param)
\r
1497 return true, param
\r
1499 func = function(name, param)
\r
1500 local err = worldedit.lua(param)
\r
1502 worldedit.player_notify(name, "code error: " .. err)
\r
1503 minetest.log("action", name.." tried to execute "..param)
\r
1505 worldedit.player_notify(name, "code successfully executed", false)
\r
1506 minetest.log("action", name.." executed "..param)
\r
1511 worldedit.register_command("luatransform", {
\r
1512 params = "<code>",
\r
1513 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
1514 privs = {worldedit=true, server=true},
\r
1516 parse = function(param)
\r
1517 return true, param
\r
1519 nodes_needed = check_region,
\r
1520 func = function(name, param)
\r
1521 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
\r
1523 worldedit.player_notify(name, "code error: " .. err, false)
\r
1524 minetest.log("action", name.." tried to execute luatransform "..param)
\r
1526 worldedit.player_notify(name, "code successfully executed", false)
\r
1527 minetest.log("action", name.." executed luatransform "..param)
\r
1532 worldedit.register_command("mtschemcreate", {
\r
1533 params = "<file>",
\r
1534 description = "Save the current WorldEdit region using the Minetest "..
\r
1535 "Schematic format to \"(world folder)/schems/<filename>.mts\"",
\r
1536 privs = {worldedit=true},
\r
1538 parse = function(param)
\r
1539 if param == "" then
\r
1542 if not check_filename(param) then
\r
1543 return false, "Disallowed file name: " .. param
\r
1545 return true, param
\r
1547 nodes_needed = check_region,
\r
1548 func = function(name, param)
\r
1549 local path = minetest.get_worldpath() .. "/schems"
\r
1550 -- Create directory if it does not already exist
\r
1551 minetest.mkdir(path)
\r
1553 local filename = path .. "/" .. param .. ".mts"
\r
1554 local ret = minetest.create_schematic(worldedit.pos1[name],
\r
1555 worldedit.pos2[name], worldedit.prob_list[name],
\r
1557 if ret == nil then
\r
1558 worldedit.player_notify(name, "Failed to create Minetest schematic")
\r
1560 worldedit.player_notify(name, "Saved Minetest schematic to " .. param)
\r
1562 worldedit.prob_list[name] = {}
\r
1566 worldedit.register_command("mtschemplace", {
\r
1567 params = "<file>",
\r
1568 description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",
\r
1569 privs = {worldedit=true},
\r
1571 parse = function(param)
\r
1572 if param == "" then
\r
1575 if not check_filename(param) then
\r
1576 return false, "Disallowed file name: " .. param
\r
1578 return true, param
\r
1580 func = function(name, param)
\r
1581 local pos = worldedit.pos1[name]
\r
1583 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"
\r
1584 if minetest.place_schematic(pos, path) == nil then
\r
1585 worldedit.player_notify(name, "failed to place Minetest schematic")
\r
1587 worldedit.player_notify(name, "placed Minetest schematic " .. param ..
\r
1588 " at " .. minetest.pos_to_string(pos))
\r
1593 worldedit.register_command("mtschemprob", {
\r
1594 params = "start/finish/get",
\r
1595 description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",
\r
1596 privs = {worldedit=true},
\r
1597 parse = function(param)
\r
1598 if param ~= "start" and param ~= "finish" and param ~= "get" then
\r
1599 return false, "unknown subcommand: " .. param
\r
1601 return true, param
\r
1603 func = function(name, param)
\r
1604 if param == "start" then --start probability setting
\r
1605 worldedit.set_pos[name] = "prob"
\r
1606 worldedit.prob_list[name] = {}
\r
1607 worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")
\r
1608 elseif param == "finish" then --finish probability setting
\r
1609 worldedit.set_pos[name] = nil
\r
1610 worldedit.player_notify(name, "finished Minetest schematic probability selection")
\r
1611 elseif param == "get" then --get all nodes that had probabilities set on them
\r
1613 local problist = worldedit.prob_list[name]
\r
1614 if problist == nil then
\r
1617 for k,v in pairs(problist) do
\r
1618 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
\r
1619 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
\r
1621 worldedit.player_notify(name, "currently set node probabilities:")
\r
1622 worldedit.player_notify(name, text)
\r
1627 minetest.register_on_player_receive_fields(function(player, formname, fields)
\r
1628 if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then
\r
1629 local name = player:get_player_name()
\r
1630 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
\r
1631 local index = table.getn(worldedit.prob_list[name]) + 1
\r
1632 worldedit.prob_list[name][index] = prob_entry
\r
1636 worldedit.register_command("clearobjects", {
\r
1638 description = "Clears all objects within the WorldEdit region",
\r
1639 privs = {worldedit=true},
\r
1641 nodes_needed = check_region,
\r
1642 func = function(name)
\r
1643 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
\r
1644 worldedit.player_notify(name, count .. " objects cleared")
\r