local O=game:GetService("\082\117\110\083\101\114\118\105\099\101")local z=bit32.band local b=bit32.bor local i=bit32.lshift local X={}local y=0 for O=0,31,1 do table.insert(X,i(1,O))end local q=.2 local M=1-q local h={}local o=0 local function c() end local B=1e-05 local function e(O,z)if O==z then return true end return math.abs(O-z)<B end local function V(O,z)if O==z then return true end if not O.Position:FuzzyEq(z.Position)then return false end if not O.LookVector:FuzzyEq(z.LookVector)then return false end if not O.RightVector:FuzzyEq(z.RightVector)then return false end if not O.UpVector:FuzzyEq(z.UpVector)then return false end return true end local function l(O,z)local i=y local q={true,i}for z,y in ipairs(z)do local M=O[y]if M~=nil then i=b(i,X[z])table.insert(q,M)end end q[2]=i return q end local function j(O,b,i)if not O then return{}end local q={}local M=table.getn(O)local h=O[2]local o=1 for M=3,M,1 do for i=o,i,1 do o=o+1 if z(h,X[i])~=y then q[b[i]]=O[M]break end end end return q end local function P(O,z,i)local q=y local M=y local h={M,q}for i,y in ipairs(i)do local o=O[y]local c=z[y]if o==nil then if c~=nil then q=b(q,X[i])table.insert(h,c)end elseif c==nil then M=b(M,X[i])else local O=typeof(o)local z=typeof(c)local y=false if O==z then if O=="\110\117\109\098\101\114"and not e(o,c)then y=true elseif O=="\086\101\099\116\111\114\051"and not o:FuzzyEq(c)then y=true elseif O=="\067\070\114\097\109\101"and not V(o,c)then y=true elseif o~=c then y=true end elseif o~=c then y=true end if y then q=b(q,X[i])table.insert(h,c)end end end h[1]=M h[2]=q return h end local function K(O,b,q,M,h)if not b then b={y,y}end if not O then O={}end local o={}local c=b[1]local B=b[2]local e={}local V=table.getn(b)local l=1 if B~=y then for O=3,V,1 do for i=l,h,1 do l=l+1 local M=X[i]if z(c,M)~=y then e[i]=true elseif z(B,M)~=y then e[i]=true o[q[i]]=b[O]break end end end end if c>=i(1,l-1)then for O=l,h,1 do l=l+1 if z(c,X[O])~=y then e[O]=true end end end for O,z in pairs(O)do local b=M[O]if b~=nil and(z~=nil and not e[b])then o[O]=z end end return o end local function J(O)return O[1]~=true end local function Q(z,b,i,X,y)local B={}local e={}for O,z in ipairs(b)do B[z]=O table.insert(e,z)end local V=table.getn(e)local Q={Encode=function(O)return l(O,e)end;Decode=function(O)return j(O,e,V)end;Diff=function(O,z)return P(O,z,e)end,Patch=function(O,z)return K(O,z,e,B,V)end,IsDelta=function(O)return J(O)end}if i==nil then Q.Send=c Q.Acknowledge=c Q.RTT=function()return 0 end else local b local c="\078\067\082\067\084\095"..z y=y~=false if O:IsServer()then if game.ReplicatedStorage:FindFirstChild(c)then error("\084\104\101\114\101 \105\115 \097\108\114\101\097\100\121 \097\110 \101\118\101\110\116 \119\105\116\104 \116\104\101 \103\105\118\101\110 \073\068 \040"..(c.."\041"))end b=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")b.Parent=game.ReplicatedStorage b.Name=c local O={}local function z(z,b)local i=b.UserId if O[i]~=nil and O[i][z]~=nil then if h[i]==nil then h[i]={LastRttTime=-math.huge;RTT=0}end local b=h[i]local X=O[i][z]if X>b.LastRttTime then local O=os.clock()-X if b.RTT==0 then b.RTT=O else b.RTT=q*O+M*b.RTT end b.LastRttTime=X end O[i][z]=nil end end b.OnServerEvent:Connect(function(O,q)local M=q[1]local h=q[2]if M==true then if h~=nil then if y then z(h,O)end if X~=nil then X(h,O,Q)end end else if M~=nil then if y and h~=nil then b:FireClient(O,{true;h})end i(M,h,J(M),O,Q)end end end)Q.Send=function(z,i,X)if z==nil then return end if y and i~=nil then local z=X.UserId if O[z]==nil then O[z]={}end if O[z][i]==nil then O[z][i]=os.clock()end end b:FireClient(X,{z,i})end Q.Acknowledge=function(O,z)b:FireClient(z,{true,O})end Q.RTT=function(O)local z=h[O.UserId]if z~=nil then return z.RTT end return 0 end else b=game.ReplicatedStorage:WaitForChild(c)local O={}local z=-math.huge local function h(b)if O[b]~=nil then local i=O[b]if i>z then local O=os.clock()-i if o==0 then o=O else o=q*O+M*o end z=i end O[b]=nil end end b.OnClientEvent:Connect(function(O)local z=O[1]local q=O[2]if z==true then if q~=nil then if y then h(q)end if X~=nil then X(q,nil,Q)end end else if z~=nil then if y and q~=nil then b:FireServer({true;q})end i(z,q,J(z),nil,Q)end end end)Q.Send=function(z,i)if z==nil then return end if y and i~=nil then if O[i]==nil then O[i]=os.clock()end end b:FireServer({z;i})end Q.Acknowledge=function(O)b:FireServer({true,O})end Q.RTT=function()return o end end end return Q end return Q
--[[
   NetworkContract v1.2 [2020-12-05 12:40]

   Facilitates Client Server communication through Events. Has Encode, Decode, Diff, Patch and Message Knowledge

   https://github.com/nidorx/roblox-network-contract

   Discussions about this script are at https://devforum.roblox.com/t/900276

   ------------------------------------------------------------------------------

   MIT License

   Copyright (c) 2020 Alex Rodin

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
]]

