-- lzr_menu: A "mini main menu" for Lazarr!
-- This generates a pirate ship that acts as a 'base' for the player.
-- It provides access to all the features of the game:
-- Play a level, enter the level editor, edit (some) game settings, read help,
-- and a few small gimmicks.
-- The name of the ship is "Old Shiny".

local S = minetest.get_translator("lzr_menu")
local F = minetest.formspec_escape
local FS = function(...) return F(S(...)) end

local COLOR_PICKER_MODIFIERS = { -32, -8, -1, 1, 8, 32 }

local MENU_LASER_ALPHA = "BF"

-- Check and respawn the ship parrots periodically,
-- every X seconds.
local MENU_RESPAWN_PARROTS_INTERVAL = 7.0

-- Time it takes (in seconds) to update the shown menu markers
local MENU_MARKER_UPDATE_INTERVAL = 0.5

lzr_menu = {}

--[[ "Main menu" ship options ]]

-- Note: All ship "OFFSET" globals are defined as offsets from
-- lzr_globals.MENU_SHIP_POS.

-- Min. and max. coordinates of the cuboid that defines the location of
-- several zones in the ship
lzr_menu.SHIP_ZONES = {
	captain = {
		min = vector.new(0, 6, 23.55),
		max = vector.new(14, 10, 33),
		exclusions = {
			{ min = vector.new(0, 6, 23), max = vector.new(3.5, 9, 26.5) },
			{ min = vector.new(11, 6, 23), max = vector.new(14, 10, 27) } }
	},
	bombs = {
		min = vector.new(1, 0, 2),
		max = vector.new(16, 5.5, 11.5)
	},
	blocks = {
		min = vector.new(0, 10, 24),
		max = vector.new(15, 16, 33.5)
	},
}

-- Where Goldie the Parrot spawns in the ship
lzr_menu.parrot_offset = nil
-- Look direction of Goldie the Parrot on spawning in ship
lzr_menu.parrot_yaw = nil

-- Where the hidden parrots spawn when the player has found them,
-- indexed by the param2 value of the parrot spawner node.
-- We store by param2 instead of name so this mod doesn’t have
-- to know anything about the internals of lzr_parrot_npc,
-- not even the parrot names.
lzr_menu.hidden_parrot_offsets = {}
-- Where in the ship the player respawns.
-- Used when player fell out of the map or got stuck (lzr_fallout)
lzr_menu.SHIP_PLAYER_RESPAWN_OFFSET = vector.new(1, 7, 29)
-- Where in the ship the player spawns on victory,
-- when all core levels have been completed.
lzr_menu.SHIP_PLAYER_WINSPAWN_OFFSET = vector.new(6, 1.25, 22)
-- Where the ship's gold stash begins. All gold blocks the player
-- has collected in the core levels will be placed here.
-- This is the minimum coordinate of a cuboid.
lzr_menu.SHIP_STASH_OFFSET = vector.new(4, 1, 9)
-- Size of the ship's gold stash cuboid
lzr_menu.SHIP_STASH_SIZE = vector.new(8, 3, 22)
-- This is the minimum coordinate of a cuboid for the 'first pass' of placing gold blocks
lzr_menu.SHIP_STASH_OFFSET_FIRST = lzr_menu.SHIP_STASH_OFFSET
-- Size of the ship's gold stash cuboi for the 'first pass' of placing gold blocks
lzr_menu.SHIP_STASH_SIZE_FIRST = vector.new(8, 2, 22)

-- Positions of interactive ship nodes, offset from the ship position.
-- Index is nodename, value is vector.
lzr_menu.interactive_ship_node_offsets = {}

-- Positions of paintings in the ship, offset from the ship position.
-- Index is nodename, value is vector.
lzr_menu.painting_offsets = {}

lzr_menu.SHIP_SIZE = nil

-- If true, the ship has been analyzed for interactive node offset
-- positions, so the lzr_menu.interactive_ship_node_offsets table
-- can be used.
lzr_menu.ship_analyzed = false

local registered_on_ship_builts = {}
-- Register callback function that is called when the ship has been built
-- and is ready for play. Will be called only once for the current game session.
function lzr_menu.register_on_ship_built(func)
	table.insert(registered_on_ship_builts, func)
end

local registered_on_ship_rebuilts = {}
-- Register callback function that is called when the ship has been built
-- OR rebuilt (after an update).
function lzr_menu.register_on_ship_rebuilt(func)
	table.insert(registered_on_ship_rebuilts, func)
end

local registered_on_player_ship_enters = {}
-- Register callback function that is called when the player enters the ship.
function lzr_menu.register_on_player_ship_enter(func)
	table.insert(registered_on_player_ship_enters, func)
end

local registered_on_parrots_respawns = {}
-- Register callback function that is called when this mod requests
-- all parrots at the ship to respawn
function lzr_menu.register_on_parrots_respawn(func)
	table.insert(registered_on_parrots_respawns, func)
end

local place_gold_stash = function(gold_blocks)
	if gold_blocks <= 0 then
		return
	end
	local min_pos = vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.SHIP_STASH_OFFSET_FIRST)
	local max_pos = vector.add(min_pos, lzr_menu.SHIP_STASH_SIZE_FIRST)
	-- The gold blocks are placed in multiple "passes": In the first pass, blocks are placed in a larger
	-- cuboid first, and when that one is full, we move to the next pass,
	-- which is a cuboid of the same area, but with Y height forced to 1 and placed above the previous
	-- cuboid.
	local pass = 1
	local pos = table.copy(min_pos)
	local posses = {}
	for g=0, gold_blocks-1 do
		if pos.z > max_pos.z then
			if pass < 3 then
				min_pos = table.copy(min_pos)
				max_pos = table.copy(max_pos)
				min_pos.y = max_pos.y
				max_pos.y = max_pos.y + 1
				pos = table.copy(min_pos)
				pass = pass + 1
			else
				minetest.log("warning", "[lzr_menu] Trying to place more gold blocks ("..gold_blocks..") in ship than available space! (g="..g..")")
				break
			end
		end
		local gpos = table.copy(pos)
		if g % 4 == 1 then
			gpos.x = gpos.x + 1
		elseif g % 4 == 2 then
			gpos.z = gpos.z + 1
		elseif g % 4 == 3 then
			gpos.x = gpos.x + 1
			gpos.z = gpos.z + 1
			pos.y = pos.y + 1
			if pos.y >= max_pos.y then
				pos.y = min_pos.y
				pos.x = pos.x + 3
				if pos.x > max_pos.x then
					pos.x = min_pos.x
					pos.z = pos.z + 3
				end
			end
		end
		table.insert(posses, gpos)
	end
	minetest.bulk_set_node(posses, {name="lzr_treasure:gold_block"})
	minetest.log("action", "[lzr_menu] Placed "..gold_blocks.." gold block(s) in ship treasure stash")
end

local ship_emerged = false
local gold_stashed = 0

