]> git.lizzy.rs Git - worldedit.git/blob - worldedit_commands/init.lua
Update help texts to be consistent
[worldedit.git] / worldedit_commands / init.lua
1 minetest.register_privilege("worldedit", "Can use WorldEdit commands")\r
2 \r
3 worldedit.pos1 = {}\r
4 worldedit.pos2 = {}\r
5 \r
6 worldedit.set_pos = {}\r
7 worldedit.inspect = {}\r
8 worldedit.prob_pos = {}\r
9 worldedit.prob_list = {}\r
10 \r
11 \r
12 \r
13 local safe_region, reset_pending = dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua")\r
14 \r
15 function worldedit.player_notify(name, message)\r
16         minetest.chat_send_player(name, "WorldEdit -!- " .. message, false)\r
17 end\r
18 \r
19 worldedit.registered_commands = {}\r
20 \r
21 local function chatcommand_handler(cmd_name, name, param)\r
22         local def = assert(worldedit.registered_commands[cmd_name])\r
23 \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
28                         return\r
29                 end\r
30         elseif def.require_pos == 1 then\r
31                 local pos1 = worldedit.pos1[name]\r
32                 if pos1 == nil then\r
33                         worldedit.player_notify(name, "no position 1 selected")\r
34                         return\r
35                 end\r
36         end\r
37 \r
38         local parsed = {def.parse(param)}\r
39         local success = table.remove(parsed, 1)\r
40         if not success then\r
41                 worldedit.player_notify(name, parsed[1] or "invalid usage")\r
42                 return\r
43         end\r
44 \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
49                         if msg then\r
50                                 minetest.chat_send_player(name, msg)\r
51                         end\r
52                 end)\r
53         else\r
54                 -- no "safe region" check\r
55                 local success, msg = def.func(name, unpack(parsed))\r
56                 if msg then\r
57                         minetest.chat_send_player(name, msg)\r
58                 end\r
59         end\r
60 end\r
61 \r
62 -- Registers a chatcommand for WorldEdit\r
63 -- name = "about" -- Name of the chat command (without any /)\r
64 -- def = {\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
72 --         -- or\r
73 --         return false\r
74 --         -- or\r
75 --         return false, "error message"\r
76 --     end,\r
77 --     nodes_needed = function(name, foo, bar, ...), -- (optional)\r
78 --         return n\r
79 --     end,\r
80 --     func = function(name, foo, bar, ...)\r
81 --         return success, "message"\r
82 --     end,\r
83 -- }\r
84 function worldedit.register_command(name, def)\r
85         local def = table.copy(def)\r
86         assert(name and #name > 0)\r
87         assert(def.privs)\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
92         else\r
93                 assert(def.parse)\r
94         end\r
95         assert(def.nodes_needed == nil or type(def.nodes_needed) == "function")\r
96         assert(def.func)\r
97 \r
98         -- for development\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
101         end--]]\r
102 \r
103         minetest.register_chatcommand("/" .. name, {\r
104                 privs = def.privs,\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
109                 end,\r
110         })\r
111         worldedit.registered_commands[name] = def\r
112 end\r
113 \r
114 \r
115 \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
119 \r
120 \r
121 local function check_region(name)\r
122         return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])\r
123 end\r
124 \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
129         local out = ""\r
130         local i = 1\r
131         while i <= #input do\r
132                 if s(i) == "\027" then -- escape sequence\r
133                         i = i + 1\r
134                         if s(i) == "(" then -- enclosed\r
135                                 i = i + 1\r
136                                 while i <= #input and s(i) ~= ")" do\r
137                                         if s(i) == "\\" then\r
138                                                 i = i + 2\r
139                                         else\r
140                                                 i = i + 1\r
141                                         end\r
142                                 end\r
143                         end\r
144                 else\r
145                         out = out .. s(i)\r
146                 end\r
147                 i = i + 1\r
148         end\r
149         --print(("%q -> %q"):format(input, out))\r
150         return out\r
151 end\r
152 \r
153 local function string_endswith(full, part)\r
154         return full:find(part, 1, true) == #full - #part + 1\r
155 end\r
156 \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
161 \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
164                 return fullname\r
165         end\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
169                         return key\r
170                 end\r
171         end\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
175                         return key\r
176                 end\r
177                 if desc == nodename .. " block" then\r
178                         -- fuzzy description match (e.g. "Steel" == "Steel Block")\r
179                         return key\r
180                 end\r
181         end\r
182 \r
183         local match = nil\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
187                                 return nil\r
188                         end\r
189                         match = key -- substring description match (only if no ambiguities)\r
190                 end\r
191         end\r
192         return match\r
193 end\r
194 \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
199         if x > y then\r
200                 if x > z then\r
201                         return "x", dir.x > 0 and 1 or -1\r
202                 end\r
203         elseif y > z then\r
204                 return "y", dir.y > 0 and 1 or -1\r
205         end\r
206         return "z", dir.z > 0 and 1 or -1\r
207 end\r
208 \r
209 local function check_filename(name)\r
210         return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil\r
211 end\r
212 \r
213 \r
214 worldedit.register_command("about", {\r
215         privs = {},\r
216         params = "",\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
223         end,\r
224 })\r
225 \r
226 -- mostly copied from builtin/chatcommands.lua with minor modifications\r
227 worldedit.register_command("help", {\r
228         privs = {},\r
229         params = "[all/<cmd>]",\r
230         description = "Get help for WorldEdit commands",\r
231         parse = function(param)\r
232                 return true, param\r
233         end,\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
239                         end\r
240                         if def.description and def.description ~= "" then\r
241                                 msg = msg .. ": " .. def.description\r
242                         end\r
243                         return msg\r
244                 end\r
245 \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
248                 end\r
249                 if param == "" then\r
250                         local msg = ""\r
251                         local cmds = {}\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
255                                 end\r
256                         end\r
257                         table.sort(cmds)\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
262                         local cmds = {}\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
266                                 end\r
267                         end\r
268                         table.sort(cmds)\r
269                         return true, "Available commands:\n"..table.concat(cmds, "\n")\r
270                 else\r
271                         local def = worldedit.registered_commands[param]\r
272                         if not def then\r
273                                 return false, "Command not available: " .. param\r
274                         else\r
275                                 return true, format_help_line(param, def)\r
276                         end\r
277                 end\r
278         end,\r
279 })\r
280 \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
287                         return true, true\r
288                 elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then\r
289                         return true, false\r
290                 end\r
291                 return false\r
292         end,\r
293         func = function(name, enable)\r
294                 if enable then\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
299                 else\r
300                         worldedit.inspect[name] = nil\r
301                         worldedit.player_notify(name, "inspector: inspection disabled")\r
302                 end\r
303         end,\r
304 })\r
305 \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
314         }\r
315         local ret = 0\r
316         for _, v in ipairs(vecs) do\r
317                 ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))\r
318         end\r
319         return ret\r
320 end\r
321 \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
329         end\r
330 end)\r
331 \r
332 worldedit.register_command("reset", {\r
333         params = "",\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
344         end,\r
345 })\r
346 \r
347 worldedit.register_command("mark", {\r
348         params = "",\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
354         end,\r
355 })\r
356 \r
357 worldedit.register_command("unmark", {\r
358         params = "",\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
369         end,\r
370 })\r
371 \r
372 worldedit.register_command("pos1", {\r
373         params = "",\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
382         end,\r
383 })\r
384 \r
385 worldedit.register_command("pos2", {\r
386         params = "",\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
395         end,\r
396 })\r
397 \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
404                         return true, param\r
405                 end\r
406                 return false, "unknown subcommand: " .. param\r
407         end,\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
421                         else\r
422                                 worldedit.player_notify(name, "position 1 not set")\r
423                         end\r
424                         if worldedit.pos2[name] ~= nil then\r
425                                 worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name]))\r
426                         else\r
427                                 worldedit.player_notify(name, "position 2 not set")\r
428                         end\r
429                 end\r
430         end,\r
431 })\r
432 \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
440                         return false\r
441                 end\r
442                 return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)}\r
443         end,\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
453                 end\r
454         end,\r
455 })\r
456 \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
478                 end\r
479         end\r
480 end)\r
481 \r
482 worldedit.register_command("volume", {\r
483         params = "",\r
484         description = "Display the volume of the current WorldEdit region",\r
485         privs = {worldedit=true},\r
486         require_pos = 2,\r
487         func = function(name)\r
488                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
489 \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
496         end,\r
497 })\r
498 \r
499 worldedit.register_command("deleteblocks", {\r
500         params = "",\r
501         description = "remove all MapBlocks (16x16x16) containing the selected area from the map",\r
502         privs = {worldedit=true},\r
503         require_pos = 2,\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
508                 if success then\r
509                         worldedit.player_notify(name, "Area deleted.")\r
510                 else\r
511                         worldedit.player_notify(name, "There was an error during deletion of the area.")\r
512                 end\r
513         end,\r
514 })\r
515 \r
516 worldedit.register_command("set", {\r
517         params = "<node>",\r
518         description = "Set the current WorldEdit region to <node>",\r
519         privs = {worldedit=true},\r
520         require_pos = 2,\r
521         parse = function(param)\r
522                 local node = worldedit.normalize_nodename(param)\r
523                 if not node then\r
524                         return false, "invalid node name: " .. param\r
525                 end\r
526                 return true, node\r
527         end,\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
532         end,\r
533 })\r
534 \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
539         require_pos = 2,\r
540         parse = function(param)\r
541                 local param2 = tonumber(param)\r
542                 if not param2 then\r
543                         return false\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
546                 end\r
547                 return true, param2\r
548         end,\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
553         end,\r
554 })\r
555 \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
560         require_pos = 2,\r
561         parse = function(param)\r
562                 local nodes = {}\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
568                                 end\r
569                         else\r
570                                 local node = worldedit.normalize_nodename(nodename)\r
571                                 if not node then\r
572                                         return false, "invalid node name: " .. nodename\r
573                                 end\r
574                                 nodes[#nodes + 1] = node\r
575                         end\r
576                 end\r
577                 return true, nodes\r
578         end,\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
584         end,\r
585 })\r
586 \r
587 local check_replace = function(param)\r
588         local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")\r
589         if found == nil then\r
590                 return false\r
591         end\r
592         local newsearchnode = worldedit.normalize_nodename(searchnode)\r
593         if not newsearchnode then\r
594                 return false, "invalid search node name: " .. searchnode\r
595         end\r
596         local newreplacenode = worldedit.normalize_nodename(replacenode)\r
597         if not newreplacenode then\r
598                 return false, "invalid replace node name: " .. replacenode\r
599         end\r
600         return true, newsearchnode, newreplacenode\r
601 end\r
602 \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
607         require_pos = 2,\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
614         end,\r
615 })\r
616 \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
621         require_pos = 2,\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
628         end,\r
629 })\r
630 \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
634                 return false\r
635         end\r
636         local node = worldedit.normalize_nodename(nodename)\r
637         if not node then\r
638                 return false, "invalid node name: " .. nodename\r
639         end\r
640         return true, tonumber(w), tonumber(h), tonumber(l), node\r
641 end\r
642 \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
647         require_pos = 1,\r
648         parse = check_cube,\r
649         nodes_needed = function(name, w, h, l, node)\r
650                 return w * h * l\r
651         end,\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
655         end,\r
656 })\r
657 \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
662         require_pos = 1,\r
663         parse = check_cube,\r
664         nodes_needed = function(name, w, h, l, node)\r
665                 return w * h * l\r
666         end,\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
670         end,\r
671 })\r
672 \r
673 local check_sphere = function(param)\r
674         local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
675         if found == nil then\r
676                 return false\r
677         end\r
678         local node = worldedit.normalize_nodename(nodename)\r
679         if not node then\r
680                 return false, "invalid node name: " .. nodename\r
681         end\r
682         return true, tonumber(radius), node\r
683 end\r
684 \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
689         require_pos = 1,\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
693         end,\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
697         end,\r
698 })\r
699 \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
704         require_pos = 1,\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
708         end,\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
712         end,\r
713 })\r
714 \r
715 local check_dome = function(param)\r
716         local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
717         if found == nil then\r
718                 return false\r
719         end\r
720         local node = worldedit.normalize_nodename(nodename)\r
721         if not node then\r
722                 return false, "invalid node name: " .. nodename\r
723         end\r
724         return true, tonumber(radius), node\r
725 end\r
726 \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
731         require_pos = 1,\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
735         end,\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
739         end,\r
740 })\r
741 \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
746         require_pos = 1,\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
750         end,\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
754         end,\r
755 })\r
756 \r
757 local check_cylinder = function(param)\r
758         -- two radii\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
761                 -- single radius\r
762                 found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")\r
763                 radius2 = radius1\r
764         end\r
765         if found == nil then\r
766                 return false\r
767         end\r
768         local node = worldedit.normalize_nodename(nodename)\r
769         if not node then\r
770                 return false, "invalid node name: " .. nodename\r
771         end\r
772         return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node\r
773 end\r
774 \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
779         require_pos = 1,\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
784         end,\r
785         func = function(name, axis, length, radius1, radius2, node)\r
786                 if axis == "?" then\r
787                         local sign\r
788                         axis, sign = worldedit.player_axis(name)\r
789                         length = length * sign\r
790                 end\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
793         end,\r
794 })\r
795 \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
800         require_pos = 1,\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
805         end,\r
806         func = function(name, axis, length, radius1, radius2, node)\r
807                 if axis == "?" then\r
808                         local sign\r
809                         axis, sign = worldedit.player_axis(name)\r
810                         length = length * sign\r
811                 end\r
812                 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)\r
813                 worldedit.player_notify(name, count .. " nodes added")\r
814         end,\r
815 })\r
816 \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
820                 return false\r
821         end\r
822         local node = worldedit.normalize_nodename(nodename)\r
823         if not node then\r
824                 return false, "invalid node name: " .. nodename\r
825         end\r
826         return true, axis, tonumber(height), node\r
827 end\r
828      \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
833         require_pos = 1,\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
837         end,\r
838         func = function(name, axis, height, node)\r
839                 if axis == "?" then\r
840                         local sign\r
841                         axis, sign = worldedit.player_axis(name)\r
842                         height = height * sign\r
843                 end\r
844                 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)\r
845                 worldedit.player_notify(name, count .. " nodes added")\r
846         end,\r
847 })\r
848 \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
853         require_pos = 1,\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
857         end,\r
858         func = function(name, axis, height, node)\r
859                 if axis == "?" then\r
860                         local sign\r
861                         axis, sign = worldedit.player_axis(name)\r
862                         height = height * sign\r
863                 end\r
864                 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)\r
865                 worldedit.player_notify(name, count .. " nodes added")\r
866         end,\r
867 })\r
868 \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
873         require_pos = 1,\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
877                         return false\r
878                 end\r
879                 local node = worldedit.normalize_nodename(nodename)\r
880                 if not node then\r
881                         return false, "invalid node name: " .. nodename\r
882                 end\r
883                 return true, tonumber(length), tonumber(height), tonumber(space), node\r
884         end,\r
885         nodes_needed = function(name, length, height, space, node)\r
886                 return (length + space) * height -- TODO: this is not the upper bound\r
887         end,\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
891         end,\r
892 })\r
893 \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
898         require_pos = 2,\r
899         parse = function(param)\r
900                 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
901                 if found == nil then\r
902                         return false\r
903                 end\r
904                 return true, axis, tonumber(amount)\r
905         end,\r
906         nodes_needed = function(name, axis, amount)\r
907                 return check_region(name) * 2\r
908         end,\r
909         func = function(name, axis, amount)\r
910                 if axis == "?" then\r
911                         local sign\r
912                         axis, sign = worldedit.player_axis(name)\r
913                         amount = amount * sign\r
914                 end\r
915 \r
916                 local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)\r
917                 worldedit.player_notify(name, count .. " nodes copied")\r
918         end,\r
919 })\r
920 \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
925         require_pos = 2,\r
926         parse = function(param)\r
927                 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
928                 if found == nil then\r
929                         return false\r
930                 end\r
931                 return true, axis, tonumber(amount)\r
932         end,\r
933         nodes_needed = function(name, axis, amount)\r
934                 return check_region(name) * 2\r
935         end,\r
936         func = function(name, axis, amount)\r
937                 if axis == "?" then\r
938                         local sign\r
939                         axis, sign = worldedit.player_axis(name)\r
940                         amount = amount * sign\r
941                 end\r
942 \r
943                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
944                 local count = worldedit.move(pos1, pos2, axis, amount)\r
945 \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
950         end,\r
951 })\r
952 \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
957         require_pos = 2,\r
958         parse = function(param)\r
959                 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
960                 if found == nil then\r
961                         return false\r
962                 end\r
963                 return true, axis, tonumber(repetitions)\r
964         end,\r
965         nodes_needed = function(name, axis, repetitions)\r
966                 return check_region(name) * math.abs(repetitions)\r
967         end,\r
968         func = function(name, axis, repetitions)\r
969                 if axis == "?" then\r
970                         local sign\r
971                         axis, sign = worldedit.player_axis(name)\r
972                         repetitions = repetitions * sign\r
973                 end\r
974 \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
979                 end)\r
980         end,\r
981 })\r
982 \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
987         require_pos = 2,\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
992                 end\r
993                 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")\r
994                 if x == nil then\r
995                         return false, "invalid increments: " .. param\r
996                 end\r
997 \r
998                 return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}\r
999         end,\r
1000         nodes_needed = function(name, repetitions, offset)\r
1001                 return check_region(name) * repetitions\r
1002         end,\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
1008                 end)\r
1009         end,\r
1010 })\r
1011 \r
1012 \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
1017         require_pos = 2,\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
1021                         return false\r
1022                 end\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
1026                 end\r
1027                 return true, stretchx, stretchy, stretchz\r
1028         end,\r
1029         nodes_needed = function(name, stretchx, stretchy, stretchz)\r
1030                 return check_region(name) * stretchx * stretchy * stretchz\r
1031         end,\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
1035 \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
1040 \r
1041                 worldedit.player_notify(name, count .. " nodes stretched")\r
1042         end,\r
1043 })\r
1044 \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
1049         require_pos = 2,\r
1050         parse = function(param)\r
1051                 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")\r
1052                 if found == nil then\r
1053                         return false\r
1054                 elseif axis1 == axis2 then\r
1055                         return false, "invalid usage: axes must be different"\r
1056                 end\r
1057                 return true, axis1, axis2\r
1058         end,\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
1065 \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
1070 \r
1071                 worldedit.player_notify(name, count .. " nodes transposed")\r
1072         end,\r
1073 })\r
1074 \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
1079         require_pos = 2,\r
1080         parse = function(param)\r
1081                 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then\r
1082                         return false\r
1083                 end\r
1084                 return true, param\r
1085         end,\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
1091         end,\r
1092 })\r
1093 \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
1098         require_pos = 2,\r
1099         parse = function(param)\r
1100                 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
1101                 if found == nil then\r
1102                         return false\r
1103                 end\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
1107                 end\r
1108                 return true, axis, angle\r
1109         end,\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
1115 \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
1120 \r
1121                 worldedit.player_notify(name, count .. " nodes rotated")\r
1122         end,\r
1123 })\r
1124 \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
1129         require_pos = 2,\r
1130         parse = function(param)\r
1131                 local found, _, angle = param:find("^([+-]?%d+)$")\r
1132                 if found == nil then\r
1133                         return false\r
1134                 end\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
1138                 end\r
1139                 return true, angle\r
1140         end,\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
1145         end,\r
1146 })\r
1147 \r
1148 worldedit.register_command("fixlight", {\r
1149         params = "",\r
1150         description = "Fix the lighting in the current WorldEdit region",\r
1151         privs = {worldedit=true},\r
1152         require_pos = 2,\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
1157         end,\r
1158 })\r
1159 \r
1160 worldedit.register_command("drain", {\r
1161         params = "",\r
1162         description = "Remove any fluid node within the current WorldEdit region",\r
1163         privs = {worldedit=true},\r
1164         require_pos = 2,\r
1165         nodes_needed = check_region,\r
1166         func = function(name)\r
1167                 -- TODO: make an API function for this\r
1168                 local count = 0\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
1177                                 count = count + 1\r
1178                         end\r
1179                 end\r
1180                 end\r
1181                 end\r
1182                 worldedit.player_notify(name, count .. " nodes updated")\r
1183         end,\r
1184 })\r
1185 \r
1186 local clearcut_cache\r
1187 \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
1194                         if (\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
1204                         ) then\r
1205                                 clearcut_cache[name] = true\r
1206                         end\r
1207                 end\r
1208         end\r
1209         local plants = clearcut_cache\r
1210 \r
1211         local count = 0\r
1212         local prev, any\r
1213 \r
1214         for x = pos1.x, pos2.x do\r
1215         for z = pos1.z, pos2.z do\r
1216                 prev = false\r
1217                 any = false\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
1221                         if plants[n] then\r
1222                                 prev = true\r
1223                                 any = true\r
1224                         elseif prev then\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
1229                                         count = count + 1\r
1230                                 else\r
1231                                         prev = false\r
1232                                 end\r
1233                         end\r
1234                 end\r
1235 \r
1236                 -- second pass: remove plants, top-to-bottom to avoid item drops\r
1237                 if any then\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
1240                                 if plants[n] then\r
1241                                         minetest.remove_node({x=x, y=y, z=z})\r
1242                                         count = count + 1\r
1243                                 end\r
1244                         end\r
1245                 end\r
1246         end\r
1247         end\r
1248 \r
1249         return count\r
1250 end\r
1251 \r
1252 worldedit.register_command("clearcut", {\r
1253         params = "",\r
1254         description = "Remove any plant, tree or foilage-like nodes in the selected region",\r
1255         privs = {worldedit=true},\r
1256         require_pos = 2,\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
1262         end,\r
1263 })\r
1264 \r
1265 worldedit.register_command("hide", {\r
1266         params = "",\r
1267         description = "Hide all nodes in the current WorldEdit region non-destructively",\r
1268         privs = {worldedit=true},\r
1269         require_pos = 2,\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
1274         end,\r
1275 })\r
1276 \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
1281         require_pos = 2,\r
1282         parse = function(param)\r
1283                 local node = worldedit.normalize_nodename(param)\r
1284                 if not node then\r
1285                         return false, "invalid node name: " .. param\r
1286                 end\r
1287                 return true, node\r
1288         end,\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
1293         end,\r
1294 })\r
1295 \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
1300         require_pos = 2,\r
1301         parse = function(param)\r
1302                 local node = worldedit.normalize_nodename(param)\r
1303                 if not node then\r
1304                         return false, "invalid node name: " .. param\r
1305                 end\r
1306                 return true, node\r
1307         end,\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
1312         end,\r
1313 })\r
1314 \r
1315 worldedit.register_command("restore", {\r
1316         params = "",\r
1317         description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",\r
1318         privs = {worldedit=true},\r
1319         require_pos = 2,\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
1324         end,\r
1325 })\r
1326 \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
1340                 )\r
1341         end\r
1342 end\r
1343 \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
1348         require_pos = 2,\r
1349         parse = function(param)\r
1350                 if param == "" then\r
1351                         return false\r
1352                 end\r
1353                 if not check_filename(param) then\r
1354                         return false, "Disallowed file name: " .. param\r
1355                 end\r
1356                 return true, param\r
1357         end,\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
1363 \r
1364                 local path = minetest.get_worldpath() .. "/schems"\r
1365                 -- Create directory if it does not already exist\r
1366                 minetest.mkdir(path)\r
1367 \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
1372                         return\r
1373                 end\r
1374                 file:write(result)\r
1375                 file:flush()\r
1376                 file:close()\r
1377 \r
1378                 worldedit.player_notify(name, count .. " nodes saved")\r
1379         end,\r
1380 })\r
1381 \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
1386         require_pos = 1,\r
1387         parse = function(param)\r
1388                 if param == "" then\r
1389                         return false\r
1390                 end\r
1391                 if not check_filename(param) then\r
1392                         return false, "Disallowed file name: " .. param\r
1393                 end\r
1394                 return true, param\r
1395         end,\r
1396         func = function(name, param)\r
1397                 local pos = worldedit.pos1[name]\r
1398 \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
1403                         return\r
1404                 end\r
1405                 local value = file:read("*a")\r
1406                 file:close()\r
1407 \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
1411                         return\r
1412                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1413                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1414                         return\r
1415                 end\r
1416                 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)\r
1417 \r
1418                 if not nodepos1 then\r
1419                         worldedit.player_notify(name, "Schematic empty, nothing allocated")\r
1420                         return\r
1421                 end\r
1422 \r
1423                 worldedit.pos1[name] = nodepos1\r
1424                 worldedit.pos2[name] = nodepos2\r
1425                 worldedit.marker_update(name)\r
1426 \r
1427                 worldedit.player_notify(name, count .. " nodes allocated")\r
1428         end,\r
1429 })\r
1430 \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
1435         require_pos = 1,\r
1436         parse = function(param)\r
1437                 if param == "" then\r
1438                         return false\r
1439                 end\r
1440                 if not check_filename(param) then\r
1441                         return false, "Disallowed file name: " .. param\r
1442                 end\r
1443                 return true, param\r
1444         end,\r
1445         func = function(name, param)\r
1446                 local pos = worldedit.pos1[name]\r
1447 \r
1448                 if param == "" then\r
1449                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1450                         return\r
1451                 end\r
1452                 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then\r
1453                         worldedit.player_notify(name, "invalid file name: " .. param)\r
1454                         return\r
1455                 end\r
1456 \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
1462                 }\r
1463                 local file, err\r
1464                 for index, path in ipairs(testpaths) do\r
1465                         file, err = io.open(path, "rb")\r
1466                         if not err then\r
1467                                 break\r
1468                         end\r
1469                 end\r
1470                 if err then\r
1471                         worldedit.player_notify(name, "could not open file \"" .. param .. "\"")\r
1472                         return\r
1473                 end\r
1474                 local value = file:read("*a")\r
1475                 file:close()\r
1476 \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
1480                         return\r
1481                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1482                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1483                         return\r
1484                 end\r
1485 \r
1486                 local count = worldedit.deserialize(pos, value)\r
1487 \r
1488                 worldedit.player_notify(name, count .. " nodes loaded")\r
1489         end,\r
1490 })\r
1491 \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
1498         end,\r
1499         func = function(name, param)\r
1500                 local err = worldedit.lua(param)\r
1501                 if err then\r
1502                         worldedit.player_notify(name, "code error: " .. err)\r
1503                         minetest.log("action", name.." tried to execute "..param)\r
1504                 else\r
1505                         worldedit.player_notify(name, "code successfully executed", false)\r
1506                         minetest.log("action", name.." executed "..param)\r
1507                 end\r
1508         end,\r
1509 })\r
1510 \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
1515         require_pos = 2,\r
1516         parse = function(param)\r
1517                 return true, param\r
1518         end,\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
1522                 if err then\r
1523                         worldedit.player_notify(name, "code error: " .. err, false)\r
1524                         minetest.log("action", name.." tried to execute luatransform "..param)\r
1525                 else\r
1526                         worldedit.player_notify(name, "code successfully executed", false)\r
1527                         minetest.log("action", name.." executed luatransform "..param)\r
1528                 end\r
1529         end,\r
1530 })\r
1531 \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
1537         require_pos = 2,\r
1538         parse = function(param)\r
1539                 if param == "" then\r
1540                         return false\r
1541                 end\r
1542                 if not check_filename(param) then\r
1543                         return false, "Disallowed file name: " .. param\r
1544                 end\r
1545                 return true, param\r
1546         end,\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
1552 \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
1556                                 filename)\r
1557                 if ret == nil then\r
1558                         worldedit.player_notify(name, "Failed to create Minetest schematic")\r
1559                 else\r
1560                         worldedit.player_notify(name, "Saved Minetest schematic to " .. param)\r
1561                 end\r
1562                 worldedit.prob_list[name] = {}\r
1563         end,\r
1564 })\r
1565 \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
1570         require_pos = 1,\r
1571         parse = function(param)\r
1572                 if param == "" then\r
1573                         return false\r
1574                 end\r
1575                 if not check_filename(param) then\r
1576                         return false, "Disallowed file name: " .. param\r
1577                 end\r
1578                 return true, param\r
1579         end,\r
1580         func = function(name, param)\r
1581                 local pos = worldedit.pos1[name]\r
1582 \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
1586                 else\r
1587                         worldedit.player_notify(name, "placed Minetest schematic " .. param ..\r
1588                                 " at " .. minetest.pos_to_string(pos))\r
1589                 end\r
1590         end,\r
1591 })\r
1592 \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
1600                 end\r
1601                 return true, param\r
1602         end,\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
1612                         local text = ""\r
1613                         local problist = worldedit.prob_list[name]\r
1614                         if problist == nil then\r
1615                                 return\r
1616                         end\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
1620                         end\r
1621                         worldedit.player_notify(name, "currently set node probabilities:")\r
1622                         worldedit.player_notify(name, text)\r
1623                 end\r
1624         end,\r
1625 })\r
1626 \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
1633         end\r
1634 end)\r
1635 \r
1636 worldedit.register_command("clearobjects", {\r
1637         params = "",\r
1638         description = "Clears all objects within the WorldEdit region",\r
1639         privs = {worldedit=true},\r
1640         require_pos = 2,\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
1645         end,\r
1646 })\r