summaryrefslogtreecommitdiff
path: root/util.lua
blob: 63903b10f66eb4935c8cc524e42242e6de292fe6 (plain)
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
-- 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

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

digtron.facedir_to_down_dir = function(facing)
	return (
		{[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}})[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)
	-- 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 then
		return 0, {}
	end

	local targetdef = minetest.registered_nodes[target.name]
	if 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)
	for k, location in pairs(inventory_positions) do
		local inv = minetest.get_inventory({type="node", pos=location.pos})
		itemstack = inv:add_item("main", itemstack)
		if itemstack:is_empty() then
			return nil
		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)
	local inv = minetest.get_inventory({type="node", pos=pos})
	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)
	--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 k, location in pairs(inventory_positions) do
		local inv = minetest.get_inventory({type="node", pos=location.pos})
		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(pos, 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

-- 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)
	local current_burned = 0
	for k, location in pairs(fuelstore_positions) do
		if current_burned > target then
			break
		end
		local inv = minetest.get_inventory({type="node", pos=location.pos})
		local invlist = inv:get_list("fuel")
		for i, itemstack in pairs(invlist) do
			local fuel_per_item = minetest.get_craft_result({method="fuel", width=1, items={itemstack:peek_item(1)}}).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

digtron.remove_builder_item = function(pos)
	local objects = minetest.env: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)
	local inv = minetest.get_inventory({type="node", pos=pos})
	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

digtron.damage_creatures = function(player, pos, amount)
	local objects = minetest.env:get_objects_inside_radius(pos, 1.0)
	if objects ~= nil then
		for _, obj in ipairs(objects) do
			if obj then
				obj:punch(player, 1.0, {
					full_punch_interval = 1.0,
					damage_groups = {fleshy = amount},
					}, nil )
			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

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
	minetest.add_entity({x=x_pos, y=buildpos.y, z=buildpos.z}, "digtron:marker")
	if x_pos >= buildpos.x then
		minetest.add_entity({x=x_pos - period, y=buildpos.y, z=buildpos.z}, "digtron:marker")
	end
	if x_pos <= buildpos.x then
		minetest.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
	minetest.add_entity({x=buildpos.x, y=y_pos, z=buildpos.z}, "digtron:marker_vertical")
	if y_pos >= buildpos.y then
		minetest.add_entity({x=buildpos.x, y=y_pos - period, z=buildpos.z}, "digtron:marker_vertical")
	end
	if y_pos <= buildpos.y then
		minetest.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
	minetest.add_entity({x=buildpos.x, y=buildpos.y, z=z_pos}, "digtron:marker"):setyaw(1.5708)
	if z_pos >= buildpos.z then
		minetest.add_entity({x=buildpos.x, y=buildpos.y, z=z_pos - period}, "digtron:marker"):setyaw(1.5708)
	end
	if z_pos <= buildpos.z then
		minetest.add_entity({x=buildpos.x, y=buildpos.y, z=z_pos + period}, "digtron:marker"):setyaw(1.5708)
	end
end