init.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. if not minetest.global_exists("mtflower") then mtflower = {} end
  2. mtflower.modpath = minetest.get_modpath("mtflower")
  3. -- Localize for performance.
  4. local vector_round = vector.round
  5. local math_floor = math.floor
  6. local math_random = math.random
  7. -- All branch positions have been tested and declared accurate.
  8. local branches = {
  9. -- Straights.
  10. {{x= 1, y=0, z= 0}, {x= 2, y=0, z= 0}},
  11. {{x=-1, y=0, z= 0}, {x=-2, y=0, z= 0}},
  12. {{x= 0, y=0, z= 1}, {x= 0, y=0, z= 2}},
  13. {{x= 0, y=0, z=-1}, {x= 0, y=0, z=-2}},
  14. -- Straight turns.
  15. {{x= 1, y=0, z= 0}, {x= 2, y=0, z= 1}},
  16. {{x= 1, y=0, z= 0}, {x= 2, y=0, z=-1}},
  17. {{x=-1, y=0, z= 0}, {x=-2, y=0, z= 1}},
  18. {{x=-1, y=0, z= 0}, {x=-2, y=0, z=-1}},
  19. {{x= 0, y=0, z= 1}, {x= 1, y=0, z= 2}},
  20. {{x= 0, y=0, z= 1}, {x=-1, y=0, z= 2}},
  21. {{x= 0, y=0, z=-1}, {x= 1, y=0, z=-2}},
  22. {{x= 0, y=0, z=-1}, {x=-1, y=0, z=-2}},
  23. -- Straight diagonals.
  24. {{x= 1, y=0, z= 1}, {x= 2, y=0, z= 2}},
  25. {{x=-1, y=0, z=-1}, {x=-2, y=0, z=-2}},
  26. {{x= 1, y=0, z=-1}, {x= 2, y=0, z=-2}},
  27. {{x=-1, y=0, z= 1}, {x=-2, y=0, z= 2}},
  28. -- Diagonal turns.
  29. {{x= 1, y=0, z= 1}, {x= 1, y=0, z= 2}},
  30. {{x= 1, y=0, z= 1}, {x= 2, y=0, z= 1}},
  31. {{x=-1, y=0, z=-1}, {x=-1, y=0, z=-2}},
  32. {{x=-1, y=0, z=-1}, {x=-2, y=0, z=-1}},
  33. {{x= 1, y=0, z=-1}, {x= 1, y=0, z=-2}},
  34. {{x= 1, y=0, z=-1}, {x= 2, y=0, z=-1}},
  35. {{x=-1, y=0, z= 1}, {x=-1, y=0, z= 2}},
  36. {{x=-1, y=0, z= 1}, {x=-2, y=0, z= 1}},
  37. }
  38. function mtflower.may_replace_node(pos, tree, leaf, is_leaf)
  39. pos = vector_round(pos)
  40. local node = minetest.get_node(pos)
  41. -- Replace air and leaves of the same kind of tree.
  42. if node.name == "air" or node.name == leaf then
  43. return true
  44. end
  45. -- Never replace ignore or trunk/branch nodes.
  46. if node.name == "ignore" or node.name == tree then
  47. return
  48. end
  49. local ndef = minetest.registered_nodes[node.name] or {}
  50. -- Leaves do not replace liquid.
  51. if is_leaf and ndef.liquidtype ~= "none" then
  52. return
  53. end
  54. -- Allow replacing `buildable_to`, `floodable`.
  55. if ndef.buildable_to or ndef.floodable then
  56. return true
  57. end
  58. -- Or liquids.
  59. if ndef.liquidtype ~= "none" then
  60. return true
  61. end
  62. end
  63. function mtflower.shape_to_positions(shape, offset)
  64. if type(shape) == "string" then
  65. if shape == "single" then
  66. return {{x=0, y=0, z=0}}, {x=0, y=0, z=0}
  67. elseif shape == "cross" then
  68. return {
  69. {x= 1, y=0, z= 0},
  70. {x= 0, y=0, z= 1}, {x= 0, y=0, z= 0}, {x= 0, y=0, z=-1},
  71. {x=-1, y=0, z= 0},
  72. }, {x=0, y=0, z=0}
  73. elseif shape == "square" then
  74. local spots = {
  75. {x=0, y=0, z=0}, {x=1, y=0, z=0},
  76. {x=0, y=0, z=1}, {x=1, y=0, z=1},
  77. }
  78. if not offset then
  79. local adjust = {x=math_random(-1, 0), y=0, z=math_random(-1, 0)}
  80. for k, v in ipairs(spots) do
  81. local p = vector.add(v, adjust)
  82. v.x = p.x
  83. v.y = p.y
  84. v.z = p.z
  85. end
  86. return spots, adjust
  87. else
  88. for k, v in ipairs(spots) do
  89. v.x = v.x + offset.x
  90. v.y = v.y + offset.y
  91. v.z = v.z + offset.z
  92. end
  93. return spots, offset
  94. end
  95. end
  96. end
  97. -- Fallback.
  98. return {{x=0, y=0, z=0}}, {x=0, y=0, z=0}
  99. end
  100. function mtflower.generate_roots(pos, shape, offset, tree, leaf)
  101. pos = vector_round(pos)
  102. -- Get a list of positions to start the bottom of the root from.
  103. local starts = mtflower.shape_to_positions(shape, offset)
  104. -- Adjust start positions downward 1 meter so they don't interfere with trunk.
  105. for k, v in ipairs(starts) do
  106. v.y = v.y - 1
  107. end
  108. -- Sanitize arguments.
  109. if type(height) ~= "number" then height = tonumber(height) or 10 end
  110. if height < 1 then height = 1 end
  111. if type(leaves) ~= "string" then leaves = "default:leaves" end
  112. if leaves == "ignore" then leaves = "" end
  113. if not minetest.registered_nodes[leaves] then return {} end
  114. if type(tree) ~= "string" then tree = "default:tree" end
  115. if tree == "ignore" then return {} end
  116. if not minetest.registered_nodes[tree] then return {} end
  117. local roots = {}
  118. -- Grow roots downward.
  119. for y = 0, -16, -1 do
  120. local stops = 0
  121. -- Increment height of all start positions, stacking upwards.
  122. for k, v in ipairs(starts) do
  123. local p = vector.add(pos, v)
  124. if mtflower.may_replace_node(p, tree, leaves) then
  125. table.insert(roots, p)
  126. else
  127. stops = stops + 1
  128. end
  129. v.y = v.y - 1
  130. end
  131. -- If hit something that completely blocks root, stop growing downward.
  132. if stops == #starts then
  133. break
  134. end
  135. end
  136. -- Place root nodes.
  137. minetest.bulk_set_node(roots, {name = tree})
  138. -- Return all positions of roots.
  139. return roots
  140. end
  141. function mtflower.generate_trunk(pos, shape, height, tree, leaves)
  142. pos = vector_round(pos)
  143. -- Get a list of positions to start the bottom of the trunk from.
  144. local starts, offset = mtflower.shape_to_positions(shape)
  145. -- Sanitize arguments.
  146. if type(height) ~= "number" then height = tonumber(height) or 10 end
  147. if height < 1 then height = 1 end
  148. if type(leaves) ~= "string" then leaves = "default:leaves" end
  149. if leaves == "ignore" then leaves = "" end
  150. if not minetest.registered_nodes[leaves] then return {} end
  151. if type(tree) ~= "string" then tree = "default:tree" end
  152. if tree == "ignore" then return {} end
  153. if not minetest.registered_nodes[tree] then return {} end
  154. local trunks = {}
  155. for y = 0, height - 1, 1 do
  156. local stops = 0
  157. -- Increment height of all start positions, stacking upwards.
  158. for k, v in ipairs(starts) do
  159. local p = vector.add(pos, v)
  160. if mtflower.may_replace_node(p, tree, leaves) then
  161. table.insert(trunks, p)
  162. else
  163. stops = stops + 1
  164. end
  165. v.y = v.y + 1
  166. end
  167. -- If hit something that completely blocks trunk, stop growing upward.
  168. if stops == #starts then
  169. break
  170. end
  171. end
  172. -- Place trunk nodes.
  173. minetest.bulk_set_node(trunks, {name = tree})
  174. -- Return all positions of trunks.
  175. return trunks, offset
  176. end
  177. function mtflower.try_spawn_branch(pos, tree, leaf, recursive, cluster_count, first_cluster)
  178. pos = vector_round(pos)
  179. -- Sanitize arguments.
  180. if type(tree) ~= "string" then tree = "default:tree" end
  181. if tree == "ignore" then return {}, {} end
  182. if not minetest.registered_nodes[tree] then return {}, {} end
  183. if type(recursive) ~= "number" then recursive = 1 end
  184. if recursive < 1 then recursive = 1 end
  185. if type(leaf) ~= "string" then leaf = "default:tree" end
  186. if not minetest.registered_nodes[leaf] then return {}, {} end
  187. if leaf == "ignore" then return {}, {} end
  188. local branch
  189. local tree_near = {x=0, y=0, z=0}
  190. local p1
  191. local p2
  192. local try_count = 1
  193. while tree_near do
  194. if try_count >= 3 then
  195. break
  196. end
  197. branch = branches[math_random(1, #branches)]
  198. p1 = vector.add(pos, branch[1])
  199. p2 = vector.add(pos, branch[2])
  200. -- Do not spawn branches if the 'end' would be too close to the trunk or a branch.
  201. tree_near = minetest.find_node_near(p2, 1, {"mtflower:ignore", tree})
  202. try_count = try_count + 1
  203. end
  204. if tree_near then return {}, {} end
  205. local all = {}
  206. local arms = {}
  207. -- Declare these positions as branch nodes, if a branch can be placed.
  208. if mtflower.may_replace_node(p1, tree, leaf) then
  209. minetest.set_node(p1, {name="mtflower:ignore"})
  210. table.insert(all, p1)
  211. -- Add this position to the list of arm positions (branch next to trunk).
  212. -- Only for top level frame of recursive function.
  213. if recursive == 1 then
  214. table.insert(arms, p1)
  215. end
  216. end
  217. if mtflower.may_replace_node(p2, tree, leaf) then
  218. minetest.set_node(p2, {name="mtflower:ignore"})
  219. table.insert(all, p2)
  220. end
  221. -- In top-level frame of recursive function -- can call self to create more complex branches.
  222. -- Recursive marker must be GREATER THAN 1 for the recursive calls!
  223. if recursive == 1 then
  224. local endpoints = {table.copy(p2)}
  225. if cluster_count >= 2 and not first_cluster then
  226. endpoints = {}
  227. -- Fork in two directions.
  228. local more1 = mtflower.try_spawn_branch(p2, tree, leaf, 2, cluster_count, first_cluster)
  229. for k, v in ipairs(more1) do
  230. table.insert(all, v)
  231. end
  232. table.insert(endpoints, more1[2])
  233. local more2 = mtflower.try_spawn_branch(p2, tree, leaf, 2, cluster_count, first_cluster)
  234. for k, v in ipairs(more2) do
  235. table.insert(all, v)
  236. end
  237. table.insert(endpoints, more2[2])
  238. end
  239. -- For big trees, make one of the branches even longer (if possible).
  240. -- Do this for all clusters including the first one.
  241. if cluster_count >= 3 and #endpoints > 0 then
  242. local randp = endpoints[math_random(1, #endpoints)]
  243. local more3 = mtflower.try_spawn_branch(randp, tree, leaf, 2, cluster_count, first_cluster)
  244. for k, v in ipairs(more3) do
  245. table.insert(all, v)
  246. end
  247. end
  248. end
  249. -- Return all the locations where branch nodes were placed.
  250. return all, arms
  251. end
  252. function mtflower.generate_branches(trunks, tries, tree, leaf)
  253. trunks = table.copy(trunks)
  254. -- Sort trunk locations by Y-height, largest first.
  255. table.sort(trunks, function(a, b) if a.y > b.y then return true end end)
  256. local all = {}
  257. local arms = {}
  258. local create_branch = function(pos, cluster_count, first_cluster)
  259. local branch, arm = mtflower.try_spawn_branch(pos, tree, leaf, 1, cluster_count, first_cluster)
  260. for k, v in ipairs(branch) do
  261. table.insert(all, v)
  262. end
  263. for k, v in ipairs(arm) do
  264. table.insert(arms, v)
  265. end
  266. end
  267. local find_trunk_at_height = function(y)
  268. local found = {}
  269. for k, v in ipairs(trunks) do
  270. if v.y == y then
  271. table.insert(found, v)
  272. end
  273. end
  274. if #found > 0 then
  275. return found[math_random(1, #found)]
  276. end
  277. end
  278. local ymax = trunks[1].y
  279. local ymin = trunks[#trunks].y
  280. local spawn_horizontal_cluster = function(y, cluster_count, first_cluster)
  281. for i = 1, tries, 1 do
  282. local found = find_trunk_at_height(y)
  283. if found then
  284. create_branch(found, cluster_count, first_cluster)
  285. end
  286. end
  287. end
  288. -- This function spawns 2 or 3 horizontal layers going down from Y-start.
  289. local spawn_cluster = function(ytop, cluster_count, first_cluster)
  290. local width = math_random(3, 5)
  291. for y = ytop, ytop - width, -2 do
  292. spawn_horizontal_cluster(y, cluster_count, first_cluster)
  293. end
  294. end
  295. -- Calcuate how many branch clusters we can spawn on this tree, based on height.
  296. local cluster_count = 0
  297. for y = ymax, ymin, -10 do
  298. cluster_count = cluster_count + 1
  299. end
  300. -- Actually spawn the clusters.
  301. local first_cluster = true
  302. local dist = math_random(-11, -8)
  303. for y = ymax, ymin, dist do
  304. if y > ymin + 8 or first_cluster then
  305. spawn_cluster(y + math_random(-1, 1), cluster_count, first_cluster)
  306. first_cluster = false
  307. end
  308. end
  309. -- Branch locations calculated, place branch nodes.
  310. minetest.bulk_set_node(all, {name=tree})
  311. return all, arms
  312. end
  313. function mtflower.spawn_leaf_cube(pos, tree, leaves)
  314. pos = vector_round(pos)
  315. local above = vector.add(pos, {x=0, y=1, z=0})
  316. -- Sanitize arguments.
  317. if type(tree) ~= "string" then tree = "default:tree" end
  318. if type(leaves) ~= "string" then leaves = "default:leaves" end
  319. if tree == "ignore" or leaves == "ignore" then return {} end
  320. if not minetest.registered_nodes[tree] then return {} end
  321. if not minetest.registered_nodes[leaves] then return {} end
  322. -- Ensure the start position really is a trunk.
  323. if minetest.get_node(pos).name ~= tree then
  324. return {}
  325. end
  326. -- Require node above to be air or leaves, to spawn more leaves.
  327. if not mtflower.may_replace_node(above, tree, leaves, true) then
  328. return {}
  329. end
  330. local minp = vector.add(pos, {x=-1, y=-1, z=-1})
  331. local maxp = vector.add(pos, {x= 1, y= 1, z= 1})
  332. local sides = {
  333. {x= 1, y= 0, z= 0},
  334. {x=-1, y= 0, z= 0},
  335. {x= 0, y= 1, z= 0},
  336. {x= 0, y=-1, z= 0},
  337. {x= 0, y= 0, z= 1},
  338. {x= 0, y= 0, z=-1},
  339. }
  340. for k, v in ipairs(sides) do
  341. v.x = v.x + pos.x
  342. v.y = v.y + pos.y
  343. v.z = v.z + pos.z
  344. end
  345. local is_side = function(pos)
  346. for k, v in ipairs(sides) do
  347. if vector.equals(v, pos) then
  348. return true
  349. end
  350. end
  351. end
  352. -- Keep track of where leaves are spawned.
  353. local spots = {}
  354. for x = minp.x, maxp.x, 1 do
  355. for y = minp.y, maxp.y, 1 do
  356. for z = minp.z, maxp.z, 1 do
  357. local p = {x=x, y=y, z=z}
  358. -- Skip placing 1 in 7 leaves, but never skip sides.
  359. if math_random(1, 7) <= 6 or is_side(p) then
  360. if mtflower.may_replace_node(p, tree, leaves, true) then
  361. minetest.set_node(p, {name=leaves})
  362. table.insert(spots, p)
  363. end
  364. end
  365. end
  366. end
  367. end
  368. -- Has a chance to spawn extra leaves outside the cube, on the sides.
  369. local extras = {
  370. {x= 2, y= 0, z= 0},
  371. {x=-2, y= 0, z= 0},
  372. {x= 0, y= 2, z= 0},
  373. {x= 0, y=-2, z= 0},
  374. {x= 0, y= 0, z= 2},
  375. {x= 0, y= 0, z=-2},
  376. }
  377. for k, v in ipairs(extras) do
  378. v.x = v.x + pos.x
  379. v.y = v.y + pos.y
  380. v.z = v.z + pos.z
  381. if math_random(1, 4) == 1 then
  382. if mtflower.may_replace_node(v, tree, leaves, true) then
  383. minetest.set_node(v, {name=leaves})
  384. table.insert(spots, v)
  385. end
  386. end
  387. end
  388. return spots
  389. end
  390. function mtflower.generate_leaves(trunks, branch, tree, leaf)
  391. local leaves = {}
  392. -- Put leaves on all eligible trunk nodes.
  393. for k, v in ipairs(trunks) do
  394. local spots = mtflower.spawn_leaf_cube(v, tree, leaf)
  395. for i, j in ipairs(spots) do
  396. table.insert(leaves, j)
  397. end
  398. end
  399. -- Put leaves around all eligible branch nodes.
  400. for k, v in ipairs(branch) do
  401. local spots = mtflower.spawn_leaf_cube(v, tree, leaf)
  402. for i, j in ipairs(spots) do
  403. table.insert(leaves, j)
  404. end
  405. end
  406. return leaves
  407. end
  408. function mtflower.branch_tries_from_shape(shape)
  409. if type(shape) == "string" then
  410. if shape == "cross" then
  411. return 16
  412. elseif shape == "square" then
  413. return 8
  414. elseif shape == "single" then
  415. return 4
  416. end
  417. end
  418. -- Fallback.
  419. return 4
  420. end
  421. function mtflower.generate_tree(pos, shape, height, tree, leaf)
  422. local tries = mtflower.branch_tries_from_shape(shape)
  423. local trunks, offset = mtflower.generate_trunk(pos, shape, height, tree, leaf)
  424. local roots = mtflower.generate_roots(pos, shape, offset, tree, leaf)
  425. if not trunks or #trunks == 0 then
  426. return
  427. end
  428. local branch, arms = mtflower.generate_branches(trunks, tries, tree, leaf)
  429. local leaves = mtflower.generate_leaves(trunks, branch, tree, leaf)
  430. -- Positions of roots, trunks, branches, arms, & leaves.
  431. return true, roots, trunks, branch, arms, leaves
  432. end
  433. function mtflower.can_grow(pos)
  434. pos = vector_round(pos)
  435. if pos.y > math_random(-200, -100) then
  436. return false
  437. end
  438. -- Reduced chance to grow if cold/ice nearby.
  439. local below = {x=pos.x, y=pos.y-1, z=pos.z}
  440. local cold = minetest.find_nodes_in_area(vector.subtract(below, 1), vector.add(below, 1), "group:cold")
  441. if #cold > math_random(0, 18) then
  442. return false
  443. end
  444. local node_under = minetest.get_node_or_nil(below)
  445. if not node_under then
  446. return false
  447. end
  448. local name_under = node_under.name
  449. local is_soil = minetest.get_item_group(name_under, "glowmineral")
  450. if is_soil == 0 then
  451. return false
  452. end
  453. local light_level = minetest.get_node_light(pos)
  454. if not light_level or light_level < 12 then
  455. return false
  456. end
  457. -- Mineral-grown trees require a heat source.
  458. if not minetest.find_node_near(pos, 10, "group:lava") then
  459. return false
  460. end
  461. return true
  462. end
  463. -- This function returns `true` if a tree was actually spawned.
  464. function mtflower.try_grow(pos, tree, leaf, lamp, mineral)
  465. --minetest.chat_send_all('growing tree')
  466. if pos.y > -100 then
  467. return
  468. end
  469. local minp = vector.add(pos, {x=-5, y=-5, z=-5})
  470. local maxp = vector.add(pos, {x= 5, y= 5, z= 5})
  471. local positions = minetest.find_nodes_in_area(minp, maxp, mineral)
  472. local height = math_floor(#positions * 1.5)
  473. if height > 40 then
  474. height = 40
  475. end
  476. height = math_floor(height * (math_random(95, 105) / 100))
  477. if height < 6 then
  478. return
  479. end
  480. local shape = "single"
  481. if height >= 30 then
  482. shape = "cross"
  483. elseif height >= math_random(20, 30) then
  484. shape = "square"
  485. end
  486. -- Create the tree.
  487. minetest.set_node(pos, {name='air'}) -- Remove sapling first.
  488. local success, roots, trunks, branch, arms, leaves =
  489. mtflower.generate_tree(pos, shape, height, tree, leaf)
  490. if not success then
  491. return
  492. end
  493. -- Remove items out of the soil (the tree 'eats' them).
  494. minetest.bulk_set_node(positions, {name="default:gravel"})
  495. for k, v in ipairs(positions) do
  496. minetest.check_for_falling(v)
  497. end
  498. -- Scatter items drawn up out of the soil in the tree itself.
  499. if arms and #arms > 0 then
  500. local amount = math_floor(#positions / 4)
  501. for i = 1, amount, 1 do
  502. local spot = arms[math_random(1, #arms)]
  503. minetest.set_node(spot, {name=lamp})
  504. end
  505. end
  506. return true
  507. end
  508. if not mtflower.registered then
  509. -- Used when generating branches.
  510. minetest.register_node("mtflower:ignore", {
  511. drawtype = "airlike",
  512. description = "MTFLOWER IGNORE (Please Report to Admin)",
  513. paramtype = "light",
  514. sunlight_propagates = true,
  515. walkable = false,
  516. pointable = false,
  517. groups = {immovable = 1},
  518. climbable = false,
  519. buildable_to = true,
  520. floodable = true,
  521. drop = "",
  522. on_finish_collapse = function(pos, node)
  523. minetest.remove_node(pos)
  524. end,
  525. on_collapse_to_entity = function(pos, node)
  526. -- Do nothing.
  527. end,
  528. })
  529. -- Register mod as reloadable if reload functionality is available.
  530. if minetest.get_modpath("reload") then
  531. local c = "mtflower:core"
  532. local f = mtflower.modpath .. "/init.lua"
  533. reload.register_file(c, f, false)
  534. end
  535. mtflower.registered = true
  536. end