local S, PS = minetest.get_translator("lzr_parrot_npc")
local FS = function(...) return minetest.formspec_escape(S(...)) end
local NS = function(s) return s end

local F = minetest.formspec_escape

local mod_storage = minetest.get_mod_storage()

lzr_parrot_npc = {}

--~ Parrot name. The player’s main companion and hint-giver. Named after gold
local GOLDIE_NAME = NS("Goldie the Parrot")
--~ Parrot name. The player’s main companion and hint-giver. Named after gold
local GOLDIE_NAME_SHORT = NS("Goldie")

local HIDDEN_PARROT_NOT_FOUND = 0
local HIDDEN_PARROT_FOUND = 1

local HIDDEN_PARROT_FLY_HEIGHT = 20
local HIDDEN_PARROT_FLY_SPEED = 3
local HIDDEN_PARROT_FLY_DESPAWN_DISTANCE = 200
local HIDDEN_PARROT_FLY_DELAY = 2.0

-- Mininum and maximum delay in second between idle parrot animations
local PARROT_ANIMATION_DELAY_MIN = 2.0
local PARROT_ANIMATION_DELAY_MAX = 8.0

-- Check if parrot is stuck in solid block every this many seconds
local PARROT_STUCK_CHECK_INTERVAL = 1.0
-- Vertical fly speed of parrot to try to get unstuck
local PARROT_UNSTUCK_FLY_SPEED = 2.0
-- Vertical fly speed of parrot if falling
local PARROT_FALL_FLY_SPEED = -1.0

-- Pitch of parrot call sound when scorched
local PARROT_SCORCHED_CALL_PITCH = 0.8

-- Radius around parrot to look for players to
-- display the "talk" HUD indivator
-- (only if _talk_mark in parrot entity is true)
local PARROT_TALK_MARKER_RADIUS = 6

local speaker_portraits = {
	goldie = "lzr_parrot_npc_goldie_portrait.png",
	ruby = "lzr_parrot_npc_ruby_portrait.png",
	emmy = "lzr_parrot_npc_emmy_portrait.png",
	saphie = "lzr_parrot_npc_saphie_portrait.png",
	garnie = "lzr_parrot_npc_garnie_portrait.png",
	tuckie = "lzr_parrot_npc_tuckie_portrait.png",
	dimey = "lzr_parrot_npc_dimey_portrait.png",
}

-- Holds name and object of hidden parrot speaker who last spawned a speak dialog
local last_hidden_speaker = nil
local last_hidden_speaker_object = nil

local PARROT_ANIMS = {
	idle = { frame_range = { x=0, y=0 }, frame_speed = 2 },
	flap_small = { frame_range = { x=0, y=2 }, frame_speed = 6 },
	flap_big = { frame_range = { x=2, y=4 }, frame_speed = 20 },
	head_bounce = { frame_range = { x=4, y=6 }, frame_speed = 10 },
	head_roll_right = { frame_range = { x=6, y=8 }, frame_speed = 2 },
	head_roll_left = { frame_range = { x=8, y=10 }, frame_speed = 2 },
	head_roll_leftright = { frame_range = { x=6, y=10 }, frame_speed = 2 },
}

local function make_call(to_player, pitch)
	minetest.sound_play({name="lzr_parrot_npc_curr"}, {to_player=to_player:get_player_name(), pitch=pitch})
end

-- List of hidden parrots. These parrots, together with Goldie,
-- replicate the 7 laser colors.
local hidden_parrots = {
	"ruby",
	"emmy",
	"saphie",
	"garnie",
	"tuckie",
	"dimey",
}
local hidden_parrot_names = {
	--~ Parrot name; named after ruby, a red gemstone
	ruby = NS("Ruby the Hidden Parrot"),
	--~ Parrot name; named after emerald, a green gemstone
	emmy = NS("Emmy the Hidden Parrot"),
	--~ Parrot name; named after saphire, a blue gemstone
	saphie = NS("Saphie the Hidden Parrot"),
	--~ Parrot name; named after garnet, a magenta gemstone
	garnie = NS("Garnie the Hidden Parrot"),
	--~ Parrot name; named after turquoise, a gemstone
	tuckie = NS("Tuckie the Hidden Parrot"),
	--~ Parrot name; named after diamond
	dimey = NS("Dimey the Hidden Parrot"),
}
local hidden_parrot_names_short = {
	--~ Parrot name; named after ruby, a red gemstone
	ruby = NS("Ruby"),
	--~ Parrot name; named after emerald, a green gemstone
	emmy = NS("Emmy"),
	--~ Parrot name; named after saphire, a blue gemstone
	saphie = NS("Saphie"),
	--~ Parrot name; named after garnet, a magenta gemstone
	garnie = NS("Garnie"),
	--~ Parrot name; named after turquoise, a gemstone
	tuckie = NS("Tuckie"),
	--~ Parrot name; named after diamond
	dimey = NS("Dimey"),
}

