manipulations.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. --- Generic node manipulations.
  2. -- @module worldedit.manipulations
  3. local mh = worldedit.manip_helpers
  4. --- Sets a region to `node_names`.
  5. -- @param pos1
  6. -- @param pos2
  7. -- @param node_names Node name or list of node names.
  8. -- @return The number of nodes set.
  9. function worldedit.set(pos1, pos2, node_names)
  10. pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  11. local manip, area = mh.init(pos1, pos2)
  12. local data = mh.get_empty_data(area)
  13. if type(node_names) == "string" then -- Only one type of node
  14. local id = minetest.get_content_id(node_names)
  15. -- Fill area with node
  16. for i in area:iterp(pos1, pos2) do
  17. data[i] = id
  18. end
  19. else -- Several types of nodes specified
  20. local node_ids = {}
  21. for i, v in ipairs(node_names) do
  22. node_ids[i] = minetest.get_content_id(v)
  23. end
  24. -- Fill area randomly with nodes
  25. local id_count, rand = #node_ids, math.random
  26. for i in area:iterp(pos1, pos2) do
  27. data[i] = node_ids[rand(id_count)]
  28. end
  29. end
  30. mh.finish(manip, data)
  31. return worldedit.volume(pos1, pos2)
  32. end
  33. --- Sets param2 of a region.
  34. -- @param pos1
  35. -- @param pos2
  36. -- @param param2 Value of param2 to set
  37. -- @return The number of nodes set.
  38. function worldedit.set_param2(pos1, pos2, param2)
  39. pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  40. local manip, area = mh.init(pos1, pos2)
  41. local param2_data = manip:get_param2_data()
  42. -- Set param2 for every node
  43. for i in area:iterp(pos1, pos2) do
  44. param2_data[i] = param2
  45. end
  46. -- Update map
  47. manip:set_param2_data(param2_data)
  48. manip:write_to_map()
  49. manip:update_map()
  50. return worldedit.volume(pos1, pos2)
  51. end
  52. --- Replaces all instances of `search_node` with `replace_node` in a region.
  53. -- When `inverse` is `true`, replaces all instances that are NOT `search_node`.
  54. -- @return The number of nodes replaced.
  55. function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
  56. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  57. local manip, area = mh.init(pos1, pos2)
  58. local data = manip:get_data()
  59. local search_id = minetest.get_content_id(search_node)
  60. local replace_id = minetest.get_content_id(replace_node)
  61. local count = 0
  62. if not inverse then
  63. for i in area:iterp(pos1, pos2) do
  64. if data[i] == search_id then
  65. data[i] = replace_id
  66. count = count + 1
  67. end
  68. end
  69. else
  70. for i in area:iterp(pos1, pos2) do
  71. if data[i] ~= search_id then
  72. data[i] = replace_id
  73. count = count + 1
  74. end
  75. end
  76. end
  77. mh.finish(manip, data)
  78. return count
  79. end
  80. local function deferred_execution(next_one, finished)
  81. -- Allocate 100% of server step for execution (might lag a little)
  82. local allocated_usecs =
  83. tonumber(minetest.settings:get("dedicated_server_step")) * 1000000
  84. local function f()
  85. local deadline = minetest.get_us_time() + allocated_usecs
  86. repeat
  87. local is_done = next_one()
  88. if is_done then
  89. if finished then
  90. finished()
  91. end
  92. return
  93. end
  94. until minetest.get_us_time() >= deadline
  95. minetest.after(0, f)
  96. end
  97. f()
  98. end
  99. --- Duplicates a region `amount` times with offset vector `direction`.
  100. -- Stacking is spread across server steps.
  101. -- @return The number of nodes stacked.
  102. function worldedit.stack2(pos1, pos2, direction, amount, finished)
  103. -- Protect arguments from external changes during execution
  104. pos1 = table.copy(pos1)
  105. pos2 = table.copy(pos2)
  106. direction = table.copy(direction)
  107. local i = 0
  108. local translated = {x=0, y=0, z=0}
  109. local function step()
  110. translated.x = translated.x + direction.x
  111. translated.y = translated.y + direction.y
  112. translated.z = translated.z + direction.z
  113. worldedit.copy2(pos1, pos2, translated)
  114. i = i + 1
  115. return i >= amount
  116. end
  117. deferred_execution(step, finished)
  118. return worldedit.volume(pos1, pos2) * amount
  119. end
  120. --- Copies a region along `axis` by `amount` nodes.
  121. -- @param pos1
  122. -- @param pos2
  123. -- @param axis Axis ("x", "y", or "z")
  124. -- @param amount
  125. -- @return The number of nodes copied.
  126. function worldedit.copy(pos1, pos2, axis, amount)
  127. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  128. -- Decide if we need to copy stuff backwards (only applies to metadata)
  129. local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)
  130. local off = {x=0, y=0, z=0}
  131. off[axis] = amount
  132. return worldedit.copy2(pos1, pos2, off, backwards)
  133. end
  134. --- Copies a region by offset vector `off`.
  135. -- @param pos1
  136. -- @param pos2
  137. -- @param off
  138. -- @param meta_backwards (not officially part of API)
  139. -- @return The number of nodes copied.
  140. function worldedit.copy2(pos1, pos2, off, meta_backwards)
  141. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  142. local src_manip, src_area = mh.init(pos1, pos2)
  143. local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride}
  144. local src_offset = vector.subtract(pos1, src_area.MinEdge)
  145. local dpos1 = vector.add(pos1, off)
  146. local dpos2 = vector.add(pos2, off)
  147. local dim = vector.add(vector.subtract(pos2, pos1), 1)
  148. local dst_manip, dst_area = mh.init(dpos1, dpos2)
  149. local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride}
  150. local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)
  151. local function do_copy(src_data, dst_data)
  152. for z = 0, dim.z-1 do
  153. local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing
  154. local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1
  155. for y = 0, dim.y-1 do
  156. local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y
  157. local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y
  158. -- Copy entire row at once
  159. local src_index_x = src_index_y + src_offset.x
  160. local dst_index_x = dst_index_y + dst_offset.x
  161. for x = 0, dim.x-1 do
  162. dst_data[dst_index_x + x] = src_data[src_index_x + x]
  163. end
  164. end
  165. end
  166. end
  167. -- Copy node data
  168. local src_data = src_manip:get_data()
  169. local dst_data = dst_manip:get_data()
  170. do_copy(src_data, dst_data)
  171. dst_manip:set_data(dst_data)
  172. -- Copy param1
  173. src_manip:get_light_data(src_data)
  174. dst_manip:get_light_data(dst_data)
  175. do_copy(src_data, dst_data)
  176. dst_manip:set_light_data(dst_data)
  177. -- Copy param2
  178. src_manip:get_param2_data(src_data)
  179. dst_manip:get_param2_data(dst_data)
  180. do_copy(src_data, dst_data)
  181. dst_manip:set_param2_data(dst_data)
  182. mh.finish(dst_manip)
  183. src_data = nil
  184. dst_data = nil
  185. -- Copy metadata
  186. local get_meta = minetest.get_meta
  187. if meta_backwards then
  188. for z = dim.z-1, 0, -1 do
  189. for y = dim.y-1, 0, -1 do
  190. for x = dim.x-1, 0, -1 do
  191. local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
  192. local meta = get_meta(pos):to_table()
  193. pos = vector.add(pos, off)
  194. get_meta(pos):from_table(meta)
  195. end
  196. end
  197. end
  198. else
  199. for z = 0, dim.z-1 do
  200. for y = 0, dim.y-1 do
  201. for x = 0, dim.x-1 do
  202. local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
  203. local meta = get_meta(pos):to_table()
  204. pos = vector.add(pos, off)
  205. get_meta(pos):from_table(meta)
  206. end
  207. end
  208. end
  209. end
  210. return worldedit.volume(pos1, pos2)
  211. end
  212. --- Deletes all node metadata in the region
  213. -- @param pos1
  214. -- @param pos2
  215. -- @return The number of nodes that had their meta deleted.
  216. function worldedit.delete_meta(pos1, pos2)
  217. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  218. local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
  219. local get_meta = minetest.get_meta
  220. for _, pos in ipairs(meta_positions) do
  221. get_meta(pos):from_table(nil)
  222. end
  223. return #meta_positions
  224. end
  225. --- Moves a region along `axis` by `amount` nodes.
  226. -- @return The number of nodes moved.
  227. function worldedit.move(pos1, pos2, axis, amount)
  228. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  229. local dim = vector.add(vector.subtract(pos2, pos1), 1)
  230. local overlap = math.abs(amount) < dim[axis]
  231. -- Decide if we need to copy metadata backwards
  232. local backwards = overlap and amount > 0
  233. local function nuke_area(my_off, my_dim)
  234. if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then
  235. return
  236. end
  237. local my_pos1 = vector.add(pos1, my_off)
  238. local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1)
  239. worldedit.set(my_pos1, my_pos2, "air")
  240. worldedit.delete_meta(my_pos1, my_pos2)
  241. end
  242. -- Copy stuff to new location
  243. local off = {x=0, y=0, z=0}
  244. off[axis] = amount
  245. worldedit.copy2(pos1, pos2, off, backwards)
  246. -- Nuke old area
  247. if not overlap then
  248. nuke_area({x=0, y=0, z=0}, dim)
  249. else
  250. -- Source and destination region are overlapping, which means we can't
  251. -- blindly delete the [pos1, pos2] area
  252. local leftover = vector.new(dim) -- size of the leftover slice
  253. leftover[axis] = math.abs(amount)
  254. if amount > 0 then
  255. nuke_area({x=0, y=0, z=0}, leftover)
  256. else
  257. local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1
  258. top[axis] = dim[axis] - math.abs(amount)
  259. nuke_area(top, leftover)
  260. end
  261. end
  262. return worldedit.volume(pos1, pos2)
  263. end
  264. --- Duplicates a region along `axis` `amount` times.
  265. -- Stacking is spread across server steps.
  266. -- @param pos1
  267. -- @param pos2
  268. -- @param axis Axis direction, "x", "y", or "z".
  269. -- @param count
  270. -- @return The number of nodes stacked.
  271. function worldedit.stack(pos1, pos2, axis, count, finished)
  272. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  273. local length = pos2[axis] - pos1[axis] + 1
  274. if count < 0 then
  275. count = -count
  276. length = -length
  277. end
  278. local i, distance = 0, 0
  279. local function step()
  280. distance = distance + length
  281. worldedit.copy(pos1, pos2, axis, distance)
  282. i = i + 1
  283. return i >= count
  284. end
  285. deferred_execution(step, finished)
  286. return worldedit.volume(pos1, pos2) * count
  287. end
  288. --- Stretches a region by a factor of positive integers along the X, Y, and Z
  289. -- axes, respectively, with `pos1` as the origin.
  290. -- @param pos1
  291. -- @param pos2
  292. -- @param stretch_x Amount to stretch along X axis.
  293. -- @param stretch_y Amount to stretch along Y axis.
  294. -- @param stretch_z Amount to stretch along Z axis.
  295. -- @return The number of nodes scaled.
  296. -- @return The new scaled position 1.
  297. -- @return The new scaled position 2.
  298. function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
  299. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  300. -- Prepare schematic of large node
  301. local get_node, get_meta, place_schematic = minetest.get_node,
  302. minetest.get_meta, minetest.place_schematic
  303. local placeholder_node = {name="", param1=255, param2=0}
  304. local nodes = {}
  305. for i = 1, stretch_x * stretch_y * stretch_z do
  306. nodes[i] = placeholder_node
  307. end
  308. local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}
  309. local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
  310. local new_pos2 = {
  311. x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
  312. y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
  313. z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
  314. }
  315. worldedit.keep_loaded(pos1, new_pos2)
  316. local pos = {x=pos2.x, y=0, z=0}
  317. local big_pos = {x=0, y=0, z=0}
  318. while pos.x >= pos1.x do
  319. pos.y = pos2.y
  320. while pos.y >= pos1.y do
  321. pos.z = pos2.z
  322. while pos.z >= pos1.z do
  323. local node = get_node(pos) -- Get current node
  324. local meta = get_meta(pos):to_table() -- Get meta of current node
  325. -- Calculate far corner of the big node
  326. local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
  327. local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
  328. local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
  329. -- Create large node
  330. placeholder_node.name = node.name
  331. placeholder_node.param2 = node.param2
  332. big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
  333. place_schematic(big_pos, schematic)
  334. -- Fill in large node meta
  335. if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
  336. -- Node has meta fields
  337. for x = 0, size_x do
  338. for y = 0, size_y do
  339. for z = 0, size_z do
  340. big_pos.x = pos_x + x
  341. big_pos.y = pos_y + y
  342. big_pos.z = pos_z + z
  343. -- Set metadata of new node
  344. get_meta(big_pos):from_table(meta)
  345. end
  346. end
  347. end
  348. end
  349. pos.z = pos.z - 1
  350. end
  351. pos.y = pos.y - 1
  352. end
  353. pos.x = pos.x - 1
  354. end
  355. return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
  356. end
  357. --- Transposes a region between two axes.
  358. -- @return The number of nodes transposed.
  359. -- @return The new transposed position 1.
  360. -- @return The new transposed position 2.
  361. function worldedit.transpose(pos1, pos2, axis1, axis2)
  362. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  363. local compare
  364. local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
  365. if extent1 > extent2 then
  366. compare = function(extent1, extent2)
  367. return extent1 > extent2
  368. end
  369. else
  370. compare = function(extent1, extent2)
  371. return extent1 < extent2
  372. end
  373. end
  374. -- Calculate the new position 2 after transposition
  375. local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
  376. new_pos2[axis1] = pos1[axis1] + extent2
  377. new_pos2[axis2] = pos1[axis2] + extent1
  378. local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}
  379. if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
  380. if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
  381. worldedit.keep_loaded(pos1, upper_bound)
  382. local pos = {x=pos1.x, y=0, z=0}
  383. local get_node, get_meta, set_node = minetest.get_node,
  384. minetest.get_meta, minetest.set_node
  385. while pos.x <= pos2.x do
  386. pos.y = pos1.y
  387. while pos.y <= pos2.y do
  388. pos.z = pos1.z
  389. while pos.z <= pos2.z do
  390. local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
  391. if compare(extent1, extent2) then -- Transpose only if below the diagonal
  392. local node1 = get_node(pos)
  393. local meta1 = get_meta(pos):to_table()
  394. local value1, value2 = pos[axis1], pos[axis2] -- Save position values
  395. pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents
  396. local node2 = get_node(pos)
  397. local meta2 = get_meta(pos):to_table()
  398. set_node(pos, node1)
  399. get_meta(pos):from_table(meta1)
  400. pos[axis1], pos[axis2] = value1, value2 -- Restore position values
  401. set_node(pos, node2)
  402. get_meta(pos):from_table(meta2)
  403. end
  404. pos.z = pos.z + 1
  405. end
  406. pos.y = pos.y + 1
  407. end
  408. pos.x = pos.x + 1
  409. end
  410. return worldedit.volume(pos1, pos2), pos1, new_pos2
  411. end
  412. --- Flips a region along `axis`.
  413. -- @return The number of nodes flipped.
  414. function worldedit.flip(pos1, pos2, axis)
  415. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  416. worldedit.keep_loaded(pos1, pos2)
  417. --- TODO: Flip the region slice by slice along the flip axis using schematic method.
  418. local pos = {x=pos1.x, y=0, z=0}
  419. local start = pos1[axis] + pos2[axis]
  420. pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
  421. local get_node, get_meta, set_node = minetest.get_node,
  422. minetest.get_meta, minetest.set_node
  423. while pos.x <= pos2.x do
  424. pos.y = pos1.y
  425. while pos.y <= pos2.y do
  426. pos.z = pos1.z
  427. while pos.z <= pos2.z do
  428. local node1 = get_node(pos)
  429. local meta1 = get_meta(pos):to_table()
  430. local value = pos[axis] -- Save position
  431. pos[axis] = start - value -- Shift position
  432. local node2 = get_node(pos)
  433. local meta2 = get_meta(pos):to_table()
  434. set_node(pos, node1)
  435. get_meta(pos):from_table(meta1)
  436. pos[axis] = value -- Restore position
  437. set_node(pos, node2)
  438. get_meta(pos):from_table(meta2)
  439. pos.z = pos.z + 1
  440. end
  441. pos.y = pos.y + 1
  442. end
  443. pos.x = pos.x + 1
  444. end
  445. return worldedit.volume(pos1, pos2)
  446. end
  447. --- Rotates a region clockwise around an axis.
  448. -- @param pos1
  449. -- @param pos2
  450. -- @param axis Axis ("x", "y", or "z").
  451. -- @param angle Angle in degrees (90 degree increments only).
  452. -- @return The number of nodes rotated.
  453. -- @return The new first position.
  454. -- @return The new second position.
  455. function worldedit.rotate(pos1, pos2, axis, angle)
  456. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  457. local other1, other2 = worldedit.get_axis_others(axis)
  458. angle = angle % 360
  459. local count
  460. if angle == 90 then
  461. worldedit.flip(pos1, pos2, other1)
  462. count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
  463. elseif angle == 180 then
  464. worldedit.flip(pos1, pos2, other1)
  465. count = worldedit.flip(pos1, pos2, other2)
  466. elseif angle == 270 then
  467. worldedit.flip(pos1, pos2, other2)
  468. count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
  469. else
  470. error("Only 90 degree increments are supported!")
  471. end
  472. return count, pos1, pos2
  473. end
  474. --- Rotates all oriented nodes in a region clockwise around the Y axis.
  475. -- @param pos1
  476. -- @param pos2
  477. -- @param angle Angle in degrees (90 degree increments only).
  478. -- @return The number of nodes oriented.
  479. function worldedit.orient(pos1, pos2, angle)
  480. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  481. local registered_nodes = minetest.registered_nodes
  482. local wallmounted = {
  483. [90] = {0, 1, 5, 4, 2, 3, 0, 0},
  484. [180] = {0, 1, 3, 2, 5, 4, 0, 0},
  485. [270] = {0, 1, 4, 5, 3, 2, 0, 0}
  486. }
  487. local facedir = {
  488. [90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
  489. 9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
  490. [180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
  491. 18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
  492. [270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
  493. 7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
  494. }
  495. angle = angle % 360
  496. if angle == 0 then
  497. return 0
  498. end
  499. if angle % 90 ~= 0 then
  500. error("Only 90 degree increments are supported!")
  501. end
  502. local wallmounted_substitution = wallmounted[angle]
  503. local facedir_substitution = facedir[angle]
  504. worldedit.keep_loaded(pos1, pos2)
  505. local count = 0
  506. local get_node, swap_node = minetest.get_node, minetest.swap_node
  507. local pos = {x=pos1.x, y=0, z=0}
  508. while pos.x <= pos2.x do
  509. pos.y = pos1.y
  510. while pos.y <= pos2.y do
  511. pos.z = pos1.z
  512. while pos.z <= pos2.z do
  513. local node = get_node(pos)
  514. local def = registered_nodes[node.name]
  515. if def then
  516. local paramtype2 = def.paramtype2
  517. if paramtype2 == "wallmounted" or
  518. paramtype2 == "colorwallmounted" then
  519. local orient = node.param2 % 8
  520. node.param2 = node.param2 - orient +
  521. wallmounted_substitution[orient + 1]
  522. swap_node(pos, node)
  523. count = count + 1
  524. elseif paramtype2 == "facedir" or
  525. paramtype2 == "colorfacedir" then
  526. local orient = node.param2 % 32
  527. node.param2 = node.param2 - orient +
  528. facedir_substitution[orient + 1]
  529. swap_node(pos, node)
  530. count = count + 1
  531. end
  532. end
  533. pos.z = pos.z + 1
  534. end
  535. pos.y = pos.y + 1
  536. end
  537. pos.x = pos.x + 1
  538. end
  539. return count
  540. end
  541. --- Attempts to fix the lighting in a region.
  542. -- @return The number of nodes updated.
  543. function worldedit.fixlight(pos1, pos2)
  544. local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  545. local vmanip = minetest.get_voxel_manip(pos1, pos2)
  546. vmanip:write_to_map()
  547. vmanip:update_map() -- this updates the lighting
  548. return worldedit.volume(pos1, pos2)
  549. end
  550. --- Clears all objects in a region.
  551. -- @return The number of objects cleared.
  552. function worldedit.clear_objects(pos1, pos2)
  553. pos1, pos2 = worldedit.sort_pos(pos1, pos2)
  554. worldedit.keep_loaded(pos1, pos2)
  555. -- Offset positions to include full nodes (positions are in the center of nodes)
  556. local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5
  557. local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5
  558. -- Center of region
  559. local center = {
  560. x = pos1x + ((pos2x - pos1x) / 2),
  561. y = pos1y + ((pos2y - pos1y) / 2),
  562. z = pos1z + ((pos2z - pos1z) / 2)
  563. }
  564. -- Bounding sphere radius
  565. local radius = math.sqrt(
  566. (center.x - pos1x) ^ 2 +
  567. (center.y - pos1y) ^ 2 +
  568. (center.z - pos1z) ^ 2)
  569. local count = 0
  570. for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
  571. local entity = obj:get_luaentity()
  572. -- Avoid players and WorldEdit entities
  573. if not obj:is_player() and (not entity or
  574. not entity.name:find("^worldedit:")) then
  575. local pos = obj:get_pos()
  576. if pos.x >= pos1x and pos.x <= pos2x and
  577. pos.y >= pos1y and pos.y <= pos2y and
  578. pos.z >= pos1z and pos.z <= pos2z then
  579. -- Inside region
  580. obj:remove()
  581. count = count + 1
  582. end
  583. end
  584. end
  585. return count
  586. end