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 |
|