local analyze_ship_schematic = function()
	local schem = minetest.read_schematic(minetest.get_modpath("lzr_menu").."/schematics/lzr_menu_ship.mts", { write_yslice_prob = "none" })
	if not schem then
		minetest.log("error", "[lzr_menu] Could not read ship schematic!")
		return
	end

	local size = schem.size
	local varea = VoxelArea(vector.zero(), vector.offset(size, -1, -1, -1))
	local nodes_to_construct = {}
	for d=1, #schem.data do
		local nodename = schem.data[d].name
		local pos = lzr_util.schematic_index_to_pos(d, size)
		-- Keep track of interactive ship nodes (level map, info bookshelves, speaker, etc.)
		if minetest.get_item_group(nodename, "interactive_ship_node") ~= 0 then
			table.insert(nodes_to_construct, pos)
			if not lzr_menu.interactive_ship_node_offsets[nodename] then
				lzr_menu.interactive_ship_node_offsets[nodename] = pos
			end
		end
		-- Same for paintings
		if minetest.get_item_group(nodename, "painting") ~= 0 then
			if not lzr_menu.painting_offsets[nodename] then
				lzr_menu.painting_offsets[nodename] = pos
			end
		end
		-- Delete parrot spawners and painting nodes from schematic,
		-- but record the positions first
		if nodename == "lzr_parrot_npc:parrot_spawner" then
			-- Convert degrotate angle to radians
			local yaw = (schem.data[d].param2 / 240) * (math.pi*2) + math.pi
			-- Store pos and yaw
			lzr_menu.parrot_offset = vector.offset(pos, 0, -0.5, 0)
			lzr_menu.parrot_yaw = yaw
			schem.data[d] = { name = "air", param1 = 255, param2 = 0 }
		elseif nodename == "lzr_parrot_npc:hidden_parrot_spawner" then
			local p2 = schem.data[d].param2
			schem.data[d] = { name = "air", param1 = 255, param2 = 0 }
			lzr_menu.hidden_parrot_offsets[p2] = vector.offset(pos, 0, -0.5, 0)
		elseif minetest.get_item_group(nodename, "painting") ~= 0 then
			schem.data[d] = { name = "air", param1 = 255, param2 = 0 }
		end
	end
	lzr_menu.ship_analyzed = true
	return schem, nodes_to_construct
end

local build_ship = function()
	local schem, nodes_to_construct = analyze_ship_schematic()
	minetest.place_schematic(lzr_globals.MENU_SHIP_POS, schem, "0", {}, true, "")
	for n=1, #nodes_to_construct do
		local pos = vector.add(lzr_globals.MENU_SHIP_POS, nodes_to_construct[n])
		local node = minetest.get_node(pos)
		local def = minetest.registered_nodes[node.name]
		if def and def.on_construct then
			def.on_construct(pos)
		else
			minetest.log("error", "[lzr_menu] Tried to call on_construct for an interactive ship node at "..minetest.pos_to_string(pos).." but function did not exist!")
		end
	end

	local core_level_data = lzr_levels.get_level_pack("__core")
	if lzr_levels.are_all_levels_completed(core_level_data) then
		lzr_menu.place_painting("perfect_plunderer")
	end

	-- Call 'rebuilt' callbacks
	for f=1, #registered_on_ship_rebuilts do
		registered_on_ship_rebuilts[f]()
	end
end

lzr_menu.update_treasure_stash = function(gold_blocks)
	if gold_stashed == gold_blocks then
		return
	end
	if ship_emerged then
		if gold_stashed > gold_blocks then
			build_ship()
		end
		place_gold_stash(gold_blocks)
		gold_stashed = gold_blocks
	else
		gold_stashed = gold_blocks
	end
end

lzr_levels.register_on_collected_treasure(function(treasures)
	lzr_menu.update_treasure_stash(treasures)
end)

local emerge_callback = function(blockpos, action, calls_remaining, param)
	local emerge_log = param
	minetest.log("verbose", "[lzr_menu] emerge_callback() ...")
	if action == minetest.EMERGE_ERRORED then
		emerge_log.errors = emerge_log.errors + 1
		minetest.log("error", "[lzr_menu] Error while emerging ship block (blockpos="..minetest.pos_to_string(blockpos)..", calls_remaining="..tostring(calls_remaining)..").")
	elseif action == minetest.EMERGE_CANCELLED then
		emerge_log.cancels = emerge_log.cancels + 1
		minetest.log("warning", "[lzr_menu] Emerging of ship block cancelled (blockpos="..minetest.pos_to_string(blockpos)..", calls_remaining="..tostring(calls_remaining)..").")
	elseif (action == minetest.EMERGE_GENERATED or action == minetest.EMERGE_FROM_DISK or action == minetest.EMERGE_FROM_MEMORY) then
		emerge_log.successes = emerge_log.successes + 1
	end

	if calls_remaining == 0 then
		minetest.log("action", "[lzr_menu] Ship emerging complete (final blockpos="..minetest.pos_to_string(blockpos)..")")
		minetest.log("action", "[lzr_menu] Ship emerge stats: successes="..emerge_log.successes..", cancels="..emerge_log.cancels..", errors="..emerge_log.errors)
		if emerge_log.errors > 0 then
			minetest.log("error", "[lzr_menu] Ship emerge callback completed with error(s). The pirate ship cannot be built!")
			return
		elseif emerge_log.cancels > 0 then
			minetest.log("warning", "[lzr_menu] Ship emerge callback completed with cancelled block(s).")
		end

		ship_emerged = true
		build_ship()
		if emerge_log.cancels == 0 then
			minetest.log("action", "[lzr_menu] Ship built successfully.")
		else
			minetest.log("warning", "[lzr_menu] Ship built into area with cancelled blocks.")
		end
		place_gold_stash(gold_stashed)

		if lzr_levels.is_intro_completed() then
			-- Now that the ship has *actually* been built,
			-- setting the player pos should guarantee the player
			-- stands on solid ground
			local player = minetest.get_player_by_name("singleplayer")
			lzr_menu.teleport_player_to_ship(player, "captain")
			lzr_gui.set_menu_gui(player, "captain")
			lzr_gamestate.set_loading(false)
		end

		-- Call 'built' callbacks
		for f=1, #registered_on_ship_builts do
			registered_on_ship_builts[f]()
		end

		-- Call 'rebuilt' callbacks
		for f=1, #registered_on_ship_rebuilts do
			registered_on_ship_rebuilts[f]()
		end
	end
end

local SPEAKER_NOTE_INTERVAL = 1.0

local emerge_ship = function(pos)
	if not lzr_menu.SHIP_SIZE then
		local ship_schem = minetest.read_schematic(minetest.get_modpath("lzr_menu").."/schematics/lzr_menu_ship.mts", {write_yslice_prob="none"})
		if not ship_schem then
			minetest.log("error", "[lzr_menu] Could not read ship schematic!")
			return
		end
		lzr_menu.SHIP_SIZE = ship_schem.size
	end

	local emerge_log = {
		successes = 0,
		cancels = 0,
		errors = 0,
	}
	minetest.emerge_area(pos, vector.add(pos, lzr_menu.SHIP_SIZE), emerge_callback, emerge_log)
end

minetest.register_on_joinplayer(function(player)
	lzr_player.set_menu_inventory(player)
	if lzr_levels.is_intro_completed() then
		lzr_gamestate.set_loading(true)
		lzr_gui.set_loading_gui(player)
	end
	minetest.log("action", "[lzr_menu] Starting to emerge the ship after player join")
	emerge_ship(lzr_globals.MENU_SHIP_POS)

	if lzr_levels.is_intro_completed() then
		lzr_gamestate.set_state(lzr_gamestate.MENU)
		-- NOTE: This teleport happens BEFORE the ship was actually built, so the
		-- player may fall for a moment. This is thus not reliable
		-- and is only made in prepatation for the future ship spawn.
		lzr_menu.teleport_player_to_ship(player, "captain")
		player:set_look_horizontal(0)
		player:set_look_vertical(0)
		local inv = player:get_inventory()
		for i=1,inv:get_size("main") do
			inv:set_stack("main", i, "")
		end
	end
end)

local on_punch_start_core = function(self, node, puncher)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU or lzr_gamestate.is_loading()then
		return
	end
	lzr_level_select.open_dialog(puncher, "core")
end
local on_rightclick_start_core = on_punch_start_core

local on_punch_start_custom = function(self, node, puncher)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU or lzr_gamestate.is_loading() then
		return
	end
	lzr_level_select.open_dialog(puncher, "custom")
end
local on_rightclick_start_custom = on_punch_start_custom