lzr_parrot_npc.get_hidden_parrot_name = function(num)
	return hidden_parrots[num]
end

lzr_parrot_npc.was_hidden_parrot_found = function(parrot_name)
	local found = mod_storage:get_int("lzr_parrot_npc:hidden_parrot_"..parrot_name)
	if found == HIDDEN_PARROT_FOUND then
		return true
	else
		return false
	end
end

lzr_parrot_npc.were_all_hidden_parrots_found = function()
	for p=1, #hidden_parrots do
		if not lzr_parrot_npc.was_hidden_parrot_found(hidden_parrots[p]) then
			return false
		end
	end
	return true
end

lzr_parrot_npc.count_found_hidden_parrots = function()
	local found = 0
	for p=1, #hidden_parrots do
		if lzr_parrot_npc.was_hidden_parrot_found(hidden_parrots[p]) then
			found = found + 1
		end
	end
	return found
end

-- Make parrot face player
local face_player = function(parrot, player)
	local papos = parrot.object:get_pos()
	local plpos = player:get_pos()
	papos.y = 0
	plpos.y = 0
	local dir = vector.direction(papos, plpos)
	local yaw = minetest.dir_to_yaw(dir)
	parrot.object:set_yaw(yaw)
end

local texture_repeat = function(texture)
	local textures = {}
	for t=1, 10 do
		table.insert(textures, texture)
	end
	return textures
end

-- Make the given parrot object react to player.
-- Shows associated text of current level (if present),
-- show
local react = function(parrot, player)
	if player and player:is_player() then
		local state = lzr_gamestate.get_state()
		local spoken = false
		if parrot._scorched then
			--~ Parrot speech indicating a parrot is coughing after an explosion
			lzr_speech.speak(player, S("*cough* *cough*"), "goldie", "lzr_parrot_npc_scorched_portrait.png", PARROT_SCORCHED_CALL_PITCH)
			spoken = true
		elseif state == lzr_gamestate.LEVEL or state == lzr_gamestate.LEVEL_COMPLETE then
			local speeches = lzr_levels.get_npc_texts()
			if speeches and speeches.goldie and speeches.goldie ~= "" then
				lzr_speech.speak(player, speeches.goldie, "goldie")
				spoken = true
			end
		elseif state == lzr_gamestate.MENU then
			local core_level_data = lzr_levels.get_level_pack("__core")
			local treasures = lzr_levels.count_total_collected_treasures(core_level_data)
			local text
			if treasures == 0 then
				text = S("I’m sad because we don’t have any treasures.")
			else
				text = PS("We have @1 gold block in our possession.", "We have @1 gold blocks in our possession.", treasures, treasures)
			end
			lzr_speech.speak(player, text, "goldie")
			spoken = true
		end
		-- Remove "Talk to me" nametag after talking
		if spoken and parrot._talk_mark then
			parrot.object:set_nametag_attributes({text=""})
			parrot._talk_mark = nil
		end
		if not spoken then
			make_call(player)
		end

		face_player(parrot, player)

		-- Bounce head
		if parrot._animation_timer > 0.5 and not parrot._falling and not parrot._unstucking then
			local anim = PARROT_ANIMS.head_bounce
			parrot.object:set_animation(anim.frame_range, anim.frame_speed, 0, false)
			parrot._animation_timer = 0
			parrot._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
		end
	end
end

