init.lua 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. local load_time_start = minetest.get_us_time()
  2. ------------------------------------- Settings ---------------------------------
  3. -- default settings
  4. treecapitator = {
  5. drop_items = false,
  6. drop_leaf = false,
  7. play_sound = true,
  8. moretrees_support = false,
  9. no_hand_capitation = false,
  10. delay = 0,
  11. stem_height_min = 3,
  12. default_tree = { --replaces not defined stuff (see below)
  13. trees = {"default:tree"},
  14. leaves = {"default:leaves"},
  15. range = 2,
  16. fruits = {},
  17. type = "default",
  18. },
  19. after_register = {},
  20. }
  21. -- load custom settings
  22. for name,v in pairs(treecapitator) do
  23. local setting = "treecapitator." .. name
  24. --local typ = type(v)
  25. local neuv
  26. if type(v) == "boolean" then
  27. neuv = minetest.settings:get_bool(setting)
  28. else--if typ == "number" then
  29. neuv = tonumber(minetest.settings:get(setting))
  30. end
  31. if neuv ~= nil then
  32. treecapitator[name] = neuv
  33. end
  34. end
  35. -------------------------- Common functions ------------------------------------
  36. local poshash = minetest.hash_node_position
  37. local function hash2(x, y)
  38. return y * 0x10000 + x
  39. end
  40. -- don't use minetest.get_node more times for the same position (caching)
  41. local known_nodes
  42. local function clean_cache()
  43. known_nodes = {}
  44. setmetatable(known_nodes, {__mode = "kv"})
  45. end
  46. clean_cache()
  47. local function remove_node(pos)
  48. known_nodes[poshash(pos)] = {name="air", param2=0}
  49. minetest.remove_node(pos)
  50. minetest.check_for_falling(pos)
  51. end
  52. local function get_node(pos)
  53. local vi = poshash(pos)
  54. local node = known_nodes[vi]
  55. if node then
  56. return node
  57. end
  58. node = minetest.get_node(pos)
  59. known_nodes[vi] = node
  60. return node
  61. end
  62. --definitions of functions for the destruction of nodes
  63. local destroy_node, drop_leaf, remove_leaf
  64. if treecapitator.drop_items then
  65. function drop_leaf(pos, item)
  66. minetest.add_item(pos, item)
  67. end
  68. function destroy_node(pos, node)
  69. local drops = minetest.get_node_drops(node.name)
  70. for _,item in pairs(drops) do
  71. minetest.add_item(pos, item)
  72. end
  73. remove_node(pos)
  74. end
  75. else
  76. if minetest.settings:get_bool"creative_mode" then
  77. function drop_leaf(_, item, inv)
  78. if inv
  79. and inv:room_for_item("main", item)
  80. and not inv:contains_item("main", item) then
  81. inv:add_item("main", item)
  82. end
  83. end
  84. else
  85. function drop_leaf(pos, item, inv)
  86. if inv
  87. and inv:room_for_item("main", item) then
  88. inv:add_item("main", item)
  89. else
  90. minetest.add_item(pos, item)
  91. end
  92. end
  93. end
  94. destroy_node = function(pos, node, digger)
  95. known_nodes[poshash(pos)] = {name="air", param2=0}
  96. minetest.node_dig(pos, node, digger)
  97. end
  98. end
  99. if not treecapitator.drop_leaf then
  100. function remove_leaf(pos, node, inv)
  101. local leaves_drops = minetest.get_node_drops(node.name)
  102. for _, itemname in pairs(leaves_drops) do
  103. if itemname ~= node.name then
  104. drop_leaf(pos, itemname, inv)
  105. end
  106. end
  107. remove_node(pos) --remove the leaves
  108. end
  109. else
  110. function remove_leaf(pos, node, _, digger)
  111. destroy_node(pos, node, digger)
  112. end
  113. end
  114. table_contains = function(t, v)
  115. for i = 1,#t do
  116. if t[i] == v then
  117. return true
  118. end
  119. end
  120. return false
  121. end
  122. -- the functions for the available types
  123. local capitate_funcs = {}
  124. ------------------------ Function for regular trees ----------------------------
  125. -- tests if the node is a trunk which could belong to the same tree sort
  126. local function is_trunk_of_tree(trees, node)
  127. return node.param2 == 0
  128. and trees ^ node.name
  129. end
  130. -- test if the trunk node there is the top trunk node of a neighbour tree
  131. -- if so, constrain the possible leaves positions
  132. local function get_a_tree(pos, tab, tr, xo,yo,zo)
  133. local p = {x=pos.x + xo, y=pos.y + yo, z=pos.z + zo}
  134. -- tests if a trunk is at the current pos
  135. local nd = get_node(p)
  136. if not is_trunk_of_tree(tr.trees, nd) then
  137. return false
  138. end
  139. -- search for a leaves or fruit node next to the trunk
  140. local leaf = get_node{x=p.x, y=p.y+1, z=p.z}.name
  141. if not tr.leaves ^ leaf
  142. and not tr.fruits ^ leaf then
  143. local leaf = get_node{x=p.x, y=p.y, z=p.z+1}.name
  144. if not tr.leaves ^ leaf
  145. and not tr.fruits ^ leaf then
  146. return false
  147. end
  148. end
  149. -- search for the requisite amount of stem trunk nodes
  150. for _ = 1, tr.stem_height_min-1 do
  151. p.y = p.y-1
  152. if not is_trunk_of_tree(tr.trees, get_node(p)) then
  153. return false
  154. end
  155. end
  156. p.y = p.y + tr.stem_height_min-1
  157. local r = tr.range
  158. local r_up = tr.range_up or r
  159. local r_down = tr.range_down or r
  160. -- reduce x and z avoidance range for thick stem neighbour trees
  161. if tr.stem_type == "2x2" then
  162. r = r - 1
  163. elseif tr.stem_type == "+" then
  164. r = r - 2
  165. end
  166. -- tag places which should not be removed
  167. local z1 = math.max(-r + zo, -r)
  168. local z2 = math.min(r + zo, r)
  169. local y1 = math.max(-r_down + yo, -r_down)
  170. local y2 = math.min(r_up + yo, r_up)
  171. local x1 = math.max(-r + xo, -r)
  172. local x2 = math.min(r + xo, r)
  173. for z = z1,z2 do
  174. for y = y1,y2 do
  175. local i = poshash{x=x1, y=y, z=z}
  176. for _ = x1,x2 do
  177. tab[i] = true
  178. i = i+1
  179. end
  180. end
  181. end
  182. return true
  183. end
  184. -- returns positions for leaves allowed to be dug
  185. local function find_valid_head_ps(pos, head_ps, trunktop_ps, tr)
  186. -- exclude the stem nodes
  187. local before_stems = {}
  188. for i = 1,#trunktop_ps do
  189. local p = vector.subtract(trunktop_ps[i], pos)
  190. before_stems[hash2(p.x, p.z)] = p.y+1
  191. end
  192. local r = tr.range
  193. local r_up = tr.range_up or r
  194. local r_down = tr.range_down or r
  195. -- firstly, detect neighbour trees of the same sort to not hurt them
  196. local tab = {}
  197. local rx2 = 2 * r
  198. local rupdown = r_up + r_down
  199. for z = -rx2, rx2 do
  200. for x = -rx2, rx2 do
  201. local bot = before_stems[hash2(x, z)] or -rupdown
  202. for y = rupdown, bot, -1 do
  203. if get_a_tree(pos, tab, tr, x,y,z) then
  204. break
  205. end
  206. end
  207. end
  208. end
  209. -- now, get the head positions without the neighbouring trees
  210. local n = #head_ps
  211. for z = -r,r do
  212. for x = -r,r do
  213. local bot = before_stems[hash2(x, z)] or -r_down
  214. for y = bot,r_up do
  215. local p = {x=x, y=y, z=z}
  216. if not tab[poshash(p)] then
  217. n = n+1
  218. head_ps[n] = vector.add(pos, p)
  219. end
  220. end
  221. end
  222. end
  223. return n
  224. end
  225. -- adds the stem to the trunks
  226. local function get_stem(trunktop_ps, trunks, tr, head_ps)
  227. if tr.cutting_leaves then
  228. treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps,
  229. get_node, is_trunk_of_tree)
  230. return
  231. end
  232. for i = 1,#trunktop_ps do
  233. local pos = trunktop_ps[i]
  234. local node = get_node(pos)
  235. while is_trunk_of_tree(tr.trees, node) do
  236. trunks[#trunks+1] = {pos, node}
  237. pos = {x=pos.x, y=pos.y+1, z=pos.z}
  238. node = get_node(pos)
  239. end
  240. -- renew trunk top position
  241. pos.y = pos.y-1
  242. trunktop_ps[i] = pos
  243. end
  244. end
  245. -- part of healthy stem searching
  246. local function here_neat_stemps(p, tr)
  247. local ps = {}
  248. for i = 1,#tr.stem_offsets do
  249. local o = tr.stem_offsets[i]
  250. local p = {x = p.x + o[1], y = p.y, z = p.z + o[2]}
  251. -- air test is too simple (makeshift solution)
  252. if get_node(p).name ~= "air" then
  253. return
  254. end
  255. p.y = p.y+1
  256. if not is_trunk_of_tree(tr.trees, get_node(p)) then
  257. return
  258. end
  259. ps[#ps+1] = p
  260. end
  261. return ps
  262. end
  263. -- gives stem positions of a healthy tree
  264. local function find_neat_stemps(pos, tr)
  265. for i = 1,#tr.stem_offsets do
  266. local o = tr.stem_offsets[i]
  267. local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]}
  268. local ps = here_neat_stemps(p, tr)
  269. if ps then
  270. return ps
  271. end
  272. end
  273. -- nothing found
  274. end
  275. -- part of incomplete stem searching
  276. local function here_incomplete_stemps(p, tr)
  277. local ps = {}
  278. for i = 1,#tr.stem_offsets do
  279. local o = tr.stem_offsets[i]
  280. local p = {x = p.x + o[1], y = p.y+1, z = p.z + o[2]}
  281. if is_trunk_of_tree(tr.trees, get_node(p)) then
  282. p.y = p.y-1
  283. local node = get_node(p)
  284. if is_trunk_of_tree(tr.trees, node) then
  285. -- stem wasn't chopped enough
  286. return {}
  287. end
  288. -- air test is too simple (makeshift solution)
  289. if node.name == "air" then
  290. p.y = p.y+1
  291. ps[#ps+1] = p
  292. end
  293. end
  294. end
  295. -- #ps ∈ [3]
  296. return ps
  297. end
  298. -- gives stem positions of an eroded tree
  299. local function find_incomplete_stemps(pos, tr)
  300. local ps
  301. local stemcount = 0
  302. for i = 1,#tr.stem_offsets do
  303. local o = tr.stem_offsets[i]
  304. local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]}
  305. local cps = here_incomplete_stemps(p, tr)
  306. local cnt = #cps
  307. if cnt == 0 then
  308. -- player needs to chop more
  309. return
  310. end
  311. if stemcount < cnt then
  312. stemcount = #cps
  313. ps = cps
  314. end
  315. end
  316. return ps
  317. end
  318. -- returns the lowest trunk node positions
  319. local function get_stem_ps(pos, tr)
  320. if not tr.stem_type then
  321. -- 1x1 stem
  322. return {{x=pos.x, y=pos.y+1, z=pos.z}}
  323. end
  324. return find_neat_stemps(pos, tr)
  325. or find_incomplete_stemps(pos, tr)
  326. end
  327. -- gets the middle position of the tree head
  328. local function get_head_center(trunktop_ps, stem_type)
  329. if stem_type == "2x2" then
  330. -- return the highest position
  331. local pos = trunktop_ps[1]
  332. for i = 2,#trunktop_ps do
  333. local p = trunktop_ps[i]
  334. if p.y > pos.y then
  335. pos = p
  336. end
  337. end
  338. return pos
  339. elseif stem_type == "+" then
  340. -- return the middle position
  341. local mid = vector.new()
  342. for i = 1,#trunktop_ps do
  343. mid = vector.add(mid, trunktop_ps[i])
  344. end
  345. return vector.round(vector.divide(mid, #trunktop_ps))
  346. else
  347. return trunktop_ps[1]
  348. end
  349. end
  350. function capitate_funcs.default(pos, tr, _, digger)
  351. local trees = tr.trees
  352. -- get the stem trunks
  353. local trunks = {}
  354. local trunktop_ps = get_stem_ps(pos, tr)
  355. if not trunktop_ps then
  356. return
  357. end
  358. local head_ps = {}
  359. get_stem(trunktop_ps, trunks, tr, head_ps)
  360. local leaves = tr.leaves
  361. local fruits = tr.fruits
  362. local hcp = get_head_center(trunktop_ps, tr.stem_type)
  363. -- abort if the tree lacks leaves/fruits
  364. local ln = get_node{x=hcp.x, y=hcp.y+1, z=hcp.z}
  365. if not leaves ^ ln.name
  366. and not fruits ^ ln.name then
  367. local leaf = get_node{x=hcp.x, y=hcp.y, z=hcp.z+1}.name
  368. if not leaves ^ leaf
  369. and not fruits ^ leaf then
  370. return
  371. end
  372. end
  373. -- get leaves, fruits and stem fruits
  374. local leaves_found = {}
  375. local n = find_valid_head_ps(hcp, head_ps, trunktop_ps, tr)
  376. local leaves_toremove = {}
  377. local fruits_toremove = {}
  378. for i = 1,n do
  379. local p = head_ps[i]
  380. local node = get_node(p)
  381. local nodename = node.name
  382. local is_trunk = trees ^ nodename
  383. if node.param2 ~= 0
  384. or not is_trunk then
  385. if leaves ^ nodename then
  386. leaves_found[nodename] = true
  387. leaves_toremove[#leaves_toremove+1] = {p, node}
  388. elseif fruits ^ nodename then
  389. fruits_toremove[#fruits_toremove+1] = {p, node}
  390. end
  391. elseif is_trunk
  392. and tr.trunk_fruit_vertical
  393. and fruits ^ nodename then
  394. trunks[#trunks+1] = {p, node}
  395. end
  396. end
  397. if tr.requisite_leaves then
  398. -- abort if specific leaves weren't found
  399. for i = 1,#tr.requisite_leaves do
  400. if not leaves_found[tr.requisite_leaves[i]] then
  401. return
  402. end
  403. end
  404. end
  405. -- remove fruits at first due to attachment
  406. -- and disable nodeupdate temporarily
  407. local nodeupdate = minetest.check_for_falling
  408. minetest.check_for_falling = function() end
  409. for i = 1,#fruits_toremove do
  410. destroy_node(fruits_toremove[i][1], fruits_toremove[i][2], digger)
  411. end
  412. minetest.check_for_falling = nodeupdate
  413. local inv = digger:get_inventory()
  414. for i = 1,#leaves_toremove do
  415. remove_leaf(leaves_toremove[i][1], leaves_toremove[i][2], inv, digger)
  416. end
  417. for i = 1,#trunks do
  418. destroy_node(trunks[i][1], trunks[i][2], digger)
  419. end
  420. -- tree was capitated, play sound
  421. if treecapitator.play_sound then
  422. minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
  423. end
  424. return true
  425. end
  426. -- metatable for shorter code: trees ^ name ≙ name ∈ trees
  427. local mt_default = {
  428. __pow = table_contains
  429. }
  430. treecapitator.after_register.default = function(tr)
  431. setmetatable(tr.trees, mt_default)
  432. setmetatable(tr.leaves, mt_default)
  433. setmetatable(tr.fruits, mt_default)
  434. tr.range_up = tr.range_up or tr.range
  435. tr.range_down = tr.range_down or tr.range
  436. tr.stem_height_min = tr.stem_height_min or treecapitator.stem_height_min
  437. if tr.stem_type == "2x2" then
  438. tr.stem_offsets = {
  439. {0,0}, {1,0},
  440. {0,1}, {1,1},
  441. }
  442. elseif tr.stem_type == "+" then
  443. tr.stem_offsets = {
  444. {0,0},
  445. {0,1},
  446. {-1,0}, {1,0},
  447. {0,-1},
  448. }
  449. end
  450. end
  451. --------------------- Acacia tree function -------------------------------------
  452. function capitate_funcs.acacia(pos, tr, node_above, digger)
  453. local trunk = tr.trees[1]
  454. -- fill tab with the stem trunks
  455. local tab, n = {{{x=pos.x, y=pos.y+1, z=pos.z}, node_above}}, 2
  456. local np = {x=pos.x, y=pos.y+2, z=pos.z}
  457. local nd = get_node(np)
  458. while trunk == nd.name
  459. and nd.param2 < 4 do
  460. tab[n] = {vector.new(np), nd}
  461. n = n+1
  462. np.y = np.y+1
  463. nd = get_node(np)
  464. end
  465. np.y = np.y-1
  466. local inv = digger:get_inventory()
  467. for z = -1,1,2 do
  468. for x = -1,1,2 do
  469. -- add the other trunks to tab
  470. local p = vector.new(np)
  471. p.x = p.x+x
  472. p.z = p.z+z
  473. local nd = get_node(p)
  474. if nd.name ~= trunk then
  475. p.y = p.y+1
  476. nd = get_node(p)
  477. if nd.name ~= trunk then
  478. return
  479. end
  480. end
  481. tab[n] = {vector.new(p), nd}
  482. p.x = p.x+x
  483. p.z = p.z+z
  484. p.y = p.y+1
  485. if get_node(p).name ~= trunk then
  486. return
  487. end
  488. tab[n+1] = {vector.new(p), nd}
  489. n = n+2
  490. -- get neighbouring acacia trunks for delimiting
  491. local no_rms = {}
  492. for z = -4,4 do
  493. for x = -4,4 do
  494. if math.abs(x+z) ~= 8
  495. and (x ~= 0 or z ~= 0) then
  496. if get_node{x=p.x+x, y=p.y, z=p.z+z}.name == trunk
  497. and get_node{x=p.x+x, y=p.y+1, z=p.z+z}.name == tr.leaf then
  498. for z = math.max(-4, z-2), math.min(4, z+2) do
  499. for x = math.max(-4, x-2), math.min(4, x+2) do
  500. no_rms[(z+4)*9 + x+4] = true
  501. end
  502. end
  503. end
  504. end
  505. end
  506. end
  507. -- remove leaves
  508. p.y = p.y+1
  509. local i = 0
  510. for z = -4,4 do
  511. for x = -4,4 do
  512. if not no_rms[i] then
  513. local p = {x=p.x+x, y=p.y, z=p.z+z}
  514. local node = get_node(p)
  515. if node.name == tr.leaf then
  516. remove_leaf(p, node, inv, digger)
  517. end
  518. end
  519. i = i+1
  520. end
  521. end
  522. end
  523. end
  524. -- play the sound, then dig the stem
  525. if treecapitator.play_sound then
  526. minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
  527. end
  528. for i = 1,n-1 do
  529. local pos,node = unpack(tab[i])
  530. destroy_node(pos, node, digger)
  531. end
  532. return true
  533. end
  534. ----------------------- Palm tree function -------------------------------------
  535. -- the 17 vectors used for walking the stem
  536. local palm_stem_dirs = {
  537. {0,1,0}
  538. }
  539. local n = 2
  540. for i = -1,1,2 do
  541. palm_stem_dirs[n] = {i,0,0}
  542. palm_stem_dirs[n+1] = {0,0,i}
  543. n = n+2
  544. end
  545. for i = -1,1,2 do
  546. palm_stem_dirs[n] = {i,0,i}
  547. palm_stem_dirs[n+1] = {i,0,-i}
  548. n = n+2
  549. end
  550. for i = -1,1,2 do
  551. palm_stem_dirs[n] = {i,1,0}
  552. palm_stem_dirs[n+1] = {0,1,i}
  553. n = n+2
  554. end
  555. for i = -1,1,2 do
  556. palm_stem_dirs[n] = {i,1,i}
  557. palm_stem_dirs[n+1] = {i,1,-i}
  558. n = n+2
  559. end
  560. for i = 1,17 do
  561. local p = palm_stem_dirs[i]
  562. palm_stem_dirs[i] = vector.new(unpack(p))
  563. end
  564. local pos_from_hash = minetest.get_position_from_hash
  565. -- gets a list of leaves positions
  566. local function get_palm_head(hcp, tr, max_forbi)
  567. local pos = {x=hcp.x, y=hcp.y+1, z=hcp.z}
  568. local leaves = {}
  569. if get_node(pos).name ~= tr.leaves then
  570. -- search hub position
  571. for xo = -1,1 do
  572. for zo = -1,1 do
  573. local p = {x=pos.x+xo, y=pos.y, z=pos.z+zo}
  574. if get_node(p).name == tr.leaves then
  575. pos = p
  576. end
  577. end
  578. end
  579. end
  580. -- collect leaves
  581. leaves[poshash(pos)] = true
  582. for i = -1,1 do
  583. for j = -1,1 do
  584. -- don't search around the corner except max_forbi time(s)
  585. local dirs = {{0,0}, {i,0}, {0,j}, {i,j}, {-i,0}, {0,-j}}
  586. local avoids = {}
  587. local todo = {pos}
  588. local sp = 1
  589. while sp > 0 do
  590. local p = todo[sp]
  591. sp = sp-1
  592. -- only walk the "forbidden" dir if still allowed
  593. local forbic = avoids[poshash(p)] or 0
  594. local dirc = 6
  595. if forbic == max_forbi then
  596. dirc = dirc - 2
  597. end
  598. -- walk the directions
  599. for i = 1,dirc do
  600. -- increase forbidden when needed
  601. local forbinc = forbic
  602. if i > 4 then
  603. forbinc = forbinc+1
  604. end
  605. local xz = dirs[i]
  606. for y = -1,2 do
  607. local p = {x=p.x+xz[1], y=p.y+y, z=p.z+xz[2]}
  608. local ph = poshash(p)
  609. local forbi = avoids[ph]
  610. if not forbi
  611. or forbi > forbinc then
  612. avoids[ph] = forbinc
  613. local dif = vector.subtract(p, pos)
  614. if get_node(p).name == tr.leaves
  615. and math.abs(dif.x) <= tr.range
  616. and math.abs(dif.z) <= tr.range
  617. and dif.y <= tr.range_up
  618. and dif.y >= -tr.range_down then
  619. sp = sp+1
  620. todo[sp] = p
  621. leaves[ph] = true
  622. end
  623. end
  624. end
  625. end
  626. end
  627. end
  628. end
  629. local ps = {}
  630. local n = 0
  631. for ph in pairs(leaves) do
  632. n = n+1
  633. ps[n] = pos_from_hash(ph)
  634. end
  635. return ps,n
  636. end
  637. -- returns positions for palm leaves allowed to be dug
  638. local function palm_find_valid_head_ps(pos, head_ps, tr)
  639. local r = tr.range
  640. local r_up = tr.range_up or r
  641. local r_down = tr.range_down or r
  642. -- firstly, detect neighbour palms' leaves to not hurt them
  643. local tab = {}
  644. local rx2 = 2 * r
  645. local rupdown = r_up + r_down
  646. for z = -rx2, rx2 do
  647. for y = -rupdown, rupdown do
  648. for x = -rx2, rx2 do
  649. local hcp = {x=pos.x+x, y=pos.y+y, z=pos.z+z}
  650. if not vector.equals(hcp, pos)
  651. and get_node(hcp).name == tr.trunk_top then
  652. local leaves,n = get_palm_head(hcp, tr, 0)
  653. for i = 1,n do
  654. tab[poshash(leaves[i])] = true
  655. end
  656. end
  657. end
  658. end
  659. end
  660. -- now, get the leaves positions without the neighbouring leaves
  661. local leaves,lc = get_palm_head(pos, tr, tr.max_forbi)
  662. local n = #head_ps
  663. for i = 1,lc do
  664. local p = leaves[i]
  665. if not tab[poshash(p)] then
  666. n = n+1
  667. head_ps[n] = p
  668. end
  669. end
  670. return n
  671. end
  672. function capitate_funcs.palm(pos, tr, node_above, digger)
  673. local trunk = tr.trees[1]
  674. -- walk the stem up to the fruit carrier
  675. pos = {x=pos.x, y=pos.y+1, z=pos.z}
  676. local trunks = {{pos, node_above}}
  677. local trunk_found = true
  678. local nohori = false
  679. local hcp
  680. while trunk_found
  681. and not hcp do
  682. trunk_found = false
  683. for i = 1,17 do
  684. local hori = i > 1 and i < 10
  685. if not hori
  686. or not nohori then
  687. local p = vector.add(pos, palm_stem_dirs[i])
  688. local node = get_node(p)
  689. if node.name == trunk then
  690. trunk_found = true
  691. trunks[#trunks+1] = {p, node}
  692. pos = p
  693. nohori = hori
  694. break
  695. end
  696. if node.name == tr.trunk_top then
  697. hcp = p
  698. trunks[#trunks+1] = {p, node}
  699. break
  700. end
  701. end
  702. end
  703. end
  704. if not hcp then
  705. return
  706. end
  707. -- collect coconuts
  708. local fruits = {}
  709. for zo = -1,1 do
  710. for xo = -1,1 do
  711. local p = {x=hcp.x+xo, y=hcp.y, z=hcp.z+zo}
  712. local node = get_node(p)
  713. if node.name:sub(1, #tr.fruit) == tr.fruit then
  714. fruits[#fruits+1] = {p, node}
  715. end
  716. end
  717. end
  718. -- find the leaves of the palm
  719. local leaves_ps = {}
  720. local lc = palm_find_valid_head_ps(hcp, leaves_ps, tr)
  721. if treecapitator.play_sound then
  722. minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
  723. end
  724. local nodeupdate = minetest.check_for_falling
  725. minetest.check_for_falling = function() end
  726. for i = 1,#fruits do
  727. local pos,node = unpack(fruits[i])
  728. destroy_node(pos, node, digger)
  729. end
  730. minetest.check_for_falling = nodeupdate
  731. for i = 1,#trunks do
  732. local pos,node = unpack(trunks[i])
  733. destroy_node(pos, node, digger)
  734. end
  735. local inv = digger:get_inventory()
  736. for i = 1,lc do
  737. local pos = leaves_ps[i]
  738. remove_leaf(pos, get_node(pos), inv, digger)
  739. end
  740. return true
  741. end
  742. ---------------------- A moretrees capitation function -------------------------
  743. -- table iteration instead of recursion
  744. local function get_tab(pos, func, max)
  745. local todo = {pos}
  746. local n = 1
  747. local tab_avoid = {[poshash(pos)] = true}
  748. local tab_done,num = {pos},2
  749. while n ~= 0 do
  750. local p = todo[n]
  751. n = n-1
  752. --[[
  753. for i = -1,1,2 do
  754. for _,p2 in pairs{
  755. {x=p.x+i, y=p.y, z=p.z},
  756. {x=p.x, y=p.y+i, z=p.z},
  757. {x=p.x, y=p.y, z=p.z+i},
  758. } do]]
  759. for i = -1,1 do
  760. for j = -1,1 do
  761. for k = -1,1 do
  762. local p2 = {x=p.x+i, y=p.y+j, z=p.z+k}
  763. local vi = poshash(p2)
  764. if not tab_avoid[vi]
  765. and func(p2) then
  766. n = n+1
  767. todo[n] = p2
  768. tab_avoid[vi] = true
  769. tab_done[num] = p2
  770. num = num+1
  771. if max
  772. and num > max then
  773. return false
  774. end
  775. end
  776. end
  777. end
  778. end
  779. end
  780. return tab_done
  781. end
  782. function capitate_funcs.moretrees(pos, tr, _, digger)
  783. local trees = tr.trees
  784. local leaves = tr.leaves
  785. local fruits = tr.fruits
  786. local minx = pos.x-tr.range
  787. local maxx = pos.x+tr.range
  788. local minz = pos.z-tr.range
  789. local maxz = pos.z+tr.range
  790. local maxy = pos.y+tr.height
  791. local num_trunks = 0
  792. local num_leaves = 0
  793. local ps = get_tab({x=pos.x, y=pos.y+1, z=pos.z}, function(pos)
  794. if pos.x < minx
  795. or pos.x > maxx
  796. or pos.z < minz
  797. or pos.z > maxz
  798. or pos.y > maxy then
  799. return false
  800. end
  801. local nam = get_node(pos).name
  802. if table_contains(trees, nam) then
  803. num_trunks = num_trunks+1
  804. elseif table_contains(leaves, nam) then
  805. num_leaves = num_leaves+1
  806. elseif not table_contains(fruits, nam) then
  807. return false
  808. end
  809. return true
  810. end, tr.max_nodes)
  811. if not ps then
  812. print"no ps found"
  813. return
  814. end
  815. if num_trunks < tr.num_trunks_min
  816. or num_trunks > tr.num_trunks_max then
  817. print("wrong trunks num: "..num_trunks)
  818. return
  819. end
  820. if num_leaves < tr.num_leaves_min
  821. or num_leaves > tr.num_leaves_max then
  822. print("wrong leaves num: "..num_leaves)
  823. return
  824. end
  825. if treecapitator.play_sound then
  826. minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 32})
  827. end
  828. local inv = digger:get_inventory()
  829. for _,p in pairs(ps) do
  830. local node = get_node(p)
  831. local nodename = node.name
  832. if table_contains(leaves, nodename) then
  833. remove_leaf(p, node, inv, digger)
  834. else
  835. destroy_node(p, node, digger)
  836. end
  837. end
  838. return true
  839. end
  840. --------------------------- api interface --------------------------------------
  841. -- the function which is used for capitating the api
  842. local capitating = false --necessary if minetest.node_dig is used
  843. function treecapitator.capitate_tree(pos, digger)
  844. if capitating
  845. or not digger
  846. or digger:get_player_control().sneak
  847. or not treecapitator.capitation_allowed(pos, digger) then
  848. return
  849. end
  850. local t1 = minetest.get_us_time()
  851. capitating = true
  852. local node_above = get_node{x=pos.x, y=pos.y+1, z=pos.z}
  853. for i = 1,#treecapitator.trees do
  854. local tr = treecapitator.trees[i]
  855. if table_contains(tr.trees, node_above.name)
  856. and node_above.param2 < 4
  857. and capitate_funcs[tr.type](pos, tr, node_above, digger) then
  858. break
  859. end
  860. end
  861. clean_cache()
  862. capitating = false
  863. minetest.log("info", "[treecapitator] tree capitated at (" ..
  864. pos.x .. "|" .. pos.y .. "|" .. pos.z .. ") after ca. " ..
  865. (minetest.get_us_time() - t1) / 1000000 .. " s")
  866. end
  867. -- delayed capitating
  868. local delay = treecapitator.delay
  869. if delay > 0 then
  870. local oldfunc = treecapitator.capitate_tree
  871. function treecapitator.capitate_tree(...)
  872. minetest.after(delay, function(...)
  873. oldfunc(...)
  874. end, ...)
  875. end
  876. end
  877. local path = minetest.get_modpath"treecapitator" .. DIR_DELIM
  878. dofile(path .. "api.lua")
  879. dofile(path .. "trees.lua")
  880. dofile(path .. "moretrees34.lua")
  881. local time = (minetest.get_us_time() - load_time_start) / 1000000
  882. local msg = "[treecapitator] loaded after ca. " .. time .. " seconds."
  883. if time > 0.01 then
  884. print(msg)
  885. else
  886. minetest.log("info", msg)
  887. end