-- A node that starts level selection
minetest.register_node("lzr_menu:level_starter", {
	--~ A node that starts level selection
	description = S("Level Starter"),
	-- symbolized by a map
	tiles = { "lzr_menu_map.png", "blank.png" },
	paramtype = "light",
	paramtype2 = "4dir",
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {
			{ -0.5, -0.5, -0.5, 0.5, -63/128, 0.5 },
		}
	},
	use_texture_alpha = "clip",
	walkable = false,
	sunlight_propagates = true,
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Start playing"))
	end,
	on_punch = on_punch_start_core,
	on_rightclick = on_rightclick_start_core,
	groups = { interactive_ship_node = 1, breakable = 1, not_in_creative_inventory = 1, },
})

-- A node that starts custom level selection
minetest.register_node("lzr_menu:custom_level_starter", {
	--~ A node that starts custom level selection
	description = S("Custom Level Starter"),
	-- symbolized by a map
	tiles = { "lzr_menu_map_custom.png", "blank.png" },
	paramtype = "light",
	paramtype2 = "4dir",
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {
			{ -0.5, -0.5, -0.5, 0.5, -63/128, 0.5 },
		}
	},
	use_texture_alpha = "clip",
	walkable = false,
	sunlight_propagates = true,
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Play custom levels"))
	end,
	on_punch = on_punch_start_custom,
	on_rightclick = on_rightclick_start_custom,
	groups = { interactive_ship_node = 1, breakable = 1, not_in_creative_inventory = 1, },
})

local on_punch_editor = function(self, node, puncher)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU or lzr_gamestate.is_loading() then
		return
	end
	lzr_editor.enter_editor(puncher)
end
local on_rightclick_editor = function(self, node, clicker)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU or lzr_gamestate.is_loading() then
		return
	end
	lzr_editor.enter_editor(clicker)
end

minetest.register_node("lzr_menu:editor_starter", {
	description = S("Level Editor Starter"),
	-- The level editor starter is represented by a workbench with a saw on top
	tiles = {
		"(lzr_menu_editor_starter_top.png)^(lzr_menu_saw.png^[transformR270)",
		"lzr_menu_editor_starter_bottom.png",
		"lzr_menu_editor_starter_sides.png",
	},
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Enter the level editor"))
	end,
	on_punch = on_punch_editor,
	on_rightclick = on_rightclick_editor,
	groups = { interactive_ship_node = 1, breakable = 1, not_in_creative_inventory = 1, },
	sounds = lzr_sounds.node_sound_wood_defaults(),
})

local audio_settings_state = {}

local open_audio_settings = function(player)
	local form = "" ..
	-- Header
		"formspec_version[7]size[8,4.5]"

	local scrollpos, ambience_enabled
	if audio_settings_state.volume_scroll_pos then
		scrollpos = audio_settings_state.volume_scroll_pos
		ambience_enabled = audio_settings_state.ambience_enabled
	else
		local sgain = lzr_ambience.get_gain()
		scrollpos = math.max(0, math.min(1000, sgain * 1000))

		ambience_enabled = lzr_ambience.is_active()
		audio_settings_state.volume_scroll_pos = scrollpos
		audio_settings_state.ambience_enabled = tostring(ambience_enabled)
	end

	form = form .. "container[0.6,0.7]" ..
		"checkbox[0,0;ambience_enabled;"..FS("Enable music")..";"..audio_settings_state.ambience_enabled.."]" ..
	"container_end[]"

	if audio_settings_state.ambience_enabled == "true" then
		form = form .. "container[0.6,1.5]" ..
			"box[-0.2,-0.3;7.3,1.4;#00000040]" ..
			"label[0,0;"..FS("Music volume").."]" ..
			"scrollbaroptions[min=1;max=1000;smallstep=50;largestep=250]" ..
			"scrollbar[0,0.4;6.9,0.45;horizontal;volume;"..scrollpos.."]" ..
		"container_end[]"
	end

	-- Main buttons
	form = form .. "container[0.5,3]" ..
		"button_exit[0,0;3.1,0.8;apply;"..FS("Apply").."]"..
		"button_exit[4,0;3.1,0.8;cancel;"..FS("Cancel").."]"..
	"container_end[]"

	minetest.show_formspec(player:get_player_name(), "lzr_menu:audio_settings", form)
end

local use_speaker = function(speaker_pos, user)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
		return
	end
	audio_settings_state = {}
	open_audio_settings(user)
end

local update_speaker_infotext = function(pos)
	if not lzr_menu.ship_analyzed then
		minetest.log("error", "[lzr_menu] Tried to update speaker infotext before the ship was analyzed!")
		return
	end
	if minetest.get_node(pos).name ~= "lzr_menu:speaker" then
		return
	end
	local meta = minetest.get_meta(pos)
	local infotext = S("Change music settings") .. "\n\n"
	local active = lzr_ambience.is_active()
	local ambience_state = meta:get_int("ambience_state")
	-- Update infotext only if neccessary
	if active and ambience_state ~= 1 then
		infotext = infotext .. S("(Music is enabled)")
		meta:set_int("ambience_state", 1)
		meta:set_string("infotext", infotext)
	elseif not active and ambience_state ~= 2 then
		infotext = infotext .. S("(Music is disabled)")
		meta:set_int("ambience_state", 2)
		meta:set_string("infotext", infotext)
	end
end

local on_punch_speaker = function(pos, node, puncher)
	use_speaker(pos, puncher)
	update_speaker_infotext(pos)
end
local on_rightclick_speaker = function(pos, node, clicker)
	use_speaker(pos, clicker)
	update_speaker_infotext(pos)
end

