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