-
Notifications
You must be signed in to change notification settings - Fork 29
/
util.lua
454 lines (407 loc) · 16.5 KB
/
util.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
-- A random assortment of methods used in various places in this mod.
dofile( minetest.get_modpath( "digtron" ) .. "/util_item_place_node.lua" ) -- separated out to avoid potential for license complexity
dofile( minetest.get_modpath( "digtron" ) .. "/util_execute_cycle.lua" ) -- separated out simply for tidiness, there's some big code in there
local node_inventory_table = {type="node"} -- a reusable parameter for get_inventory calls, set the pos parameter before using.
-- Apparently node_sound_metal_defaults is a newer thing, I ran into games using an older version of the default mod without it.
if default.node_sound_metal_defaults ~= nil then
digtron.metal_sounds = default.node_sound_metal_defaults()
else
digtron.metal_sounds = default.node_sound_stone_defaults()
end
digtron.find_new_pos = function(pos, facing)
-- finds the point one node "forward", based on facing
local dir = minetest.facedir_to_dir(facing)
return vector.add(pos, dir)
end
local facedir_to_down_dir_table = {
[0]={x=0, y=-1, z=0},
{x=0, y=0, z=-1},
{x=0, y=0, z=1},
{x=-1, y=0, z=0},
{x=1, y=0, z=0},
{x=0, y=1, z=0}
}
digtron.facedir_to_down_dir = function(facing)
return facedir_to_down_dir_table[math.floor(facing/4)]
end
digtron.find_new_pos_downward = function(pos, facing)
return vector.add(pos, digtron.facedir_to_down_dir(facing))
end
digtron.mark_diggable = function(pos, nodes_dug, player)
-- mark the node as dug, if the player provided would have been able to dig it.
-- Don't *actually* dig the node yet, though, because if we dig a node with sand over it the sand will start falling
-- and then destroy whatever node we place there subsequently (either by a builder head or by moving a digtron node)
-- I don't like sand. It's coarse and rough and irritating and it gets everywhere. And it necessitates complicated dig routines.
-- returns fuel cost and what will be dropped by digging these nodes.
local target = minetest.get_node(pos)
-- prevent digtrons from being marked for digging.
if minetest.get_item_group(target.name, "digtron") ~= 0 or
minetest.get_item_group(target.name, "digtron_protected") ~= 0 or
minetest.get_item_group(target.name, "immortal") ~= 0 then
return 0
end
local targetdef = minetest.registered_nodes[target.name]
if targetdef == nil or targetdef.can_dig == nil or targetdef.can_dig(pos, player) then
nodes_dug:set(pos.x, pos.y, pos.z, true)
if target.name ~= "air" then
local in_known_group = false
local material_cost = 0
if digtron.config.uses_resources then
if minetest.get_item_group(target.name, "cracky") ~= 0 then
in_known_group = true
material_cost = math.max(material_cost, digtron.config.dig_cost_cracky)
end
if minetest.get_item_group(target.name, "crumbly") ~= 0 then
in_known_group = true
material_cost = math.max(material_cost, digtron.config.dig_cost_crumbly)
end
if minetest.get_item_group(target.name, "choppy") ~= 0 then
in_known_group = true
material_cost = math.max(material_cost, digtron.config.dig_cost_choppy)
end
if not in_known_group then
material_cost = digtron.config.dig_cost_default
end
end
return material_cost, minetest.get_node_drops(target.name, "")
end
end
return 0
end
digtron.can_build_to = function(pos, protected_nodes, dug_nodes)
-- Returns whether a space is clear to have something put into it
if protected_nodes:get(pos.x, pos.y, pos.z) then
return false
end
-- tests if the location pointed to is clear to move something into
local target = minetest.get_node(pos)
if target.name == "air" or
dug_nodes:get(pos.x, pos.y, pos.z) == true or
minetest.registered_nodes[target.name].buildable_to == true
then
return true
end
return false
end
digtron.can_move_to = function(pos, protected_nodes, dug_nodes)
-- Same as can_build_to, but also checks if the current node is part of the digtron.
-- this allows us to disregard obstructions that *will* move out of the way.
if digtron.can_build_to(pos, protected_nodes, dug_nodes) == true or
minetest.get_item_group(minetest.get_node(pos).name, "digtron") ~= 0 then
return true
end
return false
end
digtron.place_in_inventory = function(itemname, inventory_positions, fallback_pos)
--tries placing the item in each inventory node in turn. If there's no room, drop it at fallback_pos
local itemstack = ItemStack(itemname)
if inventory_positions ~= nil then
for _, location in pairs(inventory_positions) do
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory(node_inventory_table)
itemstack = inv:add_item("main", itemstack)
if itemstack:is_empty() then
return nil
end
end
end
minetest.add_item(fallback_pos, itemstack)
end
digtron.place_in_specific_inventory = function(itemname, pos, inventory_positions, fallback_pos)
--tries placing the item in a specific inventory. Other parameters are used as fallbacks on failure
--Use this method for putting stuff back after testing and failed builds so that if the player
--is trying to keep various inventories organized manually stuff will go back where it came from,
--probably.
local itemstack = ItemStack(itemname)
node_inventory_table.pos = pos
local inv = minetest.get_inventory(node_inventory_table)
local returned_stack = inv:add_item("main", itemstack)
if not returned_stack:is_empty() then
-- we weren't able to put the item back into that particular inventory for some reason.
-- try putting it *anywhere.*
digtron.place_in_inventory(returned_stack, inventory_positions, fallback_pos)
end
end
digtron.take_from_inventory = function(itemname, inventory_positions)
if inventory_positions == nil then return nil end
--tries to take an item from each inventory node in turn. Returns location of inventory item was taken from on success, nil on failure
local itemstack = ItemStack(itemname)
for _, location in pairs(inventory_positions) do
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory(node_inventory_table)
local output = inv:remove_item("main", itemstack)
if not output:is_empty() then
return location.pos
end
end
return nil
end
-- Used to determine which coordinate is being checked for periodicity. eg, if the digtron is moving in the z direction, then periodicity is checked for every n nodes in the z axis
digtron.get_controlling_coordinate = function(_, facedir)
-- used for determining builder period and offset
local dir = digtron.facedir_to_dir_map[facedir]
if dir == 1 or dir == 3 then
return "z"
elseif dir == 2 or dir == 4 then
return "x"
else
return "y"
end
end
local fuel_craft = {method="fuel", width=1, items={}} -- reusable crafting recipe table for get_craft_result calls below
-- Searches fuel store inventories for burnable items and burns them until target is reached or surpassed
-- (or there's nothing left to burn). Returns the total fuel value burned
-- if the "test" parameter is set to true, doesn't actually take anything out of inventories.
-- We can get away with this sort of thing for fuel but not for builder inventory because there's just one
-- controller node burning stuff, not multiple build heads drawing from inventories in turn. Much simpler.
digtron.burn = function(fuelstore_positions, target, test)
if fuelstore_positions == nil then
return 0
end
local current_burned = 0
for _, location in pairs(fuelstore_positions) do
if current_burned > target then
break
end
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory(node_inventory_table)
local invlist = inv:get_list("fuel")
if invlist == nil then -- This check shouldn't be needed, it's yet another guard against https://github.com/minetest/minetest/issues/8067
break
end
for _, itemstack in pairs(invlist) do
fuel_craft.items[1] = itemstack:peek_item(1)
local fuel_per_item = minetest.get_craft_result(fuel_craft).time
if fuel_per_item ~= 0 then
local actual_burned = math.min(
math.ceil((target - current_burned)/fuel_per_item), -- burn this many, if we can.
itemstack:get_count() -- how many we have at most.
)
if test ~= true then
-- don't bother recording the items if we're just testing, nothing is actually being removed.
itemstack:set_count(itemstack:get_count() - actual_burned)
end
current_burned = current_burned + actual_burned * fuel_per_item
end
if current_burned > target then
break
end
end
if test ~= true then
-- only update the list if we're doing this for real.
inv:set_list("fuel", invlist)
end
end
return current_burned
end
-- Consume energy from the batteries
-- The same as burning coal, except that instead of destroying the items in the inventory, we merely drain
-- the charge in them, leaving them empty. The charge is converted into "coal heat units" by a downscaling
-- factor, since if taken at face value (10000 EU), the batteries would be the ultimate power source barely
-- ever needing replacement.
digtron.tap_batteries = function(battery_positions, target, test)
if (battery_positions == nil) then
return 0
end
local current_burned = 0
-- 1 coal block is 370 PU
-- 1 coal lump is 40 PU
-- An RE battery holds 10000 EU of charge
-- local power_ratio = 100 -- How much charge equals 1 unit of PU from coal
-- setting Moved to digtron.config.power_ratio
for _, location in pairs(battery_positions) do
if current_burned > target then
break
end
node_inventory_table.pos = location.pos
local inv = minetest.get_inventory(node_inventory_table)
local invlist = inv:get_list("batteries")
if (invlist == nil) then -- This check shouldn't be needed, it's yet another guard against https://github.com/minetest/minetest/issues/8067
break
end
for _, itemstack in pairs(invlist) do
local meta = minetest.deserialize(itemstack:get_metadata())
if (meta ~= nil) then
local power_available = math.floor(meta.charge / digtron.config.power_ratio)
if power_available ~= 0 then
local actual_burned = power_available -- we just take all we have from the battery, since they aren't stackable
-- don't bother recording the items if we're just testing, nothing is actually being removed.
if test ~= true then
-- since we are taking everything, the wear and charge can both be set to 0
itemstack:set_wear(0)
meta.charge = 0
itemstack:set_metadata(minetest.serialize(meta))
end
current_burned = current_burned + actual_burned
end
end
if current_burned > target then
break
end
end
if test ~= true then
-- only update the list if we're doing this for real.
inv:set_list("batteries", invlist)
end
end
return current_burned
end
digtron.remove_builder_item = function(pos)
local objects = minetest.get_objects_inside_radius(pos, 0.5)
if objects ~= nil then
for _, obj in ipairs(objects) do
if obj and obj:get_luaentity() and obj:get_luaentity().name == "digtron:builder_item" then
obj:remove()
end
end
end
end
digtron.update_builder_item = function(pos)
digtron.remove_builder_item(pos)
node_inventory_table.pos = pos
local inv = minetest.get_inventory(node_inventory_table)
if inv == nil or inv:get_size("main") < 1 then return end
local item_stack = inv:get_stack("main", 1)
if not item_stack:is_empty() then
digtron.create_builder_item = item_stack:get_name()
minetest.add_entity(pos,"digtron:builder_item")
end
end
local damage_def = {
full_punch_interval = 1.0,
damage_groups = {},
}
digtron.damage_creatures = function(player, source_pos, target_pos, amount, items_dropped)
if type(player) ~= 'userdata' then
return
end
local objects = minetest.get_objects_inside_radius(target_pos, 1.0)
if objects ~= nil then
damage_def.damage_groups.fleshy = amount
local velocity = {
x = target_pos.x-source_pos.x,
y = target_pos.y-source_pos.y + 0.2,
z = target_pos.z-source_pos.z,
}
for _, obj in ipairs(objects) do
if obj:is_player() then
-- Digtron moving logic handles owner movement
if obj:get_player_name() ~= player:get_player_name() then
-- See issue #2960 for status of a "set player velocity" method
-- instead, knock the player back
local newpos = {
x = target_pos.x + velocity.x,
y = target_pos.y + velocity.y,
z = target_pos.z + velocity.z,
}
obj:set_pos(newpos)
obj:punch(player, 1.0, damage_def, nil)
end
else
local lua_entity = obj:get_luaentity()
if lua_entity ~= nil then
if lua_entity.name == "__builtin:item" then
table.insert(items_dropped, lua_entity.itemstring)
lua_entity.itemstring = ""
obj:remove()
elseif lua_entity.name == "__builtin:falling_node" then
-- Eat all falling nodes in front of the digtron
-- to avoid them eating away our digger heads
local drops = minetest.get_node_drops(lua_entity.node, "")
for _, item in pairs(drops) do
table.insert(items_dropped, item)
end
obj:remove()
else
if obj.add_velocity ~= nil then
obj:add_velocity(velocity)
else
local vel = obj:get_velocity()
obj:set_velocity(vector.add(vel, velocity))
end
obj:punch(player, 1.0, damage_def, nil)
end
end
end
end
end
-- If we killed any mobs they might have dropped some stuff, vacuum that up now too.
objects = minetest.get_objects_inside_radius(target_pos, 1.0)
if objects ~= nil then
for _, obj in ipairs(objects) do
if not obj:is_player() then
local lua_entity = obj:get_luaentity()
if lua_entity ~= nil and lua_entity.name == "__builtin:item" then
table.insert(items_dropped, lua_entity.itemstring)
lua_entity.itemstring = ""
obj:remove()
end
end
end
end
end
digtron.is_soft_material = function(target)
local target_node = minetest.get_node(target)
if minetest.get_item_group(target_node.name, "crumbly") ~= 0 or
minetest.get_item_group(target_node.name, "choppy") ~= 0 or
minetest.get_item_group(target_node.name, "snappy") ~= 0 or
minetest.get_item_group(target_node.name, "oddly_breakable_by_hand") ~= 0 or
minetest.get_item_group(target_node.name, "fleshy") ~= 0 then
return true
end
return false
end
-- If someone sets very large offsets or intervals for the offset markers they might be added too far
-- away. safe_add_entity causes these attempts to be ignored rather than crashing the game.
-- returns the entity if successful, nil otherwise
local function safe_add_entity(pos, name)
local success, ret = pcall(minetest.add_entity, pos, name)
if success then return ret else return nil end
end
digtron.show_offset_markers = function(pos, offset, period)
local buildpos = digtron.find_new_pos(pos, minetest.get_node(pos).param2)
local x_pos = math.floor((buildpos.x+offset)/period)*period - (offset or 0)
safe_add_entity({x=x_pos, y=buildpos.y, z=buildpos.z}, "digtron:marker")
if x_pos >= buildpos.x then
safe_add_entity({x=x_pos - period, y=buildpos.y, z=buildpos.z}, "digtron:marker")
end
if x_pos <= buildpos.x then
safe_add_entity({x=x_pos + period, y=buildpos.y, z=buildpos.z}, "digtron:marker")
end
local y_pos = math.floor((buildpos.y+offset)/period)*period - offset
safe_add_entity({x=buildpos.x, y=y_pos, z=buildpos.z}, "digtron:marker_vertical")
if y_pos >= buildpos.y then
safe_add_entity({x=buildpos.x, y=y_pos - period, z=buildpos.z}, "digtron:marker_vertical")
end
if y_pos <= buildpos.y then
safe_add_entity({x=buildpos.x, y=y_pos + period, z=buildpos.z}, "digtron:marker_vertical")
end
local z_pos = math.floor((buildpos.z+offset)/period)*period - offset
local entity = safe_add_entity({x=buildpos.x, y=buildpos.y, z=z_pos}, "digtron:marker")
if entity ~= nil then entity:set_yaw(1.5708) end
if z_pos >= buildpos.z then
entity = safe_add_entity({x=buildpos.x, y=buildpos.y, z=z_pos - period}, "digtron:marker")
if entity ~= nil then entity:set_yaw(1.5708) end
end
if z_pos <= buildpos.z then
entity = safe_add_entity({x=buildpos.x, y=buildpos.y, z=z_pos + period}, "digtron:marker")
if entity ~= nil then entity:set_yaw(1.5708) end
end
end
digtron.check_protected_and_record = function(pos, player)
local name = player:get_player_name()
if minetest.is_protected(pos, name) then
minetest.record_protection_violation(pos, name)
return true
end
return false
end
digtron.protected_allow_metadata_inventory_put = function(pos, _, _, stack, player)
return digtron.check_protected_and_record(pos, player) and 0 or stack:get_count()
end
digtron.protected_allow_metadata_inventory_move = function(pos, _, _, _, _, count, player)
return digtron.check_protected_and_record(pos, player) and 0 or count
end
digtron.protected_allow_metadata_inventory_take = function(pos, _, _, stack, player)
return digtron.check_protected_and_record(pos, player) and 0 or stack:get_count()
end