minetest.register_node("lzr_menu:speaker", {
	--~ Loudspeaker
	description = S("Speaker"),
	tiles = {
		"lzr_menu_speaker_top.png",
		"lzr_menu_speaker_side.png",
		"lzr_menu_speaker_side.png",
		"lzr_menu_speaker_side.png",
		"lzr_menu_speaker_back.png",
		"lzr_menu_speaker_front.png",
	},
	paramtype2 = "4dir",
	groups = { interactive_ship_node = 1,breakable = 1, rotatable = 3, not_in_creative_inventory = 1},
	sounds = lzr_sounds.node_sound_wood_defaults(),
	is_ground_content = false,
	on_punch = on_punch_speaker,
	on_rightclick = on_rightclick_speaker,
	on_construct = function(pos)
		update_speaker_infotext(pos)
		local timer = minetest.get_node_timer(pos)
		timer:start(SPEAKER_NOTE_INTERVAL)
	end,
	on_timer = function(pos)
		if lzr_ambience.is_active() then
			local node = minetest.get_node(pos)
			local dir = minetest.fourdir_to_dir(node.param2)
			local notes = {
				"lzr_menu_speaker_note_quarter.png",
				"lzr_menu_speaker_note_eighth.png",
				"lzr_menu_speaker_note_eighth_2.png",
			}
			local note = notes[math.random(1, #notes)]
			local hue = math.random(-12, 12) * 15
			note = note .. "^[hsl:"..hue..":0:0"
			local notepos = vector.add(pos, vector.multiply(dir, -0.65))
			notepos = vector.offset(notepos, 0, -2/16, 0)
			minetest.add_particlespawner({
				amount = 1,
				time = SPEAKER_NOTE_INTERVAL / 2,
				pos = notepos,
				texture = {
					name = note,
					alpha_tween = { start = 0.6, 1, 0 },
				},
				size = 3,
				exptime = 1,
				vel = {
					min = vector.new(0, 0.2, 0),
					max = vector.new(0, 0.6, 0),
				},
			})
		end
		update_speaker_infotext(pos)
		local timer = minetest.get_node_timer(pos)
		timer:start(SPEAKER_NOTE_INTERVAL)
	end,
})

-- Instantly update the ship speaker infotext when ambience status
-- was changed (could be by chat command)
lzr_ambience.register_on_ambience_change(function(state)
	if not lzr_menu.ship_analyzed then
		return
	end
	local speaker_pos = vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.interactive_ship_node_offsets["lzr_menu:speaker"])
	update_speaker_infotext(speaker_pos)
end)

local boolean_graphics_settings = {
	-- { ID, description, tooltip, requires restart?, default value }
	{ "opaque_lasers", S("Opaque lasers"), S("If enabled, lasers aren’t translucent. Can improve performance."), true, false },
	{ "patterned_lasers", S("Draw patterns on lasers"), S("Special patterns will appear on the lasers, one for each color. Helps to distinguish lasers without relying on color alone."), true, false },
	{ "hook_overlay", S("Show rotation arrows"), S("If enabled, arrows are shown on the surface of a block when pointed to with the rotating hook."), false, false },
	{ "particles_signals",
		--~ particles that represent the activation and deactivation of blocks
		S("Signal particles"),
		S("Show particles that represent the activation and deactivation of blocks. Disabling it can improve performance."), false, true },
	{ "particles_splash", S("Footstep particles"), S("Particles appear when you move through water, on sand or other blocks."), true, true },
	{ "particles_rain",
		--~ weather effect
		S("Rain"),
		S("Enable rain in rainy levels."), false, true },
	{ "lightning",
		--~ weather effect
		S("Lightning"),
		S("Enable lightning in levels with lightning."), false, true },
}

local graphics_settings_initial = {}
for b=1, #boolean_graphics_settings do
	local settingname = boolean_graphics_settings[b][1]
	local default = boolean_graphics_settings[b][5]
	graphics_settings_initial[settingname] = minetest.settings:get_bool("lzr_"..settingname, default)
end
for c=1, #lzr_globals.COLOR_NAMES do
	local colorname = lzr_globals.COLOR_NAMES[c]
	local setting = minetest.settings:get("lzr_laser_color_"..colorname)
	if not setting or setting == "" then
		setting = lzr_laser.DEFAULT_LASER_COLORS[c]
	end
	graphics_settings_initial["color_"..colorname] = setting
end

local graphics_settings_state = table.copy(graphics_settings_initial)

local color_descriptions = {
	-- [laser color] = { <color adjective>, <laser color button tooltip>, <long description> }
	[lzr_globals.COLOR_RED] = { S("red"), S("“red” lasers"), S("Adjust the replacement color for “red” lasers") },
	[lzr_globals.COLOR_GREEN] = { S("green"), S("“green” lasers"), S("Adjust the replacement color for “green” lasers") },
	[lzr_globals.COLOR_BLUE] = { S("blue"), S("“blue” lasers"), S("Adjust the replacement color for “blue” lasers") },
	[lzr_globals.COLOR_YELLOW] = { S("yellow"), S("“yellow” lasers"), S("Adjust the replacement color for “yellow” lasers") },
	[lzr_globals.COLOR_CYAN] = { S("cyan"), S("“cyan” lasers"), S("Adjust the replacement color for “cyan” lasers") },
	[lzr_globals.COLOR_MAGENTA] = { S("magenta"), S("“magenta” lasers"), S("Adjust the replacement color for “magenta” lasers") },
	[lzr_globals.COLOR_WHITE] = { S("white"), S("“white” lasers"), S("Adjust the replacement color for “white” lasers") },
}

local open_color_picker = function(player, colorname)
	local r = graphics_settings_state["picked_color_red"]
	local g = graphics_settings_state["picked_color_green"]
	local b = graphics_settings_state["picked_color_blue"]
	local colorcode = lzr_globals.COLOR_NAMES_SWAPPED[colorname]
	-- If missing or invalid setting, fallback to default
	if not r then
		if colorcode then
			local lhexcode = lzr_laser.DEFAULT_LASER_COLORS[colorcode]
			if lhexcode then
				r, g, b = lzr_util.hexcode_to_rgb(lhexcode)
			end
		end
		if not r then
			minetest.log("error", "[lzr_menu] open_color_picker could not pick default color for: "..colorname.."!")
			return
		end
	end
	local hexcode = lzr_util.rgb_to_hexcode(r,g,b)
	local channels = {
		{ "red", S("Red"), r },
		{ "green", S("Green"), g },
		{ "blue", S("Blue"), b },
	}

	local lalpha
	if graphics_settings_state.opaque_lasers then
		lalpha = "FF"
	else
		lalpha = MENU_LASER_ALPHA
	end
	local color_adj = color_descriptions[colorcode][1]

	local form = "" ..
	"formspec_version[7]size[9,7.1]"..

	"image[8.3,0.3;0.4,0.4;lzr_menu_tooltip_icon.png]"..
	"tooltip[8.3,0.3;0.4,0.4;"..
	minetest.formspec_escape(
		S("Here you can redefine the color @1 for lasers.", color_adj).."\n"..
		S("The game will pretend this is @1, even if the actual color is different.", color_adj)
	).."]"..

	"label[0.5,0.5;"..minetest.formspec_escape(color_descriptions[colorcode][3]).."]"..
	"box[1.0,1.2;7,0.2;#"..hexcode..lalpha.."]"..
	"container[0.5,2.0]"
	for c=1, #channels do
		local id = channels[c][1]
		local desc = channels[c][2]
		local defval = channels[c][3]
		local y = c - 1
		-- get modifier
		local function gm(index)
			local txt
			if COLOR_PICKER_MODIFIERS[index] < 0 then
				--~ Subtract color value in custom laser color menu
				return FS("−@1", math.abs(COLOR_PICKER_MODIFIERS[index]))
			else
				--~ Add color value in custom laser color menu
				return FS("+@1", COLOR_PICKER_MODIFIERS[index])
			end
		end
		form = form .. "label[0.39,"..(y+0.3)..";"..minetest.formspec_escape(desc).."]" ..
			"button[2,"..y..";0.7,0.5;channel_"..id.."_mmm;"..gm(1).."]" ..
			"button[2.7,"..y..";0.7,0.5;channel_"..id.."_mm;"..gm(2).."]"..
			"button[3.4,"..y..";0.7,0.5;channel_"..id.."_m;"..gm(3).."]"..
			--~ Color value number in custom laser color menu. @1 current value, @2 maximum possible value
			"label[4.4,"..(y+0.3)..";"..FS("@1/@2", defval, 255).."]"..
			"button[5.6,"..y..";0.7,0.5;channel_"..id.."_p;"..gm(4).."]"..
			"button[6.3,"..y..";0.7,0.5;channel_"..id.."_pp;"..gm(5).."]"..
			"button[7.0,"..y..";0.7,0.5;channel_"..id.."_ppp;"..gm(6).."]"
		local chancode
		if id == "red" then
			chancode = string.format("#%02x0000", defval)
		elseif id == "green" then
			chancode = string.format("#00%02x00", defval)
		elseif id == "blue" then
			chancode = string.format("#0000%02x", defval)
		end
		form = form .. "box[0.0,"..(y+0.15)..";0.3,0.3;"..chancode..lalpha.."]"
	end
	form = form .. "container_end[]"

	form = form .. "button[0.5,4.9;2,0.5;default;"..FS("Default").."]"..
	"tooltip[default;"..FS("Pick the default color").."]"

	form = form .. "button[0.7,6;3.5,0.8;ok;"..FS("OK").."]"..
	"button[4.75,6;3.5,0.8;cancel;"..FS("Cancel").."]"

	minetest.show_formspec(player:get_player_name(), "lzr_menu:color_picker", form)
end

local open_graphics_settings = function(player)
	graphics_settings_state.picking_color = nil
	local put_asterisk = function(x, y)
		return "label["..x..","..y..";"..minetest.formspec_escape("(*)").."]" ..
		"tooltip["..(x-0.08)..","..(y-0.2)..";0.4,0.4;"..S("This setting will take effect after a restart.").."]"
	end

	local form = "" ..
	-- Header
		"formspec_version[7]size[12,10]"..

	-- Boolean settings (true/false, represented by checkbox)
		"container[-0.6,0]"

	local y = 0
	for s=1, #boolean_graphics_settings do
		local settingname = boolean_graphics_settings[s][1]
		local desc = boolean_graphics_settings[s][2]
		local tooltip = boolean_graphics_settings[s][3]
		local needs_restart = boolean_graphics_settings[s][4]

		form = form .. "box[1,"..(y+0.5)..";1,1;#0000003f]"
		if graphics_settings_state[settingname] then
			form = form .. "image[1.1,"..(y+0.6)..";0.8,0.8;lzr_menu_settings_"..settingname.."_on.png]"
		else
			form = form .. "image[1.1,"..(y+0.6)..";0.8,0.8;lzr_menu_settings_"..settingname.."_off.png]"
		end
		form = form .. "checkbox[2.3,"..(y+1)..";"..settingname..";"..F(desc)..";"..tostring(graphics_settings_state[settingname]).."]"
		form = form .. "tooltip["..settingname..";"..F(tooltip).."]"
		if needs_restart and graphics_settings_state[settingname] ~= graphics_settings_initial[settingname] then
			form = form .. put_asterisk(6, y+1)
		end
		y = y + 1.1
	end

	form = form .. "container_end[]"

	local get_color_setting_texture = function(colorname)
		local hexcode = graphics_settings_state["color_"..colorname]
		local lalpha
		if graphics_settings_state.opaque_lasers then
			lalpha = "FF"
		else
			lalpha = MENU_LASER_ALPHA
		end
		if type(hexcode) == "string" and string.len(hexcode) == 6 then
			return "[fill:16x16:#"..hexcode..lalpha.."^[mask:lzr_menu_color_select_button_mask.png"
		elseif hexcode == "" or hexcode == nil then
			local colorcode = lzr_globals.COLOR_NAMES_SWAPPED[colorname]
			hexcode = lzr_laser.DEFAULT_LASER_COLORS[colorcode]
			-- This is an error
			if not hexcode then
				minetest.log("error", "[lzr_menu] get_color_setting_texture: Could not get default color for: "..colorname)
				return "blank.png"
			end
			return "[fill:16x16:#"..hexcode..lalpha.."^[mask:lzr_menu_color_select_button_mask.png"
		else
			-- Invalid color in settings
			return "blank.png"
		end
	end

	local are_colors_modified = function(colornames)
		for c=1, #colornames do
			local colorname = colornames[c]
			if graphics_settings_initial["color_"..colorname] ~= graphics_settings_state["color_"..colorname] then
				return true
			end
		end
	end

	-- Laser color buttons
	form = form .. "container[6.5,0.6]"
	form = form .. "box[-0.2,-0.2;5.4,3;#00000040]"
	form = form .. "label[0,0;"..FS("Laser colors:").."]"


	local ui_colors = { "red", "green", "blue", "yellow", "magenta", "cyan", "white" }
	if are_colors_modified(ui_colors) then
		form = form .. put_asterisk(4.5, 0)
	end


	local ibx = 0
	local iby = 0
	local max_x = 0
	local max_y = 0
	for c=1, #ui_colors do
		local col = ui_colors[c]
		local x = 0 + ibx*1.7
		local y = 0.3 + iby
		max_x = math.max(x, max_x)
		max_y = math.max(y, max_y)

		form = form .. "image_button["..x..","..y..";1.6,0.5;"..minetest.formspec_escape(get_color_setting_texture(col))..";color_"..col..";]" ..
			"tooltip[color_"..col..";"..minetest.formspec_escape(color_descriptions[lzr_globals.COLOR_NAMES_SWAPPED[col]][2]).."]"

		ibx = ibx + 1
		if ibx >= 3 then
			ibx = 0
			iby = iby + 0.6
		end
	end
	form = form .. "button[0,"..(max_y+0.7)..";3.2,0.5;color_reset;"..FS("Reset").."]" ..
		"tooltip[color_reset;"..FS("Reset all laser colors").."]" ..
		"container_end[]"

	-- Main buttons
	form = form .. "container[0,"..(y+1).."]" ..
		"button[2.5,0;3.1,0.8;apply;"..FS("Apply").."]"..
		"button_exit[6.5,0;3.1,0.8;cancel;"..FS("Cancel").."]"..
		"container_end[]"

	minetest.show_formspec(player:get_player_name(), "lzr_menu:graphics_settings", form)
end


minetest.register_on_player_receive_fields(function(player, formname, fields)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
		return
	end

	if formname ~= "lzr_menu:audio_settings" then
		audio_settings_state = {}
	end

	local pname = player:get_player_name()
	if formname == "lzr_menu:graphics_settings" then
		for s=1, #boolean_graphics_settings do
			local settingname = boolean_graphics_settings[s][1]
			if fields[settingname] == "true" then
				graphics_settings_state[settingname] = true
				open_graphics_settings(player)
			elseif fields[settingname] == "false" then
				graphics_settings_state[settingname] = false
				open_graphics_settings(player)
			end
		end
		for c=1, #lzr_globals.COLOR_NAMES do
			local color = lzr_globals.COLOR_NAMES[c]
			if fields["color_"..color] then
				graphics_settings_state.picking_color = color
				local r, g, b = lzr_util.hexcode_to_rgb(graphics_settings_state["color_"..color])
				graphics_settings_state["picked_color_red"] = r
				graphics_settings_state["picked_color_green"] = g
				graphics_settings_state["picked_color_blue"] = b
				open_color_picker(player, color)
				return
			end
		end
		if fields.color_reset then
			for c=1, #lzr_globals.COLOR_NAMES do
				local colorname = lzr_globals.COLOR_NAMES[c]
				graphics_settings_state["color_"..colorname] = lzr_laser.DEFAULT_LASER_COLORS[c]
			end
			open_graphics_settings(player)
			return
		end
		if fields.apply then
			local restart_required = false

			for k, v in pairs(graphics_settings_initial) do
				if v ~= graphics_settings_state[k] then
					local is_boolean_setting = false
					for b=1, #boolean_graphics_settings do
						if k == boolean_graphics_settings[b][1] then
							is_boolean_setting = true
							if boolean_graphics_settings[b][4] then
								restart_required = true
							end
							break
						end
					end
					if not is_boolean_setting then
						restart_required = true
					end
					if restart_required then
						break
					end
				end
			end

			for b=1, #boolean_graphics_settings do
				local settingname = boolean_graphics_settings[b][1]
				minetest.settings:set_bool("lzr_"..settingname, graphics_settings_state[settingname])
			end
			lzr_hook.update_hook_overlay_setting()

			for c=1, #lzr_globals.COLOR_NAMES do
				local colorname = lzr_globals.COLOR_NAMES[c]
				local cvalue = graphics_settings_state["color_"..colorname]
				if not cvalue then
					cvalue = ""
				end
				minetest.settings:set("lzr_laser_color_"..colorname, cvalue)
			end

			if restart_required then
				local form = "formspec_version[7]size[10,4]"..
					"textarea[1,0.8;8,1.5;;;"..FS("The game needs to be restarted for the new graphics settings to take effect.").."]"..
					"button_exit[1,2.5;3,0.8;restart;"..FS("Restart").."]"..
					"button_exit[5.5,2.5;3,0.8;cancel;"..FS("Keep playing").."]"
				minetest.show_formspec(pname, "lzr_menu:confirm_restart", form)
			else
				minetest.close_formspec(pname, "lzr_menu:graphics_settings")
				return
			end
		elseif fields.quit or fields.cancel then
			graphics_settings_state = {}
			for b=1, #boolean_graphics_settings do
				local settingname = boolean_graphics_settings[b][1]
				local default = boolean_graphics_settings[b][5]
				graphics_settings_state[settingname] = minetest.settings:get_bool("lzr_"..settingname, default)
			end
			for c=1, #lzr_globals.COLOR_NAMES do
				local colorname = lzr_globals.COLOR_NAMES[c]
				graphics_settings_state["color_"..colorname] = minetest.settings:get("lzr_laser_color_"..colorname)
			end
		end
	elseif formname == "lzr_menu:confirm_restart" then
		if fields.restart then
			minetest.disconnect_player(pname, S("You’ve quit the game. Start the game again for the settings to take effect."))
		end
	elseif formname == "lzr_menu:color_picker" then
		local channels = { "red", "green", "blue" }
		local modifiers = { "mmm", "mm", "m", "p", "pp", "ppp" }
		for c=1, #channels do
		for m=1, #modifiers do
			local channel = channels[c]
			local modifier = modifiers[m]
			if fields["channel_"..channel.."_"..modifier] then
				local val = graphics_settings_state["picked_color_"..channel]
				if val then
					val = val + COLOR_PICKER_MODIFIERS[m]
					val = math.floor(math.min(255, math.max(0, val)))
				else
					val = 255
				end

				graphics_settings_state["picked_color_"..channel] = val
				open_color_picker(player, graphics_settings_state.picking_color)
			end
		end
		end
		if fields.default then
			local colorname = graphics_settings_state.picking_color
			if colorname then
				local colorcode = lzr_globals.COLOR_NAMES_SWAPPED[colorname]
				local default_color_hex = lzr_laser.DEFAULT_LASER_COLORS[colorcode]
				local r,g,b = lzr_util.hexcode_to_rgb(default_color_hex)
				graphics_settings_state["picked_color_red"] = r
				graphics_settings_state["picked_color_green"] = g
				graphics_settings_state["picked_color_blue"] = b
				open_color_picker(player, colorname)
			end
		end
		if fields.ok then
			local r = graphics_settings_state["picked_color_red"]
			local g = graphics_settings_state["picked_color_green"]
			local b = graphics_settings_state["picked_color_blue"]
			if r and g and b then
				r = math.floor(math.min(255, math.max(0, r)))
				g = math.floor(math.min(255, math.max(0, g)))
				b = math.floor(math.min(255, math.max(0, b)))
				local hex = lzr_util.rgb_to_hexcode(r, g, b)
				if graphics_settings_state.picking_color and hex then
					graphics_settings_state["color_"..graphics_settings_state.picking_color] = hex
				end
			end
			open_graphics_settings(player)
		elseif fields.cancel or fields.quit then
			open_graphics_settings(player)
		end
	elseif formname == "lzr_menu:reset_confirm" then
		if not minetest.global_exists("lzr_reset") then
			return
		end
		if fields.reset then
			lzr_reset.reset_progress()
			if lzr_menu.ship_analyzed then
				local pos = vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.interactive_ship_node_offsets["lzr_menu:reset_bomb"])
				pos = vector.offset(pos, 0, 0.6, 0)
				lzr_laser.bomb_explosion_audiovisuals(pos, {name="lzr_menu:reset_bomb", param2=0})
			end
		end
	elseif formname == "lzr_menu:audio_settings" then
		if fields.volume then
			local evnt = minetest.explode_scrollbar_event(fields.volume)
			if evnt.type == "CHG" then
				audio_settings_state.volume_scroll_pos = evnt.value
			end
		end
		if fields.ambience_enabled then
			audio_settings_state.ambience_enabled = fields.ambience_enabled
			open_audio_settings(player)
			return
		end
		if fields.apply then
			local temp_active = audio_settings_state.ambience_enabled == "true"
			local real_active = lzr_ambience.is_active()
			local speaker_pos = vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.interactive_ship_node_offsets["lzr_menu:speaker"])
			if real_active and not temp_active then
				lzr_ambience.toggle_ambience_by_player(player, true)
				minetest.sound_play({name="lzr_menu_speaker_turn_off", gain=0.4}, {pos=speaker_pos}, true)
			elseif not real_active and temp_active then
				lzr_ambience.toggle_ambience_by_player(player, true)
				minetest.sound_play({name="lzr_menu_speaker_turn_on", gain=0.4}, {pos=speaker_pos}, true)
			end
			if audio_settings_state.volume_scroll_pos then
				local volume = audio_settings_state.volume_scroll_pos / 1000
				volume = math.max(0, math.min(1, volume))
				lzr_ambience.set_gain(volume)
			end
			audio_settings_state = {}
		end
		if fields.cancel or fields.quit then
			audio_settings_state = {}
		end
	end
