init.lua 41.8 KB
Newer Older
1

2 3 4 5
--[[

  mech - mechanisms for Inside The Box

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License
  as published by the Free Software Foundation; either version 2.1
  of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA 02111-1307 USA

23 24
]]--

Wuzzy's avatar
Wuzzy committed
25 26
local S = minetest.get_translator("mech")

Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
27 28
mech = {}

Yukita Mayako's avatar
Yukita Mayako committed
29 30
local rng = math.random

Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
-- Logic

-- Hashes a vector as a 6-byte string
local function hash_vector(v)
	local x = v.x + 32768
	local y = v.y + 32768
	local z = v.z + 32768
	return string.char(math.floor(x / 256)) .. string.char(x % 256) ..
		string.char(math.floor(y / 256)) .. string.char(y % 256) ..
		string.char(math.floor(z / 256)) .. string.char(z % 256)
end

local function dehash_vector(s)
	return {
		x = 256 * string.byte(s, 1) + string.byte(s, 2) - 32768,
		y = 256 * string.byte(s, 3) + string.byte(s, 4) - 32768,
		z = 256 * string.byte(s, 5) + string.byte(s, 6) - 32768,
	}
end

51
local defer_tbl = {}
Auke Kok's avatar
Auke Kok committed
52 53
local rate_tbl = {}
local rate_time = 1.0
54 55 56 57

minetest.register_globalstep(function(dtime)
	local t = table.copy(defer_tbl)
	defer_tbl = {}
Auke Kok's avatar
Auke Kok committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
	for _, item in pairs(t) do
		item.func(item.pos)
	end

	-- prune rate_tbl occasionally
	rate_time = rate_time - dtime
	if rate_time > 0 then
		return
	end
	rate_time = 1.0

	-- simple decay prune
	local count = 0
	for k, v in pairs(rate_tbl) do
		count = count + 1
		if v > 1 then
			rate_tbl[k] = math.floor(v / 2)
		else
			rate_tbl[k] = nil
		end
78 79 80 81 82
	end
end)

local function defer(pos, func)
	local p = minetest.pos_to_string(pos)
Auke Kok's avatar
Auke Kok committed
83 84 85 86 87

	local r = rate_tbl[p] or 1
	rate_tbl[p] = r + 1
	if r > 15 then
		return
88
	end
