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