]> git.lizzy.rs Git - worldedit.git/blob - worldedit_commands/init.lua
b5e746e56c514b3845b67dc81acf4d2e2224f494
[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         for key, value in pairs(minetest.registered_nodes) do\r
166                 if string_endswith(key, ":" .. nodename) then -- matches name (w/o mod part)\r
167                         return key\r
168                 end\r
169         end\r
170         nodename = nodename:lower() -- lowercase both for case insensitive comparison\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 string_endswith(desc, " block") and 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 count = worldedit.move(pos1, pos2, axis, amount)\r
943 \r
944                 pos1[axis] = pos1[axis] + amount\r
945                 pos2[axis] = pos2[axis] + amount\r
946                 worldedit.mark_pos1(name)\r
947                 worldedit.mark_pos2(name)\r
948                 worldedit.player_notify(name, count .. " nodes moved")\r
949         end,\r
950 })\r
951 \r
952 worldedit.register_command("stack", {\r
953         params = "x/y/z/? <count>",\r
954         description = "Stack the current WorldEdit region along the x/y/z/? axis <count> times",\r
955         privs = {worldedit=true},\r
956         require_pos = 2,\r
957         parse = function(param)\r
958                 local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
959                 if found == nil then\r
960                         return false\r
961                 end\r
962                 return true, axis, tonumber(repetitions)\r
963         end,\r
964         nodes_needed = function(name, axis, repetitions)\r
965                 return check_region(name) * math.abs(repetitions)\r
966         end,\r
967         func = function(name, axis, repetitions)\r
968                 if axis == "?" then\r
969                         local sign\r
970                         axis, sign = worldedit.player_axis(name)\r
971                         repetitions = repetitions * sign\r
972                 end\r
973 \r
974                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
975                 local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)\r
976                 worldedit.stack(pos1, pos2, axis, repetitions, function()\r
977                         worldedit.player_notify(name, count .. " nodes stacked")\r
978                 end)\r
979         end,\r
980 })\r
981 \r
982 worldedit.register_command("stack2", {\r
983         params = "<count> <x> <y> <z>",\r
984         description = "Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>",\r
985         privs = {worldedit=true},\r
986         require_pos = 2,\r
987         parse = function(param)\r
988                 local repetitions, incs = param:match("(%d+)%s*(.+)")\r
989                 if repetitions == nil then\r
990                         return false, "invalid count: " .. param\r
991                 end\r
992                 local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")\r
993                 if x == nil then\r
994                         return false, "invalid increments: " .. param\r
995                 end\r
996 \r
997                 return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}\r
998         end,\r
999         nodes_needed = function(name, repetitions, offset)\r
1000                 return check_region(name) * repetitions\r
1001         end,\r
1002         func = function(name, repetitions, offset)\r
1003                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
1004                 local count = worldedit.volume(pos1, pos2) * repetitions\r
1005                 worldedit.stack2(pos1, pos2, offset, repetitions, function()\r
1006                         worldedit.player_notify(name, count .. " nodes stacked")\r
1007                 end)\r
1008         end,\r
1009 })\r
1010 \r
1011 \r
1012 worldedit.register_command("stretch", {\r
1013         params = "<stretchx> <stretchy> <stretchz>",\r
1014         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
1015         privs = {worldedit=true},\r
1016         require_pos = 2,\r
1017         parse = function(param)\r
1018                 local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")\r
1019                 if found == nil then\r
1020                         return false\r
1021                 end\r
1022                 stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)\r
1023                 if stretchx == 0 or stretchy == 0 or stretchz == 0 then\r
1024                         return false, "invalid scaling factors: " .. param\r
1025                 end\r
1026                 return true, stretchx, stretchy, stretchz\r
1027         end,\r
1028         nodes_needed = function(name, stretchx, stretchy, stretchz)\r
1029                 return check_region(name) * stretchx * stretchy * stretchz\r
1030         end,\r
1031         func = function(name, stretchx, stretchy, stretchz)\r
1032                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
1033                 local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)\r
1034 \r
1035                 --reset markers to scaled positions\r
1036                 worldedit.pos1[name] = pos1\r
1037                 worldedit.pos2[name] = pos2\r
1038                 worldedit.mark_pos1(name)\r
1039                 worldedit.mark_pos2(name)\r
1040 \r
1041                 worldedit.player_notify(name, count .. " nodes stretched")\r
1042         end,\r
1043 })\r
1044 \r
1045 worldedit.register_command("transpose", {\r
1046         params = "x/y/z/? x/y/z/?",\r
1047         description = "Transpose the current WorldEdit region along the x/y/z/? and x/y/z/? axes",\r
1048         privs = {worldedit=true},\r
1049         require_pos = 2,\r
1050         parse = function(param)\r
1051                 local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")\r
1052                 if found == nil then\r
1053                         return false\r
1054                 elseif axis1 == axis2 then\r
1055                         return false, "invalid usage: axes must be different"\r
1056                 end\r
1057                 return true, axis1, axis2\r
1058         end,\r
1059         nodes_needed = check_region,\r
1060         func = function(name, axis1, axis2)\r
1061                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
1062                 if axis1 == "?" then axis1 = worldedit.player_axis(name) end\r
1063                 if axis2 == "?" then axis2 = worldedit.player_axis(name) end\r
1064                 local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\r
1065 \r
1066                 --reset markers to transposed positions\r
1067                 worldedit.pos1[name] = pos1\r
1068                 worldedit.pos2[name] = pos2\r
1069                 worldedit.mark_pos1(name)\r
1070                 worldedit.mark_pos2(name)\r
1071 \r
1072                 worldedit.player_notify(name, count .. " nodes transposed")\r
1073         end,\r
1074 })\r
1075 \r
1076 worldedit.register_command("flip", {\r
1077         params = "x/y/z/?",\r
1078         description = "Flip the current WorldEdit region along the x/y/z/? axis",\r
1079         privs = {worldedit=true},\r
1080         require_pos = 2,\r
1081         parse = function(param)\r
1082                 if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then\r
1083                         return false\r
1084                 end\r
1085                 return true, param\r
1086         end,\r
1087         nodes_needed = check_region,\r
1088         func = function(name, param)\r
1089                 if param == "?" then param = worldedit.player_axis(name) end\r
1090                 local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)\r
1091                 worldedit.player_notify(name, count .. " nodes flipped")\r
1092         end,\r
1093 })\r
1094 \r
1095 worldedit.register_command("rotate", {\r
1096         params = "<axis> <angle>",\r
1097         description = "Rotate the current WorldEdit region around the axis <axis> by angle <angle> (90 degree increment)",\r
1098         privs = {worldedit=true},\r
1099         require_pos = 2,\r
1100         parse = function(param)\r
1101                 local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")\r
1102                 if found == nil then\r
1103                         return false\r
1104                 end\r
1105                 angle = tonumber(angle)\r
1106                 if angle % 90 ~= 0 or angle % 360 == 0 then\r
1107                         return false, "invalid usage: angle must be multiple of 90"\r
1108                 end\r
1109                 return true, axis, angle\r
1110         end,\r
1111         nodes_needed = check_region,\r
1112         func = function(name, axis, angle)\r
1113                 local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]\r
1114                 if axis == "?" then axis = worldedit.player_axis(name) end\r
1115                 local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)\r
1116 \r
1117                 --reset markers to rotated positions\r
1118                 worldedit.pos1[name] = pos1\r
1119                 worldedit.pos2[name] = pos2\r
1120                 worldedit.mark_pos1(name)\r
1121                 worldedit.mark_pos2(name)\r
1122 \r
1123                 worldedit.player_notify(name, count .. " nodes rotated")\r
1124         end,\r
1125 })\r
1126 \r
1127 worldedit.register_command("orient", {\r
1128         params = "<angle>",\r
1129         description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)",\r
1130         privs = {worldedit=true},\r
1131         require_pos = 2,\r
1132         parse = function(param)\r
1133                 local found, _, angle = param:find("^([+-]?%d+)$")\r
1134                 if found == nil then\r
1135                         return false\r
1136                 end\r
1137                 angle = tonumber(angle)\r
1138                 if angle % 90 ~= 0 then\r
1139                         return false, "invalid usage: angle must be multiple of 90"\r
1140                 end\r
1141                 return true, angle\r
1142         end,\r
1143         nodes_needed = check_region,\r
1144         func = function(name, angle)\r
1145                 local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)\r
1146                 worldedit.player_notify(name, count .. " nodes oriented")\r
1147         end,\r
1148 })\r
1149 \r
1150 worldedit.register_command("fixlight", {\r
1151         params = "",\r
1152         description = "Fix the lighting in the current WorldEdit region",\r
1153         privs = {worldedit=true},\r
1154         require_pos = 2,\r
1155         nodes_needed = check_region,\r
1156         func = function(name)\r
1157                 local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])\r
1158                 worldedit.player_notify(name, count .. " nodes updated")\r
1159         end,\r
1160 })\r
1161 \r
1162 worldedit.register_command("drain", {\r
1163         params = "",\r
1164         description = "Remove any fluid node within the current WorldEdit region",\r
1165         privs = {worldedit=true},\r
1166         require_pos = 2,\r
1167         nodes_needed = check_region,\r
1168         func = function(name)\r
1169                 -- TODO: make an API function for this\r
1170                 local count = 0\r
1171                 local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])\r
1172                 for x = pos1.x, pos2.x do\r
1173                 for y = pos1.y, pos2.y do\r
1174                 for z = pos1.z, pos2.z do\r
1175                         local n = minetest.get_node({x=x, y=y, z=z}).name\r
1176                         local d = minetest.registered_nodes[n]\r
1177                         if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then\r
1178                                 minetest.remove_node({x=x, y=y, z=z})\r
1179                                 count = count + 1\r
1180                         end\r
1181                 end\r
1182                 end\r
1183                 end\r
1184                 worldedit.player_notify(name, count .. " nodes updated")\r
1185         end,\r
1186 })\r
1187 \r
1188 worldedit.register_command("hide", {\r
1189         params = "",\r
1190         description = "Hide all nodes in the current WorldEdit region non-destructively",\r
1191         privs = {worldedit=true},\r
1192         require_pos = 2,\r
1193         nodes_needed = check_region,\r
1194         func = function(name)\r
1195                 local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])\r
1196                 worldedit.player_notify(name, count .. " nodes hidden")\r
1197         end,\r
1198 })\r
1199 \r
1200 worldedit.register_command("suppress", {\r
1201         params = "<node>",\r
1202         description = "Suppress all <node> in the current WorldEdit region non-destructively",\r
1203         privs = {worldedit=true},\r
1204         require_pos = 2,\r
1205         parse = function(param)\r
1206                 local node = worldedit.normalize_nodename(param)\r
1207                 if not node then\r
1208                         return false, "invalid node name: " .. param\r
1209                 end\r
1210                 return true, node\r
1211         end,\r
1212         nodes_needed = check_region,\r
1213         func = function(name, node)\r
1214                 local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)\r
1215                 worldedit.player_notify(name, count .. " nodes suppressed")\r
1216         end,\r
1217 })\r
1218 \r
1219 worldedit.register_command("highlight", {\r
1220         params = "<node>",\r
1221         description = "Highlight <node> in the current WorldEdit region by hiding everything else non-destructively",\r
1222         privs = {worldedit=true},\r
1223         require_pos = 2,\r
1224         parse = function(param)\r
1225                 local node = worldedit.normalize_nodename(param)\r
1226                 if not node then\r
1227                         return false, "invalid node name: " .. param\r
1228                 end\r
1229                 return true, node\r
1230         end,\r
1231         nodes_needed = check_region,\r
1232         func = function(name, node)\r
1233                 local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)\r
1234                 worldedit.player_notify(name, count .. " nodes highlighted")\r
1235         end,\r
1236 })\r
1237 \r
1238 worldedit.register_command("restore", {\r
1239         params = "",\r
1240         description = "Restores nodes hidden with WorldEdit in the current WorldEdit region",\r
1241         privs = {worldedit=true},\r
1242         require_pos = 2,\r
1243         nodes_needed = check_region,\r
1244         func = function(name)\r
1245                 local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])\r
1246                 worldedit.player_notify(name, count .. " nodes restored")\r
1247         end,\r
1248 })\r
1249 \r
1250 local function detect_misaligned_schematic(name, pos1, pos2)\r
1251         pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
1252         -- Check that allocate/save can position the schematic correctly\r
1253         -- The expected behaviour is that the (0,0,0) corner of the schematic stays\r
1254         -- sat pos1, this only works when the minimum position is actually present\r
1255         -- in the schematic.\r
1256         local node = minetest.get_node(pos1)\r
1257         local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"\r
1258         if not have_node_at_origin then\r
1259                 worldedit.player_notify(name,\r
1260                         "Warning: The schematic contains excessive free space and WILL be "..\r
1261                         "misaligned when allocated or loaded. To avoid this, shrink your "..\r
1262                         "area to cover exactly the nodes to be saved."\r
1263                 )\r
1264         end\r
1265 end\r
1266 \r
1267 worldedit.register_command("save", {\r
1268         params = "<file>",\r
1269         description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",\r
1270         privs = {worldedit=true},\r
1271         require_pos = 2,\r
1272         parse = function(param)\r
1273                 if param == "" then\r
1274                         return false\r
1275                 end\r
1276                 if not check_filename(param) then\r
1277                         return false, "Disallowed file name: " .. param\r
1278                 end\r
1279                 return true, param\r
1280         end,\r
1281         nodes_needed = check_region,\r
1282         func = function(name, param)\r
1283                 local result, count = worldedit.serialize(worldedit.pos1[name],\r
1284                                 worldedit.pos2[name])\r
1285                 detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])\r
1286 \r
1287                 local path = minetest.get_worldpath() .. "/schems"\r
1288                 -- Create directory if it does not already exist\r
1289                 minetest.mkdir(path)\r
1290 \r
1291                 local filename = path .. "/" .. param .. ".we"\r
1292                 local file, err = io.open(filename, "wb")\r
1293                 if err ~= nil then\r
1294                         worldedit.player_notify(name, "Could not save file to \"" .. filename .. "\"")\r
1295                         return\r
1296                 end\r
1297                 file:write(result)\r
1298                 file:flush()\r
1299                 file:close()\r
1300 \r
1301                 worldedit.player_notify(name, count .. " nodes saved")\r
1302         end,\r
1303 })\r
1304 \r
1305 worldedit.register_command("allocate", {\r
1306         params = "<file>",\r
1307         description = "Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region",\r
1308         privs = {worldedit=true},\r
1309         require_pos = 1,\r
1310         parse = function(param)\r
1311                 if param == "" then\r
1312                         return false\r
1313                 end\r
1314                 if not check_filename(param) then\r
1315                         return false, "Disallowed file name: " .. param\r
1316                 end\r
1317                 return true, param\r
1318         end,\r
1319         func = function(name, param)\r
1320                 local pos = worldedit.pos1[name]\r
1321 \r
1322                 local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"\r
1323                 local file, err = io.open(filename, "rb")\r
1324                 if err ~= nil then\r
1325                         worldedit.player_notify(name, "could not open file \"" .. filename .. "\"")\r
1326                         return\r
1327                 end\r
1328                 local value = file:read("*a")\r
1329                 file:close()\r
1330 \r
1331                 local version = worldedit.read_header(value)\r
1332                 if version == nil or version == 0 then\r
1333                         worldedit.player_notify(name, "File is invalid!")\r
1334                         return\r
1335                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1336                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1337                         return\r
1338                 end\r
1339                 local nodepos1, nodepos2, count = worldedit.allocate(pos, value)\r
1340 \r
1341                 if not nodepos1 then\r
1342                         worldedit.player_notify(name, "Schematic empty, nothing allocated")\r
1343                         return\r
1344                 end\r
1345 \r
1346                 worldedit.pos1[name] = nodepos1\r
1347                 worldedit.mark_pos1(name)\r
1348                 worldedit.pos2[name] = nodepos2\r
1349                 worldedit.mark_pos2(name)\r
1350 \r
1351                 worldedit.player_notify(name, count .. " nodes allocated")\r
1352         end,\r
1353 })\r
1354 \r
1355 worldedit.register_command("load", {\r
1356         params = "<file>",\r
1357         description = "Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin",\r
1358         privs = {worldedit=true},\r
1359         require_pos = 1,\r
1360         parse = function(param)\r
1361                 if param == "" then\r
1362                         return false\r
1363                 end\r
1364                 if not check_filename(param) then\r
1365                         return false, "Disallowed file name: " .. param\r
1366                 end\r
1367                 return true, param\r
1368         end,\r
1369         func = function(name, param)\r
1370                 local pos = worldedit.pos1[name]\r
1371 \r
1372                 if param == "" then\r
1373                         worldedit.player_notify(name, "invalid usage: " .. param)\r
1374                         return\r
1375                 end\r
1376                 if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then\r
1377                         worldedit.player_notify(name, "invalid file name: " .. param)\r
1378                         return\r
1379                 end\r
1380 \r
1381                 --find the file in the world path\r
1382                 local testpaths = {\r
1383                         minetest.get_worldpath() .. "/schems/" .. param,\r
1384                         minetest.get_worldpath() .. "/schems/" .. param .. ".we",\r
1385                         minetest.get_worldpath() .. "/schems/" .. param .. ".wem",\r
1386                 }\r
1387                 local file, err\r
1388                 for index, path in ipairs(testpaths) do\r
1389                         file, err = io.open(path, "rb")\r
1390                         if not err then\r
1391                                 break\r
1392                         end\r
1393                 end\r
1394                 if err then\r
1395                         worldedit.player_notify(name, "could not open file \"" .. param .. "\"")\r
1396                         return\r
1397                 end\r
1398                 local value = file:read("*a")\r
1399                 file:close()\r
1400 \r
1401                 local version = worldedit.read_header(value)\r
1402                 if version == nil or version == 0 then\r
1403                         worldedit.player_notify(name, "File is invalid!")\r
1404                         return\r
1405                 elseif version > worldedit.LATEST_SERIALIZATION_VERSION then\r
1406                         worldedit.player_notify(name, "File was created with newer version of WorldEdit!")\r
1407                         return\r
1408                 end\r
1409 \r
1410                 local count = worldedit.deserialize(pos, value)\r
1411 \r
1412                 worldedit.player_notify(name, count .. " nodes loaded")\r
1413         end,\r
1414 })\r
1415 \r
1416 worldedit.register_command("lua", {\r
1417         params = "<code>",\r
1418         description = "Executes <code> as a Lua chunk in the global namespace",\r
1419         privs = {worldedit=true, server=true},\r
1420         parse = function(param)\r
1421                 return true, param\r
1422         end,\r
1423         func = function(name, param)\r
1424                 local err = worldedit.lua(param)\r
1425                 if err then\r
1426                         worldedit.player_notify(name, "code error: " .. err)\r
1427                         minetest.log("action", name.." tried to execute "..param)\r
1428                 else\r
1429                         worldedit.player_notify(name, "code successfully executed", false)\r
1430                         minetest.log("action", name.." executed "..param)\r
1431                 end\r
1432         end,\r
1433 })\r
1434 \r
1435 worldedit.register_command("luatransform", {\r
1436         params = "<code>",\r
1437         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
1438         privs = {worldedit=true, server=true},\r
1439         require_pos = 2,\r
1440         parse = function(param)\r
1441                 return true, param\r
1442         end,\r
1443         nodes_needed = check_region,\r
1444         func = function(name, param)\r
1445                 local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)\r
1446                 if err then\r
1447                         worldedit.player_notify(name, "code error: " .. err, false)\r
1448                         minetest.log("action", name.." tried to execute luatransform "..param)\r
1449                 else\r
1450                         worldedit.player_notify(name, "code successfully executed", false)\r
1451                         minetest.log("action", name.." executed luatransform "..param)\r
1452                 end\r
1453         end,\r
1454 })\r
1455 \r
1456 worldedit.register_command("mtschemcreate", {\r
1457         params = "<file>",\r
1458         description = "Save the current WorldEdit region using the Minetest "..\r
1459                 "Schematic format to \"(world folder)/schems/<filename>.mts\"",\r
1460         privs = {worldedit=true},\r
1461         require_pos = 2,\r
1462         parse = function(param)\r
1463                 if param == "" then\r
1464                         return false\r
1465                 end\r
1466                 if not check_filename(param) then\r
1467                         return false, "Disallowed file name: " .. param\r
1468                 end\r
1469                 return true, param\r
1470         end,\r
1471         nodes_needed = check_region,\r
1472         func = function(name, param)\r
1473                 local path = minetest.get_worldpath() .. "/schems"\r
1474                 -- Create directory if it does not already exist\r
1475                 minetest.mkdir(path)\r
1476 \r
1477                 local filename = path .. "/" .. param .. ".mts"\r
1478                 local ret = minetest.create_schematic(worldedit.pos1[name],\r
1479                                 worldedit.pos2[name], worldedit.prob_list[name],\r
1480                                 filename)\r
1481                 if ret == nil then\r
1482                         worldedit.player_notify(name, "Failed to create Minetest schematic")\r
1483                 else\r
1484                         worldedit.player_notify(name, "Saved Minetest schematic to " .. param)\r
1485                 end\r
1486                 worldedit.prob_list[name] = {}\r
1487         end,\r
1488 })\r
1489 \r
1490 worldedit.register_command("mtschemplace", {\r
1491         params = "<file>",\r
1492         description = "Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin",\r
1493         privs = {worldedit=true},\r
1494         require_pos = 1,\r
1495         parse = function(param)\r
1496                 if param == "" then\r
1497                         return false\r
1498                 end\r
1499                 if not check_filename(param) then\r
1500                         return false, "Disallowed file name: " .. param\r
1501                 end\r
1502                 return true, param\r
1503         end,\r
1504         func = function(name, param)\r
1505                 local pos = worldedit.pos1[name]\r
1506 \r
1507                 local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts"\r
1508                 if minetest.place_schematic(pos, path) == nil then\r
1509                         worldedit.player_notify(name, "failed to place Minetest schematic")\r
1510                 else\r
1511                         worldedit.player_notify(name, "placed Minetest schematic " .. param ..\r
1512                                 " at " .. minetest.pos_to_string(pos))\r
1513                 end\r
1514         end,\r
1515 })\r
1516 \r
1517 worldedit.register_command("mtschemprob", {\r
1518         params = "start/finish/get",\r
1519         description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry",\r
1520         privs = {worldedit=true},\r
1521         parse = function(param)\r
1522                 if param ~= "start" and param ~= "finish" and param ~= "get" then\r
1523                         return false, "unknown subcommand: " .. param\r
1524                 end\r
1525                 return true, param\r
1526         end,\r
1527         func = function(name, param)\r
1528                 if param == "start" then --start probability setting\r
1529                         worldedit.set_pos[name] = "prob"\r
1530                         worldedit.prob_list[name] = {}\r
1531                         worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes")\r
1532                 elseif param == "finish" then --finish probability setting\r
1533                         worldedit.set_pos[name] = nil\r
1534                         worldedit.player_notify(name, "finished Minetest schematic probability selection")\r
1535                 elseif param == "get" then --get all nodes that had probabilities set on them\r
1536                         local text = ""\r
1537                         local problist = worldedit.prob_list[name]\r
1538                         if problist == nil then\r
1539                                 return\r
1540                         end\r
1541                         for k,v in pairs(problist) do\r
1542                                 local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100\r
1543                                 text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "\r
1544                         end\r
1545                         worldedit.player_notify(name, "currently set node probabilities:")\r
1546                         worldedit.player_notify(name, text)\r
1547                 end\r
1548         end,\r
1549 })\r
1550 \r
1551 minetest.register_on_player_receive_fields(function(player, formname, fields)\r
1552         if formname == "prob_val_enter" and not (fields.text == "" or fields.text == nil) then\r
1553                 local name = player:get_player_name()\r
1554                 local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}\r
1555                 local index = table.getn(worldedit.prob_list[name]) + 1\r
1556                 worldedit.prob_list[name][index] = prob_entry\r
1557         end\r
1558 end)\r
1559 \r
1560 worldedit.register_command("clearobjects", {\r
1561         params = "",\r
1562         description = "Clears all objects within the WorldEdit region",\r
1563         privs = {worldedit=true},\r
1564         require_pos = 2,\r
1565         nodes_needed = check_region,\r
1566         func = function(name)\r
1567                 local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])\r
1568                 worldedit.player_notify(name, count .. " objects cleared")\r
1569         end,\r
1570 })\r