init.lua 54 KB

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