local RunService = game:GetService('RunService')

-- binary operators
local band      = bit32.band
local bor       = bit32.bor
local lshift    = bit32.lshift

-- https://en.wikipedia.org/wiki/Bit_field
local FLAGS = {}
local ZERO  = 0x0
for i=0, 31 do
   -- (1 << 0) = 1, (1 << 1) = 2, (1 << 2) = 4, ... (1 << 31) = 2147483648 = "1111 1111 1111 1111 1111 1111 1111 1111"
   table.insert(FLAGS, lshift(1, i))
end

-- Exponential weighted moving average RTT (https://en.wikipedia.org/wiki/Round-trip_delay)
local RTT_ALPHA      = 0.2
local RTT_ALPHA_INV  = 1 - RTT_ALPHA
local RTTClients     = {}
local RTTServer      = 0

local function EMPTY_FN() end

-- precision (aka 1e-5)
local EPSILON = 0.00001

local function FuzzyEqNumber(a, b)
   if a == b then
      return true
   end

   return math.abs(a - b) < EPSILON
end

local function FuzzyEqCFrame(cframea, cframeb)
   if cframea == cframeb then
      return true
   end

   if not cframea.Position:FuzzyEq(cframeb.Position) then
      return false
   end

   if not cframea.LookVector:FuzzyEq(cframeb.LookVector) then
      return false
   end

   if not cframea.RightVector:FuzzyEq(cframeb.RightVector) then
      return false
   end

   if not cframea.UpVector:FuzzyEq(cframeb.UpVector) then
      return false
   end

   return true
end

--[[
   Generates an encoded record for transport.

   The resulting record has the following format: {TRUE, BITMASK, VALUE_A, VALUE_B, VALUE_N ...}

   An important detail is that the data is arranged respecting the index of the contract's attributes, this reduces 
   the time needed to perform the decoding (which validates the BITMASKs in order).
]]
local function Encode(data, IDX_TO_KEY)
   local mask  = ZERO
   local out   = { true, mask }

   for index, key in ipairs(IDX_TO_KEY) do
      local value = data[key]
      if value ~= nil then
         mask = bor(mask, FLAGS[index])
         table.insert(out, value)
      end
   end

   out[2] = mask

   return out
end

--[[
   It decodes the data encoded by the Encode method.

   An encoded record has the following signature {TRUE, BITMASK, VALUE_A, VALUE_B, VALUE_N ...}. The values are 
   arranged respecting the ordering of the contract's attributes.

   Where:
      TRUE     = Always true, to differentiate from a DIFF
      BITMASK  = Attribute Ids
      VALUE_N  = Values

   In order for the algorithm to work with the highest possible performance, it is important that the contract takes 
   into account this aspect, the attributes that are carried more frequently must be placed at the beginning of the contract.
]]
local function Decode(data, IDX_TO_KEY, LEN)
   if not data then
      return {}
   end

   local out       = {}
   local len       = table.getn(data)
   local mask      = data[2]
   local lastIdx   = 1

   for i=3, len do
      for currIdx=lastIdx, LEN do     
         lastIdx = lastIdx+1
         if band(mask, FLAGS[currIdx]) ~= ZERO then
            out[IDX_TO_KEY[currIdx]] = data[i]
            break
         end
      end
   end

   return out
end

--[[
   Generates a Diff (delta) of two objects. A diff contains the following signature:

   { BITMASK_DEL, BITMASK_MOD, VALUE_A, VALUE_B, VALUE_N ...}

   Where:
      BITMASK_DEL    = Attribute IDs removed
      BITMASK_MOD    = Modified attribute ids
      VALUE_N        = Values modified
]]
local function Diff(old, nue, IDX_TO_KEY)

   local maskMod   = ZERO
   local maskDel   = ZERO
   local out       = { maskDel, maskMod }

   for index, key in ipairs(IDX_TO_KEY) do
      local aVal = old[key]
      local bVal = nue[key]

      if aVal == nil then
         if bVal ~= nil then
            -- added value
            maskMod = bor(maskMod, FLAGS[index])
            table.insert(out, bVal)
         end
      elseif bVal == nil then
         -- A exists and B has been removed
         maskDel = bor(maskDel, FLAGS[index])
      else
         -- the two values exist, compare difference
         local aType =  typeof(aVal)
         local bType =  typeof(bVal)
         local diff = false

         if aType == bType then
            if aType == 'number' and not FuzzyEqNumber(aVal, bVal) then
               diff = true
            elseif aType == 'Vector3' and not aVal:FuzzyEq(bVal) then
               diff = true
            elseif aType == 'CFrame' and not FuzzyEqCFrame(aVal, bVal) then
               diff = true
            elseif aVal ~= bVal then
               diff = true
            end
         elseif aVal ~= bVal then
            diff = true
         end

         if diff then
            -- modified value
            maskMod = bor(maskMod, FLAGS[index])
            table.insert(out, bVal)
         end
      end
   end

   out[1] = maskDel
   out[2] = maskMod

   return out
end

--[[
   Apply the delta to an object, returning the result of the join. A diff contains the following signature:

   { BITMASK_DEL, BITMASK_MOD, VALUE_A, VALUE_B, VALUE_N ...}

   Where:
      BITMASK_DEL    = Attribute IDs removed
      BITMASK_MOD    = Modified attribute ids
      VALUE_N        = Values modified
]]
local function Patch(old, delta, IDX_TO_KEY, KEY_TO_IDX, LEN)
   if not delta then
      delta = { ZERO, ZERO }
   end

   if not old then
      old = {}
   end

   local out       = {}
   local maskDel   = delta[1] -- removals
   local maskMod   = delta[2] -- additions / changes
   local ignored   = {}       -- attributes that will be ignored when copying from old
   local len       = table.getn(delta)

   local lastIdx   = 1

   if maskMod ~= ZERO then
      for i=3, len do
         for currIdx=lastIdx, LEN do
            lastIdx     = lastIdx+1
            local flag  = FLAGS[currIdx]

            if band(maskDel, flag) ~= ZERO then
               -- data removed
               ignored[currIdx] = true
            elseif band(maskMod, flag) ~= ZERO then
               -- changed or new data
               ignored[currIdx] = true
               out[IDX_TO_KEY[currIdx]] = delta[i]
               break
            end
         end
      end
   end

   -- validation of removed items (if they still exist)
   if maskDel >= lshift(1, lastIdx-1) then
      for currIdx=lastIdx, LEN do
         lastIdx = lastIdx+1
         if band(maskDel, FLAGS[currIdx]) ~= ZERO then
            ignored[currIdx] = true
         end
      end
   end

   -- copies the original data that has not been ignored (removed or modified)
   for key,value in pairs(old) do
      local index = KEY_TO_IDX[key]
      if index ~= nil and value ~= nil and not ignored[index]  then
         out[key] = value
      end
   end

   return out
end

local function IsDelta(data)
   return data[1] ~= true
end

--[[
   Instantiates a new contract.

   Contracts are used to encode and decode messages.

   This standard format makes it possible to create delta changes and diffs

   Only simple objects (Flat) are accepted as input and preferably primitive data.

   Eg A Vector3, the ideal is to transport the elements (X, Y, Z) separately, this will reduce the size of the delta
   carried when only

   Params:
      ID                {Number|String}
      attributes        {String []}
      OnMessage         {Function (data, id, isDelta, player, contract)}
      OnAcknowledge     {Function (id, player, contract)}
      AutoAcknowledge   {bool} default true
]]
local function CreateNewContract(ID, attributes, OnMessage, OnAcknowledge, AutoAcknowledge)

   local KEY_TO_IDX = {}
   local IDX_TO_KEY = {}

   for index, key in ipairs(attributes) do
      KEY_TO_IDX[key] = index
      table.insert(IDX_TO_KEY, key)
   end

   local LEN = table.getn(IDX_TO_KEY)

   local Contract =  {
      Encode = function(data)
         return Encode(data, IDX_TO_KEY)
      end,

      Decode = function(data)
         return Decode(data, IDX_TO_KEY, LEN)
      end,

      Diff = function(old, nue)
         return Diff(old, nue, IDX_TO_KEY)
      end,

      Patch = function(old, delta)
         return Patch(old, delta, IDX_TO_KEY, KEY_TO_IDX, LEN)
      end,

      IsDelta = function(data)
         return IsDelta(data)
      end
   }

   if OnMessage == nil then
      -- If OnMessage is not informed it means that the interest in this contract is only to use the utility
      -- methods (Encode, Decode, Diff and Patch). Thus, there is no need to create events
      Contract.Send        = EMPTY_FN
      Contract.Acknowledge = EMPTY_FN
      Contract.RTT = function()
         return 0
      end
   else
      local Event
      local EventName = 'NCRCT_'..ID

      AutoAcknowledge  = AutoAcknowledge ~= false

      if RunService:IsServer() then
         if game.ReplicatedStorage:FindFirstChild(EventName) then
            error('There is already an event with the given ID ('..EventName..')')
         end

         Event = Instance.new('RemoteEvent')
         Event.Parent = game.ReplicatedStorage
         Event.Name = EventName

         -- latest messages sent per player, used to calculate RTT
         local LastMessages = {}

         local function CalculateRTT(messageId, player)
            local userId = player.UserId
            if LastMessages[userId] ~= nil and LastMessages[userId][messageId] ~= nil then               
               if RTTClients[userId] == nil then
                  RTTClients[userId] = {
                     LastRttTime = -math.huge,
                     RTT         = 0
                  }
               end
               local clientRtt   = RTTClients[userId]
               local sendTime    = LastMessages[userId][messageId]

               -- ignore if you have already processed a more recent message
               if sendTime > clientRtt.LastRttTime then
                  local SampleRTT = os.clock() - sendTime
                  if clientRtt.RTT == 0 then
                     clientRtt.RTT = SampleRTT
                  else
                     -- EstimatedRTT = α*SampleRTT + (1- α)*EstimatedRTT
                     clientRtt.RTT = RTT_ALPHA * SampleRTT + RTT_ALPHA_INV * clientRtt.RTT
                  end
                  clientRtt.LastRttTime = sendTime
               end               
               LastMessages[userId][messageId] = nil
            end
         end

         -- Receives client messages
         Event.OnServerEvent:Connect(function(player, message)
            local data  = message[1]
            local messageId    = message[2]

            if data == true then
               -- Acknowledgement  message
               if messageId ~= nil then
                  if AutoAcknowledge then
                     CalculateRTT(messageId, player)
                  end

                  if OnAcknowledge ~= nil then
                     OnAcknowledge(messageId, player, Contract)
                  end
               end
            else
               -- the message is never nil
               if data ~= nil then
                  if AutoAcknowledge and messageId ~= nil then
                     -- Sends knowledge message to the player
                     Event:FireClient(player, {true, messageId})
                  end
                  OnMessage(data, messageId, IsDelta(data), player, Contract)
               end
            end
         end)

         Contract.Send = function(data, messageId, player)
            if data == nil then
               return
            end
            if AutoAcknowledge and messageId ~= nil then
               -- Records the moment of sending the message, used to calculate the RTT of that client
               -- Only do this if auto responder is set up
               local userId = player.UserId
               if LastMessages[userId] == nil then
                  LastMessages[userId] = {}
               end
               if LastMessages[userId][messageId] == nil then
                  -- only saves the first time the message with this code was sent
                  LastMessages[userId][messageId] = os.clock()
               end
            end

            Event:FireClient(player, {data, messageId})
         end

         Contract.Acknowledge = function(id, player)
            -- Sends knowledge message to the player
            Event:FireClient(player, {true, id})
         end

         -- Average Client RTT
         Contract.RTT = function(player)
            local clientRtt = RTTClients[player.UserId]
            if clientRtt ~= nil then
               return clientRtt.RTT
            end
            return 0
         end
      else
         Event = game.ReplicatedStorage:WaitForChild(EventName)

         -- latest messages sent to the server
         local LastMessages = {}
         local LastRttTime = -math.huge

         local function CalculateRTT(messageId)
            if LastMessages[messageId] ~= nil then      
               local sendTime = LastMessages[messageId]
               -- ignore if you have already processed a more recent message
               if sendTime > LastRttTime then
                  local SampleRTT = os.clock() - sendTime
                  if RTTServer == 0 then
                     RTTServer = SampleRTT
                  else
                     -- EstimatedRTT = α*SampleRTT + (1- α)*EstimatedRTT
                     RTTServer = RTT_ALPHA * SampleRTT + RTT_ALPHA_INV * RTTServer
                  end
                  LastRttTime = sendTime
               end

               LastMessages[messageId] = nil
            end
         end

         -- Receives server messages
         Event.OnClientEvent:Connect(function(message)
            local data        = message[1]
            local messageId   = message[2]

            if data == true then
               -- Acknowledgement  message
               if messageId ~= nil then

                  if AutoAcknowledge then
                     CalculateRTT(messageId)
                  end

                  if OnAcknowledge ~= nil then
                     OnAcknowledge(messageId, nil, Contract)
                  end
               end
            else
               -- the message is never nil
               if data ~= nil then
                  if AutoAcknowledge and messageId ~= nil then
                     -- Sends knowledge message to the server
                     Event:FireServer({true, messageId})
                  end
   
                  OnMessage(data, messageId, IsDelta(data), nil, Contract)
               end
            end
         end)

         Contract.Send = function(data, messageId)
            if data == nil then
               return
            end
            if AutoAcknowledge and messageId ~= nil then
               -- Records the moment of sending the message, used to calculate the RTT of that client
               -- Only do this if auto responder is set up
               if LastMessages[messageId] == nil then
                  -- Only saves the first time the message with this code was sent
                  LastMessages[messageId] = os.clock()
               end
            end
            Event:FireServer({data, messageId})
         end

         Contract.Acknowledge = function(id)
            -- Sends knowledge message to the server
            Event:FireServer({true, id})
         end

         Contract.RTT = function()
            return RTTServer
         end
      end
   end

   return Contract
end

return CreateNewContract
