-- Allows players to spectate other players.
-- 
-- ForbiddenJ

Camera = workspace.Camera
Player = game.Players.LocalPlayer
SpawnNotificationFunction = game.ReplicatedStorage.ClientStorage.SpawnNotification

SpectateTarget = script.Parent.SpectateTarget -- ObjectValue that refers to a Player object or nil.
SpectatePrevButton = script.Parent.GuiBar.SpectatePrevButton
SpectateNextButton = script.Parent.GuiBar.SpectateNextButton
SpectateButton = script.Parent.GuiBar.SpectateButton

LastSpectated = nil
SpectateNotification = nil
SpectateTargetEventConnections = {}

function TargetCameraOnPlayer(player)
	assert(typeof(player) == "Instance" and player:IsA("Player"))
	
	-- Try to target the camera on the other player. Fallback to the local player's
	-- character if the other player's character is dead and is not the local player.
	local humanoid = (player.Character and player.Character:WaitForChild("Humanoid", 0.5)) or
		(Player.Character and Player.Character:WaitForChild("Humanoid", 0.5))
	
	Camera.CameraSubject = humanoid
end
function UpdateSpectateGui()
	local targetPlayer = SpectateTarget.Value
	local isSpectating = targetPlayer ~= nil
	SpectatePrevButton.Visible = isSpectating
	SpectateNextButton.Visible = isSpectating
	
	if targetPlayer ~= LastSpectated then
		-- Clear the old event connections.
		for i, v in pairs(SpectateTargetEventConnections) do
			v:Disconnect()
			SpectateTargetEventConnections[i] = nil
		end
		
		if isSpectating then
			-- Spectate the target player.
			if SpectateNotification == nil or SpectateNotification.Parent == nil then
				SpectateNotification = SpawnNotificationFunction:Invoke("", Color3.new(1, 1, 1), -1)
			end
			SpectateNotification.LifetimeLeft.Value = -1
			SpectateNotification.Text = "Spectating " .. targetPlayer.Name
			SpectateTargetEventConnections[1] = targetPlayer.CharacterAdded:Connect(UpdateSpectateGui)
		else
			-- Reset the GUI to normal.
			if SpectateNotification ~= nil and SpectateNotification.Parent ~= nil then
				SpectateNotification.LifetimeLeft.Value = 0
			end
			TargetCameraOnPlayer(Player)
		end
	end
	
	-- Exempt from player changes for for CharacterAdded events.
	if isSpectating then
		TargetCameraOnPlayer(SpectateTarget.Value)
	end
	
	LastSpectated = SpectateTarget.Value
end
function ToggleIsSpectating()
	if SpectateTarget.Value == nil then
		SpectateTarget.Value = Player
	else
		SpectateTarget.Value = nil
	end
end
function SpectatePrevious()
	local targetPlayer = SpectateTarget.Value
	if targetPlayer ~= nil then
		local players = game.Players:GetPlayers()
		
		-- Find the index of the player we're currently spectating.
		local currentI = nil
		for i, player in ipairs(players) do
			if player == targetPlayer then
				currentI = i
				break
			end
		end
		
		-- What's the previous valid player index?
		local targetI = currentI > 1 and currentI - 1 or #players
		
		-- Spectate the player at targetI.
		SpectateTarget.Value = players[targetI]
	end
end
function SpectateNext()
	local targetPlayer = SpectateTarget.Value
	if targetPlayer ~= nil then
		local players = game.Players:GetPlayers()
		
		-- Find the index of the player we're currently spectating.
		local currentI = nil
		for i, player in ipairs(players) do
			if player == targetPlayer then
				currentI = i
				break
			end
		end
		
		-- What's the next valid player index?
		local targetI = currentI < #players and currentI + 1 or 1
		
		-- Spectate the player at targetI.
		SpectateTarget.Value = players[targetI]
	end
end

UpdateSpectateGui()

SpectateTarget.Changed:Connect(UpdateSpectateGui)
SpectateButton.MouseButton1Click:Connect(ToggleIsSpectating)
SpectatePrevButton.MouseButton1Click:Connect(SpectatePrevious)
SpectateNextButton.MouseButton1Click:Connect(SpectateNext)
Player.PlayState.Changed:Connect(function(value)
	if value == "Playing" then
		SpectateTarget.Value = nil
	end
end)
game.Players.PlayerRemoving:Connect(function(player)
	if player == SpectateTarget.Value then
		SpectateTarget.Value = Player
	end
end)