end)

local function on_rightclick_television(_, _, player)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
		return
	end

	open_graphics_settings(player)
end
on_punch_television = on_rightclick_television

-- Television to update graphics settings
minetest.register_node("lzr_menu:television", {
	description = S("Television"),
	tiles = {
		"xdecor_television_left.png^[transformR90",
		"xdecor_television_left.png^[transformR270",
		"xdecor_television_left.png",
		"xdecor_television_left.png^[transformR180",
		"xdecor_television_back.png",
		{ name = "xdecor_television_front_animated.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 1 } },
	},
	paramtype2 = "4dir",
	groups = { interactive_ship_node = 1,breakable = 1, rotatable = 3, not_in_creative_inventory = 1},
	sounds = lzr_sounds.node_sound_metal_defaults(),
	is_ground_content = false,
	on_punch = on_punch_television,
	on_rightclick = on_rightclick_television,
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Change graphics settings"))
	end,
})

local register_bookshelf = function(id, def)
	minetest.register_node("lzr_menu:bookshelf_"..id, {
		description = def.description,
		paramtype2 = "4dir",
		tiles = {
			{name="default_wood.png", align_style="world"},
			{name="default_wood.png", align_style="world"},
			{name="default_wood.png", align_style="world"},
			{name="default_wood.png", align_style="world"},
			"default_wood.png^lzr_decor_bookshelf.png",
		},
		sounds = lzr_sounds.node_sound_wood_defaults(),
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_string("infotext", def.infotext)
		end,
		on_punch = function(self, node, puncher)
			if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
				return
			end
			def.action(puncher)
		end,
		on_rightclick = function(self, node, clicker)
			if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
				return
			end
			def.action(clicker)
		end,
		groups = { interactive_ship_node = 1,not_in_creative_inventory = 1, breakable = 1, rotatable = 3 },
	})
end

register_bookshelf("info", {
	description = S("Gameplay Info Book Bookshelf"),
	infotext = S("Read about how to play the game"),
	action = function(player)
		lzr_infobooks.open_bookshelf(player:get_player_name(), "how_to_play")
	end,
})

register_bookshelf("info_editor", {
	description = S("Editor Info Book Bookshelf"),
	infotext = S("Read about how to use the level editor"),
	action = function(player)
		lzr_infobooks.open_bookshelf(player:get_player_name(), "editor")
	end,
})

register_bookshelf("info_blocks", {
	description = S("Blocks Info Book Bookshelf"),
	infotext = S("Learn about the blocks in the game"),
	action = function(player)
		lzr_infobooks.open_bookshelf(player:get_player_name(), "blocks")
	end,
})

register_bookshelf("credits", {
	description = S("Credits Bookshelf"),
	infotext = S("Display the list of people who worked on this game"),
	action = function(player)
		lzr_credits.show_credits(player)
	end,
})

local on_rightclick_level_packs_viewer = function(_, _, player)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
		return
	end
	lzr_level_select.open_stats_dialog(player)
end
on_punch_level_packs_viewer = on_rightclick_level_packs_viewer

-- Not actually a bookshelf anymore, nodename kept for
-- historic reasons
minetest.register_node("lzr_menu:bookshelf_level_packs", {
	description = S("Level Packs Viewer"),
	tiles = {
		"xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
		"xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
		"xdecor_cabinet_sides.png", "xdecor_cabinet_front.png",
	},
	paramtype2 = "4dir",
	groups = { interactive_ship_node = 1, breakable = 1, rotatable = 3, not_in_creative_inventory = 1},
	sounds = lzr_sounds.node_sound_wood_defaults(),
	is_ground_content = false,
	on_punch = on_punch_level_packs_viewer,
	on_rightclick = on_punch_level_packs_viewer,
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Display information and stats about the installed level packs"))
	end,
})

local register_painting = function(id, desc, img, infotext)
	minetest.register_node("lzr_menu:painting_"..id, {
		description = desc,
		drawtype = "nodebox",
		paramtype2 = "wallmounted",
		paramtype = "light",
		sunlight_propagates = true,
		walkable = false,
		node_box = {
			type = "wallmounted",
			wall_top = {-0.4375, 0.4375, -0.3725, 0.4375, 0.5, 0.3725},
			wall_bottom = {-0.4375, -0.5, -0.3725, 0.4375, -0.4375, 0.3725},
			wall_side = {-0.5, -0.3725, -0.4375, -0.4375, 0.3725, 0.4375},
		},
		tiles = {img, "lzr_menu_painting_back.png"},
		use_texture_alpha = "clip",
		groups = { painting = 1, breakable = 1, rotatable = 3, not_in_creative_inventory = 1 },
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_string("infotext", infotext)
		end,
	})
end

-- This painting symbolizes completion of the all core levels
register_painting("perfect_plunderer", S("Painting: Perfect Plunderer"), "lzr_menu_painting_perfect_plunderer.png", S("Found every treasure of the known world"))

-- This painting symbolizes the player finding all parrots
register_painting("parrot_finder", S("Painting: Parrot Finder"), "lzr_menu_painting_parrot_finder.png", S("Found every hidden parrot"))


local on_punch_reset_bomb = function(self, node, puncher)
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
		return
	end
	local form = "formspec_version[7]size[6,4]"..
		"textarea[0.5,0.5;5,2;;;"..minetest.formspec_escape(S("Do you want to reset the entire game progress? This cannot be undone.")).."]"..
		"button_exit[0.5,3;2,0.6;reset;"..minetest.formspec_escape(S("Yes")).."]"..
		"set_focus[cancel]"..
		"button_exit[3.5,3;2,0.6;cancel;"..minetest.formspec_escape(S("No")).."]"
	minetest.show_formspec(puncher:get_player_name(), "lzr_menu:reset_confirm", form)
end
local on_rightclick_reset_bomb = on_punch_reset_bomb

-- This allows the player to reset the game progress
minetest.register_node("lzr_menu:reset_bomb", {
	--~ A special "bomb" that resets the game progress
	description = S("Reset Bomb"),
	paramtype2 = "facedir",
	tiles = {"lzr_menu_reset_bomb_top.png^lzr_laser_fixed.png", "lzr_menu_reset_bomb_bottom.png^lzr_laser_fixed.png", "lzr_menu_reset_bomb_side.png^lzr_laser_fixed.png"},
	groups = { interactive_ship_node = 1, breakable = 1, not_in_creative_inventory = 1 },
	sounds = lzr_sounds.node_sound_metal_defaults(),
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", S("Reset game progress"))
	end,
	on_punch = on_punch_reset_bomb,
	on_rightclick = on_rightclick_reset_bomb,
})

-- Spawn Goldie the Parrot, if it's not already there
function lzr_menu.spawn_parrot()
	if not lzr_menu.ship_analyzed then
		minetest.log("error", "[lzr_menu] Tried to spawn normal parrot before ship was analyzed!")
		return
	end
	local offset = lzr_menu.parrot_offset
	if not offset then
		minetest.log("error", "[lzr_menu] Unknown parrot ship spawn offset!")
		return
	end
	local pos = vector.add(lzr_globals.MENU_SHIP_POS, offset)
	-- Check if parrot is already spawned at this pos
	local objs = minetest.get_objects_inside_radius(pos, 0.5)
	for o=1, #objs do
		local obj = objs[o]
		local ent = obj:get_luaentity()
		if ent and ent.name == "lzr_parrot_npc:parrot" then
			minetest.log("info", "[lzr_menu] Normal parrot not spawned in ship; already there")
			-- No spawn if parrot is already there
			return
		end
	end

	-- Spawn parrot, then check for success
	local obj = minetest.add_entity(pos, "lzr_parrot_npc:parrot")
	if not obj then
		minetest.log("error", "[lzr_menu] Failed to spawn in-ship parrot!")
		return
	end
	local ent = obj:get_luaentity()
	if not ent then
		minetest.log("error", "[lzr_menu] Failed to get luaentity of in-ship parrot!")
		return
	end
	obj:set_yaw(lzr_menu.parrot_yaw)

	minetest.log("action", "[lzr_menu] Normal parrot spawned in ship")
end


-- Spawn a hidden parrot.
-- * possible_param2_values is a list of all possible param2 values
--   the corresponding spawner node might have.
-- * parrot_name: parrot identifier
function lzr_menu.spawn_hidden_parrot(possible_param2_values, parrot_name)
	if not lzr_menu.ship_analyzed then
		minetest.log("error", "[lzr_menu] Tried to spawn hidden parrot before ship was analyzed!")
		return
	end
	local offset
	-- We got a list of all possible param2 values the
	-- hidden parrot might have, so we compare against
	-- out own list.
	for i=1, #possible_param2_values do
		local p2 = possible_param2_values[i]
		offset = lzr_menu.hidden_parrot_offsets[p2]
		if offset then
			break
		end
	end
	if not offset then
		minetest.log("error", "[lzr_menu] Unknown hidden parrot ship spawn offset for parrot '"..tostring(parrot_name).."'!")
		return
	end
	local pos = vector.add(lzr_globals.MENU_SHIP_POS, offset)
	-- Check if this parrot is already spawned at this pos
	local objs = minetest.get_objects_inside_radius(pos, 0.5)
	for o=1, #objs do
		local obj = objs[o]
		local ent = obj:get_luaentity()
		if ent and ent.name == "lzr_parrot_npc:hidden_parrot" and ent._hidden_id == parrot_name then
			minetest.log("info", "[lzr_menu] Hidden parrot '"..tostring(parrot_name).."' not spawned in ship; already there")
			-- No spawn if parrot is already there
			return
		end
	end

	-- Spawn parrot, then check for success
	local obj = minetest.add_entity(pos, "lzr_parrot_npc:hidden_parrot")
	if not obj then
		minetest.log("error", "[lzr_menu] Failed to spawn in-ship hidden parrot '"..tostring(parrot_name).."'!")
		return
	end
	local ent = obj:get_luaentity()
	if not ent then
		minetest.log("error", "[lzr_menu] Failed to get luaentity of in-ship hidden parrot '"..tostring(parrot_name).."'!")
		return
	end
	-- Ship parrots look randomly
	local yaw = (math.random(0,359) / 360) * (math.pi*2)
	obj:set_yaw(yaw)

	-- Assign parrot name
	ent:_init(parrot_name)
	minetest.log("action", "[lzr_menu] Hidden parrot '"..tostring(parrot_name).."' spawned in ship")
end

-- Spawn Goldie the Parrot when ship was built, rebuilt
-- or the player enters the ship
lzr_menu.register_on_ship_built(function()
	lzr_menu.spawn_parrot()
end)

lzr_menu.register_on_ship_rebuilt(function()
	lzr_menu.spawn_parrot()
end)
lzr_menu.register_on_player_ship_enter(function()
	if lzr_menu.ship_analyzed then
		lzr_menu.spawn_parrot()
	end
end)

function lzr_menu.teleport_player_to_ship(player, location)
	if location == "captain" then
		minetest.log("action", "[lzr_menu] Teleporting player to captain's cabin")
		lzr_silent_set_pos.silent_set_pos(player, vector.add(lzr_globals.MENU_SHIP_POS, lzr_globals.MENU_SHIP_PLAYER_SPAWN_OFFSET))
		player:set_look_horizontal(0)
		player:set_look_vertical(0)
	elseif location == "skulls" then
		minetest.log("action", "[lzr_menu] Teleporting player to skulls location")
		lzr_silent_set_pos.silent_set_pos(player, vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.SHIP_PLAYER_RESPAWN_OFFSET))
		player:set_look_horizontal(math.pi)
		player:set_look_vertical(0)
	elseif location == "victory" then
		minetest.log("action", "[lzr_menu] Teleporting player to victory location")
		lzr_silent_set_pos.silent_set_pos(player, vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.SHIP_PLAYER_WINSPAWN_OFFSET))
		player:set_look_horizontal(math.pi)
		player:set_look_vertical(0)
	else
		minetest.log("error", "[lzr_menu] Tried to teleport player to invalid ship location: "..tostring(location))
		return
	end
	local zone = lzr_menu.get_player_zone(player)
	for f=1, #registered_on_player_ship_enters do
		registered_on_player_ship_enters[f](player, location)
	end
