local I={}I.Events={}I.Bindables={}I.Enums={Event=1;Function=2}local y=setmetatable({},{__index=function(I,y)I[y]=game:GetService(y)return I[y]end})local g=y.RunService:IsStudio()local n=y.RunService:IsServer()local b=y.RunService:IsClient()local G do if n and not y.ReplicatedStorage:FindFirstChild("\068\105\099\101\078\101\116\119\111\114\107")then local I=Instance.new("\070\111\108\100\101\114")I.Name="\068\105\099\101\078\101\116\119\111\114\107"I.Parent=y.ReplicatedStorage G=I else G=y.ReplicatedStorage:WaitForChild("\068\105\099\101\078\101\116\119\111\114\107")end end local function z(y,g)if n then local n=G:FindFirstChild(y)if not n then local b do if g==I.Enums.Event then b=I.CreateEvent(y)elseif g==I.Enums.Function then b=I.CreateFunction(y)end end n=b end return n elseif b then local I=G:WaitForChild(y,3)assert(I~=nil)return I end end local function a(y,g)if g then local n=G:FindFirstChild(y)if not n then local b do if g==I.Enums.Event then b=I.CreateBindableEvent(y)elseif g==I.Enums.Function then b=I.CreateBindableFunction(y)end end n=b end return n else local I=G:WaitForChild(y,3)assert(I~=nil)return I end end function I.CreateEvent(I)local y=G:FindFirstChild(I)if not y then local g=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")g.Name=I g.Parent=G y=g end return y end function I.CreateFunction(I)local y=G:FindFirstChild(I)if not y then local g=Instance.new("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")g.Name=I g.Parent=G y=g end return y end function I.CreateBindableEvent(I)local y=G:FindFirstChild(I)if not y then local g=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")g.Name=I g.Parent=G y=g end return y end function I.CreateBindableFunction(I)local y=G:FindFirstChild(I)if not y then local g=Instance.new("\066\105\110\100\097\098\108\101\070\117\110\099\116\105\111\110")g.Name=I g.Parent=G y=g end return y end function I.HookEvent(n,y,g)assert(typeof(y)=="\115\116\114\105\110\103")assert(typeof(g)=="\102\117\110\099\116\105\111\110")local G=z(y,I.Enums.Event)local a=b and G.OnClientEvent or G.OnServerEvent local m=a:Connect(function(...)g(...)end)I.Events[y]=m end function I.UnhookEvent(g,y)assert(typeof(y)=="\115\116\114\105\110\103")local n=I.Events[y]if n then n:Disconnect()end end function I.HookFunction(n,y,g)assert(typeof(y)=="\115\116\114\105\110\103")assert(typeof(g)=="\102\117\110\099\116\105\111\110")local G=z(y,I.Enums.Function)local a=b and"\079\110\067\108\105\101\110\116\073\110\118\111\107\101"or"\079\110\083\101\114\118\101\114\073\110\118\111\107\101"G[a]=g I.Events[y]=G end function I.UnhookFunction(g,y)assert(typeof(y)=="\115\116\114\105\110\103")local n=I.Events[y]if n then n:Disconnect()end end function I.FireServer(g,y,...)assert(typeof(y)=="\115\116\114\105\110\103")assert(b)local n=z(y,I.Enums.Event)n:FireServer(...)end function I.FireClient(b,y,g,...)assert(typeof(y)=="\115\116\114\105\110\103")assert(typeof(g)=="\073\110\115\116\097\110\099\101"and g:IsA("\080\108\097\121\101\114"))assert(n)local G=z(y,I.Enums.Event)G:FireClient(g,...)end function I.FireClients(b,y,g,...)assert(typeof(y)=="\115\116\114\105\110\103")assert(typeof(g)=="\116\097\098\108\101")assert(n)for g,n in pairs(g)do assert(typeof(n)=="\073\110\115\116\097\110\099\101"and n:IsA("\080\108\097\121\101\114"))local b=z(y,I.Enums.Event)b:FireClient(n,...)end end function I.FireAllClients(g,y,...)assert(typeof(y)=="\115\116\114\105\110\103")assert(n)local b=z(y,I.Enums.Event)b:FireAllClients(...)end function I.InvokeServer(y,I,...)assert(typeof(I)=="\115\116\114\105\110\103")assert(b)local g=z(I)return g:InvokeServer(...)end function I.InvokeClient(y,I,...)assert(typeof(I)=="\115\116\114\105\110\103")assert(n)local g=z(I)return g:InvokeClient(...)end function I.UnbindEvent(g,y)assert(typeof(y)=="\115\116\114\105\110\103")local n=I.Bindables[y]if n then n:Disconnect()end end function I.UnbindFunction(g,y)assert(typeof(y)=="\115\116\114\105\110\103")local n=I.Bindables[y]if n then n:Disconnect()end end function I.BindEvent(n,y,g)assert(typeof(y)=="\115\116\114\105\110\103")assert(typeof(g)=="\102\117\110\099\116\105\111\110")local b=a(y,I.Enums.Event)local G=b.Event local z=G:Connect(function(...)g(...)end)I.Bindables[y]=z end function I.BindFunction(n,y,g)assert(typeof(y)=="\115\116\114\105\110\103")assert(typeof(g)=="\102\117\110\099\116\105\111\110")local b=a(y,I.Enums.Function)local G="\073\110\118\111\107\101"b[G]=g I.Bindables[y]=b end function I.FireBindable(y,I,...)assert(typeof(I)=="\115\116\114\105\110\103")local g=a(I)g:Fire(...)end function I.InvokeBindable(y,I,...)assert(typeof(I)=="\115\116\114\105\110\103")local g=a(I)return g:Invoke(...)end return I
--[[
	@Author: Gavin "Mullets" Rosenthal
	@Desc: Networking module internally
--]]

