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