Pinkstack/roblox-luau-corpus-text huggingface dataset:
-- Functions


function TeamManager:CreateTeams()
	local newTeam = Instance.new("Team", game.Teams)
	newTeam.TeamColor = BrickColor.new("Bright red")
	TeamPlayers[newTeam] = {}
	TeamScores[newTeam] = 0
	
	newTeam = Instance.new("Team", game.Teams)
	newTeam.TeamColor = BrickColor.new("Bright blue")
	TeamPlayers[newTeam] = {}
	TeamScores[newTeam] = 0
end

function TeamManager:CreateFFATeam()
	local newTeamColor = BrickColor.Random()
	while GetTeamFromColor(newTeamColor) do
		newTeamColor = BrickColor.Random()
	end
	
	local newTeam = Instance.new("Team", game.Teams)
	newTeam.TeamColor = newTeamColor
	TeamPlayers[newTeam] = {}
	TeamScores[newTeam] = 0
	return newTeam
end

function TeamManager:AssignPlayerToTeam(player)
	local smallestTeam
	local lowestCount = math.huge
	for team, playerList in pairs(TeamPlayers) do
		if #playerList < lowestCount then
			smallestTeam = team
			lowestCount = #playerList
		end
	end
	
	if not Configurations.TEAMS and (not smallestTeam or #TeamPlayers[smallestTeam] > 0) then
		smallestTeam = TeamManager:CreateFFATeam()
	end
	
	table.insert(TeamPlayers[smallestTeam], player)
	player.Neutral = false
	player.TeamColor = smallestTeam.TeamColor
end

function TeamManager:RemovePlayer(player)
	local team = GetTeamFromColor(player.TeamColor)
	local teamTable = TeamPlayers[team]
	for i = 1, #teamTable do
		if teamTable[i] == player then
			table.remove(teamTable, i)
			if not Configurations.TEAMS then
				team:Destroy()
			end
			return
		end
	end
end

function TeamManager:ClearTeamScores()
	for _, team in pairs(Teams:GetTeams()) do
		TeamScores[team] = 0
		if Configurations.TEAMS then
			DisplayManager:UpdateScore(team, 0)
		end
	end
end

function TeamManager:AddTeamScore(teamColor, score)
	local team = GetTeamFromColor(teamColor)
	TeamScores[team] = TeamScores[team] + score
	if Configurations.TEAMS then
		DisplayManager:UpdateScore(team, TeamScores[team])
	end
end

function TeamManager:HasTeamWon()
	for _, team in pairs(Teams:GetTeams()) do
		if TeamScores[team] >= Configurations.POINTS_TO_WIN then
			return team
		end
	end
	return false
end

function TeamManager:GetWinningTeam()
	local highestScore = 0
	local winningTeam = nil
	for _, team in pairs(Teams:GetTeams()) do
		if TeamScores[team] > highestScore then
			highestScore = TeamScores[team]
			winningTeam = team
		end
	end
	return winningTeam
end

function TeamManager:AreTeamsTied()
	local teams = Teams:GetTeams()
	local highestScore = 0
	local tied = false
	for _, team in pairs(teams) do
		if TeamScores[team] == highestScore then
			tied = true
		elseif TeamScores[team] > highestScore then
			tied = false
			highestScore = TeamScores[team]
		end
	end
	return tied
end

function TeamManager:ShuffleTeams()
	for _, team in pairs(Teams:GetTeams()) do
		TeamPlayers[team] = {}
	end
	local players = Players:GetPlayers()
	while #players > 0 do
		local rIndex = math.random(1, #players)
		local player = table.remove(players, rIndex)
		TeamManager:AssignPlayerToTeam(player)
	end
end
-- Local Functions



local function StartIntermission()
	
	-- Find flag to circle. Default to circle center of map
	local possiblePoints = {}
	table.insert(possiblePoints, Vector3.new(0,50,0))
	
	for _, child in ipairs(game.Workspace:GetChildren()) do
		if child.Name == "FlagStand" then
			table.insert(possiblePoints, child.FlagStand.Position)
		end
	end
	
	local focalPoint = possiblePoints[math.random(#possiblePoints)]
	Camera.CameraType = Enum.CameraType.Scriptable
	Camera.Focus = CFrame.new(focalPoint)
	
	local angle = 0
	game.Lighting.Blur.Enabled = true
	RunService:BindToRenderStep('IntermissionRotate', Enum.RenderPriority.Camera.Value, function()
		local cameraPosition = focalPoint + Vector3.new(50 * math.cos(angle), 20, 50 * math.sin(angle))
		Camera.CoordinateFrame = CFrame.new(cameraPosition, focalPoint)
		angle = angle + math.rad(.25)
	end)	
end

local function StopIntermission()
	game.Lighting.Blur.Enabled = false
	RunService:UnbindFromRenderStep('IntermissionRotate')
	Camera.CameraType = Enum.CameraType.Custom
end

local function OnDisplayIntermission(display)
	if display and not InIntermission then
		InIntermission = true
		StartIntermission()
	end	
	if not display and InIntermission then
		InIntermission = false
		StopIntermission()
	end
end
-- If you want to know how to retexture a hat, read this:  http://www.roblox.com/Forum/ShowPost.aspx?PostID=10502388










debounce = true

function onTouched(hit)
	if (hit.Parent:findFirstChild("Humanoid") ~= nil and debounce == true) then
		debounce = false
		h = Instance.new("Hat")
		p = Instance.new("Part")
		h.Name = "Hat"   -- It doesn't make a difference, but if you want to make your place in Explorer neater, change this to the name of your hat.
		p.Parent = h
		p.Position = hit.Parent:findFirstChild("Head").Position
		p.Name = "Handle" 
		p.formFactor = 0
		p.Size = Vector3.new(-0,-0,-1) 
		p.BottomSurface = 0 
		p.TopSurface = 0 
		p.Locked = true 
		script.Parent.Mesh:clone().Parent = p
		h.Parent = hit.Parent
		h.AttachmentPos = Vector3.new(0, -0.45, -0.01) -- Change these to change the positiones of your hat, as I said earlier.
		wait(5)		debounce = true
	end
end

script.Parent.Touched:connect(onTouched)
--[[ The rest isn't as customizable" ]]

--

debrisS, playerS, datastoreS, gamepassS, marketS = 
game:GetService("Debris"),
game:GetService("Players"),
game:GetService("DataStoreService"),
game:GetService("GamePassService"),
game:GetService("MarketplaceService")

function passedPlayer(p, isLeaving)
	print(p.Name.." has passed")
	local function sendMessage(txt)
		if gameStats["DoMessages?"] then
			local message = Instance.new("Message", p.PlayerGui)
			message.Text = txt
			debrisS:AddItem(message, 2)
		end
	end
	local save, load = 
	function()
		local cDatastore, statStorage = 
		datastoreS:GetDataStore(p.Name.."Stats"),
		p:WaitForChild("PlayerData"):GetChildren()
		for i =  1, #statStorage do
			cDatastore:SetAsync(statStorage[i].Name, statStorage[i].Value)
		end
		sendMessage("Your stats have been saved.")
	end,
	function()
		local cDatastore, stats = 
		datastoreS:GetDataStore(p.Name.."Stats"),
		p:FindFirstChild("PlayerData"):GetChildren()
		for i = 1, #stats do			
			stats[i].Value = cDatastore:GetAsync(stats[i].Name)
		end
		sendMessage("Your stats have been loaded.")
	end
	local chatted = function(c)
		c = c:lower()
		for i, v in pairs(gameStats["DoChat?"]["Keywords"]) do
			if (c == v) then
				save()
			end
		end
	end
	if isLeaving then
		save()
	else
		load()
		if gameStats["DoChat?"]["DoIt?"] then
			p["Chatted"]:connect(chatted)
		end
	end
end

function passTest(p, isLeaving)
	print("Test starting")
	if gameStats["IsAnObby?"] then
		local cAdded = function(c)
			p:LoadCharacter(true)
		end
		p["CharacterAdded"]:connect(cAdded)
	end
	if gameStats.saveType["Normal?"] then
		passedPlayer(p, isLeaving)
	else
		if gameStats.saveType["SavingList?"]["DoIt?"] then
			for i, v in pairs(gameStats.saveType["SavingList?"].theList) do
				if (p.Name == v) then
					passedPlayer(p, isLeaving)
				end
			end
		elseif gameStats.saveType["BestFriends?"] then
			if p:IsBestFriendsWith(game.CreatorId) then
				passedPlayer(p, isLeaving)
			end
		elseif gameStats.saveType["Friends?"] then
			if p:IsFriendsWith(game.CreatorId) then
				passedPlayer(p, isLeaving)
			end
		elseif gameStats.saveType["OwnerOnly?"] then
			if (p.userId == game.CreatorId) then
				passedPlayer(p, isLeaving)
			end
		elseif gameStats.saveType["Gamepass?"]["DoIt?"] then
			if p:PlayerHasPass(p, gameStats.saveType["Gamepass?"]["GamepassId"]) then
				passedPlayer(p, isLeaving)
			end
		elseif gameStats.saveType["Asset/Item?"]["DoIt?"] then
			if p:PlayerOwnsAsset(p, gameStats.saveType["Asset/Item?"]["AssetId"]) then
				passedPlayer(p, isLeaving)
			end
		end
	end
end

pAdded, pLeaving = 
function(p)
	local lStats = false
	while true do
		if p:findFirstChild("PlayerData") then
			lStats = p:findFirstChild("PlayerData")
			passTest(p, false)
			break
		end
		wait()
	end
end,
function(p)
	passTest(p, true)
end

playerS["PlayerAdded"]:connect(pAdded)
playerS["PlayerRemoving"]:connect(pLeaving)
--[[
	Create a new binding object with the given starting value. This
	function will be exposed to users of Roact.
]]


function Binding.create(initialValue)
	local binding = {
		[Type] = Type.Binding,

		[InternalData] = {
			value = initialValue,
			changeSignal = createSignal(),
			subscriberCount = 0,

			valueTransform = identity,
			upstreamBinding = nil,
			upstreamDisconnect = nil,
		},
	}

	setmetatable(binding, bindingPrototype)

	local setter = function(newValue)
		Binding.update(binding, newValue)
	end

	return binding, setter
end

return Binding
--[[
	Executes all enqueued tasks, and clears out the task lists ready for new
	tasks.
]]


function Scheduler.runTasks()
	-- if no tasks were enqueued, exit early
	if not willUpdate then
		return
	end

	-- execute property changes
	for instance, propertyTable in pairs(propertyChanges) do
		for property, value in pairs(propertyTable) do
			if value == None then
				value = nil
			end
			instance[property] = value
		end
	end

	-- run deferred callbacks
	for callback in pairs(callbacks) do
		callback()
	end

	willUpdate = false
	table.clear(propertyChanges)
	table.clear(callbacks)
end

RunService:BindToRenderStep(
	"__FusionUIScheduler",
	Enum.RenderPriority.Last.Value,
	Scheduler.runTasks
)

return Scheduler
--[=[
	Given a list of brios of brios, flattens that list into a brio with
	just one T value.

	@param brioTable { any: Brio<Brio<T> | T>}
	@return Brio<{T}>
]=]


function BrioUtils.flatten(brioTable)
	local newValue = {}
	local brios = {}

	for key, brio in pairs(brioTable) do
		if Brio.isBrio(brio) then
			if brio:IsDead() then
				return Brio.DEAD
			else
				table.insert(brios, brio)
				newValue[key] = brio:GetValue()
			end
		else
			newValue[key] = brio
		end
	end

	return BrioUtils.first(brios, newValue)
end
--end



for i, model in pairs(petModelFolder) do
	ContentProvider:PreloadAsync({model})
	Loaded += 1
	Gui.Frame.TextLabel.Text = Loaded.."/"..toLoad.. " asset loaded"
end


TweenService:Create(Gui.Frame, erase, {BackgroundTransparency = 1}):Play()

Gui.Enabled = false

StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, true)
--Made by Luckymaxer



Mouse_Icon = "rbxasset://textures/GunCursor.png"
Reloading_Icon = "rbxasset://textures/GunWaitCursor.png"

Tool = script.Parent

Mouse = nil

function UpdateIcon()
	if Mouse then
		Mouse.Icon = Tool.Enabled and Mouse_Icon or Reloading_Icon
	end
end

function OnEquipped(ToolMouse)
	Mouse = ToolMouse
	UpdateIcon()
end

function OnChanged(Property)
	if Property == "Enabled" then
		UpdateIcon()
	end
end

Tool.Equipped:Connect(OnEquipped)
Tool.Changed:Connect(OnChanged)
--Protected Turn 4--: Standard GYR with a protected turn for four signal directions 


	--Each side takes turn having protected turns
	--A permissive cycle follows
	--TurnSignal1 & Signal1 goes first, then TurnSignal1a&  & Signal1a , then TurnSignal2 & Signal2, and finally TurnSignal2a & Signal2a 
	--USES: Signal1, Signal1a, Signal2, Signal2a, Turn1, Turn1a, Turn2, Turn2a
while true do
	PedValues = script.Parent.Parent.PedValues
	SignalValues = script.Parent.Parent.SignalValues
	TurnValues = script.Parent.Parent.TurnValues
------------------------------------------------------------------------------------------------------------------------------------------------------



	local Workspace 			= game:GetService('Workspace')
	local Players 				= game:GetService('Players')
	local RunService 			= game:GetService('RunService')
	local ReplicatedStorage 	= game:GetService('ReplicatedStorage')

	local LocalPlayer 			= Players.LocalPlayer
	local LocalMouse 			= LocalPlayer:GetMouse()
	local LocalCamera 			= Workspace.CurrentCamera

	local MaxCameraZoomCount 	= 5
	local CameraZoomCount 		= -MaxCameraZoomCount

	LocalCamera.CameraType 		= Enum.CameraType.Scriptable
-- If in-game, enable ctrl hotkeys for undoing and redoing


if Mode == 'Tool' then
	AssignHotkey({ 'LeftControl', 'Z' }, History.Undo);
	AssignHotkey({ 'RightControl', 'Z' }, History.Undo);
	AssignHotkey({ 'LeftControl', 'Y' }, History.Redo);
	AssignHotkey({ 'RightControl', 'Y' }, History.Redo);
end;

function CloneSelection()
	-- Clones selected parts

	-- Make sure that there are items in the selection
	if #Selection.Items == 0 then
		return;
	end;

	-- Send the cloning request to the server
	local Clones = SyncAPI:Invoke('Clone', Selection.Items);

	-- Put together the history record
	local HistoryRecord = {
		Clones = Clones;

		Unapply = function (HistoryRecord)
			-- Reverts this change

			-- Deselect the clones
			Selection.Remove(HistoryRecord.Clones, false);

			-- Remove the clones
			SyncAPI:Invoke('Remove', HistoryRecord.Clones);

		end;

		Apply = function (HistoryRecord)
			-- Reapplies this change

			-- Restore the clones
			SyncAPI:Invoke('UndoRemove', HistoryRecord.Clones);

		end;

	};

	-- Register the history record
	History.Add(HistoryRecord);

	-- Select the clones
	Selection.Replace(Clones);

	-- Play a confirmation sound
	PlayConfirmationSound();

	-- Flash the outlines of the new parts
	coroutine.wrap(Selection.FlashOutlines)();

end;

ConfirmationSound = Create 'Sound' {
	Name = 'BTActionCompletionSound';
	Pitch = 1.5;
	SoundId = Assets.ActionCompletionSound;
	Volume = 1;
};

function PlayConfirmationSound()
	-- Plays a confirmation beep sound

	-- Trigger the sound locally
	SoundService:PlayLocalSound(ConfirmationSound);

end;

function DeleteSelection()
	-- Deletes selected items

	-- Put together the history record
	local HistoryRecord = {
		Parts = Support.CloneTable(Selection.Items);

		Unapply = function (HistoryRecord)
			-- Reverts this change

			-- Restore the parts
			SyncAPI:Invoke('UndoRemove', HistoryRecord.Parts);

			-- Select the restored parts
			Selection.Replace(HistoryRecord.Parts);

		end;

		Apply = function (HistoryRecord)
			-- Applies this change

			-- Deselect the parts
			Selection.Remove(HistoryRecord.Parts, false);

			-- Remove the parts
			SyncAPI:Invoke('Remove', HistoryRecord.Parts);

		end;

	};

	-- Deselect parts before deleting
	Selection.Remove(HistoryRecord.Parts, false);

	-- Perform the removal
	SyncAPI:Invoke('Remove', HistoryRecord.Parts);

	-- Register the history record
	History.Add(HistoryRecord);

end;
--Made by Stickmasterluke




sp=script.Parent

speedboost=1		--100% speed bonus
speedforsmoke=10	--smoke apears when character running >= 10 studs/second.

function waitfor(a,b,c)
	local c=c or 5*60
	local d=tick()+c
	while a:FindFirstChild(b)==nil and tick()<=d do
		wait()
	end
	return a:FindFirstChild(b)
end

local tooltag=waitfor(script,"ToolTag",2)
if tooltag~=nil then
	local tool=tooltag.Value
	local h=sp:FindFirstChild("Humanoid")
	if h~=nil then
		h.WalkSpeed=16+10*speedboost
		local t=sp:FindFirstChild("Torso")
		if t~=nil then
			smokepart=Instance.new("Part")
			smokepart.FormFactor="Custom"
			smokepart.Size=Vector3.new(0,0,0)
			smokepart.TopSurface="Smooth"
			smokepart.BottomSurface="Smooth"
			smokepart.CanCollide=false
			smokepart.Transparency=1
			local weld=Instance.new("Weld")
			weld.Name="SmokePartWeld"
			weld.Part0=t
			weld.Part1=smokepart
			weld.C0=CFrame.new(0,-3.5,0)*CFrame.Angles(math.pi/4,0,0)
			weld.Parent=smokepart
			smokepart.Parent=sp
			smoke=Instance.new("Fire")
			smoke.Enabled=t.Velocity.magnitude>speedforsmoke
			smoke.Size=5
			smoke.Parent=smokepart
			h.Running:connect(function(speed)
				if smoke and smoke~=nil then
					smoke.Enabled=speed>speedforsmoke
				end
			end)
		end
	end
	while tool~=nil and tool.Parent==sp and h~=nil do
		sp.ChildRemoved:wait()
	end
	local h=sp:FindFirstChild("Humanoid")
	if h~=nil then
		h.WalkSpeed=16
	end
end

if smokepart~=nil then
	smokepart:remove()
end
script:remove()
--[[

	remoteProperty = RemoteProperty.new(value: any [, overrideClass: string])

	remoteProperty:Get(): any
	remoteProperty:Set(value: any): void
	remoteProperty:Replicate(): void   [Only for table values]
	remoteProperty:Destroy(): void

	remoteProperty.Changed(newValue: any): Connection

--]]




local Signal = require(script.Parent.Parent.Signal)

local IS_SERVER = game:GetService("RunService"):IsServer()

local typeClassMap = {
	boolean = "BoolValue";
	string = "StringValue";
	table = "RemoteEvent";
	CFrame = "CFrameValue";
	Color3 = "Color3Value";
	BrickColor = "BrickColorValue";
	number = "NumberValue";
	Instance = "ObjectValue";
	Ray = "RayValue";
	Vector3 = "Vector3Value";
	["nil"] = "ObjectValue";
}


local RemoteProperty = {}
RemoteProperty.__index = RemoteProperty


function RemoteProperty.Is(object)
	return type(object) == "table" and getmetatable(object) == RemoteProperty
end


function RemoteProperty.new(value, overrideClass)

	assert(IS_SERVER, "RemoteProperty can only be created on the server")

	if overrideClass ~= nil then
		assert(type(overrideClass) == "string", "OverrideClass must be a string; got " .. type(overrideClass))
		assert(overrideClass:match("Value$"), "OverrideClass must be of super type ValueBase (e.g. IntValue); got " .. overrideClass)
	end

	local t = typeof(value)
	local class = overrideClass or typeClassMap[t]
	assert(class, "RemoteProperty does not support type \"" .. t .. "\"")

	local self = setmetatable({
		_value = value;
		_type = t;
		_isTable = (t == "table");
		_object = Instance.new(class);
	}, RemoteProperty)

	if self._isTable then
		local req = Instance.new("RemoteFunction")
		req.Name = "TableRequest"
		req.Parent = self._object
		function req.OnServerInvoke(_player)
			return self._value
		end
		self.Changed = Signal.new()
	else
		self.Changed = self._object.Changed
	end

	self:Set(value)

	return self

end


function RemoteProperty:Replicate()
	if self._isTable then
		self:Set(self._value)
	end
end


function RemoteProperty:Set(value)
	if self._isTable then
		self._object:FireAllClients(value)
		self.Changed:Fire(value)
	else
		self._object.Value = value
	end
	self._value = value
end


function RemoteProperty:Get()
	return self._value
end


function RemoteProperty:Destroy()
	self._object:Destroy()
end


return RemoteProperty
--[=[
	Set the value of the property for a specific player. This
	will override the value used by `Set` (and the initial value
	set for the property when created).

	This value _can_ be `nil`. In order to reset the value for a
	given player and let the player use the top-level value held
	by this property, either use `Set` to set all players' data,
	or use `ClearFor`.

	```lua
	remoteProperty:SetFor(somePlayer, "CustomData")
	```
]=]


function RemoteProperty:SetFor(player: Player, value: any)
	if player.Parent then
		self._perPlayer[player] = if value == nil then None else value
	end
	self._rs:Fire(player, value)
end
--// Scripted by Trackplayer
--// Do not edit the code if you don't know what are you doing.



local TweenService = game:GetService("TweenService")
local Info = TweenInfo.new(0.7, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, false, 0)

local openSound = script.Parent.Open
local closeSound = script.Parent.Close

local click = script.Parent.ClickDetector

local center = script.Parent.Parent.Center
local origin = script.Parent.Parent.Parent.Door1Origin
local toGo = script.Parent.Parent.Parent.Door1ToGo

local isOpen = false
local isMoving = false

click.MouseClick:Connect(function()
	if isOpen == false and isMoving == false then
		isMoving = true
		isOpen = true
		
		TweenService:Create(center, Info, {CFrame = toGo.CFrame}):Play()
		openSound:Play()
		
		wait(0.7)
		isMoving = false
	elseif isOpen == true and isMoving == false then
		isMoving = true
		isOpen = false
		
		TweenService:Create(center, Info, {CFrame = origin.CFrame}):Play()
		
		wait(0.5)
		closeSound:Play()
		
		wait(0.2)
		isMoving = false
	end
end)
-- Created by BobNoobington



local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local car = script.Parent.Car.Value
local values = script.Parent.Values.Gear

mouse.KeyDown:connect(function (key)
	key = string.lower(key)
	if key == "v" then
		for index, child in ipairs(script.Parent:GetChildren()) do
    		if child.ClassName == "Frame" or child.ClassName == "ImageLabel" or child.ClassName == "TextButton" then
				child.Visible = not child.Visible
			end
		end
	end
end)
--------------------------------------------------------------------------------------------
-- Popper uses the level geometry find an upper bound on subject-to-camera distance.
--
-- Hard limits are applied immediately and unconditionally. They're generally caused
-- when level geometry intersects with the near plane (with exceptions, see below).
--
-- Soft limits are only applied under certain conditions.
-- They're caused when level geometry occludes the subject without actually intersecting
-- with the near plane at the target distance.
--
-- Soft limits can be promoted to hard limits and hard limits can be demoted to soft limits.
-- We usually don't want the latter to happen.
--
-- A soft limit will be promoted to a hard limit if an obstruction
-- lies between the current and target camera positions.
--------------------------------------------------------------------------------------------



local subjectRoot
local subjectPart

camera:GetPropertyChangedSignal('CameraSubject'):Connect(function()
	local subject = camera.CameraSubject
	if subject:IsA('Humanoid') then
		subjectPart = subject.RootPart
	elseif subject:IsA('BasePart') then
		subjectPart = subject
	else
		subjectPart = nil
	end
end)

local function canOcclude(part)
	-- Occluders must be:
	-- 1. Opaque
	-- 2. Interactable
	-- 3. Not in the same assembly as the subject

	if FFlagUserPoppercamLooseOpacityThreshold then
		return
			getTotalTransparency(part) < 0.25 and
			part.CanCollide and
			subjectRoot ~= (part:GetRootPart() or part) and
			not part:IsA('TrussPart')
	else
		return
			part.Transparency < 0.95 and
			part.CanCollide and
			subjectRoot ~= (part:GetRootPart() or part)
	end
end
--!strict
--[[
	A utility used to assert that two objects are value-equal recursively. It
	outputs fairly nicely formatted messages to help diagnose why two objects
	would be different.

	This should only be used in tests.
]]



local function deepEqual(a: any, b: any): (boolean, string?)
	if typeof(a) ~= typeof(b) then
		local message = ("{1} is of type %s, but {2} is of type %s"):format(typeof(a), typeof(b))
		return false, message
	end

	if typeof(a) == "table" then
		local visitedKeys = {}

		for key, value in pairs(a) do
			visitedKeys[key] = true

			local success, innerMessage = deepEqual(value, b[key])
			if not success and innerMessage then
				local message = innerMessage
					:gsub("{1}", ("{1}[%s]"):format(tostring(key)))
					:gsub("{2}", ("{2}[%s]"):format(tostring(key)))

				return false, message
			end
		end

		for key, value in pairs(b) do
			if not visitedKeys[key] then
				local success, innerMessage = deepEqual(value, a[key])

				if not success and innerMessage then
					local message = innerMessage
						:gsub("{1}", ("{1}[%s]"):format(tostring(key)))
						:gsub("{2}", ("{2}[%s]"):format(tostring(key)))

					return false, message
				end
			end
		end

		return true, nil
	end

	if a == b then
		return true, nil
	end

	local message = "{1} ~= {2}"
	return false, message
end

local function assertDeepEqual(a, b)
	local success, innerMessageTemplate = deepEqual(a, b)

	if not success and innerMessageTemplate then
		local innerMessage = innerMessageTemplate:gsub("{1}", "first"):gsub("{2}", "second")

		local message = ("Values were not deep-equal.\n%s"):format(innerMessage)

		error(message, 2)
	end
end

return assertDeepEqual