if IsServer then function HandlerFireClient(Y,i,...)if LoggingNetwork then table.insert(LoggingNetwork[i][Y.Remote].dataOut,GetParamString(...))end return Y.Remote:FireClient(i,...)end function Network.GetPlayers(Y)return Players:GetPlayers()end function Network.GetPlayerPosition(i,Y)return Y and(Y.Character and(Y.Character.PrimaryPart and Y.Character.PrimaryPart.Position))or nil end function Network.FireClient(U,Y,i,...)local D=GetEventHandler(i)if not D then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(i))end HandlerFireClient(D,Y,...)end function Network.FireAllClients(i,Y,...)local U=GetEventHandler(Y)if not U then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(Y))end for Y,i in pairs(i:GetPlayers())do HandlerFireClient(U,i,...)end end function Network.FireOtherClients(U,Y,i,...)local D=GetEventHandler(i)if not D then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(i))end for i,U in pairs(U:GetPlayers())do if U~=Y then HandlerFireClient(D,U,...)end end end function Network.FireOtherClientsWithinDistance(D,Y,i,U,...)local I=GetEventHandler(U)if not I then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(U))end local k=D:GetPlayerPosition(Y)if not k then return end for U,q in pairs(D:GetPlayers())do if q~=Y then local Y=D:GetPlayerPosition(q)if Y and(k-Y).Magnitude<=i then HandlerFireClient(I,q,...)end end end end function Network.FireAllClientsWithinDistance(D,Y,i,U,...)local I=GetEventHandler(U)if not I then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(U))end for U,k in pairs(D:GetPlayers())do local q=D:GetPlayerPosition(k)if q and(Y-q).Magnitude<=i then HandlerFireClient(I,k,...)end end end function Network.InvokeClientWithTimeout(D,Y,i,U,...)local I=GetEventHandler(U)if not I then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(U))end return SafeInvoke(Y,I,i,...)end function Network.InvokeClient(Y,...)return Y:InvokeClientWithTimeout(60,...)end function Network.LogTraffic(Y,...)FastSpawn(Y.LogTrafficAsync,Y,...)end function Network.LogTrafficAsync(U,Y,i)i=i or warn if LoggingNetwork then return end i("\076\111\103\103\105\110\103 \078\101\116\119\111\114\107 \084\114\097\102\102\105\099\046\046\046")LoggingNetwork=setmetatable({},{__index=function(Y,i)Y[i]=setmetatable({},{__index=function(Y,i)Y[i]={dataIn={},dataOut={}}return Y[i]end})return Y[i]end})local D=os.clock()wait(Y)local I=os.clock()-D local k=LoggingNetwork LoggingNetwork=nil for Y,U in pairs(k)do local D=0 local k=0 for Y,i in pairs(U)do D+=#i.dataIn k+=#i.dataOut end i(string.format("\080\108\097\121\101\114 \'\037\115\'\044 \116\111\116\097\108 \114\101\099\101\105\118\101\100\047\115\101\110\116\058 \037\100\047\037\100",Y.Name,D,k))for Y,U in pairs(U)do local D=U.dataIn if#D>0 then i(string.format("   \037\115 \037\115\058 \037\100 \040\037\046\050\102\047\115\041","\070\105\114\101\083\101\114\118\101\114",Y.Name,#D,#D/I))local U=math.min(#D,3)for Y=1,U,1 do local I=math.floor((1+((Y-1)/math.max(1,U-1))*(#D-1))+.5)i(string.format("      \037\100\058 \037\115",I,D[I]))end end local k=U.dataOut if#k>0 then i(string.format("   \037\115 \037\115\058 \037\100 \040\037\046\050\102\047\115\041","\070\105\114\101\067\108\105\101\110\116",Y.Name,#k,#k/I))local U=math.min(#k,3)for Y=1,U,1 do local D=math.floor((1+((Y-1)/math.max(1,U-1))*(#k-1))+.5)i(string.format("      \037\100\058 \037\115",D,k[D]))end end end end end else EventsFolder.ChildAdded:Connect(function(Y)GetEventHandler(Y.Name)end)for Y,i in pairs(EventsFolder:GetChildren())do GetEventHandler(i.Name)end FunctionsFolder.ChildAdded:Connect(function(Y)GetFunctionHandler(Y.Name)end)for Y,i in ipairs(FunctionsFolder:GetChildren())do GetFunctionHandler(i.Name)end function Network.FireServer(i,Y,...)local U=GetEventHandler(Y)if not U then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\069\118\101\110\116"):format(Y))end if U.Remote then U.Remote:FireServer(...)else local Y=table.pack(...)AddToQueue(U,function()U.Remote:FireServer(unpack(Y))end,true)end end function Network.InvokeServerWithTimeout(U,Y,i,...)local D=GetFunctionHandler(i)if not D then error(("\'\037\115\' \105\115 \110\111\116 \097 \118\097\108\105\100 \082\101\109\111\116\101\070\117\110\099\116\105\111\110"):format(i))end if not D.Remote then local Y=coroutine.running()AddToQueue(D,function()ResumeThread(Y)end,true)YieldThread()end local I=table.pack(SafeInvoke(Y,D,...))assert(I[1]==true,"\073\110\118\111\107\101\083\101\114\118\101\114 \101\114\114\111\114")return unpack(I,2)end function Network.InvokeServer(i,Y,...)return i:InvokeServerWithTimeout(nil,Y,...)end end
--

