-- How far away bombs deal "damage" to players
-- (damage is only an animation with no gameplay
-- effect)
local BOMB_DAMAGE_RADIUS = 3

-- Bomb "damage" radius for parrots
-- (triggers scorched texture)
local BOMB_DAMAGE_RADIUS_PARROT = 1.333

-- Time a parrot remains scorched by a bomb
local BOMB_PARROT_SCORCH_TIME = 5.0

-- How many a player is slowed down
-- when hit by a bomb
local BOMB_SLOWDOWN_TIME = 6.0

local mirror_out = {
	[0] = {1,0,0},
	[1] = {0,0,-1},
	[2] = {-1,0,0},
	[3] = {0,0,1},

	[4] = {1,0,0},
	[5] = {0,1,0},
	[6] = {-1,0,0},
	[7] = {0,-1,0},

	[8] = {1,0,0},
	[9] = {0,-1,0},
	[10] = {-1,0,0},
	[11] = {0,1,0},

	[12] = {0,-1,0},
	[13] = {0,0,-1},
	[14] = {0,1,0},
	[15] = {0,0,1},

	[16] = {0,1,0},
	[17] = {0,0,-1},
	[18] = {0,-1,0},
	[19] = {0,0,1},

	[20] = {-1,0,0},
	[21] = {0,0,-1},
	[22] = {1,0,0},
	[23] = {0,0,1},
}

lzr_laser.get_front_dir = function(param2)
	if not param2 then
		return
	end
	local dir_input = minetest.facedir_to_dir(param2)
	if not dir_input then
		return
	end
	local v = vector.new(dir_input[1], dir_input[2], dir_input[3])
	dir_input = vector.multiply(v, -1)
	return dir_input
end

lzr_laser.get_top_dir = function(param2)
	if not param2 then
		return
	end
	if (param2 >= 0 and param2 <= 3) then
		return vector.new(0, 1, 0)
	elseif (param2 >= 4 and param2 <= 7) then
		return vector.new(0, 0, 1)
	elseif (param2 >= 8 and param2 <= 11) then
		return vector.new(0, 0, -1)
	elseif (param2 >= 12 and param2 <= 15) then
		return vector.new(1, 0, 0)
	elseif (param2 >= 16 and param2 <= 19) then
		return vector.new(-1, 0, 0)
	elseif (param2 >= 20 and param2 <= 23) then
		return vector.new(0, -1, 0)
	else
		return vector.new(0, 1, 0)
	end

end

-- Takes a param2 value (for paramtype2="facedir") and returns the
-- axis a pillar-shaped node (barrel, pillar, etc.) is facing
-- ("x", "y" or "z"), assuming the node is standing upright for
-- param2 = 0. The y axis means the pillar is standing upright.
lzr_laser.get_pillar_axis = function(param2)
	if not param2 then
		return
	end
	if (param2 >= 0 and param2 <= 3) or (param2 >= 20 and param2 <= 23) then
		return "y"
	elseif (param2 >= 4 and param2 <= 11) then
		return "z"
	elseif (param2 >= 12 and param2 <= 19) then
		return "x"
	else
		return "y"
	end
end

lzr_laser.get_pane_axis = function(param2)
	if not param2 then
		return
	end
	local dir = minetest.facedir_to_dir(param2)
	if dir.x ~= 0 then
		return "x"
	elseif dir.y ~= 0 then
		return "y"
	elseif dir.z ~= 0 then
		return "z"
	end
end

lzr_laser.check_front = function(param2, laser_dir)
	local node_dir_in = lzr_laser.get_front_dir(param2)
	if not node_dir_in then
		return false
	end
	local reverse_laser_dir = vector.multiply(laser_dir, -1)
	if vector.equals(reverse_laser_dir, node_dir_in) then
		return true
	end
	return false
end

-- Check if the given laser color would activate the given
-- detector node name.
lzr_laser.check_detector_color = function(nodename, laser_color)
	local detector_group = minetest.get_item_group(nodename, "detector")
	if detector_group == 0 then
		return false
	end
	local detector_color = minetest.get_item_group(nodename, "detector_color")
	-- There is no laser: Fail
	if laser_color == 0 then
		return false
	-- Detector does not care about color: Success
	elseif detector_color == 0 then
		return true
	-- If detector *does* care about color, it must be an exact match
	elseif laser_color == detector_color then
		return true
	end
	return false
end

