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