local pos_needs_unstuck = function(pos)
	local node = minetest.get_node(pos)
	local def = minetest.registered_nodes[node.name]
	if def and def.walkable and (minetest.get_item_group(node.name, "takable") ~= 0 or minetest.get_item_group(node.name, "skull") ~= 0) then
		local apos = vector.offset(pos, 0, 1, 0)
		local anode = minetest.get_node(apos)
		local adef = minetest.registered_nodes[anode.name]
		if adef and adef.walkable then
			if minetest.get_item_group(anode.name, "takable") ~= 0 or minetest.get_item_group(anode.name, "skull") ~= 0 then
				return true
			else
				return false
			end
		else
			return true
		end
	else
		return false
	end
end
local is_on_solid = function(pos)
	local bpos = vector.offset(pos, 0, -0.05, 0)
	local node = minetest.get_node(bpos)
	local def = minetest.registered_nodes[node.name]
	if not def or def.walkable then
		return true
	else
		return false
	end
end

local parrot_check_stuck = function(self)
	if not self._unstucking and not self._falling then
		local pos = self.object:get_pos()
		if pos_needs_unstuck(pos) then
			self._unstucking = true
			self._falling = false
			self.object:set_velocity({x=0, y=PARROT_UNSTUCK_FLY_SPEED, z=0})
			local anim = PARROT_ANIMS.flap_big
			self.object:set_animation(anim.frame_range, anim.frame_speed, 0, true)
		elseif not is_on_solid(pos) then
			self._falling = true
			self._unstucking = false
			self.object:set_velocity({x=0, y=PARROT_FALL_FLY_SPEED, z=0})
			local anim = PARROT_ANIMS.flap_big
			self.object:set_animation(anim.frame_range, anim.frame_speed, 0, true)
		end
		self._stuck_check_timer = 0
	end
end

local parrot_activate = function(self)
	self.object:set_armor_groups({immortal=1})
	self._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
end
local parrot_activate_goldie = function(self)
	parrot_activate(self)
	self.object:set_properties({
		infotext = S(GOLDIE_NAME_SHORT)
	})
end