-- Check if laser goes into detector (ignoring color)
lzr_laser.check_detector_input = function(nodename, param2, laser_dir)
	local detector_group = minetest.get_item_group(nodename, "detector")
	if detector_group == 0 then
		return false
	end
	-- Check if laser goes into hole
	if lzr_laser.check_front(param2, laser_dir) then
		return true
	end
	return false
end

lzr_laser.get_mirror_dirs = function(param2)
	local dir_input = minetest.facedir_to_dir(param2)
	if not dir_input then
		return
	end
	dir_input = vector.multiply(dir_input, -1)
	local dir_output = vector.new(unpack(mirror_out[param2]))

	return dir_input, dir_output
end

lzr_laser.get_double_mirror_dirs = function(param2)
	local dir_input = minetest.facedir_to_dir(param2)
	if not dir_input then
		return
	end
	local dir_input_front = vector.multiply(dir_input, -1)
	local dir_output_front = vector.new(unpack(mirror_out[param2]))

	local dir_input_back = vector.new(dir_input.x, dir_input.y, dir_input.z)
	local dir_output_back = vector.multiply(vector.new(unpack(mirror_out[param2])), -1)

	return dir_input_front, dir_output_front, dir_input_back, dir_output_back
end

-- Mirror a laser that touches a mirror with a laser coming towards
-- the mirror with the laser_dir direction (direction vector).
-- * nodename: Name of mirror node
-- * param2: param2 of mirror node
-- * laser_dir: Direction of incoming laser
-- Returns <output direction>, <mirror ingoing>, <mirror backside>, where:
-- * <output direction>: Direction of the outgoing mirror or false if none
-- * <mirror ingoing>: true if the laser has hit the 'ingoing'
--   side of the mirror or false if not. This is because on any mirror,
--   the laser can come from two directions, the true/false distinguishes
--   which direction it was.
-- * <mirror backside>: true if the laser hit the backside of the mirror
--   (double mirror only) or false if not. nil if laser was not mirrored at all
--
-- 	1) If the laser can be mirrored, then <output direction> is the direction of
-- 	the mirrored laser and <mirror output> denotes on of the 2 possible
-- 	sides the laser came from: true if it came in the front side, false if
-- 	it came from the angled side. <mirror output> is useful for the
-- 	beam splitter (transmissive mirror).
-- 	2) If the laser cannot be mirrored <mirror output> is false and
-- 	<mirror output side> is nil
lzr_laser.get_mirrored_laser_dir = function(nodename, param2, laser_dir)
	local mirror_group = minetest.get_item_group(nodename, "mirror")
	local mirror_transmissive_group = minetest.get_item_group(nodename, "transmissive_mirror")
	local mirror_double_group = minetest.get_item_group(nodename, "double_mirror")
	if mirror_group == 0 and mirror_transmissive_group == 0 and mirror_double_group == 0 then
		return false
	end
	local reverse_laser_dir = vector.multiply(laser_dir, -1)
	if mirror_double_group ~= 0 then
		local dir_front_in, dir_front_out, dir_back_in, dir_back_out = lzr_laser.get_double_mirror_dirs(param2)
		if not dir_front_in then
			return false
		end
		if vector.equals(reverse_laser_dir, dir_front_in) then
			return dir_front_out, true, false
		elseif vector.equals(reverse_laser_dir, dir_front_out) then
			return dir_front_in, false, false
		elseif vector.equals(reverse_laser_dir, dir_back_in) then
			return dir_back_out, true, true
		elseif vector.equals(reverse_laser_dir, dir_back_out) then
			return dir_back_in, true, true
		end
	else
		local reverse_laser_dir = vector.multiply(laser_dir, -1)
		local mirror_dir_in, mirror_dir_out = lzr_laser.get_mirror_dirs(param2)
		if not mirror_dir_in then
			return false
		end
		if vector.equals(reverse_laser_dir, mirror_dir_in) then
			return mirror_dir_out, true, false
		elseif vector.equals(reverse_laser_dir, mirror_dir_out) then
			return mirror_dir_in, false, false
		end
	end
	return false
end

lzr_laser.get_mixer_input_dirs = function(param2)
	local front_dir = lzr_laser.get_front_dir(param2)
	local _, input_r = lzr_laser.get_mirror_dirs(param2)
	local input_l = vector.multiply(input_r, -1)
	return input_l, input_r
end