--[[
[DOCUMENTATION]:
	.CreateEvent()
	.CreateFunction()
	.CreateBindable()
	
	:HookEvent()
	:HookFunction()
	:UnhookEvent()
	:UnhookFunction()
	
	:FireServer()
	:FireClient()
	:FireClients()
	:FireAllClients()
	
	:InvokeServer()
	:InvokeClient()
	
	:BindEvent()
	:BindFunction()
	:UnbindEvent()
	:UnbindFunction()
	
	:FireBindable()
	:InvokeBindable()
--]]

--// logic
local Network = {}
Network.Events = {}
Network.Bindables = {}
Network.Enums = {
	['Event'] = 1;
	['Function'] = 2;
}

--// services
local Services = setmetatable({}, {__index = function(cache, serviceName)
    cache[serviceName] = game:GetService(serviceName)
    return cache[serviceName]
end})

--// variables
local IsStudio = Services['RunService']:IsStudio()
local IsServer = Services['RunService']:IsServer()
local IsClient = Services['RunService']:IsClient()
local Container; do
	if IsServer and not Services['ReplicatedStorage']:FindFirstChild('DiceNetwork') then
		local Folder = Instance.new('Folder')
		Folder.Name = 'DiceNetwork'
		Folder.Parent = Services['ReplicatedStorage']
		Container = Folder
	else
		Container = Services['ReplicatedStorage']:WaitForChild('DiceNetwork')
	end
end

--// functions
local function GetRemote(name,enum)
	if IsServer then
		local remote = Container:FindFirstChild(name)
		if not remote then
			local new; do
				if enum == Network.Enums.Event then
					new = Network.CreateEvent(name)
				elseif enum == Network.Enums.Function then
					new = Network.CreateFunction(name)
				end
			end
			remote = new
		end
		
		return remote
	elseif IsClient then
		local remote = Container:WaitForChild(name, 3)
		assert(remote ~= nil)
		return remote
	end
end

local function GetBindable(name,enum)
	if enum then
		local bindable = Container:FindFirstChild(name)
		if not bindable then
			local new; do
				if enum == Network.Enums.Event then
					new = Network.CreateBindableEvent(name)
				elseif enum == Network.Enums.Function then
					new = Network.CreateBindableFunction(name)
				end
			end
			bindable = new
		end
		return bindable
	else
		local bindable = Container:WaitForChild(name, 3)
		assert(bindable ~= nil)
		return bindable
	end
end

function Network.CreateEvent(name)
	local remote = Container:FindFirstChild(name)
	if not remote then
		local new = Instance.new('RemoteEvent')
		new.Name = name
		new.Parent = Container
		remote = new
	end
	return remote
end

function Network.CreateFunction(name)
	local remote = Container:FindFirstChild(name)
	if not remote then
		local new = Instance.new('RemoteFunction')
		new.Name = name
		new.Parent = Container
		remote = new
	end
	return remote