local parrot_step_idle = function(self, dtime)
	if self._scorched then
		self._scorched_timer = self._scorched_timer + dtime
		if self._scorched_timer >= self._unscorched_time then
			self.object:set_properties({
				textures = self._base_textures,
			})
			self._scorched = false
		end
	end
	self._stuck_check_timer = self._stuck_check_timer + dtime
	if self._stuck_check_timer >= PARROT_STUCK_CHECK_INTERVAL then
		parrot_check_stuck(self)
	end
	if self._talk_mark then
		local pos = self.object:get_pos()
		local objs = minetest.get_objects_inside_radius(pos, PARROT_TALK_MARKER_RADIUS)
		local player_found = false
		for o=1, #objs do
			if objs[o]:is_player() then
				player_found = true
				break
			end
		end
		if player_found then
			--~ A parrot asks the player to talk to it
			self.object:set_nametag_attributes({
				text = S("Talk to me!"),
				color = "#FFFF00FF",
				bgcolor = "#00000080",
			})
		else
			self.object:set_nametag_attributes({text = ""})
		end
	end
	if self._unstucking then
		local pos = self.object:get_pos()
		if not pos_needs_unstuck(pos) then
			self.object:set_velocity({x=0, y=0, z=0})
			self.object:set_animation(PARROT_ANIMS.idle.frame_range, PARROT_ANIMS.idle.frame_speed, 0, false)
			self._falling = false
			self._unstucking = false
		end
		return
	elseif self._falling then
		local pos = self.object:get_pos()
		if is_on_solid(pos) then
			self.object:set_velocity({x=0, y=0, z=0})
			self.object:set_animation(PARROT_ANIMS.idle.frame_range, PARROT_ANIMS.idle.frame_speed, 0, false)
			self._falling = false
			self._unstucking = false
		end
		return
	end

	-- Play random animation in random intervals
	self._animation_timer = self._animation_timer + dtime
	if self._animation_timer >= self._next_animation_at then
		local idle_anims = {
			"flap_small",
			"flap_small",
			"flap_small",
			"head_bounce",
			"head_roll_right",
			"head_roll_right",
			"head_roll_right",
			"head_roll_left",
			"head_roll_left",
			"head_roll_left",
		}
		local a = math.random(1, #idle_anims)
		local anim = PARROT_ANIMS[idle_anims[a]]
		self.object:set_animation(anim.frame_range, anim.frame_speed, 0, false)

		self._animation_timer = 0
		self._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
	end
end

local parrot_scorch = function(self, time)
	self.object:set_properties({
		textures=texture_repeat("lzr_parrot_npc_scorched.png"),
	})
	self._scorched = true
	self._scorched_timer = 0
	self._unscorched_time = time
end

minetest.register_entity("lzr_parrot_npc:parrot", {
	initial_properties = {
		visual = "mesh",
		mesh = "lzr_parrot_npc_parrot.gltf",
		visual_size = { x=1, y=1, z=1 },
		textures = texture_repeat("lzr_parrot_npc_goldie.png"),
		backface_culling = false,
		static_save = false,
		physical = false,
		collide_with_objects = false,
		selectionbox = {
			-0.15, 0, -0.15, 0.15, 0.65, 0.3, rotate = true,
		},
	},
	_animation_timer = 0,
	_next_animation_at = nil,
	_stuck_check_timer = 0,
	_unstucking = false,
	_scorched = false,
	_scorched_timer = nil,
	_unchorched_time = nil,
	_scorch = parrot_scorch,
	_base_textures = texture_repeat("lzr_parrot_npc_goldie.png"),

	on_activate = parrot_activate_goldie,
	on_step = parrot_step_idle,
	on_rightclick = function(self, clicker)
		react(self, clicker)
	end,
	on_punch = function(self, puncher)
		react(self, puncher)
	end,
})

local mark_hidden_parrot_as_found = function(player, parrot_name)
	local found = mod_storage:get_int("lzr_parrot_npc:hidden_parrot_"..parrot_name)
	if found == HIDDEN_PARROT_FOUND then
		if lzr_parrot_npc.were_all_hidden_parrots_found() then
			lzr_menu.place_painting("parrot_finder")
		end
		return false
	else
		mod_storage:set_int("lzr_parrot_npc:hidden_parrot_"..parrot_name, HIDDEN_PARROT_FOUND)
		lzr_menu.spawn_hidden_parrot(parrot_name)
		minetest.log("action", "[lzr_parrot_npc] Player found hidden parrot '"..parrot_name.."'")
		if lzr_parrot_npc.were_all_hidden_parrots_found() then
			lzr_menu.place_painting("parrot_finder")
		end
		return true
	end
end

local react_hidden = function(parrot, player)
	if player and player:is_player() then
		local hidden_parrot_identity = parrot._hidden_id
		local call_pitch
		if hidden_parrot_identity and mark_hidden_parrot_as_found(player, hidden_parrot_identity) then
			local found = lzr_parrot_npc.count_found_hidden_parrots()
			local text

			-- First found parrot
			if found == 1 then
				local friends = {}
				for h=1, #hidden_parrots do
					if hidden_parrots[h] ~= hidden_parrot_identity then
						table.insert(friends, S(hidden_parrot_names_short[hidden_parrots[h]]))
					end
				end
				local f1, f2, f3, f4, f5 = friends[1], friends[2], friends[3], friends[4], friends[5]
				text = S("Hey, you found my secret hideout! Now it’s useless, such a shame …").."\n"..
				S("I’ve got an idea. Let me come with you, okay?").."\n\n"..
				S("I have five friends scattered around the world, I want to meet them again. Their names are @1, @2, @3, @4 and @5.", f1, f2, f3, f4, f5)
			-- Last found parrot
			elseif found == #hidden_parrots then
				text = S("Incredible! It’s you! I’ve heard a lot from your journeys. Now the whole gang is re-united!").."\n"
				if hidden_parrot_identity == "tuckie" then
					text = text .. S("I’ve got a beautiful portrait of myself. You can have it. How nice is that?")
				else
					text = text .. S("I’ve found a beautiful portrait of our friend Tuckie. You can have it. How nice is that?")
				end
			-- Found parrot, neither first or last
			else
				local remaining = #hidden_parrots - found
				text = S("You found my secret hiding spot!").."\n"..
				PS("Only @1 parrot is still in hiding.", "There are still @1 hidden parrots left to go.", remaining, remaining)
			end

			last_hidden_speaker = hidden_parrot_identity
			last_hidden_speaker_object = parrot

			lzr_speech.speak(player, text, hidden_parrot_identity, "lzr_parrot_npc_scorched_portrait.png", PARROT_SCORCHED_CALL_PITCH)
			face_player(parrot, player)
			return
		else
			make_call(player, call_pitch)
		end
		if parrot._flying_home_phase == 0 then
			face_player(parrot, player)
			-- Bounce head
			if parrot._animation_timer > 0.5 then
				local anim = PARROT_ANIMS.head_bounce
				parrot.object:set_animation(anim.frame_range, anim.frame_speed, 0, false)
				parrot._animation_timer = 0
				parrot._next_animation_at = math.random(PARROT_ANIMATION_DELAY_MIN, PARROT_ANIMATION_DELAY_MAX)
			end
		end
	end
end

local hidden_parrot_fly_home = function(parrot)
	if parrot._flying_home_phase < 1 then
		parrot._flying_home_phase = 1
		parrot._fly_timer = 0
		minetest.log("action", "[lzr_parrot_npc] Hidden parrot will fly home soon: "..tostring(last_hidden_speaker))
	end
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname == "lzr_speech:speech" and fields.quit and last_hidden_speaker then
		local parrot = last_hidden_speaker_object
		hidden_parrot_fly_home(parrot)
	end
end)

