summaryrefslogtreecommitdiff
path: root/nodes/node_builders.lua
blob: ecb76cd6289574ac9f34bf6b498b72ef1f1ac205 (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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")

-- Note: builders go in group 4 and have both test_build and execute_build methods.

local displace_due_to_help_button = 1.0
if minetest.get_modpath("doc") then
	displace_due_to_help_button = 0.0
end

local builder_formspec =
	"size[8,5.2]" ..
	default.gui_bg ..
	default.gui_bg_img ..
	default.gui_slots ..
	"list[current_name;main;".. tostring(displace_due_to_help_button/2) ..",0;1,1;]" ..
	"label[" .. tostring(displace_due_to_help_button/2).. ",0.8;" .. S("Block to build") .. "]" ..
	"field[" .. tostring(displace_due_to_help_button + 1.3) ..",0.8;1,0.1;extrusion;" .. S("Extrusion") .. ";${extrusion}]" ..
	"tooltip[extrusion;" .. S("Builder will extrude this many blocks in the direction it is facing.\nCan be set from 1 to @1.\nNote that Digtron won't build into unloaded map regions.", digtron.config.maximum_extrusion) .. "]" ..
	"field[" .. tostring(displace_due_to_help_button + 2.3) ..",0.8;1,0.1;period;" .. S("Periodicity") .. ";${period}]" ..
	"tooltip[period;" .. S("Builder will build once every n steps.\nThese steps are globally aligned, so all builders with the\nsame period and offset will build on the same location.") .. "]" ..
	"field[" .. tostring(displace_due_to_help_button + 3.3) ..",0.8;1,0.1;offset;" .. S("Offset") .. ";${offset}]" ..
	"tooltip[offset;" .. S("Offsets the start of periodicity counting by this amount.\nFor example, a builder with period 2 and offset 0 builds\nevery even-numbered block and one with period 2 and\noffset 1 builds every odd-numbered block.") .. "]" ..
	"button_exit[" .. tostring(displace_due_to_help_button + 4.0) ..",0.5;1,0.1;set;" .. S("Save &\nShow") .. "]" ..
	"tooltip[set;" .. S("Saves settings") .. "]" ..
	"field[" .. tostring(displace_due_to_help_button + 5.3) .. ",0.8;1,0.1;build_facing;" .. S("Facing") .. ";${build_facing}]" ..
	"tooltip[build_facing;" .. S("Value from 0-23. Not all block types make use of this.\nUse the 'Read & Save' button to copy the facing of the block\ncurrently in the builder output location.") .. "]" ..
	"button_exit[" .. tostring(displace_due_to_help_button + 6.0) ..",0.5;1,0.1;read;" .. S("Read &\nSave") .. "]" ..
	"tooltip[read;" .. S("Reads the facing of the block currently in the build location,\nthen saves all settings.") .. "]" ..
	"list[current_player;main;0,1.3;8,1;]" ..
	default.get_hotbar_bg(0,1.3) ..
	"list[current_player;main;0,2.5;8,3;8]" ..
	"listring[current_player;main]" ..
	"listring[current_name;main]"

if minetest.get_modpath("doc") then
	builder_formspec = builder_formspec ..
		"button_exit[7.0,0.5;1,0.1;help;" .. S("Help") .. "]" ..
		"tooltip[help;" .. S("Show documentation about this block") .. "]"
end
	
-- Builds objects in the targeted node. This is a complicated beastie.
minetest.register_node("digtron:builder", {
	description = S("Digtron Builder Module"),
	_doc_items_longdesc = digtron.doc.builder_longdesc,
    _doc_items_usagehelp = digtron.doc.builder_usagehelp,
	_digtron_formspec = builder_formspec,
	groups = {cracky = 3,  oddly_breakable_by_hand=3, digtron = 4},
	drop = "digtron:builder",
	sounds = digtron.metal_sounds,
	paramtype = "light",
	paramtype2= "facedir",
	is_ground_content = false,
	tiles = {
		"digtron_plate.png^[transformR90",
		"digtron_plate.png^[transformR270",
		"digtron_plate.png",
		"digtron_plate.png^[transformR180",
		"digtron_plate.png^digtron_builder.png",
		"digtron_plate.png",
	},
	
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {
			{-0.25, 0.3125, 0.3125, 0.25, 0.5, 0.5}, -- FrontFrame_top
			{-0.25, -0.5, 0.3125, 0.25, -0.3125, 0.5}, -- FrontFrame_bottom
			{0.3125, -0.25, 0.3125, 0.5, 0.25, 0.5}, -- FrontFrame_right
			{-0.5, -0.25, 0.3125, -0.3125, 0.25, 0.5}, -- FrontFrame_left
			{-0.5, 0.25, -0.5, -0.25, 0.5, 0.5}, -- edge_topright
			{-0.5, -0.5, -0.5, -0.25, -0.25, 0.5}, -- edge_bottomright
			{0.25, 0.25, -0.5, 0.5, 0.5, 0.5}, -- edge_topleft
			{0.25, -0.5, -0.5, 0.5, -0.25, 0.5}, -- edge_bottomleft
			{-0.25, 0.4375, -0.5, 0.25, 0.5, -0.4375}, -- backframe_top
			{-0.25, -0.5, -0.5, 0.25, -0.4375, -0.4375}, -- backframe_bottom
			{-0.5, -0.25, -0.5, -0.4375, 0.25, -0.4375}, -- backframe_left
			{0.4375, -0.25, -0.5, 0.5, 0.25, -0.4375}, -- Backframe_right
			{-0.0625, -0.3125, 0.3125, 0.0625, 0.3125, 0.375}, -- frontcross_vertical
			{-0.3125, -0.0625, 0.3125, 0.3125, 0.0625, 0.375}, -- frontcross_horizontal
		}
	},
	
	on_construct = function(pos)
        local meta = minetest.get_meta(pos)
        meta:set_string("formspec", builder_formspec)
		meta:set_int("period", 1) 
		meta:set_int("offset", 0) 
		meta:set_int("build_facing", 0)
		meta:set_int("extrusion", 1)
				
		local inv = meta:get_inventory()
		inv:set_size("main", 1)
    end,
	
	on_receive_fields = function(pos, formname, fields, sender)
        local meta = minetest.get_meta(pos)
		local period = tonumber(fields.period)
		local offset = tonumber(fields.offset)
		local build_facing = tonumber(fields.build_facing)
		local extrusion = tonumber(fields.extrusion)
		
		if period and period > 0 then
			meta:set_int("period", math.floor(tonumber(fields.period)))
		else
			period = meta:get_int("period")
		end
		if offset then
			meta:set_int("offset", math.floor(tonumber(fields.offset)))
		else
			offset = meta:get_int("offset")
		end
		if build_facing and build_facing >= 0 and build_facing < 24 then
			-- TODO: wallmounted facings only run from 0-5, a player could theoretically put a wallmounted item into the builder and then manually set the build facing to an invalid number
			-- Should prevent that somehow. But not tonight.
			meta:set_int("build_facing", math.floor(build_facing))
		end
		if extrusion and extrusion > 0 and extrusion <= digtron.config.maximum_extrusion then
			meta:set_int("extrusion", math.floor(tonumber(fields.extrusion)))
		else
			extrusion = meta:get_int("extrusion")
		end
		
		if fields.set then
			digtron.show_offset_markers(pos, offset, period)

		elseif fields.read then
			local facing = minetest.get_node(pos).param2
			local buildpos = digtron.find_new_pos(pos, facing)
			local target_node = minetest.get_node(buildpos)
			if target_node.name ~= "air" and minetest.get_item_group(target_node.name, "digtron") == 0 then
				local meta = minetest.get_meta(pos)
				local inv = meta:get_inventory()
				local target_name = digtron.builder_read_item_substitutions[target_node.name] or target_node.name
				inv:set_stack("main", 1, target_name)
				meta:set_int("build_facing", target_node.param2)
			end
		end
		
		if fields.help and minetest.get_modpath("doc") then --check for mod in case someone disabled it after this digger was built
			minetest.after(0.5, doc.show_entry, sender:get_player_name(), "nodes", "digtron:builder", true)
		end

		digtron.update_builder_item(pos)
	end,
	
	on_destruct = function(pos)
		digtron.remove_builder_item(pos)
	end,
	
	after_place_node = function(pos)
		digtron.update_builder_item(pos)
	end,

	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
		if minetest.get_item_group(stack:get_name(), "digtron") ~= 0 then
			return 0 -- don't allow builders to be set to build Digtron nodes, they'll just clog the output.
		end	
		local inv = minetest.get_inventory({type="node", pos=pos})
		inv:set_stack(listname, index, stack:take_item(1))
		return 0
	end,
	
	allow_metadata_inventory_take = function(pos, listname, index, stack, player)
		local inv = minetest.get_inventory({type="node", pos=pos})
		inv:set_stack(listname, index, ItemStack(""))
		return 0
	end,
	
	-- "builder at pos, imagine that you're in test_pos. If you're willing and able to build from there, take the item you need from inventory.
	-- return the item you took and the inventory location you took it from so it can be put back after all the other builders have been tested.
	-- If you couldn't get the item from inventory, return an error code so we can abort the cycle.
	-- If you're not supposed to build at all, or the location is obstructed, return 0 to let us know you're okay and we shouldn't abort."
	
	--return code and accompanying value:
	-- 0, {}								-- not supposed to build, no error
	-- 1, {{itemstack, source inventory pos}, ...} -- can build, took items from inventory
	-- 2, {{itemstack, source inventory pos}, ...}, itemstack	-- was supposed to build, but couldn't get the item from inventory
	-- 3, {}								-- builder configuration error
	test_build = function(pos, test_pos, inventory_positions, protected_nodes, nodes_dug, controlling_coordinate, controller_pos)
		local meta = minetest.get_meta(pos)
		local facing = minetest.get_node(pos).param2
		local buildpos = digtron.find_new_pos(test_pos, facing)
		
		if (buildpos[controlling_coordinate] + meta:get_int("offset")) % meta:get_int("period") ~= 0 then
			--It's not the builder's turn to build right now.
			return 0, {}
		end
		
		local extrusion_count = 0
		local extrusion_target = meta:get_int("extrusion")
		if extrusion_target == nil or extrusion_target < 1 or extrusion_target > 100 then
			extrusion_target = 1 -- failsafe
		end
		
		local return_items = {}
		
		local inv = minetest.get_inventory({type="node", pos=pos})
		local item_stack = inv:get_stack("main", 1)

		if item_stack:is_empty() then
			return 3, {} -- error code for "this builder's item slot is unset"
		end
		
		while extrusion_count < extrusion_target do
			if not digtron.can_move_to(buildpos, protected_nodes, nodes_dug) then
				--using "can_move_to" instead of "can_build_to" test case in case the builder is pointed "backward", and will thus
				--be building into the space that it's currently in and will be vacating after moving, or in case the builder is aimed
				--sideways and a fellow digtron node was ahead of it (will also be moving out of the way).
				
				--If the player has built his digtron stupid (eg has another digtron node in the place the builder wants to build) this
				--assumption is wrong, but I can't hold the player's hand through *every* possible bad design decision. Worst case,
				--the digtron will think its inventory can't handle the next build step and abort the build when it actually could have
				--managed one more cycle. That's not a bad outcome for a digtron array that was built stupidly to begin with.
				return 1, return_items
			end
			
			local source_location = digtron.take_from_inventory(item_stack:get_name(), inventory_positions)
			if source_location ~= nil then
				table.insert(return_items, {item=item_stack, location=source_location})
			else
				return 2, return_items, item_stack -- error code for "needed an item but couldn't get it from inventory"
			end
			extrusion_count = extrusion_count + 1
			buildpos = digtron.find_new_pos(buildpos, facing)
		end
		
		return 1, return_items
	end,
	
	execute_build = function(pos, player, inventory_positions, protected_nodes, nodes_dug, controlling_coordinate, controller_pos)
		local meta = minetest.get_meta(pos)
		local build_facing = tonumber(meta:get_int("build_facing"))
		local facing = minetest.get_node(pos).param2
		local buildpos = digtron.find_new_pos(pos, facing)
		
		if (buildpos[controlling_coordinate] + meta:get_int("offset")) % meta:get_int("period") ~= 0 then
			return 0
		end
		
		local extrusion_count = 0
		local extrusion_target = meta:get_int("extrusion")
		if extrusion_target == nil or extrusion_target < 1 or extrusion_target > 100 then
			extrusion_target = 1 -- failsafe
		end
		local built_count = 0
		
		local inv = minetest.get_inventory({type="node", pos=pos})
		local item_stack = inv:get_stack("main", 1)
		if item_stack:is_empty() then
			return built_count
		end
		
		while extrusion_count < extrusion_target do
			if not digtron.can_build_to(buildpos, protected_nodes, nodes_dug) then
				return built_count
			end

			local oldnode = minetest.get_node(buildpos)

			if not digtron.config.uses_resources then
				local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, build_facing)
				if success == true then
					minetest.log("action", string.format("%s uses Digtron to build %s at (%d, %d, %d), displacing %s", player:get_player_name(), item_stack:get_name(), buildpos.x, buildpos.y, buildpos.z, oldnode.name))
					nodes_dug:set(buildpos.x, buildpos.y, buildpos.z, false)
					built_count = built_count + 1
				else
					return built_count
				end
			end
		
			local sourcepos = digtron.take_from_inventory(item_stack:get_name(), inventory_positions)
			if sourcepos == nil then
				-- item not in inventory! Need to sound the angry buzzer to let the player know, so return a negative number.
				return (built_count + 1) * -1
			end
			local returned_stack, success = digtron.item_place_node(item_stack, player, buildpos, build_facing)
			if success == true then
				minetest.log("action", string.format("%s uses Digtron to build %s at (%d, %d, %d), displacing %s", player:get_player_name(), item_stack:get_name(), buildpos.x, buildpos.y, buildpos.z, oldnode.name))
				--flag this node as *not* to be dug.
				nodes_dug:set(buildpos.x, buildpos.y, buildpos.z, false)
				digtron.award_item_built(item_stack:get_name(), player:get_player_name())
				built_count = built_count + 1
			else
				--failed to build, target node probably obstructed. Put the item back in inventory.
				--Should probably never reach this since we're guarding against can_build_to, above, but this makes things safe if we somehow do.
				digtron.place_in_specific_inventory(item_stack, sourcepos, inventory_positions, controller_pos)
				return built_count
			end

			extrusion_count = extrusion_count + 1
			buildpos = digtron.find_new_pos(buildpos, facing)
		end
		return built_count
	end,
})