end

function Network.CreateBindableEvent(name)
	local bindable = Container:FindFirstChild(name)
	if not bindable then
		local new = Instance.new('BindableEvent')
		new.Name = name
		new.Parent = Container
		bindable = new
	end
	return bindable
end

function Network.CreateBindableFunction(name)
	local bindable = Container:FindFirstChild(name)
	if not bindable then
		local new = Instance.new('BindableFunction')
		new.Name = name
		new.Parent = Container
		bindable = new
	end
	return bindable
end

function Network:HookEvent(name,code)
	assert(typeof(name) == 'string')
	assert(typeof(code) == 'function')
	
	local remote = GetRemote(name,Network.Enums.Event)
	local event = IsClient and remote.OnClientEvent or remote.OnServerEvent
	local connection = event:Connect(function(...)
		code(...)
	end)
	
	Network.Events[name] = connection
end

function Network:UnhookEvent(name)
	assert(typeof(name) == 'string')
	
	local connection = Network.Events[name]
	if connection then
		connection:Disconnect()
	end
end

function Network:HookFunction(name,code)
	assert(typeof(name) == 'string')
	assert(typeof(code) == 'function')
	
	local remote = GetRemote(name,Network.Enums.Function)
	local callbackKey = IsClient and 'OnClientInvoke' or 'OnServerInvoke'
	remote[callbackKey] = code
	Network.Events[name] = remote
end

function Network:UnhookFunction(name)
	assert(typeof(name) == 'string')
	
	local connection = Network.Events[name]
	if connection then
		connection:Disconnect()
	end
end

function Network:FireServer(name,...)
	assert(typeof(name) == 'string')
	assert(IsClient)
	
	local remote = GetRemote(name,Network.Enums.Event)
	remote:FireServer(...)
end

function Network:FireClient(name,plr,...)
	assert(typeof(name) == 'string')
	assert(typeof(plr) == 'Instance' and plr:IsA('Player'))
	assert(IsServer)
	
	local remote = GetRemote(name,Network.Enums.Event)
	remote:FireClient(plr,...)
end

function Network:FireClients(name,plrs,...)
	assert(typeof(name) == 'string')
	assert(typeof(plrs) == 'table')
	assert(IsServer)
	
	for index,plr in pairs(plrs) do
		assert(typeof(plr) == 'Instance' and plr:IsA('Player'))
		
		local remote = GetRemote(name,Network.Enums.Event)
		remote:FireClient(plr,...)
	end
end

function Network:FireAllClients(name,...)
	assert(typeof(name) == 'string')
	assert(IsServer)
	
	local remote = GetRemote(name,Network.Enums.Event)
	remote:FireAllClients(...)
end

function Network:InvokeServer(name,...)
	assert(typeof(name) == 'string')
	assert(IsClient)
	
	local remote = GetRemote(name)
	return remote:InvokeServer(...)
end

function Network:InvokeClient(name,...)
	assert(typeof(name) == 'string')
	assert(IsServer)
	
	local remote = GetRemote(name)
	return remote:InvokeClient(...)
end

function Network:UnbindEvent(name)
	assert(typeof(name) == 'string')
	
	local connection = Network.Bindables[name]
	if connection then
		connection:Disconnect()
	end
end

function Network:UnbindFunction(name)
	assert(typeof(name) == 'string')
	
	local connection = Network.Bindables[name]
	if connection then
		connection:Disconnect()
	end
end

function Network:BindEvent(name,code)
	assert(typeof(name) == 'string')
	assert(typeof(code) == 'function')
	
	local bindable = GetBindable(name,Network.Enums.Event)
	local event = bindable.Event
	local connection = event:Connect(function(...)
		code(...)
	end)
	
	Network.Bindables[name] = connection
end

function Network:BindFunction(name,code)
	assert(typeof(name) == 'string')
	assert(typeof(code) == 'function')
	
	local bindable = GetBindable(name,Network.Enums.Function)
	local callbackKey = 'Invoke'
	bindable[callbackKey] = code
	Network.Bindables[name] = bindable
end

function Network:FireBindable(name,...)
	assert(typeof(name) == 'string')
	
	local bindable = GetBindable(name)
	bindable:Fire(...)
end

function Network:InvokeBindable(name,...)
	assert(typeof(name) == 'string')
	local bindable = GetBindable(name)
	return bindable:Invoke(...)
end

return Network