local parrot_step_hidden = function(self, dtime)
	if self._flying_home_phase ~= 0 then
		if self._scorched and self._base_textures then
			self._scorched_timer = self._scorched_timer + dtime
			if self._scorched_timer >= self._unscorched_time then
				self.object:set_properties({
					textures = self._base_textures,
				})
				self._scorched = false
			end
		end

		-- Phase 1: Wait for a short time before lifting off
		if self._flying_home_phase == 1 then
			self._fly_timer = self._fly_timer + dtime
			if self._fly_timer > HIDDEN_PARROT_FLY_DELAY then
				self._flying_home_phase = 2
				self._fly_start_pos = self.object:get_pos()
				self.object:set_velocity({x=0,y=3,z=0})
				local anim = PARROT_ANIMS.flap_big
				self.object:set_animation(anim.frame_range, anim.frame_speed, 0, true)
				minetest.log("action", "[lzr_parrot_npc] Hidden parrot lifts off: "..tostring(self._hidden_id))
			end
		-- Phase 2: Liftoff! Fly upwards until we reach a certain height
		elseif self._flying_home_phase == 2 then
			local pos = self.object:get_pos()
			local fly_height = pos.y - self._fly_start_pos.y
			if fly_height >= HIDDEN_PARROT_FLY_HEIGHT then
				self.object:set_velocity(vector.zero())
				self._flying_home_phase = 3
				local hpos = table.copy(pos)
				hpos.y = 0
				local spos = table.copy(lzr_globals.MENU_SHIP_POS)
				spos.y = 0
				local dir = vector.direction(hpos, spos)
				local vel = vector.multiply(dir, HIDDEN_PARROT_FLY_SPEED)
				self.object:set_velocity(vel)
				local yaw = minetest.dir_to_yaw(dir)
				self.object:set_yaw(yaw)
				minetest.log("action", "[lzr_parrot_npc] Hidden parrot flies home: "..tostring(self._hidden_id))
				return
			end
		-- Phase 3: Fly horizontally towards the main ship (rough direction)
		elseif self._flying_home_phase == 3 then
			local pos = self.object:get_pos()
			if vector.distance(pos, self._fly_start_pos) > HIDDEN_PARROT_FLY_DESPAWN_DISTANCE then
				minetest.log("action", "[lzr_parrot_npc] Hidden parrot has flown enough and despawns: "..tostring(self._hidden_id))
				self.object:remove()
				return
			end
		end
	else
		parrot_step_idle(self, dtime)
	end
end