end

function lzr_menu.remove_hidden_parrots()
	if not lzr_menu.SHIP_SIZE then
		return
	end
	local max_pos = vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.SHIP_SIZE)
	local objs = minetest.get_objects_in_area(lzr_globals.MENU_SHIP_POS, max_pos)
	for o=1, #objs do
		local obj = objs[o]
		local ent = obj:get_luaentity()
		if ent and ent.name == "lzr_parrot_npc:hidden_parrot" then
			obj:remove()
		end
	end
end

function lzr_menu.place_painting(painting)
	if not lzr_menu.ship_analyzed then
		minetest.log("error", "[lzr_menu] Tried to place painting before the ship was analyzed!")
	end
	if painting == "perfect_plunderer" then
		minetest.set_node(vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.painting_offsets["lzr_menu:painting_perfect_plunderer"]), {name="lzr_menu:painting_perfect_plunderer", param2=4})
		minetest.log("action", "[lzr_menu] Added painting 'perfect_plunderer'")
	elseif painting == "parrot_finder" then
		minetest.set_node(vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.painting_offsets["lzr_menu:painting_parrot_finder"]), {name="lzr_menu:painting_parrot_finder", param2=4})
		minetest.log("action", "[lzr_menu] Added painting 'parrot_finder'")
	end
