Gábriel
2024-03-25 d5ff69d1d9efd683d852562af6cfddac5ac69879
commit | author | age
8e03d7 1 -- Configuration
R 2
9019eb 3 local chainsaw_max_charge      = 30000 -- Maximum charge of the saw
S 4 -- Cut down tree leaves.  Leaf decay may cause slowness on large trees
5 -- if this is disabled.
6 local chainsaw_leaves = true
7
0921c3 8 local chainsaw_efficiency = 0.92 -- Drops less items
e66bb2 9
0921c3 10 -- Maximal dimensions of the tree to cut (giant sequoia)
7d3913 11 local tree_max_radius = 10
S 12 local tree_max_height = 70
72d541 13
5cf765 14 local S = technic.getter
S 15
7d3913 16 --[[
S 17 Format: [node_name] = dig_cost
18
19 This table is filled automatically afterwards to support mods such as:
20
21     cool_trees
22     ethereal
23     moretrees
24 ]]
25 local tree_nodes = {
26     -- For the sake of maintenance, keep this sorted alphabetically!
27     ["default:acacia_bush_stem"] = -1,
28     ["default:bush_stem"] = -1,
29     ["default:pine_bush_stem"] = -1,
30
31     ["default:cactus"] = -1,
32     ["default:papyrus"] = -1,
33
091bb2 34     -- dfcaves "fruits"
S 35     ["df_trees:blood_thorn_spike"] = -1,
36     ["df_trees:blood_thorn_spike_dead"] = -1,
37     ["df_trees:tunnel_tube_fruiting_body"] = -1,
38
7d3913 39     ["ethereal:bamboo"] = -1,
S 40 }
41
0921c3 42 local tree_nodes_by_cid = {
S 43     -- content ID indexed table, data populated on mod load.
44     -- Format: [node_name] = cost_number
45 }
46
7d3913 47 -- Function to decide whether or not to cut a certain node (and at which energy cost)
S 48 local function populate_costs(name, def)
49     repeat
0921c3 50         if tree_nodes[name] then
S 51             break -- Manually specified node to chop
7d3913 52         end
S 53         if (def.groups.tree or 0) > 0 then
54             break -- Tree node
55         end
56         if (def.groups.leaves or 0) > 0 and chainsaw_leaves then
57             break -- Leaves
58         end
59         if (def.groups.leafdecay_drop or 0) > 0 then
60             break -- Food
61         end
62         return -- Abort function: do not dig this node
63
64     -- luacheck: push ignore 511
65     until 1
66     -- luacheck: pop
67
0921c3 68     -- Add the node cost to the content ID indexed table
7d3913 69     local content_id = minetest.get_content_id(name)
S 70
0921c3 71     -- Make it so that the giant sequoia can be cut with a full charge
S 72     local cost = tree_nodes[name] or 0
7d3913 73     if def.groups.choppy then
0921c3 74         cost = math.max(cost, def.groups.choppy * 14) -- trunks (usually 3 * 14)
7d3913 75     end
0921c3 76     if def.groups.snappy then
S 77         cost = math.max(cost, def.groups.snappy * 2) -- leaves
78     end
79     tree_nodes_by_cid[content_id] = math.max(4, cost)
7d3913 80 end
S 81
82 minetest.register_on_mods_loaded(function()
83     local ndefs = minetest.registered_nodes
84     -- Populate hardcoded nodes
85     for name in pairs(tree_nodes) do
86         local ndef = ndefs[name]
87         if ndef and ndef.groups then
88             populate_costs(name, ndef)
89         end
90     end
91
92     -- Find all trees and leaves
93     for name, def in pairs(ndefs) do
94         if def.groups then
95             populate_costs(name, def)
96         end
97     end
98 end)
99
100
5cf765 101 technic.register_power_tool("technic:chainsaw", chainsaw_max_charge)
S 102
7d3913 103 local pos9dir = {
S 104     { 1, 0,  0},
105     {-1, 0,  0},
106     { 0, 0,  1},
107     { 0, 0, -1},
108     { 1, 0,  1},
109     {-1, 0, -1},
110     { 1, 0, -1},
111     {-1, 0,  1},
112     { 0, 1,  0}, -- up
113 }
114
115 local cutter = {
116     -- See function cut_tree()
117 }
118
dfcf64 119 local safe_cut = minetest.settings:get_bool("technic_safe_chainsaw") ~= false
7d3913 120 local c_air = minetest.get_content_id("air")
S 121 local function dig_recursive(x, y, z)
122     local i = cutter.area:index(x, y, z)
123     if cutter.seen[i] then
124         return
125     end
126     cutter.seen[i] = 1 -- Mark as visited
127
dfcf64 128     if safe_cut and cutter.param2[i] ~= 0 then
7d3913 129         -- Do not dig manually placed nodes
0921c3 130         -- Problem: moretrees' generated jungle trees use param2 = 2
7d3913 131         return
31a052 132     end
D 133
7d3913 134     local c_id = cutter.data[i]
0921c3 135     local cost = tree_nodes_by_cid[c_id]
7d3913 136     if not cost or cost > cutter.charge then
S 137         return -- Cannot dig this node
138     end
31a052 139
7d3913 140     -- Count dug nodes
S 141     cutter.drops[c_id] = (cutter.drops[c_id] or 0) + 1
142     cutter.seen[i] = 2 -- Mark as dug (for callbacks)
143     cutter.data[i] = c_air
144     cutter.charge = cutter.charge - cost
8e03d7 145
7d3913 146     -- Expand maximal bounds for area protection check
S 147     if x < cutter.minp.x then cutter.minp.x = x end
148     if y < cutter.minp.y then cutter.minp.y = y end
149     if z < cutter.minp.z then cutter.minp.z = z end
150     if x > cutter.maxp.x then cutter.maxp.x = x end
151     if y > cutter.maxp.y then cutter.maxp.y = y end
152     if z > cutter.maxp.z then cutter.maxp.z = z end
153
154     -- Traverse neighbors
155     local xn, yn, zn
156     for _, offset in ipairs(pos9dir) do
157         xn, yn, zn = x + offset[1], y + offset[2], z + offset[3]
158         if cutter.area:contains(xn, yn, zn) then
159              dig_recursive(xn, yn, zn)
c63658 160         end
9019eb 161     end
S 162 end
163
7d3913 164 local handle_drops
9019eb 165
7d3913 166 local function chainsaw_dig(player, pos, remaining_charge)
S 167     local minp = {
168         x = pos.x - (tree_max_radius + 1),
169         y = pos.y,
170         z = pos.z - (tree_max_radius + 1)
171     }
172     local maxp = {
173         x = pos.x + (tree_max_radius + 1),
174         y = pos.y + tree_max_height,
175         z = pos.z + (tree_max_radius + 1)
176     }
8e03d7 177
7d3913 178     local vm = minetest.get_voxel_manip()
S 179     local emin, emax = vm:read_from_map(minp, maxp)
9019eb 180
7d3913 181     cutter = {
S 182         area = VoxelArea:new{MinEdge=emin, MaxEdge=emax},
183         data = vm:get_data(),
184         param2 = vm:get_param2_data(),
185         seen = {},
186         drops = {}, -- [content_id] = count
187         minp = vector.copy(pos),
188         maxp = vector.copy(pos),
189         charge = remaining_charge
190     }
191
192     dig_recursive(pos.x, pos.y, pos.z)
193
194     -- Check protection
195     local player_name = player:get_player_name()
196     if minetest.is_area_protected(cutter.minp, cutter.maxp, player_name, 6) then
197         minetest.chat_send_player(player_name, "The chainsaw cannot cut this tree. The cuboid " ..
198             minetest.pos_to_string(cutter.minp) .. ", " .. minetest.pos_to_string(cutter.maxp) ..
199             " contains protected nodes.")
200         minetest.record_protection_violation(pos, player_name)
201         return
9019eb 202     end
S 203
7d3913 204     minetest.sound_play("chainsaw", {
S 205         pos = pos,
206         gain = 1.0,
207         max_hear_distance = 20
208     })
9019eb 209
7d3913 210     handle_drops(pos)
S 211
212     vm:set_data(cutter.data)
213     vm:write_to_map(true)
214     vm:update_map()
215
216     -- Update falling nodes
217     for i, status in pairs(cutter.seen) do
218         if status == 2 then -- actually dug
219             minetest.check_for_falling(cutter.area:position(i))
9019eb 220         end
S 221     end
c63658 222 end
P 223
224 -- Function to randomize positions for new node drops
225 local function get_drop_pos(pos)
226     local drop_pos = {}
227
228     for i = 0, 8 do
229         -- Randomize position for a new drop
230         drop_pos.x = pos.x + math.random(-3, 3)
231         drop_pos.y = pos.y - 1
232         drop_pos.z = pos.z + math.random(-3, 3)
233
234         -- Move the randomized position upwards until
235         -- the node is air or unloaded.
236         for y = drop_pos.y, drop_pos.y + 5 do
237             drop_pos.y = y
238             local node = minetest.get_node_or_nil(drop_pos)
239
240             if not node then
241                 -- If the node is not loaded yet simply drop
242                 -- the item at the original digging position.
243                 return pos
244             elseif node.name == "air" then
9019eb 245                 -- Add variation to the entity drop position,
S 246                 -- but don't let drops get too close to the edge
247                 drop_pos.x = drop_pos.x + (math.random() * 0.8) - 0.5
248                 drop_pos.z = drop_pos.z + (math.random() * 0.8) - 0.5
c63658 249                 return drop_pos
P 250             end
251         end
252     end
253
254     -- Return the original position if this takes too long
255     return pos
e23f87 256 end
19c9a0 257
7d3913 258 local drop_inv = minetest.create_detached_inventory("technic:chainsaw_drops", {}, ":technic")
S 259 handle_drops = function(pos)
260     local n_slots = 100
261     drop_inv:set_size("main", n_slots)
262     drop_inv:set_list("main", {})
5cf765 263
7d3913 264     -- Put all dropped items into the detached inventory
S 265     for c_id, count in pairs(cutter.drops) do
266         local name = minetest.get_name_from_content_id(c_id)
267
268         -- Add drops in bulk -> keep some randomness
269         while count > 0 do
270             local drops = minetest.get_node_drops(name, "")
271             -- higher numbers are faster but return uneven sapling counts
272             local decrement = math.ceil(count * 0.3)
273             decrement = math.min(count, math.max(5, decrement))
274
275             for _, stack in ipairs(drops) do
276                 stack = ItemStack(stack)
277                 local total = math.ceil(stack:get_count() * decrement * chainsaw_efficiency)
278                 local stack_max = stack:get_stack_max()
279
280                 -- Split into full stacks
281                 while total > 0 do
282                     local size = math.min(total, stack_max)
283                     stack:set_count(size)
284                     drop_inv:add_item("main", stack)
285                     total = total - size
286                 end
287             end
288             count = count - decrement
c63658 289         end
7d3913 290     end
S 291
292     -- Drop in random places
293     for i = 1, n_slots do
294         local stack = drop_inv:get_stack("main", i)
295         if stack:is_empty() then
296             break
297         end
9019eb 298         minetest.add_item(get_drop_pos(pos), stack)
S 299     end
300
7d3913 301     drop_inv:set_size("main", 0) -- free RAM
5cf765 302 end
S 303
304
305 minetest.register_tool("technic:chainsaw", {
306     description = S("Chainsaw"),
307     inventory_image = "technic_chainsaw.png",
308     stack_max = 1,
99fd5d 309     wear_represents = "technic_RE_charge",
00d7c9 310     on_refill = technic.refill_RE_charge,
5cf765 311     on_use = function(itemstack, user, pointed_thing)
S 312         if pointed_thing.type ~= "node" then
313             return itemstack
314         end
9019eb 315
a08ba2 316         local meta = technic.get_stack_meta(itemstack)
C 317         local charge = meta:get_int("technic:charge")
5cf765 318
9019eb 319         local name = user:get_player_name()
51c02e 320         if minetest.is_protected(pointed_thing.under, name) then
S 321             minetest.record_protection_violation(pointed_thing.under, name)
322             return
9019eb 323         end
S 324
325         -- Send current charge to digging function so that the
326         -- chainsaw will stop after digging a number of nodes
a08ba2 327         chainsaw_dig(user, pointed_thing.under, charge)
C 328         charge = cutter.charge
7d3913 329
S 330         cutter = {} -- Free RAM
331
b8c902 332         if not technic.creative_mode then
a08ba2 333             meta:set_int("technic:charge", charge)
C 334             technic.set_RE_wear(itemstack, charge, chainsaw_max_charge)
b8c902 335         end
5cf765 336         return itemstack
S 337     end,
338 })
339
e4c34c 340 local mesecons_button = minetest.get_modpath("mesecons_button")
T 341 local trigger = mesecons_button and "mesecons_button:button_off" or "default:mese_crystal_fragment"
342
5cf765 343 minetest.register_craft({
9019eb 344     output = "technic:chainsaw",
c63658 345     recipe = {
e4c34c 346         {"technic:stainless_steel_ingot", trigger,                      "technic:battery"},
44cb8d 347         {"basic_materials:copper_wire",      "basic_materials:motor",              "technic:battery"},
5e4a87 348         {"",                              "",                           "technic:stainless_steel_ingot"},
44cb8d 349     },
VD 350     replacements = { {"basic_materials:copper_wire", "basic_materials:empty_spool"}, },
351
5cf765 352 })
9019eb 353