-- Hidden Parrot NPC. One of the game's hidden parrots.
-- When interacted with, the parrot counts as found.
minetest.register_entity("lzr_parrot_npc:hidden_parrot", {
	initial_properties = {
		visual = "mesh",
		mesh = "lzr_parrot_npc_parrot.gltf",
		visual_size = { x=1, y=1, z=1 },
		textures = {
			texture_repeat("lzr_parrot_npc_ruby.png"),
		},
		static_save = false,
		physical = false,
		collide_with_objects = false,
		selectionbox = {
			-0.15, 0, -0.15, 0.15, 0.65, 0.3, rotate = true,
		},
		backface_culling = false,
	},
	_animation_timer = 0,
	_next_animation_at = nil,
	_stuck_check_timer = 0,
	_unstucking = false,
	_scorched = false,
	_scorched_timer = nil,
	_unchorched_time = nil,
	_scorch = parrot_scorch,
	_base_textures = texture_repeat("lzr_parrot_npc_ruby.png"),

	_fly_start_pos = nil,
	_fly_timer = 0,
	_flying_home_phase = 0,
	-- Hidden parrot ID
	_hidden_id = nil,
	-- Initialize hidden parrot with the given ID (parrot identifier).
	-- MUST be called right after spawning entity.
	_init = function(self, hidden_id)
		self._hidden_id = hidden_id
		local name = S(hidden_parrot_names_short[hidden_id])
		local textures = texture_repeat("lzr_parrot_npc_"..hidden_id..".png")
		self.object:set_properties({
			textures = textures,
			infotext = name,
		})
		self._base_textures = textures
	end,

	on_activate = parrot_activate,
	on_step = parrot_step_hidden,
	on_rightclick = function(self, clicker)
		react_hidden(self, clicker)
	end,
	on_punch = function(self, puncher)
		react_hidden(self, puncher)
	end,
})

minetest.register_node("lzr_parrot_npc:stand", {
	description = S("Parrot Stand"),
	drawtype = "nodebox",
	paramtype = "light",
	paramtype2 = "4dir",
	sunlight_propagates = true,
	is_ground_content = false,
	node_box = {
		type = "fixed",
		fixed = {
			{ -0.5, -0.5, -0.5, 0.5, 1/16, 0.5 }, -- base (slab-like)
			{ -1/16, 1/16, -1/16, 1/16, 7/16, 1/16 }, -- vertical stick
			{ -6/16, 7/16, -1/16, 6/16, 0.5, 1/16 }, -- parrot stick
		},
	},
	tiles = { { name = "default_wood.png", align_style = "world" } },
	groups = { breakable = 1 },
	sounds = lzr_sounds.node_sound_wood_defaults(),
})

minetest.register_node("lzr_parrot_npc:parrot_spawner", {
	--~ A block that spawns a normal parrot
	description = S("Parrot Spawner"),
	_tt_help = S("Goldie the Parrot will spawn here").."\n"..
		S("(only one per level allowed)"),
	tiles = {
		{ name = "lzr_parrot_npc_goldie_spawner_top.png", backface_culling = true },
		{ name = "lzr_parrot_npc_goldie_spawner_bottom.png", backface_culling = true },
		{ name = "lzr_parrot_npc_goldie_spawner_left.png", backface_culling = true },
		{ name = "lzr_parrot_npc_goldie_spawner_right.png", backface_culling = true },
		{ name = "lzr_parrot_npc_goldie_spawner_back.png", backface_culling = true },
		{ name = "lzr_parrot_npc_goldie_spawner_front.png", backface_culling = true },
	},
	paramtype2 = "degrotate",
	drawtype = "mesh",
	mesh = "lzr_parrot_npc_cube.obj",
	selection_box = {
		type = "fixed",
		fixed = { -0.4, -0.4, -0.4, 0.4, 0.4, 0.4 },
	},
	visual_scale = 0.8,
	wield_scale = { x=0.8, y=0.8, z=0.8 },
	paramtype = "light",
	sunlight_propagates = true,
	walkable = false,
	groups = { breakable = 1, rotatable = 3, spawner = 1 },
	use_texture_alpha = "clip",
	sounds = {
		_rotate = "",
	},
})