end
function lzr_menu.remove_painting(painting)
	if not lzr_menu.ship_analyzed then
		minetest.log("error", "[lzr_menu] Tried to remove a painting before the ship was analyzed!")
	end
	if painting == "perfect_plunderer" then
		minetest.remove_node(vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.painting_offsets["lzr_menu:painting_perfect_plunderer"]))
		minetest.log("action", "[lzr_menu] Removed painting 'perfect_plunderer'")
	elseif painting == "parrot_finder" then
		minetest.remove_node(vector.add(lzr_globals.MENU_SHIP_POS, lzr_menu.painting_offsets["lzr_menu:painting_parrot_finder"]))
		minetest.log("action", "[lzr_menu] Removed painting 'parrot_finder'")
	end
end

lzr_gamestate.register_on_exit_state(function(state)
	if state == lzr_gamestate.MENU then
		local players = minetest.get_connected_players()
		for p=1, #players do
			lzr_gui.hide_menu_markers(players[p])
		end
	end
end)

lzr_gamestate.register_on_enter_state(function(state)
	if state == lzr_gamestate.MENU then
		local player = minetest.get_player_by_name("singleplayer")
		lzr_player.set_menu_inventory(player)

		lzr_ambience.set_ambience("pirates")
		lzr_sky.set_sky("bright_blue")
		lzr_weather.set_weather("clear")
		lzr_triggers.reset_triggers()

		-- Goldie the Parrot explains the ship the first time the player enters it
		if not lzr_levels.was_menu_seen() then
			local core_level_data = lzr_levels.get_level_pack("__core")
			-- ... but only the player did NOT already finished the core levels, to avoid collision with the "game completed" message.
			-- (This scenario can happen when the level completion status was manipulated.)
			if not (core_level_data and lzr_levels.are_all_levels_completed(core_level_data)) then
				lzr_speech.speak(player,
					--~ "Old Shiny" is the name of the player’s pirate ship. Feel free to translate the name, or leave it as-is. The choice is yours.
					S("Welcome to the Old Shiny, our ship! This is the center of all our pirate operations.").."\n"..
					S("Here you can plan your next travel, change the game settings, check out the loot and do more.").."\n\n"..
					S("Let’s fill this ship with gold!"),
					"goldie")
			end
			lzr_levels.set_menu_seen(true)
		end
	end
end)

