init.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. --[[
  2. lumberjack
  3. ==========
  4. Copyright (C) 2018 Joachim Stolberg
  5. LGPLv2.1+
  6. See LICENSE.txt for more information
  7. Mod to completely cut trees by destroying only one block.
  8. This mod allows to destroy the bottom of the tree and the whole tree is felled
  9. and moved to the players inventory.
  10. To distinguish between "grown" trees and placed tree nodes, the attribute
  11. 'node.param1' is used to identify placed nodes.
  12. The number of necessary lumberjack points has to be configured via 'settingtypes.txt'
  13. ]]--
  14. lumberjack = {}
  15. local MY_PARAM1_VAL = 7 -- to identify placed nodes
  16. -- Necessary number of points for dug trees and placed sapling to get lumberjack privs
  17. local LUMBERJACK_TREE_POINTS = tonumber(minetest.settings:get("lumberjack_points")) or 400
  18. local LUMBERJACK_SAPL_POINTS = LUMBERJACK_TREE_POINTS / 6
  19. local lTrees = {} -- List of registered tree items
  20. --
  21. -- Check if used tool is some kind of axe and is used by a player
  22. --
  23. local function chopper_tool(digger)
  24. if digger and digger:is_player() then
  25. local tool = digger:get_wielded_item()
  26. if tool:get_name() == "screwdriver:screwdriver" then
  27. return true
  28. end
  29. if tool then
  30. local caps = tool:get_tool_capabilities()
  31. return caps.groupcaps and caps.groupcaps.choppy
  32. end
  33. end
  34. return false
  35. end
  36. --
  37. -- Remove/add tree steps
  38. --
  39. local function remove_steps(pos)
  40. local pos1 = {x=pos.x-1, y=pos.y, z=pos.z-1}
  41. local pos2 = {x=pos.x+1, y=pos.y, z=pos.z+1}
  42. for _,pos in ipairs(minetest.find_nodes_in_area(pos1, pos2, "lumberjack:step")) do
  43. minetest.remove_node(pos)
  44. end
  45. end
  46. local function add_steps(pos, digger)
  47. local facedir = minetest.dir_to_facedir(digger:get_look_dir(), false)
  48. local dir = minetest.facedir_to_dir((facedir + 2) % 4)
  49. local newpos = vector.add(pos, dir)
  50. if minetest.get_node(newpos).name == "air" then
  51. minetest.add_node(newpos, {name="lumberjack:step", param2=facedir})
  52. end
  53. end
  54. local function on_punch(pos, node, puncher, pointed_thing)
  55. if chopper_tool(puncher) and node.param1 == 0 then -- grown tree?
  56. if not minetest.is_protected(pos, puncher:get_player_name()) then
  57. add_steps(pos, puncher)
  58. end
  59. end
  60. minetest.node_punch(pos, node, puncher, pointed_thing)
  61. end
  62. --
  63. -- tool wearing
  64. --
  65. local function add_wear(digger, node, num_nodes)
  66. local tool = digger:get_wielded_item()
  67. if tool then
  68. local caps = tool:get_tool_capabilities()
  69. if caps.groupcaps and caps.groupcaps.choppy then
  70. local uses = caps.groupcaps.choppy.uses or 10
  71. uses = uses * 9
  72. tool:add_wear(65535 * num_nodes / uses)
  73. digger:set_wielded_item(tool)
  74. end
  75. end
  76. end
  77. --
  78. -- Remove all treen nodes including steps in the given area
  79. --
  80. local function remove_items(pos1, pos2, name)
  81. local cnt = 0
  82. for _,pos in ipairs(minetest.find_nodes_in_area(pos1, pos2, name)) do
  83. minetest.remove_node(pos)
  84. remove_steps(pos)
  85. cnt = cnt + 1
  86. end
  87. return cnt
  88. end
  89. --
  90. -- Check for tree nodes on the next higher level
  91. -- We have to check more than one level, because Ethereal allows stem gaps
  92. --
  93. local function is_top_tree_node(pos, name)
  94. local pos1 = {x=pos.x-1, y=pos.y+1, z=pos.z-1}
  95. local pos2 = {x=pos.x+1, y=pos.y+3, z=pos.z+1}
  96. for _,pos in ipairs(minetest.find_nodes_in_area(pos1, pos2, name)) do
  97. return false
  98. end
  99. return true
  100. end
  101. --
  102. -- Check for the necessary number of points and grant lumberjack privs if level is reached
  103. --
  104. local function check_points(player)
  105. local points = tonumber(player:get_attribute("lumberjack_tree_points") or LUMBERJACK_TREE_POINTS)
  106. points = points + tonumber(player:get_attribute("lumberjack_sapl_points") or LUMBERJACK_SAPL_POINTS)
  107. if points > 0 then
  108. return false
  109. elseif points == 0 then
  110. local privs = minetest.get_player_privs(player:get_player_name())
  111. privs.lumberjack = true
  112. minetest.set_player_privs(player:get_player_name(), privs)
  113. player:set_attribute("lumberjack_tree_points", "-1")
  114. player:set_attribute("lumberjack_sapl_points", "-1")
  115. minetest.chat_send_player(player:get_player_name(), "You got lumberjack privs now")
  116. minetest.log("action", player:get_player_name().." got lumberjack privs")
  117. end
  118. return true
  119. end
  120. --
  121. -- Maintain lumberjack points and grant lumberjack privs if level is reached
  122. --
  123. local function needed_points(digger)
  124. local points = tonumber(digger:get_attribute("lumberjack_tree_points") or LUMBERJACK_TREE_POINTS)
  125. if points > 0 then
  126. digger:set_attribute("lumberjack_tree_points", tostring(points - 1))
  127. end
  128. if points == 0 then
  129. return check_points(digger)
  130. end
  131. return false
  132. end
  133. --
  134. -- Decrement sapling points
  135. --
  136. local function after_place_sapling(pos, placer)
  137. local points = tonumber(placer:get_attribute("lumberjack_sapl_points") or LUMBERJACK_SAPL_POINTS)
  138. if points > 0 then
  139. placer:set_attribute("lumberjack_sapl_points", tostring(points - 1))
  140. end
  141. if points == 0 then
  142. check_points(placer)
  143. end
  144. end
  145. --
  146. -- Remove the complete tree and return the number of removed items
  147. --
  148. local function remove_tree(pos, radius, name)
  149. local level = 1
  150. local num_nodes = 0
  151. while true do
  152. -- We have to check more than one level, because Ethereal allows stem gaps
  153. local pos1 = {x=pos.x-radius, y=pos.y+level, z=pos.z-radius}
  154. local pos2 = {x=pos.x+radius, y=pos.y+level+2, z=pos.z+radius}
  155. local cnt = remove_items(pos1, pos2, name)
  156. if cnt == 0 then break end
  157. num_nodes = num_nodes + cnt
  158. level = level + 3
  159. end
  160. return num_nodes
  161. end
  162. --
  163. -- Add tree items to the players inventory
  164. --
  165. local function add_to_inventory(digger, name, len, pos)
  166. local inv = digger:get_inventory()
  167. local items = ItemStack(name .. " " .. len)
  168. if inv and items and inv:room_for_item("main", items) then
  169. inv:add_item("main", items)
  170. else
  171. minetest.item_drop(items, digger, pos)
  172. end
  173. end
  174. --
  175. -- Remove the complete tree if the destroyed node belongs to a tree
  176. --
  177. local function after_dig_node(pos, oldnode, oldmetadata, digger)
  178. -- Player placed node?
  179. if oldnode.param1 ~= 0 then return end
  180. remove_steps(pos)
  181. -- don't remove whole tree?
  182. if not digger or digger:get_player_control().sneak then return end
  183. -- Get tree parameters
  184. local height_min = 3
  185. local radius = 0
  186. local registered_tree = lTrees[oldnode.name]
  187. if registered_tree then
  188. height_min = registered_tree.height_min or height_min
  189. radius = registered_tree.radius or radius
  190. end
  191. -- Or root nodes?
  192. local test_pos = {x=pos.x, y=pos.y+height_min-1, z=pos.z}
  193. if minetest.get_node(test_pos).name ~= oldnode.name then return end
  194. -- Fell the tree
  195. local num_nodes = remove_tree(pos, radius, oldnode.name)
  196. add_to_inventory(digger, oldnode.name, num_nodes, pos)
  197. add_wear(digger, oldnode, num_nodes)
  198. minetest.log("action", digger:get_player_name().." fells "..oldnode.name..
  199. " ("..num_nodes.." items)".." at "..minetest.pos_to_string(pos))
  200. minetest.sound_play("tree_falling", {pos = pos, max_hear_distance = 16})
  201. end
  202. --
  203. -- Mark node as "placed by player"
  204. --
  205. local function on_construct(pos)
  206. local node = minetest.get_node(pos)
  207. if node then
  208. minetest.swap_node(pos, {name=node.name, param1=MY_PARAM1_VAL, param2=node.param2})
  209. end
  210. end
  211. local function can_dig(pos, digger)
  212. if not digger then
  213. return true
  214. end
  215. if minetest.is_protected(pos, digger:get_player_name()) then
  216. return false
  217. end
  218. if minetest.check_player_privs(digger:get_player_name(), "lumberjack") then
  219. if chopper_tool(digger) then
  220. return true
  221. else
  222. minetest.chat_send_player(digger:get_player_name(), "[Lumberjack Mod] You have to use an axe")
  223. return false
  224. end
  225. end
  226. local node = minetest.get_node(pos)
  227. if node.param1 ~= 0 then
  228. return true
  229. end
  230. if is_top_tree_node(pos, node.name) or needed_points(digger) then
  231. return true
  232. end
  233. minetest.chat_send_player(digger:get_player_name(), "[Lumberjack Mod] From the top, please")
  234. return false
  235. end
  236. minetest.register_privilege("lumberjack",
  237. {description = "Gives you the rights to fell a tree at once",
  238. give_to_singleplayer = true})
  239. minetest.register_node("lumberjack:step", {
  240. description = "Lumberjack Step",
  241. drawtype = "nodebox",
  242. tiles = {"lumberjack_steps.png"},
  243. node_box = {
  244. type = "fixed",
  245. fixed = {
  246. { -0.5, -0.5, 0.49, 0.5, 0.5, 0.5},
  247. },
  248. },
  249. paramtype2 = "facedir",
  250. is_ground_content = false,
  251. climbable = true,
  252. paramtype = "light",
  253. use_texture_alpha = true,
  254. sunlight_propagates = true,
  255. walkable = false,
  256. pointable = false,
  257. drop = "",
  258. groups = {flammable=1, not_in_creative_inventory=1},
  259. })
  260. --
  261. -- Register the tree node to the lumberjack mod.
  262. -- 'tree_name' is the tree item name,, e.g. "default:tree"
  263. -- 'sapling_name' is the tree sapling name, e.g. "default:sapling"
  264. -- 'radius' the the range in nodes (+x/-x/+z/-z), where all available tree nodes will be removed.
  265. -- 'stem_height_min' is the minimum number of tree nodes, to be a valid stem (and not the a root item).
  266. --
  267. function lumberjack.register_tree(tree_name, sapling_name, radius, stem_height_min)
  268. -- check tree attributes
  269. local data = minetest.registered_nodes[tree_name]
  270. if data == nil then
  271. error("[lumberjack] "..tree_name.." is no valid item")
  272. end
  273. if data.after_dig_node then
  274. error("[lumberjack] "..tree_name.." has already an 'after_dig_node' function")
  275. end
  276. if data.on_construct then
  277. error("[lumberjack] "..tree_name.." has already an 'on_construct' function")
  278. end
  279. if data.can_dig then
  280. error("[lumberjack] "..tree_name.." has already a 'can_dig' function")
  281. end
  282. if not data.groups.choppy then
  283. error("[lumberjack] "..tree_name.." has no 'choppy' property")
  284. end
  285. -- check sapling attributes
  286. if minetest.registered_nodes[sapling_name].after_place_node then
  287. error("[lumberjack] "..sapling_name.." has already an 'after_place_node' function")
  288. end
  289. minetest.override_item(tree_name, {
  290. after_dig_node = after_dig_node,
  291. on_construct = on_construct,
  292. can_dig = can_dig,
  293. on_punch = on_punch,
  294. })
  295. minetest.override_item(sapling_name, {
  296. after_place_node = after_place_sapling
  297. })
  298. lTrees[tree_name] = {radius=radius, height_min=stem_height_min, choppy=data.groups.choppy}
  299. end
  300. lumberjack.register_tree("default:tree", "default:sapling", 1, 2)
  301. lumberjack.register_tree("default:jungletree", "default:junglesapling", 1, 5)
  302. lumberjack.register_tree("default:acacia_tree", "default:acacia_sapling", 2, 2)
  303. lumberjack.register_tree("default:aspen_tree", "default:aspen_sapling", 0, 5)
  304. lumberjack.register_tree("default:pine_tree", "default:pine_sapling", 0, 3)
  305. if minetest.get_modpath("ethereal") and minetest.global_exists("ethereal") then
  306. lumberjack.register_tree("ethereal:palm_trunk", "ethereal:palm_sapling", 1, 3)
  307. lumberjack.register_tree("ethereal:mushroom_trunk", "ethereal:mushroom_sapling", 1, 3)
  308. lumberjack.register_tree("ethereal:birch_trunk", "ethereal:birch_sapling", 0, 3)
  309. lumberjack.register_tree("ethereal:banana_trunk", "ethereal:banana_tree_sapling", 1, 3)
  310. lumberjack.register_tree("ethereal:willow_trunk", "ethereal:willow_sapling", 4, 3)
  311. lumberjack.register_tree("ethereal:frost_tree", "ethereal:frost_tree_sapling", 1, 3)
  312. end
  313. if minetest.get_modpath("moretrees") and minetest.global_exists("moretrees") then
  314. lumberjack.register_tree("moretrees:beech_trunk", "moretrees:beech_sapling", 1, 3)
  315. lumberjack.register_tree("moretrees:apple_tree_trunk", "moretrees:apple_tree_sapling", 8, 3)
  316. lumberjack.register_tree("moretrees:oak_trunk", "moretrees:oak_sapling", 13,5 )
  317. lumberjack.register_tree("moretrees:sequoia_trunk", "moretrees:sequoia_sapling", 9, 3)
  318. lumberjack.register_tree("moretrees:birch_trunk", "moretrees:birch_sapling", 12,5)
  319. lumberjack.register_tree("moretrees:palm_trunk", "moretrees:palm_sapling", 5, 3)
  320. -- lumberjack.register_tree("moretrees:palm_fruit_trunk", "moretrees:palm_sapling", 5, 3)
  321. lumberjack.register_tree("moretrees:spruce_trunk", "moretrees:spruce_sapling", 1, 3)
  322. lumberjack.register_tree("moretrees:pine_trunk", "moretrees:pine_sapling", 0, 3)
  323. lumberjack.register_tree("moretrees:willow_trunk", "moretrees:willow_sapling",1,3)
  324. lumberjack.register_tree("moretrees:rubber_tree_trunk", "moretrees:rubber_tree_sapling", 7, 3)
  325. -- lumberjack.register_tree("moretrees:jungletree_trunk", "moretrees:jungletree_sapling", 1, 5) -- crashes
  326. lumberjack.register_tree("moretrees:fir_trunk", "moretrees:fir_sapling", 5, 3) -- below leaves by 5
  327. end