]> git.lizzy.rs Git - worldedit.git/blob - worldedit_commands/init.lua
Replace more deprecated functions
[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                 local count = worldedit.stack(worldedit.pos1[name], worldedit.pos2[name], axis, repetitions)\r
823                 worldedit.player_notify(name, count .. " nodes stacked")\r
824         end,\r
825         function(name, param)\r
826                 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
827                 if found == nil then\r
828                         worldedit.player_notify(name, "invalid usage: " .. param)\r
829                         return\r
830                 end\r
831                 local count = check_region(name, param)\r
832                 if count then return (tonumber(repetitions) + 1) * count end\r
833                 return nil\r
834         end),\r
835 })\r
836 \r
837 minetest.register_chatcommand("/stack2", {\r
838         params = "<count> <x> <y> <z>",\r
839         description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",\r
840         privs = {worldedit=true},\r
841         func = function(name, param)\r
842                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
843                 if pos1 == nil or pos2 == nil then\r
844                         worldedit.player_notify(name, "Select a position first!")\r
845                         return\r
846                 end\r
847                 local repetitions, incs = param:match("(%d+)%s*(.+)")\r
848                 if repetitions == nil then\r
849                         worldedit.player_notify(name, "invalid count: " .. param)\r
850                         return\r
851                 end\r
852                 repetitions = tonumber(repetitions)\r
853 \r
854                 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")\r
855                 if x == nil then\r
856                         worldedit.player_notify(name, "invalid increments: " .. param)\r
857                         return\r
858                 end\r
859                 x, y, z = tonumber(x), tonumber(y), tonumber(z)\r
860 \r
861                 local count = worldedit.volume(pos1, pos2) * repetitions\r
862 \r
863                 return safe_region(function()\r
864                         worldedit.stack2(pos1, pos2, {x=x, y=y, z=z}, repetitions,\r
865                                 function() worldedit.player_notify(name, count .. " nodes stacked") end)\r
866                 end, function()\r
867                         return count\r
868                 end)(name,param) -- more hax --wip: clean this up a little bit\r
869         end\r
870 })\r
871 \r
872 \r
873 minetest.register_chatcommand("/stretch", {\r
874         params = "<stretchx> <stretchy> <stretchz>",\r
875         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
876         privs = {worldedit=true},\r
877         func = safe_region(function(name, param)\r
878                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
879                 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")\r
880                 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)\r
881                 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)\r
882 \r
883                 --reset markers to scaled positions\r
884                 worldedit.pos1[name] = pos1\r
885                 worldedit.pos2[name] = pos2\r
886                 worldedit.mark_pos1(name)\r
887                 worldedit.mark_pos2(name)\r
888 \r
889                 worldedit.player_notify(name, count .. " nodes stretched")\r
890         end,\r
891         function(name, param)\r
892                 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")\r
893                 if found == nil then\r
894                         worldedit.player_notify(name, "invalid usage: " .. param)\r
895                         return nil\r
896                 end\r
897                 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)\r
898                 if stretchx == 0 or stretchy == 0 or stretchz == 0 then\r
899                         worldedit.player_notify(name, "invalid scaling factors: " .. param)\r
900                         return nil\r
901                 end\r
902                 local count = check_region(name, param)\r
903                 if count then\r
904                         return stretchx * stretchy * stretchz * count\r
905                 end\r
906                 return nil\r
907         end),\r
908 })\r
909 \r
910 minetest.register_chatcommand("/transpose", {\r
911         params = "x/y/z/? x/y/z/?",\r
912         description = "Transpose the current WorldEdit region along the x/y/z/? and x/y/z/? axes",\r
913         privs = {worldedit=true},\r
914         func = safe_region(function(name, param)\r
915                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
916                 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")\r
917                 if axis1 == "?" then axis1 = worldedit.player_axis(name) end\r
918                 if axis2 == "?" then axis2 = worldedit.player_axis(name) end\r
919                 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\r
920 \r
921                 --reset markers to transposed positions\r
922                 worldedit.pos1[name] = pos1\r
923                 worldedit.pos2[name] = pos2\r
924                 worldedit.mark_pos1(name)\r
925                 worldedit.mark_pos2(name)\r
926 \r
927                 worldedit.player_notify(name, count .. " nodes transposed")\r
928         end,\r
929         function(name, param)\r
930                 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")\r
931                 if found == nil then\r
932                         worldedit.player_notify(name, "invalid usage: " .. param)\r
933                         return nil\r
934                 end\r
935                 if axis1 == axis2 then\r
936                         worldedit.player_notify(name, "invalid usage: axes must be different")\r
937                         return nil\r
938                 end\r
939                 return check_region(name, param)\r
940         end),\r
941 })\r
942 \r
943 minetest.register_chatcommand("/flip", {\r
944         params = "x/y/z/?",\r
945         description = "Flip the current WorldEdit region along the x/y/z/? axis",\r
946         privs = {worldedit=true},\r
947         func = safe_region(function(name, param)\r
948                 if param == "?" then param = worldedit.player_axis(name) end\r
949                 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)\r
950                 worldedit.player_notify(name, count .. " nodes flipped")\r
951         end,\r
952         function(name, param)\r
953                 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then\r
954                         worldedit.player_notify(name, "invalid usage: " .. param)\r
955                         return nil\r
956                 end\r
957                 return check_region(name, param)\r
958         end),\r
959 })\r
960 \r
961 minetest.register_chatcommand("/rotate", {\r
962         params = "<axis> <angle>",\r
963         description = "Rotate the current WorldEdit region around the axis <axis> by angle <angle> (90 degree increment)",\r
964         privs = {worldedit=true},\r
965         func = safe_region(function(name, param)\r
966                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
967                 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
968                 if axis == "?" then axis = worldedit.player_axis(name) end\r
969                 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)\r
970 \r
971                 --reset markers to rotated positions\r
972                 worldedit.pos1[name] = pos1\r
973                 worldedit.pos2[name] = pos2\r
974                 worldedit.mark_pos1(name)\r
975                 worldedit.mark_pos2(name)\r
976 \r
977                 worldedit.player_notify(name, count .. " nodes rotated")\r
978         end,\r
979         function(name, param)\r
980                 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
981                 if found == nil then\r
982                         worldedit.player_notify(name, "invalid usage: " .. param)\r
983                         return nil\r
984                 end\r
985                 if angle % 90 ~= 0 then\r
986                         worldedit.player_notify(name, "invalid usage: angle must be multiple of 90")\r
987                         return nil\r
988                 end\r
989                 return check_region(name, param)\r
990         end),\r
991 })\r
992 \r
993 minetest.register_chatcommand("/orient", {\r
994         params = "<angle>",\r
995         description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",\r
996         privs = {worldedit=true},\r
997         func = safe_region(function(name, param)\r
998                 local found, _, angle = param:find("^([+-]?%d+)$")\r
999                 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)\r
1000                 worldedit.player_notify(name, count .. " nodes oriented")\r
1001         end,\r
1002         function(name, param)\r
1003                 local found, _, angle = param:find("^([+-]?%d+)$")\r
1004                 if found == nil then\r
1005                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1006                         return nil\r
1007                 end\r
1008                 if angle % 90 ~= 0 then\r
1009                         worldedit.player_notify(name, "invalid usage: angle must be multiple of 90")\r
1010                         return nil\r
1011                 end\r
1012                 return check_region(name, param)\r
1013         end),\r
1014 })\r
1015 \r
1016 minetest.register_chatcommand("/fixlight", {\r
1017         params = "",\r
1018         description = "Fix the lighting in the current WorldEdit region",\r
1019         privs = {worldedit=true},\r
1020         func = safe_region(function(name, param)\r
1021                 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])\r
1022                 worldedit.player_notify(name, count .. " nodes updated")\r
1023         end),\r
1024 })\r
1025 \r
1026 minetest.register_chatcommand("/drain", {\r
1027         params = "",\r
1028         description = "Remove any fluid node within the current WorldEdit region",\r
1029         privs = {worldedit=true},\r
1030         func = safe_region(function(name, param)\r
1031                 -- TODO: make an API function for this\r
1032                 local count = 0\r
1033                 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])\r
1034                 for x = pos1.x, pos2.x do\r
1035                 for y = pos1.y, pos2.y do\r
1036                 for z = pos1.z, pos2.z do\r
1037                         local n = minetest.get_node({x=x, y=y, z=z}).name\r
1038                         local d = minetest.registered_nodes[n]\r
1039                         if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then\r
1040                                 minetest.remove_node({x=x, y=y, z=z})\r
1041                                 count = count + 1\r
1042                         end\r
1043                 end\r
1044                 end\r
1045                 end\r
1046                 worldedit.player_notify(name, count .. " nodes updated")\r
1047         end),\r
1048 })\r
1049 \r
1050 minetest.register_chatcommand("/hide", {\r
1051         params = "",\r
1052         description = "Hide all nodes in the current WorldEdit region non-destructively",\r
1053         privs = {worldedit=true},\r
1054         func = safe_region(function(name, param)\r
1055                 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])\r
1056                 worldedit.player_notify(name, count .. " nodes hidden")\r
1057         end),\r
1058 })\r
1059 \r
1060 minetest.register_chatcommand("/suppress", {\r
1061         params = "<node>",\r
1062         description = "Suppress all <node> in the current WorldEdit region non-destructively",\r
1063         privs = {worldedit=true},\r
1064         func = safe_region(function(name, param)\r
1065                 local node = get_node(name, param)\r
1066                 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)\r
1067                 worldedit.player_notify(name, count .. " nodes suppressed")\r
1068         end, check_region),\r
1069 })\r
1070 \r
1071 minetest.register_chatcommand("/highlight", {\r
1072         params = "<node>",\r
1073         description = "Highlight <node> in the current WorldEdit region by hiding everything else non-destructively",\r
1074         privs = {worldedit=true},\r
1075         func = safe_region(function(name, param)\r
1076                 local node = get_node(name, param)\r
1077                 local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)\r
1078                 worldedit.player_notify(name, count .. " nodes highlighted")\r
1079         end, check_region),\r
1080 })\r
1081 \r
1082 minetest.register_chatcommand("/restore", {\r
1083         params = "",\r
1084         description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",\r
1085         privs = {worldedit=true},\r
1086         func = safe_region(function(name, param)\r
1087                 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])\r
1088                 worldedit.player_notify(name, count .. " nodes restored")\r
1089         end),\r
1090 })\r
1091 \r
1092 minetest.register_chatcommand("/save", {\r
1093         params = "<file>",\r
1094         description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",\r
1095         privs = {worldedit=true},\r
1096         func = safe_region(function(name, param)\r
1097                 if param == "" then\r
1098                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1099                         return\r
1100                 end\r
1101                 if not check_filename(param) then\r
1102                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1103                         return\r
1104                 end\r
1105                 local result, count = worldedit.serialize(worldedit.pos1[name],\r
1106                                 worldedit.pos2[name])\r
1107 \r
1108                 local path = minetest.get_worldpath() .. "/schems"\r
1109                 -- Create directory if it does not already exist\r
1110                 minetest.mkdir(path)\r
1111 \r
1112                 local filename = path .. "/" .. param .. ".we"\r
1113                 local file, err = io.open(filename, "wb")\r
1114                 if err ~= nil then\r
1115                         worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")\r
1116                         return\r
1117                 end\r
1118                 file:write(result)\r
1119                 file:flush()\r
1120                 file:close()\r
1121 \r
1122                 worldedit.player_notify(name, count .. " nodes saved")\r
1123         end),\r
1124 })\r
1125 \r
1126 minetest.register_chatcommand("/allocate", {\r
1127         params = "<file>",\r
1128         description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",\r
1129         privs = {worldedit=true},\r
1130         func = function(name, param)\r
1131                 local pos = get_position(name)\r
1132                 if pos == nil then return end\r
1133 \r
1134                 if param == "" then\r
1135                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1136                         return\r
1137                 end\r
1138                 if not check_filename(param) then\r
1139                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1140                         return\r
1141                 end\r
1142 \r
1143                 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"\r
1144                 local file, err = io.open(filename, "rb")\r
1145                 if err ~= nil then\r
1146                         worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")\r
1147                         return\r
1148                 end\r
1149                 local value = file:read("*a")\r
1150                 file:close()\r
1151 \r
1152                 local version = worldedit.read_header(value)\r
1153                 if version == 0 then\r
1154                         worldedit.player_notify(name, "File is invalid!")\r
1155                         return\r
1156                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1157                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1158                         return\r
1159                 end\r
1160                 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)\r
1161 \r
1162                 if not nodepos1 then\r
1163                         worldedit.player_notify(name, "Schematic empty, nothing allocated")\r
1164                         return\r
1165                 end\r
1166 \r
1167                 worldedit.pos1[name] = nodepos1\r
1168                 worldedit.mark_pos1(name)\r
1169                 worldedit.pos2[name] = nodepos2\r
1170                 worldedit.mark_pos2(name)\r
1171 \r
1172                 worldedit.player_notify(name, count .. " nodes allocated")\r
1173         end,\r
1174 })\r
1175 \r
1176 minetest.register_chatcommand("/load", {\r
1177         params = "<file>",\r
1178         description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",\r
1179         privs = {worldedit=true},\r
1180         func = function(name, param)\r
1181                 local pos = get_position(name)\r
1182                 if pos == nil then return end\r
1183 \r
1184                 if param == "" then\r
1185                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1186                         return\r
1187                 end\r
1188                 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then\r
1189                         worldedit.player_notify(name, "invalid file name: " .. param)\r
1190                         return\r
1191                 end\r
1192 \r
1193                 --find the file in the world path\r
1194                 local testpaths = {\r
1195                         minetest.get_worldpath() .. "/schems/" .. param,\r
1196                         minetest.get_worldpath() .. "/schems/" .. param .. ".we",\r
1197                         minetest.get_worldpath() .. "/schems/" .. param .. ".wem",\r
1198                 }\r
1199                 local file, err\r
1200                 for index, path in ipairs(testpaths) do\r
1201                         file, err = io.open(path, "rb")\r
1202                         if not err then\r
1203                                 break\r
1204                         end\r
1205                 end\r
1206                 if err then\r
1207                         worldedit.player_notify(name, "could not open file \"" .. param .. "\"")\r
1208                         return\r
1209                 end\r
1210                 local value = file:read("*a")\r
1211                 file:close()\r
1212 \r
1213                 local version = worldedit.read_header(value)\r
1214                 if version == 0 then\r
1215                         worldedit.player_notify(name, "File is invalid!")\r
1216                         return\r
1217                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1218                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1219                         return\r
1220                 end\r
1221 \r
1222                 local count = worldedit.deserialize(pos, value)\r
1223 \r
1224                 worldedit.player_notify(name, count .. " nodes loaded")\r
1225         end,\r
1226 })\r
1227 \r
1228 minetest.register_chatcommand("/lua", {\r
1229         params = "<code>",\r
1230         description = "Executes <code> as a Lua chunk in the global namespace",\r
1231         privs = {worldedit=true, server=true},\r
1232         func = function(name, param)\r
1233                 local err = worldedit.lua(param)\r
1234                 if err then\r
1235                         worldedit.player_notify(name, "code error: " .. err)\r
1236                         minetest.log("action", name.." tried to execute "..param)\r
1237                 else\r
1238                         worldedit.player_notify(name, "code successfully executed", false)\r
1239                         minetest.log("action", name.." executed "..param)\r
1240                 end\r
1241         end,\r
1242 })\r
1243 \r
1244 minetest.register_chatcommand("/luatransform", {\r
1245         params = "<code>",\r
1246         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
1247         privs = {worldedit=true, server=true},\r
1248         func = safe_region(function(name, param)\r
1249                 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)\r
1250                 if err then\r
1251                         worldedit.player_notify(name, "code error: " .. err, false)\r
1252                         minetest.log("action", name.." tried to execute luatransform "..param)\r
1253                 else\r
1254                         worldedit.player_notify(name, "code successfully executed", false)\r
1255                         minetest.log("action", name.." executed luatransform "..param)\r
1256                 end\r
1257         end),\r
1258 })\r
1259 \r
1260 minetest.register_chatcommand("/mtschemcreate", {\r
1261         params = "<file>",\r
1262         description = "Save the current WorldEdit region using the Minetest "..\r
1263                 "Schematic format to \"(world folder)/schems/<filename>.mts\"",\r
1264         privs = {worldedit=true},\r
1265         func = safe_region(function(name, param)\r
1266                 if param == nil then\r
1267                         worldedit.player_notify(name, "No filename specified")\r
1268                         return\r
1269                 end\r
1270                 if not check_filename(param) then\r
1271                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1272                         return\r
1273                 end\r
1274 \r
1275                 local path = minetest.get_worldpath() .. "/schems"\r
1276                 -- Create directory if it does not already exist\r
1277                 minetest.mkdir(path)\r
1278 \r
1279                 local filename = path .. "/" .. param .. ".mts"\r
1280                 local ret = minetest.create_schematic(worldedit.pos1[name],\r
1281                                 worldedit.pos2[name], worldedit.prob_list[name],\r
1282                                 filename)\r
1283                 if ret == nil then\r
1284                         worldedit.player_notify(name, "Failed to create Minetest schematic", false)\r
1285                 else\r
1286                         worldedit.player_notify(name, "Saved Minetest schematic to " .. param, false)\r
1287                 end\r
1288                 worldedit.prob_list[name] = {}\r
1289         end),\r
1290 })\r
1291 \r
1292 minetest.register_chatcommand("/mtschemplace", {\r
1293         params = "<file>",\r
1294         description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",\r
1295         privs = {worldedit=true},\r
1296         func = function(name, param)\r
1297                 if param == "" then\r
1298                         worldedit.player_notify(name, "no filename specified")\r
1299                         return\r
1300                 end\r
1301                 if not check_filename(param) then\r
1302                         worldedit.player_notify(name, "Disallowed file name: " .. param)\r
1303                         return\r
1304                 end\r
1305 \r
1306                 local pos = get_position(name)\r
1307                 if pos == nil then return end\r
1308 \r
1309                 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"\r
1310                 if minetest.place_schematic(pos, path) == nil then\r
1311                         worldedit.player_notify(name, "failed to place Minetest schematic", false)\r
1312                 else\r
1313                         worldedit.player_notify(name, "placed Minetest schematic " .. param ..\r
1314                                 " at " .. minetest.pos_to_string(pos), false)\r
1315                 end\r
1316         end,\r
1317 })\r
1318 \r
1319 minetest.register_chatcommand("/mtschemprob", {\r
1320         params = "start/finish/get",\r
1321         description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",\r
1322         privs = {worldedit=true},\r
1323         func = function(name, param)\r
1324                 if param == "start" then --start probability setting\r
1325                         worldedit.set_pos[name] = "prob"\r
1326                         worldedit.prob_list[name] = {}\r
1327                         worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")\r
1328                 elseif param == "finish" then --finish probability setting\r
1329                         worldedit.set_pos[name] = nil\r
1330                         worldedit.player_notify(name, "finished Minetest schematic probability selection")\r
1331                 elseif param == "get" then --get all nodes that had probabilities set on them\r
1332                         local text = ""\r
1333                         local problist = worldedit.prob_list[name]\r
1334                         if problist == nil then\r
1335                                 return\r
1336                         end\r
1337                         for k,v in pairs(problist) do\r
1338                                 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100\r
1339                                 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "\r
1340                         end\r
1341                         worldedit.player_notify(name, "currently set node probabilities:")\r
1342                         worldedit.player_notify(name, text)\r
1343                 else\r
1344                         worldedit.player_notify(name, "unknown subcommand: " .. param)\r
1345                 end\r
1346         end,\r
1347 })\r
1348 \r
1349 minetest.register_on_player_receive_fields(function(player, formname, fields)\r
1350         if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then\r
1351                 local name = player:get_player_name()\r
1352                 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}\r
1353                 local index = table.getn(worldedit.prob_list[name]) + 1\r
1354                 worldedit.prob_list[name][index] = prob_entry\r
1355         end\r
1356 end)\r
1357 \r
1358 minetest.register_chatcommand("/clearobjects", {\r
1359         params = "",\r
1360         description = "Clears all objects within the WorldEdit region",\r
1361         privs = {worldedit=true},\r
1362         func = safe_region(function(name, param)\r
1363                 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])\r
1364                 worldedit.player_notify(name, count .. " objects cleared")\r
1365         end),\r
1366 })\r