lzr_menu.get_player_zone = function(player)
	local pos = player:get_pos()

	for zone_id, zone in pairs(lzr_menu.SHIP_ZONES) do
		-- Main area
		if vector.in_area(pos,
				vector.add(lzr_globals.MENU_SHIP_POS, zone.min),
				vector.add(lzr_globals.MENU_SHIP_POS, zone.max)) then
			if zone.exclusions then
				local in_exclusion = false
				-- Exclusion zones
				for i=1, #zone.exclusions do
					if vector.in_area(pos,
							vector.add(lzr_globals.MENU_SHIP_POS, zone.exclusions[i].min),
							vector.add(lzr_globals.MENU_SHIP_POS, zone.exclusions[i].max)) then
						in_exclusion = true
						break
					end
				end
				if not in_exclusion then
					return zone_id
				end
			else
				return zone_id
			end
		end
	end
	return nil
end

local timer = 0
minetest.register_globalstep(function(dtime)
	timer = timer + dtime
	if timer < MENU_MARKER_UPDATE_INTERVAL then
		return
	end
	timer = 0
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU or lzr_gamestate.is_loading() then
		return
	end
	local player = minetest.get_player_by_name("singleplayer")
	if not player then
		return
	end
	local pos = player:get_pos()

	local zone = lzr_menu.get_player_zone(player)
	if zone ~= nil then
		lzr_gui.show_menu_markers(player, zone)
	else
		lzr_gui.hide_menu_markers(player)
	end
end)

-- Periodically respawn the parrots at the ship
-- if they’re missing.
-- Because, parrots may sometimes fail to spawn
-- (or fail to stay spawned) correctly.
-- If the parrots are already there, this does nothing.
local ptimer = 0
minetest.register_globalstep(function(dtime)
	ptimer = ptimer + dtime
	if ptimer < MENU_RESPAWN_PARROTS_INTERVAL then
		return
	end
	ptimer = 0
	if not lzr_menu.ship_analyzed then
		return
	end
	if lzr_gamestate.get_state() ~= lzr_gamestate.MENU or lzr_gamestate.is_loading() then
		return
	end

	-- We can respawn Goldie directly
	lzr_menu.spawn_parrot()

	-- Call callbacks to request parrot respawning
	-- from other mods
	for i=1, #registered_on_parrots_respawns do
		registered_on_parrots_respawns[i]()
	end
end)

-- Force-load the player spawn position
local function forceload()
	local ok = minetest.forceload_block(vector.add(lzr_globals.MENU_SHIP_POS, lzr_globals.MENU_SHIP_PLAYER_SPAWN_OFFSET), true)
	if not ok then
		minetest.log("warning", "[lzr_menu] Could not forceload menu ship")
	else
		minetest.log("action", "[lzr_menu] Menu ship successfully forceloaded")
	end
end
minetest.after(0, function()
	forceload()
end)

minetest.register_chatcommand("ship", {
	privs = {},
	params = "",
	description = S("Go to the ship"),
	func = function(name, param)
		if lzr_gamestate.is_loading() then
			return false, S("Can’t go to the ship while loading!")
		end
		local state = lzr_gamestate.get_state()
		if state == lzr_gamestate.MENU then
			return false, S("You’re already on the ship!")
		else
			lzr_levels.go_to_menu()
			return true
		end
	end,
})

minetest.register_alias("lzr_menu:editorbook", "lzr_menu:editor_starter")
minetest.register_alias("lzr_menu:startbook", "lzr_menu:level_starter")
minetest.register_alias("lzr_menu:startbook_custom", "lzr_menu:custom_level_starter")