minetest.register_node("lzr_parrot_npc:hidden_parrot_spawner", {
	--~ A block that spawns a hidden parrot
	description = S("Hidden Parrot Spawner"),
	_tt_help = S("A hidden parrot will spawn here"),
	tiles = {
		{ name = "lzr_parrot_npc_hidden_parrot_spawner_top.png", backface_culling = true },
		{ name = "lzr_parrot_npc_hidden_parrot_spawner_bottom.png", backface_culling = true },
		{ name = "lzr_parrot_npc_hidden_parrot_spawner_left.png", backface_culling = true },
		{ name = "lzr_parrot_npc_hidden_parrot_spawner_right.png", backface_culling = true },
		{ name = "lzr_parrot_npc_hidden_parrot_spawner_back.png", backface_culling = true },
		{ name = "lzr_parrot_npc_hidden_parrot_spawner_front.png", backface_culling = true },
	},
	paramtype2 = "color4dir",
	drawtype = "mesh",
	palette = "lzr_parrot_npc_hidden_parrot_spawner_palette.png",
	mesh = "lzr_parrot_npc_cube.obj",
	selection_box = {
		type = "fixed",
		fixed = { -0.4, -0.4, -0.4, 0.4, 0.4, 0.4 },
	},
	visual_scale = 0.8,
	wield_scale = { x=0.8, y=0.8, z=0.8 },
	paramtype = "light",
	sunlight_propagates = true,
	walkable = false,
	-- hide the hidden parrot spawner ;)
	groups = { breakable = 1, rotatable = 3, spawner = 1, not_in_creative_inventory = 1 },
	use_texture_alpha = "clip",
	sounds = {
		_rotate = "",
	},
	drop = "lzr_parrot_npc:hidden_parrot_spawner",
})

function lzr_parrot_npc.clear_hidden_parrot_progress()
	for h=1, #hidden_parrots do
		mod_storage:set_int("lzr_parrot_npc:hidden_parrot_"..hidden_parrots[h], HIDDEN_PARROT_NOT_FOUND)
	end
	lzr_menu.remove_painting("parrot_finder")

	minetest.log("action", "[lzr_parrot_npc] Hidden parrot progress was cleared")
end

local function spawn_menu_parrots()
	for p=1, #hidden_parrots do
		local parrot_name = hidden_parrots[p]
		local found = mod_storage:get_int("lzr_parrot_npc:hidden_parrot_"..parrot_name)
		if found == HIDDEN_PARROT_FOUND then
			lzr_menu.spawn_hidden_parrot(parrot_name)
		end
	end
end

lzr_menu.register_on_ship_built(function()
	spawn_menu_parrots()
end)

lzr_menu.register_on_ship_rebuilt(function()
	spawn_menu_parrots()
	if lzr_parrot_npc.were_all_hidden_parrots_found() then
		lzr_menu.place_painting("parrot_finder")
	end
end)

lzr_menu.register_on_player_ship_enter(function()
	spawn_menu_parrots()
end)

-- Notify parrots to instantly trigger unstuck algorithm when a node was placed
minetest.register_on_placenode(function(pos, node)
	local def = minetest.registered_nodes[node.name]
	if def and def.walkable then
		local objs = minetest.get_objects_inside_radius(pos, 1)
		for o=1, #objs do
			local obj = objs[o]
			local ent = obj:get_luaentity()
			if ent and (ent.name == "lzr_parrot_npc:parrot" or ent.name == "lzr_parrot_npc:hidden_parrot") then
				parrot_check_stuck(ent)
			end
		end
	end
end)
-- Notify parrots to instantly trigger unstuck algorithm when a node was dug
minetest.register_on_dignode(function(pos, node)
	local apos = vector.offset(pos, 0, 1, 0)
	local objs = minetest.get_objects_inside_radius(apos, 1)
	for o=1, #objs do
		local obj = objs[o]
		local ent = obj:get_luaentity()
		if ent and (ent.name == "lzr_parrot_npc:parrot" or ent.name == "lzr_parrot_npc:hidden_parrot") then
			parrot_check_stuck(ent)
		end
	end
end)

-- Register parrot speakers
for id, portrait in pairs(speaker_portraits) do
	local name
	if id == "goldie" then
		name = S(GOLDIE_NAME)
	else
		name = S(hidden_parrot_names[id])
	end
	lzr_speech.register_speaker(id, name, portrait, "lzr_parrot_npc_curr")
end