-- Template function for `after_place_node` callback for
-- trigger nodes.
lzr_laser.trigger_after_place_node = function(pos, placer, itemstack, pointed_thing)
	local state = lzr_gamestate.get_state()
	if state == lzr_gamestate.EDITOR then
		-- Save trigger in trigger list for the editor
		local minpos, maxpos = lzr_world.get_level_bounds()
		if vector.in_area(pos, minpos, maxpos) then
			local trigger_id = lzr_triggers.add_trigger(pos)
			if trigger_id then
				local meta = minetest.get_meta(pos)
				meta:set_string("trigger_id", trigger_id)
			end
		end
		return
	elseif state == lzr_gamestate.LEVEL or state == lzr_gamestate.LEVEL_TEST then
		local minpos, maxpos = lzr_world.get_level_bounds()
		if not vector.in_area(pos, minpos, maxpos) then
			minetest.log("warning", "[lzr_laser] Placed a trigger node out of level bounds at: "..minetest.pos_to_string(pos))
			return
		end
		-- Copy item's trigger_id into node
		local imeta = itemstack:get_meta()
		local nmeta = minetest.get_meta(pos)
		local trigger_id = imeta:get_string("trigger_id")
		nmeta:set_string("trigger_id", trigger_id)

		-- Update trigger location
		lzr_triggers.set_trigger_location(trigger_id, pos)
	end
end

-- Template function for `after_dig_node` callback for
-- trigger nodes.
lzr_laser.trigger_after_dig_node = function(pos, oldnode, oldmeta, drops)
	if not oldmeta and not oldmeta and not oldmeta.fields.trigger_id then
		return
	end
	local trigger_id = oldmeta.fields.trigger_id
	local state = lzr_gamestate.get_state()
	if state ~= lzr_gamestate.LEVEL and state ~= lzr_gamestate.LEVEL_TEST then
		lzr_triggers.remove_trigger(trigger_id)
		return
	end
end

-- From a list of natural numbers from 1 to m,
-- pick up to n random numbers and return a table
-- with the picked numbers. If a number was
-- picked, it cannot be picked again.
-- Returns the numbers as indexes.

