]> git.lizzy.rs Git - worldedit.git/blob - worldedit_commands/init.lua
Warn when "misaligned" schematics are created
[worldedit.git] / worldedit_commands / init.lua
1 minetest.register_privilege("worldedit", "Can use WorldEdit commands")\r
2 \r
3 worldedit.set_pos = {}\r
4 worldedit.inspect = {}\r
5 \r
6 worldedit.pos1 = {}\r
7 worldedit.pos2 = {}\r
8 if minetest.place_schematic then\r
9         worldedit.prob_pos = {}\r
10         worldedit.prob_list = {}\r
11 end\r
12 \r
13 dofile(minetest.get_modpath("worldedit_commands") .. "/cuboid.lua")\r
14 dofile(minetest.get_modpath("worldedit_commands") .. "/mark.lua")\r
15 dofile(minetest.get_modpath("worldedit_commands") .. "/wand.lua")\r
16 local safe_region, check_region, reset_pending = dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua")\r
17 \r
18 local function get_position(name) --position 1 retrieval function for when not using `safe_region`\r
19         local pos1 = worldedit.pos1[name]\r
20         if pos1 == nil then\r
21                 worldedit.player_notify(name, "no position 1 selected")\r
22         end\r
23         return pos1\r
24 end\r
25 \r
26 -- normalize_nodename wrapper for convenience purposes\r
27 local function get_node(name, nodename)\r
28         local node = worldedit.normalize_nodename(nodename)\r
29         if not node then\r
30                 worldedit.player_notify(name, "invalid node name: " .. nodename)\r
31                 return nil\r
32         end\r
33         return node\r
34 end\r
35 \r
36 function worldedit.player_notify(name, message)\r
37         minetest.chat_send_player(name, "WorldEdit -!- " .. message, false)\r
38 end\r
39 \r
40 local function string_endswith(full, part)\r
41         return full:find(part, 1, true) == #full - #part + 1\r
42 end\r
43 \r
44 -- normalizes node "description" `nodename`, returning a string (or nil)\r
45 worldedit.normalize_nodename = function(nodename)\r
46         nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces\r
47         if nodename == "" then return nil end\r
48 \r
49         local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases\r
50         if minetest.registered_nodes[fullname] or fullname == "air" then -- full name\r
51                 return fullname\r
52         end\r
53         for key, value in pairs(minetest.registered_nodes) do\r
54                 if string_endswith(key, ":" .. nodename) then -- matches name (w/o mod part)\r
55                         return key\r
56                 end\r
57         end\r
58         nodename = nodename:lower() -- lowercase both for case insensitive comparison\r
59         for key, value in pairs(minetest.registered_nodes) do\r
60                 local desc = value.description:lower()\r
61                 if desc == nodename then -- matches description\r
62                         return key\r
63                 end\r
64                 if string_endswith(desc, " block") and desc == nodename.." block" then\r
65                         -- fuzzy description match (e.g. "Steel" == "Steel Block")\r
66                         return key\r
67                 end\r
68         end\r
69 \r
70         local match = nil\r
71         for key, value in pairs(minetest.registered_nodes) do\r
72                 if value.description:lower():find(nodename, 1, true) ~= nil then\r
73                         if match ~= nil then\r
74                                 return nil\r
75                         end\r
76                         match = key -- substring description match (only if no ambiguities)\r
77                 end\r
78         end\r
79         return match\r
80 end\r
81 \r
82 -- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1)\r
83 function worldedit.player_axis(name)\r
84         local dir = minetest.get_player_by_name(name):get_look_dir()\r
85         local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z)\r
86         if x > y then\r
87                 if x > z then\r
88                         return "x", dir.x > 0 and 1 or -1\r
89                 end\r
90         elseif y > z then\r
91                 return "y", dir.y > 0 and 1 or -1\r
92         end\r
93         return "z", dir.z > 0 and 1 or -1\r
94 end\r
95 \r
96 local function check_filename(name)\r
97         return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil\r
98 end\r
99 \r
100 \r
101 minetest.register_chatcommand("/about", {\r
102         params = "",\r
103         description = "Get information about the WorldEdit mod",\r
104         func = function(name, param)\r
105                 worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string .. " is available on this server. Type /help to get a list of commands, or get more information at https://github.com/Uberi/Minetest-WorldEdit/")\r
106         end,\r
107 })\r
108 \r
109 -- mostly copied from builtin/chatcommands.lua with minor modifications\r
110 minetest.register_chatcommand("/help", {\r
111         privs = {},\r
112         params = "[all/<cmd>]",\r
113         description = "Get help for WorldEdit commands",\r
114         func = function(name, param)\r
115                 local function is_we_command(cmd)\r
116                         return cmd:sub(0, 1) == "/"\r
117                 end\r
118                 local function format_help_line(cmd, def)\r
119                         local msg = minetest.colorize("#00ffff", "/"..cmd)\r
120                         if def.params and def.params ~= "" then\r
121                                 msg = msg .. " " .. def.params\r
122                         end\r
123                         if def.description and def.description ~= "" then\r
124                                 msg = msg .. ": " .. def.description\r
125                         end\r
126                         return msg\r
127                 end\r
128 \r
129                 if not minetest.check_player_privs(name, "worldedit") then\r
130                         return false, "You are not allowed to use any WorldEdit commands."\r
131                 end\r
132                 if param == "" then\r
133                         local msg = ""\r
134                         local cmds = {}\r
135                         for cmd, def in pairs(minetest.chatcommands) do\r
136                                 if is_we_command(cmd) and minetest.check_player_privs(name, def.privs) then\r
137                                         cmds[#cmds + 1] = cmd:sub(2) -- strip the /\r
138                                 end\r
139                         end\r
140                         table.sort(cmds)\r
141                         return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"\r
142                                         .. "Use '//help <cmd>' to get more information,"\r
143                                         .. " or '//help all' to list everything."\r
144                 elseif param == "all" then\r
145                         local cmds = {}\r
146                         for cmd, def in pairs(minetest.chatcommands) do\r
147                                 if is_we_command(cmd) and minetest.check_player_privs(name, def.privs) then\r
148                                         cmds[#cmds + 1] = format_help_line(cmd, def)\r
149                                 end\r
150                         end\r
151                         table.sort(cmds)\r
152                         return true, "Available commands:\n"..table.concat(cmds, "\n")\r
153                 else\r
154                         return minetest.chatcommands["help"].func(name, "/" .. param)\r
155                 end\r
156         end,\r
157 })\r
158 \r
159 minetest.register_chatcommand("/inspect", {\r
160         params = "on/off/1/0/true/false/yes/no/enable/disable/<blank>",\r
161         description = "Enable or disable node inspection",\r
162         privs = {worldedit=true},\r
163         func = function(name, param)\r
164                 if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then\r
165                         worldedit.inspect[name] = true\r
166                         local axis, sign = worldedit.player_axis(name)\r
167                         worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis",\r
168                                 name, axis .. (sign > 0 and "+" or "-")))\r
169                 elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then\r
170                         worldedit.inspect[name] = nil\r
171                         worldedit.player_notify(name, "inspector: inspection disabled")\r
172                 else\r
173                         worldedit.player_notify(name, "invalid usage: " .. param)\r
174                 end\r
175         end,\r
176 })\r
177 \r
178 local function get_node_rlight(pos)\r
179         local vecs = { -- neighboring nodes\r
180                 {x= 1, y= 0, z= 0},\r
181                 {x=-1, y= 0, z= 0},\r
182                 {x= 0, y= 1, z= 0},\r
183                 {x= 0, y=-1, z= 0},\r
184                 {x= 0, y= 0, z= 1},\r
185                 {x= 0, y= 0, z=-1},\r
186         }\r
187         local ret = 0\r
188         for _, v in ipairs(vecs) do\r
189                 ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))\r
190         end\r
191         return ret\r
192 end\r
193 \r
194 minetest.register_on_punchnode(function(pos, node, puncher)\r
195         local name = puncher:get_player_name()\r
196         if worldedit.inspect[name] then\r
197                 local axis, sign = worldedit.player_axis(name)\r
198                 local message = string.format("inspector: %s at %s (param1=%d, param2=%d, received light=%d) punched facing the %s axis",\r
199                         node.name, minetest.pos_to_string(pos), node.param1, node.param2, get_node_rlight(pos), axis .. (sign > 0 and "+" or "-"))\r
200                 worldedit.player_notify(name, message)\r
201         end\r
202 end)\r
203 \r
204 minetest.register_chatcommand("/reset", {\r
205         params = "",\r
206         description = "Reset the region so that it is empty",\r
207         privs = {worldedit=true},\r
208         func = function(name, param)\r
209                 worldedit.pos1[name] = nil\r
210                 worldedit.pos2[name] = nil\r
211                 worldedit.mark_pos1(name)\r
212                 worldedit.mark_pos2(name)\r
213                 worldedit.set_pos[name] = nil\r
214                 --make sure the user does not try to confirm an operation after resetting pos:\r
215                 reset_pending(name)\r
216                 worldedit.player_notify(name, "region reset")\r
217         end,\r
218 })\r
219 \r
220 minetest.register_chatcommand("/mark", {\r
221         params = "",\r
222         description = "Show markers at the region positions",\r
223         privs = {worldedit=true},\r
224         func = function(name, param)\r
225                 worldedit.mark_pos1(name)\r
226                 worldedit.mark_pos2(name)\r
227                 worldedit.player_notify(name, "region marked")\r
228         end,\r
229 })\r
230 \r
231 minetest.register_chatcommand("/unmark", {\r
232         params = "",\r
233         description = "Hide markers if currently shown",\r
234         privs = {worldedit=true},\r
235         func = function(name, param)\r
236                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
237                 worldedit.pos1[name] = nil\r
238                 worldedit.pos2[name] = nil\r
239                 worldedit.mark_pos1(name)\r
240                 worldedit.mark_pos2(name)\r
241                 worldedit.pos1[name] = pos1\r
242                 worldedit.pos2[name] = pos2\r
243                 worldedit.player_notify(name, "region unmarked")\r
244         end,\r
245 })\r
246 \r
247 minetest.register_chatcommand("/pos1", {\r
248         params = "",\r
249         description = "Set WorldEdit region position 1 to the player's location",\r
250         privs = {worldedit=true},\r
251         func = function(name, param)\r
252                 local pos = minetest.get_player_by_name(name):get_pos()\r
253                 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
254                 worldedit.pos1[name] = pos\r
255                 worldedit.mark_pos1(name)\r
256                 worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))\r
257         end,\r
258 })\r
259 \r
260 minetest.register_chatcommand("/pos2", {\r
261         params = "",\r
262         description = "Set WorldEdit region position 2 to the player's location",\r
263         privs = {worldedit=true},\r
264         func = function(name, param)\r
265                 local pos = minetest.get_player_by_name(name):get_pos()\r
266                 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
267                 worldedit.pos2[name] = pos\r
268                 worldedit.mark_pos2(name)\r
269                 worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))\r
270         end,\r
271 })\r
272 \r
273 minetest.register_chatcommand("/p", {\r
274         params = "set/set1/set2/get",\r
275         description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region",\r
276         privs = {worldedit=true},\r
277         func = function(name, param)\r
278                 if param == "set" then --set both WorldEdit positions\r
279                         worldedit.set_pos[name] = "pos1"\r
280                         worldedit.player_notify(name, "select positions by punching two nodes")\r
281                 elseif param == "set1" then --set WorldEdit position 1\r
282                         worldedit.set_pos[name] = "pos1only"\r
283                         worldedit.player_notify(name, "select position 1 by punching a node")\r
284                 elseif param == "set2" then --set WorldEdit position 2\r
285                         worldedit.set_pos[name] = "pos2"\r
286                         worldedit.player_notify(name, "select position 2 by punching a node")\r
287                 elseif param == "get" then --display current WorldEdit positions\r
288                         if worldedit.pos1[name] ~= nil then\r
289                                 worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name]))\r
290                         else\r
291                                 worldedit.player_notify(name, "position 1 not set")\r
292                         end\r
293                         if worldedit.pos2[name] ~= nil then\r
294                                 worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name]))\r
295                         else\r
296                                 worldedit.player_notify(name, "position 2 not set")\r
297                         end\r
298                 else\r
299                         worldedit.player_notify(name, "unknown subcommand: " .. param)\r
300                 end\r
301         end,\r
302 })\r
303 \r
304 minetest.register_chatcommand("/fixedpos", {\r
305         params = "set1/set2 x y z",\r
306         description = "Set a WorldEdit region position to the position at (<x>, <y>, <z>)",\r
307         privs = {worldedit=true},\r
308         func = function(name, param)\r
309                 local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")\r
310                 if found == nil then\r
311                         worldedit.player_notify(name, "invalid usage: " .. param)\r
312                         return\r
313                 end\r
314                 local pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)}\r
315                 if flag == "set1" then\r
316                         worldedit.pos1[name] = pos\r
317                         worldedit.mark_pos1(name)\r
318                         worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))\r
319                 else --flag == "set2"\r
320                         worldedit.pos2[name] = pos\r
321                         worldedit.mark_pos2(name)\r
322                         worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))\r
323                 end\r
324         end,\r
325 })\r
326 \r
327 minetest.register_on_punchnode(function(pos, node, puncher)\r
328         local name = puncher:get_player_name()\r
329         if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position\r
330                 if worldedit.set_pos[name] == "pos1" then --setting position 1\r
331                         worldedit.pos1[name] = pos\r
332                         worldedit.mark_pos1(name)\r
333                         worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation\r
334                         worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))\r
335                 elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only\r
336                         worldedit.pos1[name] = pos\r
337                         worldedit.mark_pos1(name)\r
338                         worldedit.set_pos[name] = nil --finished setting positions\r
339                         worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos))\r
340                 elseif worldedit.set_pos[name] == "pos2" then --setting position 2\r
341                         worldedit.pos2[name] = pos\r
342                         worldedit.mark_pos2(name)\r
343                         worldedit.set_pos[name] = nil --finished setting positions\r
344                         worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos))\r
345                 elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities\r
346                         worldedit.prob_pos[name] = pos\r
347                         minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]")\r
348                 end\r
349         end\r
350 end)\r
351 \r
352 minetest.register_chatcommand("/volume", {\r
353         params = "",\r
354         description = "Display the volume of the current WorldEdit region",\r
355         privs = {worldedit=true},\r
356         func = function(name, param)\r
357                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
358                 if pos1 == nil or pos2 == nil then\r
359                         worldedit.player_notify(name, "no region selected")\r
360                         return nil\r
361                 end\r
362 \r
363                 local volume = worldedit.volume(pos1, pos2)\r
364                 local abs = math.abs\r
365                 worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes ("\r
366                         .. abs(pos2.x - pos1.x) + 1 .. "*"\r
367                         .. abs(pos2.y - pos1.y) + 1 .. "*"\r
368                         .. abs(pos2.z - pos1.z) + 1 .. ")")\r
369         end,\r
370 })\r
371 \r
372 minetest.register_chatcommand("/deleteblocks", {\r
373         params = "",\r
374         description = "remove all MapBlocks (16x16x16) containing the selected area from the map",\r
375         privs = {worldedit=true},\r
376         func = safe_region(function(name, param)\r
377                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
378                 local success = minetest.delete_area(pos1, pos2)\r
379                 if success then\r
380                         worldedit.player_notify(name, "Area deleted.")\r
381                 else\r
382                         worldedit.player_notify(name, "There was an error during deletion of the area.")\r
383                 end\r
384         end),\r
385 })\r
386 \r
387 minetest.register_chatcommand("/set", {\r
388         params = "<node>",\r
389         description = "Set the current WorldEdit region to <node>",\r
390         privs = {worldedit=true},\r
391         func = safe_region(function(name, param)\r
392                 local node = get_node(name, param)\r
393                 if not node then return end\r
394 \r
395                 local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)\r
396                 worldedit.player_notify(name, count .. " nodes set")\r
397         end, check_region),\r
398 })\r
399 \r
400 minetest.register_chatcommand("/param2", {\r
401         params = "<param2>",\r
402         description = "Set param2 of all nodes in the current WorldEdit region to <param2>",\r
403         privs = {worldedit=true},\r
404         func = safe_region(function(name, param)\r
405                 local param2 = tonumber(param)\r
406                 if not param2 then\r
407                         worldedit.player_notify(name, "Invalid or missing param2 argument")\r
408                         return\r
409                 elseif param2 < 0 or param2 > 255 then\r
410                         worldedit.player_notify(name, "Param2 is out of range (must be between 0 and 255 inclusive)!")\r
411                         return\r
412                 end\r
413 \r
414                 local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)\r
415                 worldedit.player_notify(name, count .. " nodes altered")\r
416         end, check_region),\r
417 })\r
418 \r
419 minetest.register_chatcommand("/mix", {\r
420         params = "<node1> [<weighting1>] [<node2> [<weighting2>]] ...",\r
421         description = "Fill the current WorldEdit region with a random mix of <node1>, ...",\r
422         privs = {worldedit=true},\r
423         func = safe_region(function(name, param)\r
424                 local nodes = {}\r
425                 for nodename in param:gmatch("[^%s]+") do\r
426                         if tonumber(nodename) ~= nil and #nodes > 0 then\r
427                                 local last_node = nodes[#nodes]\r
428                                 for i = 1, tonumber(nodename) do\r
429                                         nodes[#nodes + 1] = last_node\r
430                                 end\r
431                         else\r
432                                 local node = get_node(name, nodename)\r
433                                 if not node then return end\r
434                                 nodes[#nodes + 1] = node\r
435                         end\r
436                 end\r
437 \r
438                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
439                 local count = worldedit.set(pos1, pos2, nodes)\r
440                 worldedit.player_notify(name, count .. " nodes set")\r
441         end, check_region),\r
442 })\r
443 \r
444 local check_replace = function(name, param)\r
445         local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")\r
446         if found == nil then\r
447                 worldedit.player_notify(name, "invalid usage: " .. param)\r
448                 return nil\r
449         end\r
450         local newsearchnode = worldedit.normalize_nodename(searchnode)\r
451         if not newsearchnode then\r
452                 worldedit.player_notify(name, "invalid search node name: " .. searchnode)\r
453                 return nil\r
454         end\r
455         local newreplacenode = worldedit.normalize_nodename(replacenode)\r
456         if not newreplacenode then\r
457                 worldedit.player_notify(name, "invalid replace node name: " .. replacenode)\r
458                 return nil\r
459         end\r
460         return check_region(name, param)\r
461 end\r
462 \r
463 minetest.register_chatcommand("/replace", {\r
464         params = "<search node> <replace node>",\r
465         description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region",\r
466         privs = {worldedit=true},\r
467         func = safe_region(function(name, param)\r
468                 local found, _, search_node, replace_node = param:find("^([^%s]+)%s+(.+)$")\r
469                 local norm_search_node = worldedit.normalize_nodename(search_node)\r
470                 local norm_replace_node = worldedit.normalize_nodename(replace_node)\r
471                 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],\r
472                                 norm_search_node, norm_replace_node)\r
473                 worldedit.player_notify(name, count .. " nodes replaced")\r
474         end, check_replace),\r
475 })\r
476 \r
477 minetest.register_chatcommand("/replaceinverse", {\r
478         params = "<search node> <replace node>",\r
479         description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region",\r
480         privs = {worldedit=true},\r
481         func = safe_region(function(name, param)\r
482                 local found, _, search_node, replace_node = param:find("^([^%s]+)%s+(.+)$")\r
483                 local norm_search_node = worldedit.normalize_nodename(search_node)\r
484                 local norm_replace_node = worldedit.normalize_nodename(replace_node)\r
485                 local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],\r
486                                 norm_search_node, norm_replace_node, true)\r
487                 worldedit.player_notify(name, count .. " nodes replaced")\r
488         end, check_replace),\r
489 })\r
490 \r
491 local check_cube = function(name, param)\r
492         if worldedit.pos1[name] == nil then\r
493                 worldedit.player_notify(name, "no position 1 selected")\r
494                 return nil\r
495         end\r
496         local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
497         if found == nil then\r
498                 worldedit.player_notify(name, "invalid usage: " .. param)\r
499                 return nil\r
500         end\r
501         local node = get_node(name, nodename)\r
502         if not node then return nil end\r
503         return tonumber(w) * tonumber(h) * tonumber(l)\r
504 end\r
505 \r
506 minetest.register_chatcommand("/hollowcube", {\r
507         params = "<width> <height> <length> <node>",\r
508         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
509         privs = {worldedit=true},\r
510         func = safe_region(function(name, param)\r
511                 local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
512                 local node = get_node(name, nodename)\r
513                 local count = worldedit.cube(worldedit.pos1[name], tonumber(w), tonumber(h), tonumber(l), node, true)\r
514                 worldedit.player_notify(name, count .. " nodes added")\r
515         end, check_cube),\r
516 })\r
517 \r
518 minetest.register_chatcommand("/cube", {\r
519         params = "<width> <height> <length> <node>",\r
520         description = "Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.",\r
521         privs = {worldedit=true},\r
522         func = safe_region(function(name, param)\r
523                 local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
524                 local node = get_node(name, nodename)\r
525                 local count = worldedit.cube(worldedit.pos1[name], tonumber(w), tonumber(h), tonumber(l), node)\r
526                 worldedit.player_notify(name, count .. " nodes added")\r
527         end, check_cube),\r
528 })\r
529 \r
530 local check_sphere = function(name, param)\r
531         if worldedit.pos1[name] == nil then\r
532                 worldedit.player_notify(name, "no position 1 selected")\r
533                 return nil\r
534         end\r
535         local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
536         if found == nil then\r
537                 worldedit.player_notify(name, "invalid usage: " .. param)\r
538                 return nil\r
539         end\r
540         local node = get_node(name, nodename)\r
541         if not node then return nil end\r
542         return math.ceil((4 * math.pi * (tonumber(radius) ^ 3)) / 3) --volume of sphere\r
543 end\r
544 \r
545 minetest.register_chatcommand("/hollowsphere", {\r
546         params = "<radius> <node>",\r
547         description = "Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",\r
548         privs = {worldedit=true},\r
549         func = safe_region(function(name, param)\r
550                 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
551                 local node = get_node(name, nodename)\r
552                 local count = worldedit.sphere(worldedit.pos1[name], tonumber(radius), node, true)\r
553                 worldedit.player_notify(name, count .. " nodes added")\r
554         end, check_sphere),\r
555 })\r
556 \r
557 minetest.register_chatcommand("/sphere", {\r
558         params = "<radius> <node>",\r
559         description = "Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>",\r
560         privs = {worldedit=true},\r
561         func = safe_region(function(name, param)\r
562                 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
563                 local node = get_node(name, nodename)\r
564                 local count = worldedit.sphere(worldedit.pos1[name], tonumber(radius), node)\r
565                 worldedit.player_notify(name, count .. " nodes added")\r
566         end, check_sphere),\r
567 })\r
568 \r
569 local check_dome = function(name, param)\r
570         if worldedit.pos1[name] == nil then\r
571                 worldedit.player_notify(name, "no position 1 selected")\r
572                 return nil\r
573         end\r
574         local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
575         if found == nil then\r
576                 worldedit.player_notify(name, "invalid usage: " .. param)\r
577                 return nil\r
578         end\r
579         local node = get_node(name, nodename)\r
580         if not node then return nil end\r
581         return math.ceil((2 * math.pi * (tonumber(radius) ^ 3)) / 3) --volume of dome\r
582 end\r
583 \r
584 minetest.register_chatcommand("/hollowdome", {\r
585         params = "<radius> <node>",\r
586         description = "Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",\r
587         privs = {worldedit=true},\r
588         func = safe_region(function(name, param)\r
589                 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
590                 local node = get_node(name, nodename)\r
591                 local count = worldedit.dome(worldedit.pos1[name], tonumber(radius), node, true)\r
592                 worldedit.player_notify(name, count .. " nodes added")\r
593         end, check_dome),\r
594 })\r
595 \r
596 minetest.register_chatcommand("/dome", {\r
597         params = "<radius> <node>",\r
598         description = "Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>",\r
599         privs = {worldedit=true},\r
600         func = safe_region(function(name, param)\r
601                 local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")\r
602                 local node = get_node(name, nodename)\r
603                 local count = worldedit.dome(worldedit.pos1[name], tonumber(radius), node)\r
604                 worldedit.player_notify(name, count .. " nodes added")\r
605         end, check_dome),\r
606 })\r
607 \r
608 local check_cylinder = function(name, param)\r
609         if worldedit.pos1[name] == nil then\r
610                 worldedit.player_notify(name, "no position 1 selected")\r
611                 return nil\r
612         end\r
613         -- two radii\r
614         local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
615         if found == nil then\r
616                 -- single radius\r
617                 found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")\r
618                 radius2 = radius1\r
619         end\r
620         if found == nil then\r
621                 worldedit.player_notify(name, "invalid usage: " .. param)\r
622                 return nil\r
623         end\r
624         local node = get_node(name, nodename)\r
625         if not node then return nil end\r
626         local radius = math.max(tonumber(radius1), tonumber(radius2))\r
627         return math.ceil(math.pi * (radius ^ 2) * tonumber(length))\r
628 end\r
629 \r
630 minetest.register_chatcommand("/hollowcylinder", {\r
631         params = "x/y/z/? <length> <radius1> [radius2] <node>",\r
632         description = "Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>",\r
633         privs = {worldedit=true},\r
634         func = safe_region(function(name, param)\r
635                 -- two radii\r
636                 local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
637                 if found == nil then\r
638                         -- single radius\r
639                         found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")\r
640                         radius2 = radius1\r
641                 end\r
642                 length = tonumber(length)\r
643                 if axis == "?" then\r
644                         local sign\r
645                         axis, sign = worldedit.player_axis(name)\r
646                         length = length * sign\r
647                 end\r
648                 local node = get_node(name, nodename)\r
649                 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, tonumber(radius1), tonumber(radius2), node, true)\r
650                 worldedit.player_notify(name, count .. " nodes added")\r
651         end, check_cylinder),\r
652 })\r
653 \r
654 minetest.register_chatcommand("/cylinder", {\r
655         params = "x/y/z/? <length> <radius1> [radius2] <node>",\r
656         description = "Add cylinder at WorldEdit position 1 along the x/y/z/? axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>",\r
657         privs = {worldedit=true},\r
658         func = safe_region(function(name, param)\r
659                 -- two radii\r
660                 local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
661                 if found == nil then\r
662                         -- single radius\r
663                         found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")\r
664                         radius2 = radius1\r
665                 end\r
666                 length = tonumber(length)\r
667                 if axis == "?" then\r
668                         local sign\r
669                         axis, sign = worldedit.player_axis(name)\r
670                         length = length * sign\r
671                 end\r
672                 local node = get_node(name, nodename)\r
673                 local count = worldedit.cylinder(worldedit.pos1[name], axis, length, tonumber(radius1), tonumber(radius2), node)\r
674                 worldedit.player_notify(name, count .. " nodes added")\r
675         end, check_cylinder),\r
676 })\r
677 \r
678 local check_pyramid = function(name, param)\r
679         if worldedit.pos1[name] == nil then\r
680                 worldedit.player_notify(name, "no position 1 selected")\r
681                 return nil\r
682         end\r
683         local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")\r
684         if found == nil then\r
685                 worldedit.player_notify(name, "invalid usage: " .. param)\r
686                 return nil\r
687         end\r
688         local node = get_node(name, nodename)\r
689         if not node then return nil end\r
690         height = tonumber(height)\r
691         return math.ceil(((height * 2 + 1) ^ 2) * height / 3)\r
692 end\r
693      \r
694 minetest.register_chatcommand("/hollowpyramid", {\r
695         params = "x/y/z/? <height> <node>",\r
696         description = "Add hollow pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height <height>, composed of <node>",\r
697         privs = {worldedit=true},\r
698         func = safe_region(function(name, param)\r
699                 local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")\r
700                 height = tonumber(height)\r
701                 if axis == "?" then\r
702                         local sign\r
703                         axis, sign = worldedit.player_axis(name)\r
704                         height = height * sign\r
705                 end\r
706                 local node = get_node(name, nodename)\r
707                 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)\r
708                 worldedit.player_notify(name, count .. " nodes added")\r
709         end, check_pyramid),\r
710 })\r
711 \r
712 minetest.register_chatcommand("/pyramid", {\r
713         params = "x/y/z/? <height> <node>",\r
714         description = "Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height <height>, composed of <node>",\r
715         privs = {worldedit=true},\r
716         func = safe_region(function(name, param)\r
717                 local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")\r
718                 height = tonumber(height)\r
719                 if axis == "?" then\r
720                         local sign\r
721                         axis, sign = worldedit.player_axis(name)\r
722                         height = height * sign\r
723                 end\r
724                 local node = get_node(name, nodename)\r
725                 local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)\r
726                 worldedit.player_notify(name, count .. " nodes added")\r
727         end, check_pyramid),\r
728 })\r
729 \r
730 minetest.register_chatcommand("/spiral", {\r
731         params = "<length> <height> <space> <node>",\r
732         description = "Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>",\r
733         privs = {worldedit=true},\r
734         func = safe_region(function(name, param)\r
735                 local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
736                 local node = get_node(name, nodename)\r
737                 local count = worldedit.spiral(worldedit.pos1[name], tonumber(length), tonumber(height), tonumber(space), node)\r
738                 worldedit.player_notify(name, count .. " nodes added")\r
739         end,\r
740         function(name, param)\r
741                 if worldedit.pos1[name] == nil then\r
742                         worldedit.player_notify(name, "no position 1 selected")\r
743                         return nil\r
744                 end\r
745                 local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")\r
746                 if found == nil then\r
747                         worldedit.player_notify(name, "invalid usage: " .. param)\r
748                         return nil\r
749                 end\r
750                 local node = get_node(name, nodename)\r
751                 if not node then return nil end\r
752                 return 1 -- TODO: return an useful value\r
753         end),\r
754 })\r
755 \r
756 minetest.register_chatcommand("/copy", {\r
757         params = "x/y/z/? <amount>",\r
758         description = "Copy the current WorldEdit region along the x/y/z/? axis by <amount> nodes",\r
759         privs = {worldedit=true},\r
760         func = safe_region(function(name, param)\r
761                 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
762                 if found == nil then\r
763                         worldedit.player_notify(name, "invalid usage: " .. param)\r
764                         return\r
765                 end\r
766                 amount = tonumber(amount)\r
767                 if axis == "?" then\r
768                         local sign\r
769                         axis, sign = worldedit.player_axis(name)\r
770                         amount = amount * sign\r
771                 end\r
772 \r
773                 local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)\r
774                 worldedit.player_notify(name, count .. " nodes copied")\r
775         end,\r
776         function(name, param)\r
777                 local volume = check_region(name, param)\r
778                 return volume and volume * 2 or volume\r
779         end),\r
780 })\r
781 \r
782 minetest.register_chatcommand("/move", {\r
783         params = "x/y/z/? <amount>",\r
784         description = "Move the current WorldEdit region along the x/y/z/? axis by <amount> nodes",\r
785         privs = {worldedit=true},\r
786         func = safe_region(function(name, param)\r
787                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
788                 local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
789                 if found == nil then\r
790                         worldedit.player_notify(name, "invalid usage: " .. param)\r
791                         return\r
792                 end\r
793                 amount = tonumber(amount)\r
794                 if axis == "?" then\r
795                         local sign\r
796                         axis, sign = worldedit.player_axis(name)\r
797                         amount = amount * sign\r
798                 end\r
799 \r
800                 local count = worldedit.move(pos1, pos2, axis, amount)\r
801 \r
802                 pos1[axis] = pos1[axis] + amount\r
803                 pos2[axis] = pos2[axis] + amount\r
804                 worldedit.mark_pos1(name)\r
805                 worldedit.mark_pos2(name)\r
806                 worldedit.player_notify(name, count .. " nodes moved")\r
807         end, check_region),\r
808 })\r
809 \r
810 minetest.register_chatcommand("/stack", {\r
811         params = "x/y/z/? <count>",\r
812         description = "Stack the current WorldEdit region along the x/y/z/? axis <count> times",\r
813         privs = {worldedit=true},\r
814         func = safe_region(function(name, param)\r
815                 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
816                 repetitions = tonumber(repetitions)\r
817                 if axis == "?" then\r
818                         local sign\r
819                         axis, sign = worldedit.player_axis(name)\r
820                         repetitions = repetitions * sign\r
821                 end\r
822 \r
823                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
824                 local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)\r
825                 worldedit.stack(pos1, pos2, axis, repetitions, function()\r
826                         worldedit.player_notify(name, count .. " nodes stacked")\r
827                 end)\r
828         end,\r
829         function(name, param)\r
830                 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
831                 if found == nil then\r
832                         worldedit.player_notify(name, "invalid usage: " .. param)\r
833                         return\r
834                 end\r
835 \r
836                 local count = check_region(name, param)\r
837                 if count then return tonumber(repetitions) * count end\r
838                 return nil\r
839         end),\r
840 })\r
841 \r
842 minetest.register_chatcommand("/stack2", {\r
843         params = "<count> <x> <y> <z>",\r
844         description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",\r
845         privs = {worldedit=true},\r
846         func = safe_region(function(name, param)\r
847                 local repetitions, incs = param:match("(%d+)%s*(.+)")\r
848                 if repetitions == nil then\r
849                         return\r
850                 end\r
851                 repetitions = tonumber(repetitions)\r
852 \r
853                 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")\r
854                 if x == nil then\r
855                         worldedit.player_notify(name, "invalid increments: " .. param)\r
856                         return\r
857                 end\r
858                 x, y, z = tonumber(x), tonumber(y), tonumber(z)\r
859 \r
860                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
861                 local count = worldedit.volume(pos1, pos2) * repetitions\r
862                 worldedit.stack2(pos1, pos2, {x=x, y=y, z=z}, repetitions, function()\r
863                         worldedit.player_notify(name, count .. " nodes stacked")\r
864                 end)\r
865         end,\r
866         function(name, param)\r
867                 local repetitions, incs = param:match("(%d+)%s*(.+)")\r
868                 if repetitions == nil then\r
869                         worldedit.player_notify(name, "invalid count: " .. param)\r
870                         return\r
871                 end\r
872                 repetitions = tonumber(repetitions)\r
873 \r
874                 local count = check_region(name, param)\r
875                 if count then return repetitions * count end\r
876                 return nil\r
877         end),\r
878 })\r
879 \r
880 \r
881 minetest.register_chatcommand("/stretch", {\r
882         params = "<stretchx> <stretchy> <stretchz>",\r
883         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
884         privs = {worldedit=true},\r
885         func = safe_region(function(name, param)\r
886                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
887                 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")\r
888                 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)\r
889                 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)\r
890 \r
891                 --reset markers to scaled positions\r
892                 worldedit.pos1[name] = pos1\r
893                 worldedit.pos2[name] = pos2\r
894                 worldedit.mark_pos1(name)\r
895                 worldedit.mark_pos2(name)\r
896 \r
897                 worldedit.player_notify(name, count .. " nodes stretched")\r
898         end,\r
899         function(name, param)\r
900                 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")\r
901                 if found == nil then\r
902                         worldedit.player_notify(name, "invalid usage: " .. param)\r
903                         return nil\r
904                 end\r
905                 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)\r
906                 if stretchx == 0 or stretchy == 0 or stretchz == 0 then\r
907                         worldedit.player_notify(name, "invalid scaling factors: " .. param)\r
908                         return nil\r
909                 end\r
910                 local count = check_region(name, param)\r
911                 if count then\r
912                         return stretchx * stretchy * stretchz * count\r
913                 end\r
914                 return nil\r
915         end),\r
916 })\r
917 \r
918 minetest.register_chatcommand("/transpose", {\r
919         params = "x/y/z/? x/y/z/?",\r
920         description = "Transpose the current WorldEdit region along the x/y/z/? and x/y/z/? axes",\r
921         privs = {worldedit=true},\r
922         func = safe_region(function(name, param)\r
923                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
924                 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")\r
925                 if axis1 == "?" then axis1 = worldedit.player_axis(name) end\r
926                 if axis2 == "?" then axis2 = worldedit.player_axis(name) end\r
927                 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\r
928 \r
929                 --reset markers to transposed positions\r
930                 worldedit.pos1[name] = pos1\r
931                 worldedit.pos2[name] = pos2\r
932                 worldedit.mark_pos1(name)\r
933                 worldedit.mark_pos2(name)\r
934 \r
935                 worldedit.player_notify(name, count .. " nodes transposed")\r
936         end,\r
937         function(name, param)\r
938                 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")\r
939                 if found == nil then\r
940                         worldedit.player_notify(name, "invalid usage: " .. param)\r
941                         return nil\r
942                 end\r
943                 if axis1 == axis2 then\r
944                         worldedit.player_notify(name, "invalid usage: axes must be different")\r
945                         return nil\r
946                 end\r
947                 return check_region(name, param)\r
948         end),\r
949 })\r
950 \r
951 minetest.register_chatcommand("/flip", {\r
952         params = "x/y/z/?",\r
953         description = "Flip the current WorldEdit region along the x/y/z/? axis",\r
954         privs = {worldedit=true},\r
955         func = safe_region(function(name, param)\r
956                 if param == "?" then param = worldedit.player_axis(name) end\r
957                 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)\r
958                 worldedit.player_notify(name, count .. " nodes flipped")\r
959         end,\r
960         function(name, param)\r
961                 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then\r
962                         worldedit.player_notify(name, "invalid usage: " .. param)\r
963                         return nil\r
964                 end\r
965                 return check_region(name, param)\r
966         end),\r
967 })\r
968 \r
969 minetest.register_chatcommand("/rotate", {\r
970         params = "<axis> <angle>",\r
971         description = "Rotate the current WorldEdit region around the axis <axis> by angle <angle> (90 degree increment)",\r
972         privs = {worldedit=true},\r
973         func = safe_region(function(name, param)\r
974                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
975                 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
976                 if axis == "?" then axis = worldedit.player_axis(name) end\r
977                 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)\r
978 \r
979                 --reset markers to rotated positions\r
980                 worldedit.pos1[name] = pos1\r
981                 worldedit.pos2[name] = pos2\r
982                 worldedit.mark_pos1(name)\r
983                 worldedit.mark_pos2(name)\r
984 \r
985                 worldedit.player_notify(name, count .. " nodes rotated")\r
986         end,\r
987         function(name, param)\r
988                 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
989                 if found == nil then\r
990                         worldedit.player_notify(name, "invalid usage: " .. param)\r
991                         return nil\r
992                 end\r
993                 if angle % 90 ~= 0 or angle % 360 == 0 then\r
994                         worldedit.player_notify(name, "invalid usage: angle must be multiple of 90")\r
995                         return nil\r
996                 end\r
997                 return check_region(name, param)\r
998         end),\r
999 })\r
1000 \r
1001 minetest.register_chatcommand("/orient", {\r
1002         params = "<angle>",\r
1003         description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",\r
1004         privs = {worldedit=true},\r
1005         func = safe_region(function(name, param)\r
1006                 local found, _, angle = param:find("^([+-]?%d+)$")\r
1007                 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)\r
1008                 worldedit.player_notify(name, count .. " nodes oriented")\r
1009         end,\r
1010         function(name, param)\r
1011                 local found, _, angle = param:find("^([+-]?%d+)$")\r
1012                 if found == nil then\r
1013                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1014                         return nil\r
1015                 end\r
1016                 if angle % 90 ~= 0 then\r
1017                         worldedit.player_notify(name, "invalid usage: angle must be multiple of 90")\r
1018                         return nil\r
1019                 end\r
1020                 return check_region(name, param)\r
1021         end),\r
1022 })\r
1023 \r
1024 minetest.register_chatcommand("/fixlight", {\r
1025         params = "",\r
1026         description = "Fix the lighting in the current WorldEdit region",\r
1027         privs = {worldedit=true},\r
1028         func = safe_region(function(name, param)\r
1029                 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])\r
1030                 worldedit.player_notify(name, count .. " nodes updated")\r
1031         end),\r
1032 })\r
1033 \r
1034 minetest.register_chatcommand("/drain", {\r
1035         params = "",\r
1036         description = "Remove any fluid node within the current WorldEdit region",\r
1037         privs = {worldedit=true},\r
1038         func = safe_region(function(name, param)\r
1039                 -- TODO: make an API function for this\r
1040                 local count = 0\r
1041                 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])\r
1042                 for x = pos1.x, pos2.x do\r
1043                 for y = pos1.y, pos2.y do\r
1044                 for z = pos1.z, pos2.z do\r
1045                         local n = minetest.get_node({x=x, y=y, z=z}).name\r
1046                         local d = minetest.registered_nodes[n]\r
1047                         if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then\r
1048                                 minetest.remove_node({x=x, y=y, z=z})\r
1049                                 count = count + 1\r
1050                         end\r
1051                 end\r
1052                 end\r
1053                 end\r
1054                 worldedit.player_notify(name, count .. " nodes updated")\r
1055         end),\r
1056 })\r
1057 \r
1058 minetest.register_chatcommand("/hide", {\r
1059         params = "",\r
1060         description = "Hide all nodes in the current WorldEdit region non-destructively",\r
1061         privs = {worldedit=true},\r
1062         func = safe_region(function(name, param)\r
1063                 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])\r
1064                 worldedit.player_notify(name, count .. " nodes hidden")\r
1065         end),\r
1066 })\r
1067 \r
1068 minetest.register_chatcommand("/suppress", {\r
1069         params = "<node>",\r
1070         description = "Suppress all <node> in the current WorldEdit region non-destructively",\r
1071         privs = {worldedit=true},\r
1072         func = safe_region(function(name, param)\r
1073                 local node = get_node(name, param)\r
1074                 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)\r
1075                 worldedit.player_notify(name, count .. " nodes suppressed")\r
1076         end, check_region),\r
1077 })\r
1078 \r
1079 minetest.register_chatcommand("/highlight", {\r
1080         params = "<node>",\r
1081         description = "Highlight <node> in the current WorldEdit region by hiding everything else non-destructively",\r
1082         privs = {worldedit=true},\r
1083         func = safe_region(function(name, param)\r
1084                 local node = get_node(name, param)\r
1085                 local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)\r
1086                 worldedit.player_notify(name, count .. " nodes highlighted")\r
1087         end, check_region),\r
1088 })\r
1089 \r
1090 minetest.register_chatcommand("/restore", {\r
1091         params = "",\r
1092         description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",\r
1093         privs = {worldedit=true},\r
1094         func = safe_region(function(name, param)\r
1095                 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])\r
1096                 worldedit.player_notify(name, count .. " nodes restored")\r
1097         end),\r
1098 })\r
1099 \r
1100 local function detect_misaligned_schematic(name, pos1, pos2)\r
1101         pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
1102         -- Check that allocate/save can position the schematic correctly\r
1103         -- The expected behaviour is that the (0,0,0) corner of the schematic stays\r
1104         -- sat pos1, this only works when the minimum position is actually present\r
1105         -- in the schematic.\r
1106         local node = minetest.get_node(pos1)\r
1107         local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"\r
1108         if not have_node_at_origin then\r
1109                 worldedit.player_notify(name,\r
1110                         "Warning: The schematic contains excessive free space and WILL be "..\r
1111                         "misaligned when allocated or loaded. To avoid this, shrink your "..\r
1112                         "area to cover exactly the nodes to be saved."\r
1113                 )\r
1114         end\r
1115 end\r
1116 \r
1117 minetest.register_chatcommand("/save", {\r
1118         params = "<file>",\r
1119         description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",\r
1120         privs = {worldedit=true},\r
1121         func = safe_region(function(name, param)\r
1122                 if param == "" then\r
1123                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1124                         return\r
1125                 end\r
1126                 if not check_filename(param) then\r
1127                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1128                         return\r
1129                 end\r
1130                 local result, count = worldedit.serialize(worldedit.pos1[name],\r
1131                                 worldedit.pos2[name])\r
1132                 detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])\r
1133 \r
1134                 local path = minetest.get_worldpath() .. "/schems"\r
1135                 -- Create directory if it does not already exist\r
1136                 minetest.mkdir(path)\r
1137 \r
1138                 local filename = path .. "/" .. param .. ".we"\r
1139                 local file, err = io.open(filename, "wb")\r
1140                 if err ~= nil then\r
1141                         worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")\r
1142                         return\r
1143                 end\r
1144                 file:write(result)\r
1145                 file:flush()\r
1146                 file:close()\r
1147 \r
1148                 worldedit.player_notify(name, count .. " nodes saved")\r
1149         end),\r
1150 })\r
1151 \r
1152 minetest.register_chatcommand("/allocate", {\r
1153         params = "<file>",\r
1154         description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",\r
1155         privs = {worldedit=true},\r
1156         func = function(name, param)\r
1157                 local pos = get_position(name)\r
1158                 if pos == nil then return end\r
1159 \r
1160                 if param == "" then\r
1161                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1162                         return\r
1163                 end\r
1164                 if not check_filename(param) then\r
1165                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1166                         return\r
1167                 end\r
1168 \r
1169                 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"\r
1170                 local file, err = io.open(filename, "rb")\r
1171                 if err ~= nil then\r
1172                         worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")\r
1173                         return\r
1174                 end\r
1175                 local value = file:read("*a")\r
1176                 file:close()\r
1177 \r
1178                 local version = worldedit.read_header(value)\r
1179                 if version == nil or version == 0 then\r
1180                         worldedit.player_notify(name, "File is invalid!")\r
1181                         return\r
1182                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1183                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1184                         return\r
1185                 end\r
1186                 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)\r
1187 \r
1188                 if not nodepos1 then\r
1189                         worldedit.player_notify(name, "Schematic empty, nothing allocated")\r
1190                         return\r
1191                 end\r
1192 \r
1193                 worldedit.pos1[name] = nodepos1\r
1194                 worldedit.mark_pos1(name)\r
1195                 worldedit.pos2[name] = nodepos2\r
1196                 worldedit.mark_pos2(name)\r
1197 \r
1198                 worldedit.player_notify(name, count .. " nodes allocated")\r
1199         end,\r
1200 })\r
1201 \r
1202 minetest.register_chatcommand("/load", {\r
1203         params = "<file>",\r
1204         description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",\r
1205         privs = {worldedit=true},\r
1206         func = function(name, param)\r
1207                 local pos = get_position(name)\r
1208                 if pos == nil then return end\r
1209 \r
1210                 if param == "" then\r
1211                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1212                         return\r
1213                 end\r
1214                 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then\r
1215                         worldedit.player_notify(name, "invalid file name: " .. param)\r
1216                         return\r
1217                 end\r
1218 \r
1219                 --find the file in the world path\r
1220                 local testpaths = {\r
1221                         minetest.get_worldpath() .. "/schems/" .. param,\r
1222                         minetest.get_worldpath() .. "/schems/" .. param .. ".we",\r
1223                         minetest.get_worldpath() .. "/schems/" .. param .. ".wem",\r
1224                 }\r
1225                 local file, err\r
1226                 for index, path in ipairs(testpaths) do\r
1227                         file, err = io.open(path, "rb")\r
1228                         if not err then\r
1229                                 break\r
1230                         end\r
1231                 end\r
1232                 if err then\r
1233                         worldedit.player_notify(name, "could not open file \"" .. param .. "\"")\r
1234                         return\r
1235                 end\r
1236                 local value = file:read("*a")\r
1237                 file:close()\r
1238 \r
1239                 local version = worldedit.read_header(value)\r
1240                 if version == nil or version == 0 then\r
1241                         worldedit.player_notify(name, "File is invalid!")\r
1242                         return\r
1243                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1244                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1245                         return\r
1246                 end\r
1247 \r
1248                 local count = worldedit.deserialize(pos, value)\r
1249 \r
1250                 worldedit.player_notify(name, count .. " nodes loaded")\r
1251         end,\r
1252 })\r
1253 \r
1254 minetest.register_chatcommand("/lua", {\r
1255         params = "<code>",\r
1256         description = "Executes <code> as a Lua chunk in the global namespace",\r
1257         privs = {worldedit=true, server=true},\r
1258         func = function(name, param)\r
1259                 local err = worldedit.lua(param)\r
1260                 if err then\r
1261                         worldedit.player_notify(name, "code error: " .. err)\r
1262                         minetest.log("action", name.." tried to execute "..param)\r
1263                 else\r
1264                         worldedit.player_notify(name, "code successfully executed", false)\r
1265                         minetest.log("action", name.." executed "..param)\r
1266                 end\r
1267         end,\r
1268 })\r
1269 \r
1270 minetest.register_chatcommand("/luatransform", {\r
1271         params = "<code>",\r
1272         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
1273         privs = {worldedit=true, server=true},\r
1274         func = safe_region(function(name, param)\r
1275                 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)\r
1276                 if err then\r
1277                         worldedit.player_notify(name, "code error: " .. err, false)\r
1278                         minetest.log("action", name.." tried to execute luatransform "..param)\r
1279                 else\r
1280                         worldedit.player_notify(name, "code successfully executed", false)\r
1281                         minetest.log("action", name.." executed luatransform "..param)\r
1282                 end\r
1283         end),\r
1284 })\r
1285 \r
1286 minetest.register_chatcommand("/mtschemcreate", {\r
1287         params = "<file>",\r
1288         description = "Save the current WorldEdit region using the Minetest "..\r
1289                 "Schematic format to \"(world folder)/schems/<filename>.mts\"",\r
1290         privs = {worldedit=true},\r
1291         func = safe_region(function(name, param)\r
1292                 if param == nil then\r
1293                         worldedit.player_notify(name, "No filename specified")\r
1294                         return\r
1295                 end\r
1296                 if not check_filename(param) then\r
1297                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1298                         return\r
1299                 end\r
1300 \r
1301                 local path = minetest.get_worldpath() .. "/schems"\r
1302                 -- Create directory if it does not already exist\r
1303                 minetest.mkdir(path)\r
1304 \r
1305                 local filename = path .. "/" .. param .. ".mts"\r
1306                 local ret = minetest.create_schematic(worldedit.pos1[name],\r
1307                                 worldedit.pos2[name], worldedit.prob_list[name],\r
1308                                 filename)\r
1309                 if ret == nil then\r
1310                         worldedit.player_notify(name, "Failed to create Minetest schematic", false)\r
1311                 else\r
1312                         worldedit.player_notify(name, "Saved Minetest schematic to " .. param, false)\r
1313                 end\r
1314                 worldedit.prob_list[name] = {}\r
1315         end),\r
1316 })\r
1317 \r
1318 minetest.register_chatcommand("/mtschemplace", {\r
1319         params = "<file>",\r
1320         description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",\r
1321         privs = {worldedit=true},\r
1322         func = function(name, param)\r
1323                 if param == "" then\r
1324                         worldedit.player_notify(name, "no filename specified")\r
1325                         return\r
1326                 end\r
1327                 if not check_filename(param) then\r
1328                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1329                         return\r
1330                 end\r
1331 \r
1332                 local pos = get_position(name)\r
1333                 if pos == nil then return end\r
1334 \r
1335                 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"\r
1336                 if minetest.place_schematic(pos, path) == nil then\r
1337                         worldedit.player_notify(name, "failed to place Minetest schematic", false)\r
1338                 else\r
1339                         worldedit.player_notify(name, "placed Minetest schematic " .. param ..\r
1340                                 " at " .. minetest.pos_to_string(pos), false)\r
1341                 end\r
1342         end,\r
1343 })\r
1344 \r
1345 minetest.register_chatcommand("/mtschemprob", {\r
1346         params = "start/finish/get",\r
1347         description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",\r
1348         privs = {worldedit=true},\r
1349         func = function(name, param)\r
1350                 if param == "start" then --start probability setting\r
1351                         worldedit.set_pos[name] = "prob"\r
1352                         worldedit.prob_list[name] = {}\r
1353                         worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")\r
1354                 elseif param == "finish" then --finish probability setting\r
1355                         worldedit.set_pos[name] = nil\r
1356                         worldedit.player_notify(name, "finished Minetest schematic probability selection")\r
1357                 elseif param == "get" then --get all nodes that had probabilities set on them\r
1358                         local text = ""\r
1359                         local problist = worldedit.prob_list[name]\r
1360                         if problist == nil then\r
1361                                 return\r
1362                         end\r
1363                         for k,v in pairs(problist) do\r
1364                                 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100\r
1365                                 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "\r
1366                         end\r
1367                         worldedit.player_notify(name, "currently set node probabilities:")\r
1368                         worldedit.player_notify(name, text)\r
1369                 else\r
1370                         worldedit.player_notify(name, "unknown subcommand: " .. param)\r
1371                 end\r
1372         end,\r
1373 })\r
1374 \r
1375 minetest.register_on_player_receive_fields(function(player, formname, fields)\r
1376         if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then\r
1377                 local name = player:get_player_name()\r
1378                 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}\r
1379                 local index = table.getn(worldedit.prob_list[name]) + 1\r
1380                 worldedit.prob_list[name][index] = prob_entry\r
1381         end\r
1382 end)\r
1383 \r
1384 minetest.register_chatcommand("/clearobjects", {\r
1385         params = "",\r
1386         description = "Clears all objects within the WorldEdit region",\r
1387         privs = {worldedit=true},\r
1388         func = safe_region(function(name, param)\r
1389                 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])\r
1390                 worldedit.player_notify(name, count .. " objects cleared")\r
1391         end),\r
1392 })\r