Auke Kok's avatar
Auke Kok committed
89 90

	defer_tbl[#defer_tbl + 1] = {pos = pos, func = func}
91 92
end

Yukita Mayako's avatar
Yukita Mayako committed
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
function mech.send_trigger_to(pos)
	local node = minetest.get_node(pos)
	if node and minetest.registered_nodes[node.name] and
			minetest.registered_nodes[node.name].on_trigger then
		defer(pos, minetest.registered_nodes[node.name].on_trigger)
	elseif node and node.name ~= "air" and node.name ~= "nodes:placeholder" then
		-- mech breaking
		local def = minetest.registered_nodes[node.name]
		local sounds = def.sounds or {}
		if sounds.dug then
			minetest.sound_play(sounds.dug, {pos = pos})
		end
		minetest.remove_node(pos)
		minetest.check_for_falling(pos)

		-- throw some particles around
		if not def.tiles then
			return
		end
		local texture = def.tiles and def.tiles[1] and def.tiles[1].name or
		      def.tiles[1] or def.tiles or "dirt.png"
		if type(texture) ~= "string" then
			return
		end
		minetest.add_particlespawner({
			amount = 16,
			time = 0.05,
			minpos = vector.add(pos, -0.5),
			maxpos = vector.add(pos, 0.5),
			minvel = {x = -0.4, y = -0.4, z = -0.4},
			maxvel = {x = -0.4, y = -0.4, z = -0.4},
			minacc = {x = 0, y = -10, z = 0},
			maxacc = {x = 0, y = -10, z = 0},
			minexptime = 0.3,
			maxexptime = 0.7,
			minsize = 1.0,
			maxsize = 2.4,
			collisiondetection = true,
			texture = texture .. "^[sheet:4x4:" .. rng(4) .. "," .. rng(4)
		})
	end
end

function mech.send_untrigger_to(pos)
	local node = minetest.get_node(pos)
	if node and minetest.registered_nodes[node.name] and
			minetest.registered_nodes[node.name].on_untrigger then
		defer(pos, minetest.registered_nodes[node.name].on_untrigger)
	end
end

Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
144 145
function mech.trigger(pos)
	local meta = minetest.get_meta(pos)
146
	local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
147
	for v, _ in pairs(offsets) do
Yukita Mayako's avatar
Yukita Mayako committed
148
		mech.send_trigger_to(vector.add(pos, dehash_vector(v)))
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
149 150 151 152 153
	end
end

function mech.untrigger(pos)
	local meta = minetest.get_meta(pos)
154
	local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
155
	for v, _ in pairs(offsets) do
Yukita Mayako's avatar
Yukita Mayako committed
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
		mech.send_untrigger_to(vector.add(pos, dehash_vector(v)))
	end
end

-- Build the connections list as an array rather than a hash table
-- Saves it to triggers, also checks for and respects untrigger hash table.
-- (used by Randomizer)
local function update_trigger_list(pos)
	local meta = minetest.get_meta(pos)
	local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
	local untrigger = minetest.deserialize(meta:get_string("untrigger")) or {}
	local triggers = {}
	for v in pairs(offsets) do
		if not untrigger[v] then
			table.insert(triggers, v)
		end
	end
	meta:set_string("triggers", minetest.serialize(triggers))
	meta:mark_as_private("triggers")

	-- Check for dirty untrigger list, just in case
	-- (we don't want to untrigger things we aren't connected to anymore)
	local dirty = false
	for v in pairs(untrigger) do
		if not offsets[v] then
			untrigger[v] = nil
			dirty = true
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
183 184
		end
	end
Yukita Mayako's avatar
Yukita Mayako committed
185 186 187 188 189 190
	if dirty then
		meta:set_string("untrigger", minetest.serialize(untrigger))
		meta:mark_as_private("untrigger")
	end

	return triggers
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
191 192 193 194 195 196 197 198 199 200
end

function mech.link(pos1, pos2)
	local meta1 = minetest.get_meta(pos1)
	local off1 = minetest.deserialize(meta1:get_string("offsets")) or {}
	off1[hash_vector(vector.subtract(pos2, pos1))] = true

	local meta2 = minetest.get_meta(pos2)
	local off2 = minetest.deserialize(meta2:get_string("roffsets")) or {}
	off2[hash_vector(vector.subtract(pos1, pos2))] = true
201 202 203 204 205 206 207 208 209 210 211 212 213 214

	local function c(t)
		local n = 0
		for _, _ in pairs(t) do
			n = n + 1
		end
		return n
	end

	if c(off1) > 64 or c(off2) > 64 then
		return false
	end

	meta1:set_string("offsets", minetest.serialize(off1))
Auke Kok's avatar
Auke Kok committed
215
	meta1:mark_as_private("offsets")
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
216
	meta2:set_string("roffsets", minetest.serialize(off2))
Auke Kok's avatar
Auke Kok committed
217
	meta1:mark_as_private("roffsets")
218

Yukita Mayako's avatar
Yukita Mayako committed
219 220 221 222
	if meta1:get_string("triggers") ~= "" then
		update_trigger_list(pos1)
	end

223
	return true
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
224 225 226 227 228 229 230 231 232 233
end

local function unlink(pos, meta)
	local offsets = minetest.deserialize(meta.fields.offsets or "") or {}
	for v, _ in pairs(offsets) do
		local np = vector.add(pos, dehash_vector(v))
		local meta2 = minetest.get_meta(np)
		local roff = minetest.deserialize(meta2:get_string("roffsets")) or {}
		roff[hash_vector(vector.subtract(pos, np))] = nil
		meta2:set_string("roffsets", minetest.serialize(roff))
Auke Kok's avatar
Auke Kok committed
234
		meta2:mark_as_private("roffsets")
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
235 236 237 238 239 240 241 242
	end
	local roffsets = minetest.deserialize(meta.fields.roffsets or "") or {}
	for v, _ in pairs(roffsets) do
		local np = vector.add(pos, dehash_vector(v))
		local meta2 = minetest.get_meta(np)
		local off = minetest.deserialize(meta2:get_string("offsets")) or {}
		off[hash_vector(vector.subtract(pos, np))] = nil
		meta2:set_string("offsets", minetest.serialize(off))
Auke Kok's avatar
Auke Kok committed
243
		meta2:mark_as_private("offsets")
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
244
	end
Yukita Mayako's avatar
Yukita Mayako committed
245 246 247 248

	if meta and meta.fields and meta.fields.triggers then
		update_trigger_list(pos)
	end
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
249 250 251 252 253 254
end

function mech.after_dig(pos, oldnode, oldmetadata, digger)
	unlink(pos, oldmetadata)
end

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
local function mech_connect(itemstack, placer, pointed_thing, rightclick)
	if not pointed_thing or not pointed_thing.under then
		return
	end
	if not placer then
		return
	end
	local name = placer:get_player_name()
	if not boxes.players_editing_boxes[name] and not minetest.check_player_privs(name, "server") then
		return
	end
	local box = boxes.players_editing_boxes[name]
	if not box then
		box = {
			minp = { x = -32768, y = -32768, z = -32768 },
			maxp = { x =  32768, y =  32768, z =  32768 },
		}
	end
	local pos = pointed_thing.under
	if pos.x <= box.minp.x or pos.x >= box.maxp.x or
		pos.y <= box.minp.y or pos.y >= box.maxp.y or
		pos.z <= box.minp.z or pos.z >= box.maxp.z
	then
		return
	end

	local tmeta = itemstack:get_metadata()
282 283
	if rightclick then
		if tmeta == "" then
284 285 286
			minetest.chat_send_player(placer:get_player_name(),
				"Left-click a node first.")
			return itemstack
287
		end
288

289 290
		local pos1 = dehash_vector(tmeta)
		local pos2 = pointed_thing.under
291

292
		if placer:get_player_control().sneak then
293 294 295 296 297 298
			-- special version of unlink()
			local m1 = minetest.get_meta(pos1)
			local t1 = m1:to_table()
			local offsets = minetest.deserialize(t1.fields.offsets or "") or {}
			offsets[hash_vector(vector.subtract(pos2, pos1))] = nil
			m1:set_string("offsets", minetest.serialize(offsets))
Auke Kok's avatar
Auke Kok committed
299
			m1:mark_as_private("offsets")
300 301 302 303 304 305

			local m2 = minetest.get_meta(pos2)
			local t2 = m2:to_table()
			local roffsets = minetest.deserialize(t2.fields.roffsets or "") or {}
			roffsets[hash_vector(vector.subtract(pos1, pos2))] = nil
			m2:set_string("roffsets", minetest.serialize(roffsets))
Auke Kok's avatar
Auke Kok committed
306
			m2:mark_as_private("roffsets")
307

308 309 310
			minetest.chat_send_player(placer:get_player_name(), S("Connection removed from \"@1\" at ",
					minetest.get_node(pos2).name) ..
					minetest.pos_to_string(pos2) .. ".")
311
			minetest.sound_play("button_untrigger", {pos = pointed_thing.under})
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
312
		else
313
			if mech.link(pos1, pos2) then
314 315 316
				minetest.chat_send_player(placer:get_player_name(), S("Connection completed with \"@1\" at ",
						minetest.get_node(pos2).name) ..
						minetest.pos_to_string(pos2) .. ".")
317 318
					minetest.sound_play("button_untrigger", {pos = pointed_thing.under})
			else
Wuzzy's avatar
Wuzzy committed
319
				minetest.chat_send_player(placer:get_player_name(), S("Connection failed. Too many connections."))
320
			end
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
321
		end
322 323
	else -- left click
		itemstack:set_metadata(hash_vector(pointed_thing.under))
324 325 326
		minetest.chat_send_player(placer:get_player_name(), S("Connection started with \"@1\" at ",
				minetest.get_node(pointed_thing.under).name) ..
				minetest.pos_to_string(pointed_thing.under) .. ".")
327 328
			minetest.sound_play("button_trigger", {pos = pointed_thing.under})
		local meta = itemstack:get_meta()
Wuzzy's avatar
Wuzzy committed
329
		meta:set_string("description", S("Connector tool").."\n" ..
330 331 332 333 334 335
			S("Right-click creates a connection from \"@1\" at ",
				minetest.get_node(pointed_thing.under).name) ..
				minetest.pos_to_string(pointed_thing.under) .. "\n" ..
			S("Right click + Shift to remove the connection from \"@1\" at ",
			minetest.get_node(pointed_thing.under).name) ..
			minetest.pos_to_string(pointed_thing.under) .. ".")
336 337 338
	end
	return itemstack
end
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
339

340
minetest.register_tool("mech:connector", {
Wuzzy's avatar
Wuzzy committed
341 342 343
	description = S("Connector tool").."\n"..
		S("Left-click to start a link").."\n"..
		S("Right-click to complete a link"),
344 345 346 347 348 349 350
	inventory_image = "connector_tool.png",
	on_use = function(itemstack, placer, pointed_thing)
		return mech_connect(itemstack, placer, pointed_thing, false)
	end,
	on_place = function(itemstack, placer, pointed_thing)
		return mech_connect(itemstack, placer, pointed_thing, true)
	end,
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
351
})
352
frame.register("mech:connector")
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
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
local function do_player_place(pos, node, placer, itemstack, pointed_thing)
	local name = placer:get_player_name()
	-- check placer is playing a box, otherwise it's invalid anyway
	local box = boxes.players_in_boxes[name]
	if not box then
		return
	end
	-- double check box coords
	if boxes.find_box(pos).box_id ~= box.box_id then
		return
	end
	if not itemstack then
		return
	end
	local item = itemstack:get_name()
	if not item then
		return
	end
	local def = minetest.registered_nodes[item]
	if not def or not def.groups or not def.groups.node and
	   not def.groups.shovel and not def.groups.pickaxe and
	   not def.groups.axe then
		return
	end

	-- check for placeholder in target location
	local tnode = minetest.get_node(pointed_thing.above)
	if tnode.name ~= "nodes:placeholder" then
		return itemstack
	end
	local meta = minetest.get_meta(pointed_thing.above)
	local placeable = meta:get_string("placeable")
	if placeable == "" then
		return itemstack
	end
	local t = minetest.parse_json(placeable)
	if not t[item] then
		return itemstack
	end

	minetest.set_node(pointed_thing.above, {name = item})
395 396 397 398
	local sounds = def.sounds or {}
	if sounds.place then
		minetest.sound_play(sounds.place, {pos = pointed_thing.above})
	end
399 400 401
	itemstack:take_item()
	return itemstack
end
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
402

Yukita Mayako's avatar
Yukita Mayako committed
403

404 405 406 407 408 409 410 411 412 413 414
-- secrets (keys?)
-- collection points

-- boxes:
-- - fake
-- - real with key
-- - real with tools

-- event creators:
-- buttons
minetest.register_node("mech:button", {
Wuzzy's avatar
Wuzzy committed
415
	description = S("Button"),
416
	drawtype = "mesh",
417
	mesh = "button_up.obj",
418 419 420
	tiles = {"button_switch.png"},
	paramtype = "light",
	paramtype2 = "facedir",
421
	walkable = false,
422
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
423
	sounds = sounds.metal,
424 425 426 427 428 429 430 431
	collision_box = {
		type = "fixed",
		fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
	},
	selection_box = {
		type = "fixed",
		fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
	},
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
432
	after_dig_node = mech.after_dig,
433 434 435 436 437
	on_punch = function(pos, node, puncher, pointed_thing)
		mech.trigger(pos)
		node.name = "mech:button_down"
		minetest.swap_node(pos, node)
		minetest.get_node_timer(pos):start(1)
Auke Kok's avatar
Auke Kok committed
438
		minetest.sound_play("button_trigger", {pos = pos})
439
	end,
440
	on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
441 442 443 444
		mech.trigger(pos)
		node.name = "mech:button_down"
		minetest.swap_node(pos, node)
		minetest.get_node_timer(pos):start(1)
Auke Kok's avatar
Auke Kok committed
445
		minetest.sound_play("button_trigger", {pos = pos})
446
		return itemstack
447
	end,
448 449 450
})

minetest.register_node("mech:button_down", {
Wuzzy's avatar
Wuzzy committed
451
	description = S("Button (pressed)"),
452
	drawtype = "mesh",
453
	mesh = "button_down.obj",
454 455 456
	tiles = {"button_switch.png"},
	paramtype = "light",
	paramtype2 = "facedir",
457
	walkable = false,
458
	groups = {node = 1, unbreakable = 1, mech = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
459
	sounds = sounds.metal,
460 461 462 463 464 465 466 467
	collision_box = {
		type = "fixed",
		fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
	},
	selection_box = {
		type = "fixed",
		fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
	},
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
468
	after_dig_node = mech.after_dig,
469 470 471 472 473
	on_timer = function(pos)
		mech.untrigger(pos)
		local node = minetest.get_node(pos)
		node.name = "mech:button"
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
474
		minetest.sound_play("button_untrigger", {pos = pos})
475
	end,
476 477 478 479
})

-- switches
minetest.register_node("mech:switch", {
Wuzzy's avatar
Wuzzy committed
480
	description = S("Switch (off)"),
481 482 483 484 485
	drawtype = "mesh",
	mesh = "switch.obj",
	tiles = {"button_switch.png"},
	paramtype = "light",
	paramtype2 = "facedir",
486
	walkable = false,
487
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
488
	sounds = sounds.metal,
489 490 491 492 493 494 495 496
	collision_box = {
		type = "fixed",
		fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
	},
	selection_box = {
		type = "fixed",
		fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
	},
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
497 498 499 500 501
	after_dig_node = mech.after_dig,
	on_punch = function(pos, node, puncher, pointed_thing)
		mech.trigger(pos)
		node.name = "mech:switch_on"
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
502
		minetest.sound_play("button_trigger", {pos = pos})
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
503
	end,
504
	on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
505 506 507
		mech.trigger(pos)
		node.name = "mech:switch_on"
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
508
		minetest.sound_play("button_trigger", {pos = pos})
509
		return itemstack
510
	end,
511 512 513
})

minetest.register_node("mech:switch_on", {
Wuzzy's avatar
Wuzzy committed
514
	description = S("Switch (on)"),
515 516 517 518 519
	drawtype = "mesh",
	mesh = "switch_on.obj",
	tiles = {"button_switch.png"},
	paramtype = "light",
	paramtype2 = "facedir",
520
	walkable = false,
521
	groups = {node = 1, unbreakable = 1, mech = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
522
	sounds = sounds.metal,
523 524 525 526 527 528 529 530
	collision_box = {
		type = "fixed",
		fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
	},
	selection_box = {
		type = "fixed",
		fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
	},
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
531 532 533 534 535
	after_dig_node = mech.after_dig,
	on_punch = function(pos, node, puncher, pointed_thing)
		mech.untrigger(pos)
		node.name = "mech:switch"
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
536
		minetest.sound_play("button_untrigger", {pos = pos})
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
537
	end,
538
	on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
539 540 541
		mech.untrigger(pos)
		node.name = "mech:switch"
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
542
		minetest.sound_play("button_untrigger", {pos = pos})
543
		return itemstack
544
	end,
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
545

546 547 548 549
})

-- pressure plates
minetest.register_node("mech:pressure_plate", {
Wuzzy's avatar
Wuzzy committed
550
	description = S("Pressure plate"),
551
	drawtype = "nodebox",
552
	tiles = {"blocks_tiles.png^[sheet:8x8:3,2"},
553 554 555 556 557
	node_box = {
		type = "fixed",
		fixed = {{-7/16, -1/2, -7/16, 7/16, -7/16, 7/16}},
	},
	paramtype = "light",
558
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
559
	sounds = sounds.metal,
Auke Kok's avatar
Auke Kok committed
560
	walkable = false,
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
561
	after_dig_node = mech.after_dig,
562
	on_walk_over = function(pos, node, player)
Auke Kok's avatar
Auke Kok committed
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
		--
		-- somewhat complex case becayse of the on_walk_over
		-- combination with triggers will put the plate back
		-- again right after removal happens, and so the plate
		-- never gets removed if you self-remove the plate
		--
		local remove
		local meta = minetest.get_meta(pos)
		local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
		local h = hash_vector({x = 0, y = 0, z = 0})
		for v, _ in pairs(offsets) do
			if v == h then
				remove = true
			end
		end

579
		mech.trigger(pos)
Auke Kok's avatar
Auke Kok committed
580 581 582 583 584

		if remove then
			return
		end

585 586 587
		node.name = "mech:pressure_plate_down"
		minetest.swap_node(pos, node)
		minetest.get_node_timer(pos):start(0.5)
Auke Kok's avatar
Auke Kok committed
588
		minetest.sound_play("button_trigger", {pos = pos})
589
	end,
590 591 592
})

minetest.register_node("mech:pressure_plate_down", {
Wuzzy's avatar
Wuzzy committed
593
	description = S("Pressure plate (pressed)"),
594
	drawtype = "nodebox",
595
	tiles = {"blocks_tiles.png^[sheet:8x8:3,2"},
596 597 598 599 600
	node_box = {
		type = "fixed",
		fixed = {{-7/16, -1/2, -7/16, 7/16, -1/2 + 0.001, 7/16}},
	},
	paramtype = "light",
601
	groups = {node = 1, unbreakable = 1, mech = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
602
	sounds = sounds.metal,
Nathanaëlle Courant's avatar
Nathanaëlle Courant committed
603
	after_dig_node = mech.after_dig,
604
	walkable = false,
605 606 607 608 609 610 611 612
	on_walk_over = function(pos, node, player)
		minetest.get_node_timer(pos):start(0.5)
	end,
	on_timer = function(pos)
		mech.untrigger(pos)
		local node = minetest.get_node(pos)
		node.name = "mech:pressure_plate"
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
613
		minetest.sound_play("button_untrigger", {pos = pos})
614
	end,
615
})
Auke Kok's avatar
Auke Kok committed
616 617 618 619 620

-- piston
local pistondir = {
	[0] = {x = 0, y = 1, z = 0},
	[1] = {x = 0, y = 0, z = 1},
Auke Kok's avatar
Auke Kok committed
621 622 623
	[2] = {x = 0, y = 0, z = -1},
	[3] = {x = 1, y = 0, z = 0},
	[4] = {x = -1, y = 0, z = 0},
Auke Kok's avatar
Auke Kok committed
624 625 626
	[5] = {x = 0, y = -1, z = 0},
}

Auke Kok's avatar
Auke Kok committed
627 628 629
local function piston_on_trigger(pos)
	local node = minetest.get_node(pos)
	local dir = pistondir[math.floor(node.param2 / 4)]
Auke Kok's avatar
Auke Kok committed
630 631 632 633 634 635

	-- determine what the first buildable_to node is ahead of the piston
	local _p = vector.add(pos, dir)
	local stack = 32
	while true do
		local _n = minetest.get_node(_p)
636 637 638
		if minetest.registered_nodes[_n.name].unpushable then
			return
		end
Auke Kok's avatar
Auke Kok committed
639 640 641 642 643 644
		if minetest.registered_nodes[_n.name].buildable_to then
			break
		end
		stack = stack - 1
		if stack == 0 then
			-- can't push a single node
Auke Kok's avatar
Auke Kok committed
645 646
			return
		end
Auke Kok's avatar
Auke Kok committed
647 648 649 650 651 652 653 654
		-- try next block
		_p = vector.add(_p, dir)
	end

	-- now swap all the nodes outward
	while stack < 32 do
		local _pp = vector.subtract(_p, dir)
		local _nn = minetest.get_node(_pp)
655 656 657 658 659
		local _ppmeta = minetest.get_meta(_pp)
		local _pmeta = minetest.get_meta(_p)
		minetest.set_node(_p, _nn)
		_pmeta:from_table(_ppmeta:to_table())

Auke Kok's avatar
Auke Kok committed
660 661 662 663
		minetest.check_for_falling(_pp)

		stack = stack + 1
		_p = _pp
Auke Kok's avatar
Auke Kok committed
664 665 666 667 668 669 670 671
	end

	-- set the piston head
	if node.name == "mech:piston_base" then
		node.name = "mech:piston_top"
	else
		node.name = "mech:piston_top_sticky"
	end
672 673
	-- clear meta
	minetest.set_node(_p, node)
Auke Kok's avatar
Auke Kok committed
674 675 676 677

	-- last, piston base
	node.name = "mech:piston_base_extended"
	minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
678
	minetest.sound_play("piston_trigger", {pos = pos})
679 680

	-- make nodes fall if needed
Auke Kok's avatar
Auke Kok committed
681
	minetest.check_for_falling(_p)
Auke Kok's avatar
Auke Kok committed
682 683
end

Auke Kok's avatar
Auke Kok committed
684
minetest.register_node("mech:piston_base", {
Wuzzy's avatar
Wuzzy committed
685
	description = S("Piston"),
Auke Kok's avatar
Auke Kok committed
686 687
	paramtype2 = "facedir",
	tiles = {"piston_top_normal.png", "piston_bottom.png", "piston_side.png"},
Auke Kok's avatar
Auke Kok committed
688
	groups = {node = 1, unbreakable = 1},
Auke Kok's avatar
Auke Kok committed
689
	sounds = sounds.wood,
Auke Kok's avatar
Auke Kok committed
690
	after_dig_node = mech.after_dig,
Auke Kok's avatar
Auke Kok committed
691
	on_trigger = piston_on_trigger,
Auke Kok's avatar
Auke Kok committed
692 693 694
})

minetest.register_node("mech:piston_base_sticky", {
Wuzzy's avatar
Wuzzy committed
695
	description = S("Sticky piston"),
Auke Kok's avatar
Auke Kok committed
696 697
	paramtype2 = "facedir",
	tiles = {"piston_top_sticky.png", "piston_bottom.png", "piston_side.png"},
Auke Kok's avatar
Auke Kok committed
698
	groups = {node = 1, unbreakable = 1},
Auke Kok's avatar
Auke Kok committed
699
	sounds = sounds.wood,
Auke Kok's avatar
Auke Kok committed
700
	after_dig_node = mech.after_dig,
Auke Kok's avatar
Auke Kok committed
701
	on_trigger = piston_on_trigger,
Auke Kok's avatar
Auke Kok committed
702 703 704
})

minetest.register_node("mech:piston_base_extended", {
Wuzzy's avatar
Wuzzy committed
705
	description = S("Extended piston base"),
Auke Kok's avatar
Auke Kok committed
706 707 708 709 710 711 712 713 714 715 716
	paramtype = "light",
	paramtype2 = "facedir",
	drawtype = "nodebox",
	tiles = {"piston_inner.png", "piston_bottom.png", "[combine:16x16:0,0=piston_side.png:0,-12=piston_side.png"},
	node_box = {
		type = "fixed",
		fixed = {
			{-1/2, -1/2, -1/2, 1/2, 1/4, 1/2}, -- base
			{-1/8, 1/4, -1/8, 1/8, 1/2, 1/8}, -- rod
		}
	},
717
	groups = {node = 1, unbreakable = 1, mech = 1},
718
	unpushable = 1,
Auke Kok's avatar
Auke Kok committed
719
	sounds = sounds.wood,
Auke Kok's avatar
Auke Kok committed
720
	after_dig_node = mech.after_dig,
721
	on_trigger = function(pos) end,
Auke Kok's avatar
Auke Kok committed
722 723 724 725 726
	on_untrigger = function(pos)
		local node = minetest.get_node(pos)
		local dir = pistondir[math.floor(node.param2 / 4)]
		local npos = vector.add(pos, dir)
		local nnode = minetest.get_node(npos)
727
		local nnpos = vector.add(npos, dir)
Auke Kok's avatar
Auke Kok committed
728 729
		if nnode.name == "mech:piston_top_sticky" then
			local nnnode = minetest.get_node(nnpos)
730
			if not minetest.registered_nodes[nnnode.name].unpushable then
731 732
				local nnmeta = minetest.get_meta(nnpos)
				local nmeta = minetest.get_meta(npos)
733
				minetest.swap_node(npos, nnnode)
734 735
				nmeta:from_table(nnmeta:to_table())
				minetest.remove_node(nnpos)
736 737 738
			else
				minetest.remove_node(npos)
			end
Auke Kok's avatar
Auke Kok committed
739
			node.name = "mech:piston_base_sticky"
740
		elseif nnode.name == "mech:piston_top" then
Auke Kok's avatar
Auke Kok committed
741 742
			minetest.remove_node(npos)
			node.name = "mech:piston_base"
743 744 745
		else
			-- wall exploit otherwise
			return
Auke Kok's avatar
Auke Kok committed
746 747
		end
		minetest.swap_node(pos, node)
Auke Kok's avatar
Auke Kok committed
748
		minetest.sound_play("piston_untrigger", {pos = pos})
749 750

		-- make nodes fall if needed
Auke Kok's avatar
Auke Kok committed
751 752
		minetest.check_for_falling(npos)
		minetest.check_for_falling(nnpos)
Auke Kok's avatar
Auke Kok committed
753 754 755 756
	end,
})

minetest.register_node("mech:piston_top", {
Wuzzy's avatar
Wuzzy committed
757
	description = S("Piston head"),
Auke Kok's avatar
Auke Kok committed
758 759 760 761 762 763 764 765 766 767 768
	paramtype = "light",
	paramtype2 = "facedir",
	drawtype = "nodebox",
	tiles = {"piston_top_normal.png", "piston_inner.png", "piston_side.png"},
	node_box = {
		type = "fixed",
		fixed = {
			{-1/2, 1/4, -1/2, 1/2, 1/2, 1/2}, -- head
			{-1/8, -1/2, -1/8, 1/8, 1/4, 1/8}, -- rod
		}
	},
769
	groups = {node = 1, unbreakable = 1, piston_top = 1},
770
	unpushable = 1,
Auke Kok's avatar
Auke Kok committed
771
	sounds = sounds.wood,
772
	on_trigger = function(pos) end,
Auke Kok's avatar
Auke Kok committed
773 774 775
})

minetest.register_node("mech:piston_top_sticky", {
Wuzzy's avatar
Wuzzy committed
776
	description = S("Sticky piston head"),
Auke Kok's avatar
Auke Kok committed
777 778 779 780
	paramtype = "light",
	paramtype2 = "facedir",
	drawtype = "nodebox",
	tiles = {"piston_top_sticky.png", "piston_inner.png", "piston_side.png"},
781
	groups = {node = 1, unbreakable = 1, piston_top = 1},
782
	unpushable = 1,
Auke Kok's avatar
Auke Kok committed
783
	sounds = sounds.wood,
Auke Kok's avatar
Auke Kok committed
784 785 786 787 788 789 790
	node_box = {
		type = "fixed",
		fixed = {
			{-1/2, 1/4, -1/2, 1/2, 1/2, 1/2}, -- head
			{-1/8, -1/2, -1/8, 1/8, 1/4, 1/8}, -- rod
		}
	},
791
	on_trigger = function(pos) end,
Auke Kok's avatar
Auke Kok committed
792 793
})

Auke Kok's avatar
Auke Kok committed
794
minetest.register_node("mech:delayer", {
Wuzzy's avatar
Wuzzy committed
795
	description = S("Delayer"),
Auke Kok's avatar
Auke Kok committed
796 797
	tiles = {"delayer.png"},
	place_param2 = 1,
798
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
799
	sounds = sounds.metal,
Auke Kok's avatar
Auke Kok committed
800 801 802 803 804 805 806 807 808 809 810
	after_dig_node = mech.after_dig,
	on_trigger = function(pos)
		local node = minetest.get_node(pos)
		minetest.after(node.param2, mech.trigger, pos)
	end,
	on_untrigger = function(pos)
		local node = minetest.get_node(pos)
		minetest.after(node.param2, mech.untrigger, pos)
	end,
	on_punch = function(pos, node, puncher, pointed_thing)
		local name = puncher:get_player_name()
811
		if not puncher or not boxes.players_editing_boxes[name] then
Auke Kok's avatar
Auke Kok committed
812 813 814 815 816 817
			return
		end
		node.param2 = (node.param2 % 16) + 1
		minetest.chat_send_player(name, "Delay = " .. node.param2)
		minetest.swap_node(pos, node)
	end,
818
	on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
819
		local name = puncher:get_player_name()
820 821
		if not puncher then
			return itemstack
822
		elseif not boxes.players_editing_boxes[name] then
823
			return do_player_place(pos, node, puncher, itemstack, pointed_thing)
824 825 826 827 828
		end
		node.param2 = (node.param2 - 2) % 16 + 1
		minetest.chat_send_player(name, "Delay = " .. node.param2)
		minetest.swap_node(pos, node)
	end,
Auke Kok's avatar
Auke Kok committed
829 830 831 832 833 834
	on_reveal = function(name, pos)
		local node = minetest.get_node(pos)
		minetest.chat_send_player(name, minetest.colorize(
			"#4444ff", "> delay = " .. node.param2
			))
	end,
Auke Kok's avatar
Auke Kok committed
835 836
})

Auke Kok's avatar
Auke Kok committed
837
minetest.register_node("mech:extender", {
Wuzzy's avatar
Wuzzy committed
838
	description = S("Extender"),
Auke Kok's avatar
Auke Kok committed
839 840
	tiles = {"extender.png"},
	place_param2 = 1,
841
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
842
	sounds = sounds.metal,
Auke Kok's avatar
Auke Kok committed
843 844 845 846 847 848 849 850
	after_dig_node = mech.after_dig,
	on_trigger = mech.trigger,
	on_untrigger = function(pos)
		local node = minetest.get_node(pos)
		minetest.after(node.param2, mech.untrigger, pos)
	end,
	on_punch = function(pos, node, puncher, pointed_thing)
		local name = puncher:get_player_name()
851
		if not puncher or not boxes.players_editing_boxes[name] then
Auke Kok's avatar
Auke Kok committed
852 853 854 855 856 857
			return
		end
		node.param2 = (node.param2 % 16) + 1
		minetest.chat_send_player(name, "Delay = " .. node.param2)
		minetest.swap_node(pos, node)
	end,
858
	on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
859
		local name = puncher:get_player_name()
860 861
		if not puncher then
			return itemstack
862
		elseif not boxes.players_editing_boxes[name] then
863
			return do_player_place(pos, node, puncher, itemstack, pointed_thing)
864 865 866 867 868
		end
		node.param2 = (node.param2 - 2) % 16 + 1
		minetest.chat_send_player(name, "Delay = " .. node.param2)
		minetest.swap_node(pos, node)
	end,
Auke Kok's avatar
Auke Kok committed
869 870 871 872 873 874
	on_reveal = function(name, pos)
		local node = minetest.get_node(pos)
		minetest.chat_send_player(name, minetest.colorize(
			"#4444ff", "> extension = " .. node.param2
			))
	end,
Auke Kok's avatar
Auke Kok committed
875 876
})

Auke Kok's avatar
Auke Kok committed
877
minetest.register_node("mech:inverter", {
Wuzzy's avatar
Wuzzy committed
878
	description = S("Inverter"),
Auke Kok's avatar
Auke Kok committed
879 880
	tiles = {"inverter.png"},
	place_param2 = 1,
881
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
882
	sounds = sounds.metal,
Auke Kok's avatar
Auke Kok committed
883 884 885 886
	after_dig_node = mech.after_dig,
	on_trigger = mech.untrigger,
	on_untrigger = mech.trigger,
})
Auke Kok's avatar
Auke Kok committed
887

888
minetest.register_node("mech:filter", {
Wuzzy's avatar
Wuzzy committed
889
	description = S("Filter"),
890 891
	tiles = {"filter.png"},
	place_param2 = 1,
892
	groups = {node = 1, unbreakable = 1, trigger = 1},
Auke Kok's avatar
Auke Kok committed
893
	sounds = sounds.metal,
894 895 896
	after_dig_node = mech.after_dig,
	on_trigger = mech.trigger,
})
897 898

minetest.register_node("mech:adder", {
Wuzzy's avatar
Wuzzy committed
899
	description = S("Adder"),
900
	tiles = {"adder.png"},
901
	place_param2 = 32,
902
	groups = {node = 1, unbreakable = 1, trigger = 1},
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
	sounds = sounds.metal,
	after_dig_node = mech.after_dig,
	on_trigger = function(pos)
		local node = minetest.get_node(pos)
		local count = (node.param2 % 16)
		local need = math.floor(node.param2 / 16)
		if count < 15 then
			count = count + 1
		end
		if count == need then
			mech.trigger(pos)
		end
		node.param2 = need * 16 + count
		minetest.swap_node(pos, node)
	end,
	on_untrigger = function(pos)
		local node = minetest.get_node(pos)
		local count = (node.param2 % 16)
		local need = math.floor(node.param2 / 16)
		if count > 0 then
			count = count - 1
		end
		if count == (need - 1) then
			mech.untrigger(pos)
		end
		node.param2 = need * 16 + count
		minetest.swap_node(pos, node)
	end,
	on_punch = function(pos, node, puncher, pointed_thing)
		local name = puncher:get_player_name()
933
		if not puncher or not boxes.players_editing_boxes[name] then
934 935 936
			return
		end
		node.param2 = (node.param2 + 16) % 256
937
		minetest.chat_send_player(name, "Count = " .. math.floor(node.param2 / 16))
938 939
		minetest.swap_node(pos, node)
	end,
940
	on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
941
		local name = puncher:get_player_name()
942 943
		if not puncher then
			return itemstack
944
		elseif not boxes.players_editing_boxes[name] then
945
			return do_player_place(pos, node, puncher, itemstack, pointed_thing)
946 947
		end
		node.param2 = (node.param2 - 16) % 256
948
		minetest.chat_send_player(name, "Count = " .. math.floor(node.param2 / 16))
949 950
		minetest.swap_node(pos, node)
	end,
Auke Kok's avatar
Auke Kok committed
951 952 953 954 955 956 957 958 959 960 961 962 963
	on_reveal = function(name, pos)
		local node = minetest.get_node(pos)
		local count = (node.param2 % 16)
		local need = math.floor(node.param2 / 16)
		minetest.chat_send_player(name, minetest.colorize(
			"#4444ff",
			"> target count = " .. need
			))
		minetest.chat_send_player(name, minetest.colorize(
			"#4444ff",
			"> current count = " .. count
			))
	end,
964 965
})

Auke Kok's avatar
Auke Kok committed
966 967 968 969 970 971 972 973 974
local facedir_top = {
	[0] = {x = 0, y = 1, z = 0},
	[1] = {x = 0, y = 0, z = 1},
	[2] = {x