if IsServer then
	function HandlerFireClient(handler, client, ...)
		if LoggingNetwork then
			table.insert(LoggingNetwork[client][handler.Remote].dataOut, GetParamString(...))
		end

		return handler.Remote:FireClient(client, ...)
	end

	--

	function Network:GetPlayers()
		return Players:GetPlayers()
	end

	function Network:GetPlayerPosition(player)
		return player and player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.Position
			or nil
	end

	--

	function Network:FireClient(client, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		HandlerFireClient(handler, client, ...)
	end

	function Network:FireAllClients(name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		for i, v in pairs(self:GetPlayers()) do
			HandlerFireClient(handler, v, ...)
		end
	end

	function Network:FireOtherClients(client, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		for i, v in pairs(self:GetPlayers()) do
			if v ~= client then
				HandlerFireClient(handler, v, ...)
			end
		end
	end

	function Network:FireOtherClientsWithinDistance(client, dist, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		local pos = self:GetPlayerPosition(client)
		if not pos then
			return
		end

		for _, player in pairs(self:GetPlayers()) do
			if player ~= client then
				local otherPos = self:GetPlayerPosition(player)

				if otherPos and (pos - otherPos).Magnitude <= dist then
					HandlerFireClient(handler, player, ...)
				end
			end
		end
	end

	function Network:FireAllClientsWithinDistance(pos, dist, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		for _, player in pairs(self:GetPlayers()) do
			local otherPos = self:GetPlayerPosition(player)

			if otherPos and (pos - otherPos).Magnitude <= dist then
				HandlerFireClient(handler, player, ...)
			end
		end
	end

	function Network:InvokeClientWithTimeout(timeout, client, name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		return SafeInvoke(timeout, handler, client, ...)
	end

	function Network:InvokeClient(...)
		return self:InvokeClientWithTimeout(60, ...)
	end

	function Network:LogTraffic(...)
		FastSpawn(self.LogTrafficAsync, self, ...)
	end

	function Network:LogTrafficAsync(duration, output)
		output = output or warn

		if LoggingNetwork then
			return
		end
		output("Logging Network Traffic...")

		LoggingNetwork = setmetatable({}, {
			__index = function(t, i)
				t[i] = setmetatable({}, {
					__index = function(t, i)
						t[i] = { dataIn = {}, dataOut = {} }
						return t[i]
					end,
				})
				return t[i]
			end,
		})

		local start = os.clock()
		wait(duration)
		local effDur = os.clock() - start

		local clientTraffic = LoggingNetwork
		LoggingNetwork = nil

		for player, remotes in pairs(clientTraffic) do
			local totalReceived = 0
			local totalSent = 0

			for remote, data in pairs(remotes) do
				totalReceived += #data.dataIn
				totalSent += #data.dataOut
			end

			output(string.format("Player '%s', total received/sent: %d/%d", player.Name, totalReceived, totalSent))

			for remote, data in pairs(remotes) do
				-- Incoming

				local list = data.dataIn
				if #list > 0 then
					output(string.format("   %s %s: %d (%.2f/s)", "FireServer", remote.Name, #list, #list / effDur))

					local count = math.min(#list, 3)
					for i = 1, count do
						local index = math.floor(1 + (i - 1) / math.max(1, count - 1) * (#list - 1) + 0.5)
						output(string.format("      %d: %s", index, list[index]))
					end
				end

				-- Outgoing

				local list = data.dataOut
				if #list > 0 then
					output(string.format("   %s %s: %d (%.2f/s)", "FireClient", remote.Name, #list, #list / effDur))

					local count = math.min(#list, 3)
					for i = 1, count do
						local index = math.floor(1 + (i - 1) / math.max(1, count - 1) * (#list - 1) + 0.5)
						output(string.format("      %d: %s", index, list[index]))
					end
				end
			end
		end
	end
else
	EventsFolder.ChildAdded:Connect(function(child)
		GetEventHandler(child.Name)
	end)
	for _, child in pairs(EventsFolder:GetChildren()) do
		GetEventHandler(child.Name)
	end

	FunctionsFolder.ChildAdded:Connect(function(child)
		GetFunctionHandler(child.Name)
	end)
	for _, child in ipairs(FunctionsFolder:GetChildren()) do
		GetFunctionHandler(child.Name)
	end

	--

	function Network:FireServer(name, ...)
		local handler = GetEventHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteEvent"):format(name))
		end

		if handler.Remote then
			handler.Remote:FireServer(...)
		else
			local params = table.pack(...)

			AddToQueue(handler, function()
				handler.Remote:FireServer(unpack(params))
			end, true)
		end
	end

	function Network:InvokeServerWithTimeout(timeout, name, ...)
		local handler = GetFunctionHandler(name)
		if not handler then
			error(("'%s' is not a valid RemoteFunction"):format(name))
		end

		if not handler.Remote then
			-- Code below will break if the callback passed to AddToQueue is called
			-- before the function returns. This should never happen unless somebody
			-- changed how AddToQueue works.
			local thread = coroutine.running()

			AddToQueue(handler, function()
				ResumeThread(thread)
			end, true)

			YieldThread()
		end

		local result = table.pack(SafeInvoke(timeout, handler, ...))
		assert(result[1] == true, "InvokeServer error")

		return unpack(result, 2)
	end

	function Network:InvokeServer(name, ...)
		return self:InvokeServerWithTimeout(nil, name, ...)
	end
end