-- Restriction:
-- * n and m must be integers.
local pick_n_of_m = function(n, m)
	if n <= 0 then
		return {}
	end
	local available = {}
	for i=1, m do
		table.insert(available, i)
	end
	local picks = {}
	if n < m then
		for i=1, n do
			if #available == 0 then
				break
			end
			local r = math.random(1, #available)
			table.insert(picks, available[r])
			table.remove(available, r)
		end
	else
		picks = available
	end

	-- Return picked numbers as table indexes
	local ret = {}
	for p=1, #picks do
		ret[picks[p]] = true
	end
	return ret
end

lzr_effects_limiter.register_effect("barricade_burn_or_break", { duration = 0.8, max_count = 20 })
lzr_effects_limiter.register_effect("bomb_explode", { duration = 0.3, max_count = 16 })

lzr_laser.burn_and_destroy = function(nodes_to_remove)

	minetest.bulk_set_node(nodes_to_remove.barricades, {name="air"})

	-- Collect all node set operations and perform them
	-- in a bulk_set_node at the end for slightly better
	-- performance
	local queued_node_hashes = {}
	local bulk_set_node_groups = {}
	local queue_set_node = function(pos, node)
		local param2 = node.param2 or 0
		local node_id = node.name.." "..tostring(param2)
		local hash = minetest.hash_node_position(pos)
		if not queued_node_hashes[hash] then
			queued_node_hashes[hash] = true
			if bulk_set_node_groups[node_id] then
				bulk_set_node_groups[node_id][hash] = true
			else
				bulk_set_node_groups[node_id] = {}
				bulk_set_node_groups[node_id][hash] = true
			end
		end
	end

	local ignited_barricades = {}
	local ignited_bombs_slow = {}
	local ignited_bombs_quick = {}

	local neighbors_barricade = {
		vector.new(-1,0,0),
		vector.new(1,0,0),
		vector.new(0,-1,0),
		vector.new(0,1,0),
		vector.new(0,0,-1),
		vector.new(0,0,1),
	}

	-- Burn up barricades. A burned-up barricade does:
	-- * get destroyed
	-- * create an audiovisual effect (fire and break sound)
	-- * spread its fire to direct neighbors (not diagonals)

	-- play_sounds_at restricts the number of sounds to be played
	-- at once. If true, there are no restrictions and a sound is
	-- played for all destroyed barricades. If a table, its keys
	-- are the iterator values for the 'for' loop where sounds
	-- *can* be played
	local play_sounds_at = true
	if #nodes_to_remove.barricades > lzr_globals.MAX_DESTROY_SOUNDS_AT_ONCE then
		-- Pick random 'for' loop indexes in which sounds can be played.
		-- We choose randomness so the sounds are more evenly distributed.
		play_sounds_at = pick_n_of_m(lzr_globals.MAX_DESTROY_SOUNDS_AT_ONCE, #nodes_to_remove.barricades)
	end
	for r=1, #nodes_to_remove.barricades do
		local rpos = nodes_to_remove.barricades[r]
		-- spawn a few particles for the removed node
		minetest.add_particlespawner({
			amount = 16,
			time = 0.001,
			minpos = vector.subtract(rpos, vector.new(0.5, 0.5, 0.5)),
			maxpos = vector.add(rpos, vector.new(0.5, 0.5, 0.5)),
			minvel = vector.new(-0.5, -0.2, -0.5),
			maxvel = vector.new(0.5, 0.2, 0.5),
			minacc = vector.new(0, -lzr_globals.GRAVITY, 0),
			maxacc = vector.new(0, -lzr_globals.GRAVITY, 0),
			minsize = 1.5,
			maxsize = 1.5,
			node = {name="lzr_laser:barricade"},
		})

		-- Play randomly-pitched break sound
		if play_sounds_at == true or play_sounds_at[r] then
			if lzr_effects_limiter.add_effect_if_possible("barricade_burn_or_break") then
				local pitch = 1.0+math.random(-100, 100)*0.001 -- 0.9..1.1
				minetest.sound_play({name="lzr_sounds_dug_sticks", gain=1.0}, {gain=1.0, pitch=pitch, pos=rpos}, true)
			end
		end

		-- propagate fire to neighbors
		for n=1, #neighbors_barricade do

			local ppos = vector.add(rpos, neighbors_barricade[n])
			local phash = minetest.hash_node_position(ppos)
			if not queued_node_hashes[phash] then
				local node = minetest.get_node(ppos)
				local def = minetest.registered_nodes[node.name]
				if def and def._lzr_active then
					-- Ignite flammable blocks
					if minetest.get_item_group(node.name, "flammable") == 1 then
						queue_set_node(ppos, {name=def._lzr_active, param2=node.param2})
						if def._lzr_active == "lzr_laser:barricade_on" then
							if (play_sounds_at == true or play_sounds_at[r]) then
								if lzr_effects_limiter.add_effect_if_possible("barricade_burn_or_break") then
									minetest.sound_play({name="lzr_laser_quickburn", gain=1.0}, {pos=ppos}, true)
								end
							end
							table.insert(ignited_barricades, ppos)
						end
					elseif minetest.get_item_group(node.name, "flammable") == 2 then
						-- Top-only flammable
						local top_dir = lzr_laser.get_top_dir(node.param2)
						local inverted_dir = vector.multiply(neighbors_barricade[n], -1)
						if vector.equals(inverted_dir, top_dir) then
							queue_set_node(ppos, {name=def._lzr_active, param2=node.param2})
							if minetest.get_item_group(node.name, "bomb") ~= 0 then
								table.insert(ignited_bombs_slow, ppos)
							end
						end
					end
				end
			end
		end
	end

	-- Explode bombs. An exploding bomb does:
	-- * get destroyed
	-- * create an audiovisual explosion effect (sound+particles)
	-- * destroy neighboring blocks (3×3×3 area) vulnerable to explosions
	-- * ignite neighboring bombs (3×3×3 area)
	-- * create a (non-lethal) damage effect for players, causing
	--   a damage screen and temp. slowdown
	minetest.bulk_set_node(nodes_to_remove.bombs, {name="air"})

	-- bomb_effects_at restricts the number of explosion sounds and particles
	-- to be used, similar to the barricades loop above.
	local bomb_effects_at = true
	if #nodes_to_remove.bombs > lzr_globals.MAX_DESTROY_SOUNDS_AT_ONCE then
		bomb_effects_at = pick_n_of_m(lzr_globals.MAX_DESTROY_SOUNDS_AT_ONCE, #nodes_to_remove.bombs)
	end
	for r=1, #nodes_to_remove.bombs do
		local rpos = nodes_to_remove.bombs[r]

		-- explosion effect (but not too many at once)
		if bomb_effects_at == true or bomb_effects_at[r] then
			if lzr_effects_limiter.add_effect_if_possible("bomb_explode") then
				-- node table for particle
				local bomb_node = { name = "lzr_laser:bomb_takable" }
				lzr_laser.bomb_explosion_audiovisuals(rpos, bomb_node)
			end
		end

		-- destroy nodes and deal player damage
		local destroyed_nodes = lzr_laser.deal_bomb_damage(rpos)
		for d=1, #destroyed_nodes do
			queue_set_node(destroyed_nodes[d], {name="air"})
		end

		-- ignite neighbor bombs (with reduced fuse time)
		local to_ignite = lzr_laser.find_ignitible_bomb_neighbors(rpos)
		for i=1, #to_ignite do

			local ipos = to_ignite[i]
			local ihash = minetest.hash_node_position(ipos)
			if not queued_node_hashes[ihash] then
				local inode = minetest.get_node(ipos)
				if minetest.get_item_group(inode.name, "bomb") == 1 then
					local def = minetest.registered_nodes[inode.name]
					queue_set_node(ipos, {name=def._lzr_active, param2=inode.param2})
					table.insert(ignited_bombs_quick, ipos)
				end
			end
		end
	end

	-- Perform all the queued set_node operations in bulk
	for id, hashed_positions in pairs(bulk_set_node_groups) do
		local positions = {}
		for hash, _ in pairs(hashed_positions) do
			local hpos = minetest.get_position_from_hash(hash)
			table.insert(positions, hpos)
		end
		local splt = string.split(id, " ")
		local nodename = splt[1]
		local param2 = tonumber(splt[2])
		local node = { name = nodename, param2 = param2 }
		minetest.bulk_set_node(positions, node)
	end

	return {
		barricades = ignited_barricades,
		bombs_slow = ignited_bombs_slow,
		bombs_quick = ignited_bombs_quick,
	}
end

-- Create particles of an ignited bomb fuse for a bomb at pos.
-- Returns particlespawner handle.
function lzr_laser.spawn_bomb_fuse_particles(pos, burn_time)
	local node = minetest.get_node(pos)
	local fuse_offset = vector.multiply(lzr_laser.get_top_dir(node.param2), 0.55)
	local fuse_pos = vector.add(pos, fuse_offset)
	local handle_p = minetest.add_particlespawner({
		amount = 16,
		time = burn_time,
		minpos = vector.subtract(fuse_pos, vector.new(0.025, 0.025, 0.025)),
		maxpos = vector.add(fuse_pos, vector.new(0.025, 0.025, 0.025)),
		minvel = vector.new(-0.1, 0.2, -0.1),
		maxvel = vector.new(0.1, 0.3, 0.1),
		minacc = vector.zero(),
		maxacc = vector.zero(),
		minsize = 0.5,
		maxsize = 0.8,
		texture = "lzr_laser_bomb_smoke.png",
	})
	return handle_p
end

-- Create sound and particle of an exploding bomb at pos.
-- bomb_node is the node table of the bomb to explode.
function lzr_laser.bomb_explosion_audiovisuals(pos, bomb_node)
	minetest.sound_play({name="lzr_laser_bomb_explode", gain=1.0}, {pos=pos}, true)

	minetest.add_particlespawner({
		amount = 30,
		time = 0.001,
		minpos = vector.subtract(pos, vector.new(0.5, 0.5, 0.5)),
		maxpos = vector.add(pos, vector.new(0.5, 0.5, 0.5)),
		minvel = vector.new(-4, -4, -4),
		maxvel = vector.new(4, 4, 4),
		minacc = vector.new(0, -lzr_globals.GRAVITY, 0),
		maxacc = vector.new(0, -lzr_globals.GRAVITY, 0),
		minsize = 1.3,
		maxsize = 1.7,
		node = bomb_node,
	})

	minetest.add_particlespawner({
		amount = 64,
		time = 0.6,
		pos = {
			min = vector.subtract(pos, 3 / 2),
			max = vector.add(pos, 3 / 2),
		},
		vel = {
			min = vector.new(-8, -8, -8),
			max = vector.new(8, 8, 8),
		},
		acc = vector.zero(),
		exptime = { min = 0.2, max = 1.0 },
		size = { min = 4, max = 8 },
		drag = vector.new(1,1,1),
		texture = {
			name = "lzr_laser_bomb_smoke_anim_1.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = -1 },
			name = "lzr_laser_bomb_smoke_anim_2.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = -1 },
			name = "lzr_laser_bomb_smoke_anim_1.png^[transformFX", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = -1 },
			name = "lzr_laser_bomb_smoke_anim_2.png^[transformFX", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = -1 },
		},
	})
	minetest.add_particlespawner({
		amount = 1,
		time = 0.01,
		pos = pos,
		vel = vector.zero(),
		acc = vector.zero(),
		exptime = 0.7,
		size = 25,
		texture = "lzr_laser_bomb_smoke_ball_big.png",
		animation = { type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = -1, },
	})
	minetest.add_particlespawner({
		amount = 4,
		time = 0.25,
		pos = pos,
		vel = vector.zero(),
		acc = vector.zero(),
		exptime = { min = 0.6, max = 0.9 },
		size = { min = 6, max = 8 },
		radius = { min = 0.5, max = math.max(0.6, 2.25) },
		texture = "lzr_laser_bomb_smoke_ball_medium.png",
		animation = { type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = -1, },
	})
end

-- Perform the damage that an exploding bomb
-- at pos would do (destroy nodes, "damage" players).
-- Note: Nodes are not actually destroyed. Instead, a list
-- of ondes to destroy will be returned.
-- Does not ignite neighboring bombs.
lzr_laser.deal_bomb_damage = function(pos)
	-- collect nodes that are vulnerable to explosions
	local explody = minetest.find_nodes_in_area(vector.subtract(pos, vector.new(1,1,1)), vector.add(pos, vector.new(1,1,1)), "group:explosion_destroys")
	local destroyed_nodes = {}
	for c=1, #explody do
		local cpos = explody[c]
		local node = minetest.get_node(cpos)
		table.insert(destroyed_nodes, cpos)
		minetest.add_particlespawner({
			amount = 16,
			time = 0.001,
			minpos = vector.subtract(cpos, vector.new(0.5, 0.5, 0.5)),
			maxpos = vector.add(cpos, vector.new(0.5, 0.5, 0.5)),
			minvel = vector.new(-0.5, -0.2, -0.5),
			maxvel = vector.new(0.5, 0.2, 0.5),
			minacc = vector.new(0, -lzr_globals.GRAVITY, 0),
			maxacc = vector.new(0, -lzr_globals.GRAVITY, 0),
			minsize = 1.5,
			maxsize = 1.5,
			node = node,
		})
	end

	-- "damage" players (visual effect + temporary slowdown)
	-- and parrots (scorch texture)
	local gs = lzr_gamestate.get_state()
	if gs == lzr_gamestate.LEVEL or gs == lzr_gamestate.LEVEL_COMPLETE then
		local objs = minetest.get_objects_inside_radius(pos, BOMB_DAMAGE_RADIUS)
		for o=1, #objs do
			if objs[o]:is_player() then
				local dist = vector.distance(pos, objs[o]:get_pos())
				local ratio = math.max(0, math.min(1, 1 - dist / BOMB_DAMAGE_RADIUS))
				lzr_damage.damage_player(objs[o], math.ceil(ratio * lzr_damage.MAX_DAMAGE), "scorch")
				lzr_slowdown.slowdown(objs[o], ratio * BOMB_SLOWDOWN_TIME, BOMB_SLOWDOWN_TIME)
			else
				local dist = vector.distance(pos, objs[o]:get_pos())
				local ent = objs[o]:get_luaentity()
				if dist <= BOMB_DAMAGE_RADIUS_PARROT and (ent.name == "lzr_parrot_npc:parrot" or ent.name == "lzr_parrot_npc:hidden_parrot") then
					ent:_scorch(BOMB_PARROT_SCORCH_TIME)
				end
			end
		end
	end

	return destroyed_nodes
end

-- Returns list of all bomb neighbors of the bomb at pos
-- that can be ignited by an explosion.
function lzr_laser.find_ignitible_bomb_neighbors(pos)
	-- ignite bombs
	local bombs = minetest.find_nodes_in_area(vector.subtract(pos, vector.new(1,1,1)), vector.add(pos, vector.new(1,1,1)), "group:bomb")
	local to_ignite = {}
	for b=1, #bombs do
		local node = minetest.get_node(bombs[b])
		if minetest.get_item_group(node.name, "bomb") == 1 then
			local def = minetest.registered_nodes[node.name]
			table.insert(to_ignite, bombs[b])
		end
	end
	return to_ignite
end
