local j=require(script.GAResourceFlowType)local c=require(script.GAProgressionStatus)local L=require(script.GAErrorSeverity)local b={EGAResourceFlowType=j,EGAProgressionStatus=c,EGAErrorSeverity=L}local f=require(script.Logger)local E=require(script.Threading)local q=require(script.State)local Z=require(script.Validation)local r=require(script.Store)local o=require(script.Events)local G=game:GetService("\080\108\097\121\101\114\115")local y=game:GetService("\077\097\114\107\101\116\112\108\097\099\101\083\101\114\118\105\099\101")local u=game:GetService("\082\117\110\083\101\114\118\105\099\101")local t=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local U=game:GetService("\076\111\099\097\108\105\122\097\116\105\111\110\083\101\114\118\105\099\101")local H=game:GetService("\083\099\114\105\112\116\067\111\110\116\101\120\116")local M=t:WaitForChild("\077\111\100\117\108\101\115")local p=require(M.src.utils.Postie)local z local C={}local a=3600 local A=10 local V={}local W={}local K={}local P={}local B={}local function J(j,...)if P~=nil then table.insert(P,{Func=j;Args={...}})f:i("\065\100\100\101\100 \101\118\101\110\116 \116\111 \105\110\105\116\105\097\108\105\122\097\116\105\111\110 \113\117\101\117\101")else f:w("\073\110\105\116\105\097\108\105\122\097\116\105\111\110 \113\117\101\117\101 \097\108\114\101\097\100\121 \099\108\101\097\114\101\100\046")end end local function g(j,c,...)if not b:isPlayerReady(j)then if B[j]==nil then B[j]={}end table.insert(B[j],{Func=c;Args={...}})f:i("\065\100\100\101\100 \101\118\101\110\116 \116\111 \112\108\097\121\101\114 \105\110\105\116\105\097\108\105\122\097\116\105\111\110 \113\117\101\117\101")else f:w("\080\108\097\121\101\114 \105\110\105\116\105\097\108\105\122\097\116\105\111\110 \113\117\101\117\101 \097\108\114\101\097\100\121 \099\108\101\097\114\101\100\046")end end local function N(j)local c=j.playerId or nil local L=j.needsInitialized or true local b=j.shouldWarn or false local E=j.message or""if L and not q.Initialized then if b then f:w(E.." \083\068\075 \105\115 \110\111\116 \105\110\105\116\105\097\108\105\122\101\100")end return false end if L and(c and not q:isEnabled(c))then if b then f:w(E.." \083\068\075 \105\115 \100\105\115\097\098\108\101\100")end return false end if L and(c and not q:sessionIsStarted(c))then if b then f:w(E.." \083\101\115\115\105\111\110 \104\097\115 \110\111\116 \115\116\097\114\116\101\100 \121\101\116")end return false end return true end function b.configureAvailableCustomDimensions01(c,j)if N({needsInitialized=true;shouldWarn=false})then f:w("\065\118\097\105\108\097\098\108\101 \099\117\115\116\111\109 \100\105\109\101\110\115\105\111\110\115 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100")return end q:setAvailableCustomDimensions01(j)end function b.configureAvailableCustomDimensions02(c,j)if N({needsInitialized=true,shouldWarn=false})then f:w("\065\118\097\105\108\097\098\108\101 \099\117\115\116\111\109 \100\105\109\101\110\115\105\111\110\115 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100")return end q:setAvailableCustomDimensions02(j)end function b.configureAvailableCustomDimensions03(c,j)if N({needsInitialized=true;shouldWarn=false})then f:w("\065\118\097\105\108\097\098\108\101 \099\117\115\116\111\109 \100\105\109\101\110\115\105\111\110\115 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100")return end q:setAvailableCustomDimensions03(j)end function b.configureAvailableResourceCurrencies(c,j)if N({needsInitialized=true;shouldWarn=false})then f:w("\065\118\097\105\108\097\098\108\101 \114\101\115\111\117\114\099\101 \099\117\114\114\101\110\099\105\101\115 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100")return end o:setAvailableResourceCurrencies(j)end function b.configureAvailableResourceItemTypes(c,j)if N({needsInitialized=true;shouldWarn=false})then f:w("\065\118\097\105\108\097\098\108\101 \114\101\115\111\117\114\099\101 \105\116\101\109 \116\121\112\101\115 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100")return end o:setAvailableResourceItemTypes(j)end function b.configureBuild(c,j)if N({needsInitialized=true;shouldWarn=false})then f:w("\066\117\105\108\100 \118\101\114\115\105\111\110 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100\046")return end o:setBuild(j)end function b.configureAvailableGamepasses(c,j)if N({needsInitialized=true,shouldWarn=false})then f:w("\065\118\097\105\108\097\098\108\101 \103\097\109\101\112\097\115\115\101\115 \109\117\115\116 \098\101 \115\101\116 \098\101\102\111\114\101 \083\068\075 \105\115 \105\110\105\116\105\097\108\105\122\101\100\046")return end q:setAvailableGamepasses(j)end function b.startNewSession(L,j,c)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end if not q.Initialized then f:w("\067\097\110\110\111\116 \115\116\097\114\116 \110\101\119 \115\101\115\115\105\111\110\046 \083\068\075 \105\115 \110\111\116 \105\110\105\116\105\097\108\105\122\101\100 \121\101\116\046")return end q:startNewSession(j,c)end)end function b.endSession(c,j)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end q:endSession(j)end)end function b.filterForBusinessEvent(c,j)return string.gsub(j,"\091\094\065-\090\097-\122\048-\057\037\115\037-\095\037\046\037\040\037\041\033\037\063\093","")end function b.addBusinessEvent(L,j,c)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end if not N({playerId=j,needsInitialized=true,shouldWarn=false,message="\067\111\117\108\100 \110\111\116 \097\100\100 \098\117\115\105\110\101\115\115 \101\118\101\110\116"})then if j then g(j,b.addBusinessEvent,b,j,c)else J(b.addBusinessEvent,b,j,c)end return end local L=c.amount or 0 local f=c.itemType or""local E=c.itemId or""local Z=c.cartType or""local y=math.floor((L*.7)*.35)local u=c.gamepassId or nil o:addBusinessEvent(j,"\085\083\068",y,f,E,Z)if f=="\071\097\109\101\112\097\115\115"and Z~="\087\101\098\115\105\116\101"then local c=G:GetPlayerByUserId(j)local L=r:GetPlayerData(c)if not L.OwnedGamepasses then L.OwnedGamepasses={}end table.insert(L.OwnedGamepasses,u)r.PlayerCache[j]=L r:SavePlayerData(c)end end)end function b.addResourceEvent(L,j,c)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end if not N({playerId=j,needsInitialized=true,shouldWarn=false;message="\067\111\117\108\100 \110\111\116 \097\100\100 \114\101\115\111\117\114\099\101 \101\118\101\110\116"})then if j then g(j,b.addResourceEvent,b,j,c)else J(b.addResourceEvent,b,j,c)end return end local L=c.flowType or 0 local f=c.currency or""local E=c.amount or 0 local Z=c.itemType or""local r=c.itemId or""o:addResourceEvent(j,L,f,E,Z,r)end)end function b.addProgressionEvent(L,j,c)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end if not N({playerId=j;needsInitialized=true;shouldWarn=false;message="\067\111\117\108\100 \110\111\116 \097\100\100 \112\114\111\103\114\101\115\115\105\111\110 \101\118\101\110\116"})then if j then g(j,b.addProgressionEvent,b,j,c)else J(b.addProgressionEvent,b,j,c)end return end local L=c.progressionStatus or 0 local f=c.progression01 or""local E=c.progression02 or nil local Z=c.progression03 or nil local r=c.score or nil o:addProgressionEvent(j,L,f,E,Z,r)end)end function b.addDesignEvent(L,j,c)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end if not N({playerId=j,needsInitialized=true,shouldWarn=false,message="\067\111\117\108\100 \110\111\116 \097\100\100 \100\101\115\105\103\110 \101\118\101\110\116"})then if j then g(j,b.addDesignEvent,b,j,c)else J(b.addDesignEvent,b,j,c)end return end local L=c.eventId or""local f=c.value or nil o:addDesignEvent(j,L,f)end)end function b.addErrorEvent(L,j,c)E:performTaskOnGAThread(function()if not q:isEventSubmissionEnabled()then return end if not N({playerId=j,needsInitialized=true;shouldWarn=false;message="\067\111\117\108\100 \110\111\116 \097\100\100 \101\114\114\111\114 \101\118\101\110\116"})then if j then g(j,b.addErrorEvent,b,j,c)else J(b.addErrorEvent,b,j,c)end return end local L=c.severity or 0 local f=c.message or""o:addErrorEvent(j,L,f)end)end function b.setEnabledDebugLog(c,j)if u:IsStudio()then if j then f:setDebugLog(j)f:i("\068\101\098\117\103 \108\111\103\103\105\110\103 \101\110\097\098\108\101\100")else f:i("\068\101\098\117\103 \108\111\103\103\105\110\103 \100\105\115\097\098\108\101\100")f:setDebugLog(j)end else f:i("\115\101\116\069\110\097\098\108\101\100\068\101\098\117\103\076\111\103 \099\097\110 \111\110\108\121 \098\101 \117\115\101\100 \105\110 \115\116\117\100\105\111")end end function b.setEnabledInfoLog(c,j)if j then f:setInfoLog(j)f:i("\073\110\102\111 \108\111\103\103\105\110\103 \101\110\097\098\108\101\100")else f:i("\073\110\102\111 \108\111\103\103\105\110\103 \100\105\115\097\098\108\101\100")f:setInfoLog(j)end end function b.setEnabledVerboseLog(c,j)if j then f:setVerboseLog(j)f:ii("\086\101\114\098\111\115\101 \108\111\103\103\105\110\103 \101\110\097\098\108\101\100")else f:ii("\086\101\114\098\111\115\101 \108\111\103\103\105\110\103 \100\105\115\097\098\108\101\100")f:setVerboseLog(j)end end function b.setEnabledEventSubmission(c,j)E:performTaskOnGAThread(function()if j then q:setEventSubmission(j)f:i("\069\118\101\110\116 \115\117\098\109\105\115\115\105\111\110 \101\110\097\098\108\101\100")else f:i("\069\118\101\110\116 \115\117\098\109\105\115\115\105\111\110 \100\105\115\097\098\108\101\100")q:setEventSubmission(j)end end)end function b.setCustomDimension01(L,j,c)E:performTaskOnGAThread(function()if not Z:validateDimension(q._availableCustomDimensions01,c)then f:w("\067\111\117\108\100 \110\111\116 \115\101\116 \099\117\115\116\111\109\048\049 \100\105\109\101\110\115\105\111\110 \118\097\108\117\101 \116\111 \'"..(c.."\'\046 \086\097\108\117\101 \110\111\116 \102\111\117\110\100 \105\110 \097\118\097\105\108\097\098\108\101 \099\117\115\116\111\109\048\049 \100\105\109\101\110\115\105\111\110 \118\097\108\117\101\115"))return end if not N({playerId=j;needsInitialized=true,shouldWarn=true,message="\067\111\117\108\100 \110\111\116 \115\101\116 \099\117\115\116\111\109\048\049 \100\105\109\101\110\115\105\111\110"})then return end q:setCustomDimension01(j,c)end)end function b.setCustomDimension02(L,j,c)E:performTaskOnGAThread(function()if not Z:validateDimension(q._availableCustomDimensions02,c)then f:w("\067\111\117\108\100 \110\111\116 \115\101\116 \099\117\115\116\111\109\048\050 \100\105\109\101\110\115\105\111\110 \118\097\108\117\101 \116\111 \'"..(c.."\'\046 \086\097\108\117\101 \110\111\116 \102\111\117\110\100 \105\110 \097\118\097\105\108\097\098\108\101 \099\117\115\116\111\109\048\050 \100\105\109\101\110\115\105\111\110 \118\097\108\117\101\115"))return end if not N({playerId=j,needsInitialized=true,shouldWarn=true;message="\067\111\117\108\100 \110\111\116 \115\101\116 \099\117\115\116\111\109\048\050 \100\105\109\101\110\115\105\111\110"})then return end q:setCustomDimension02(j,c)end)end function b.setCustomDimension03(L,j,c)E:performTaskOnGAThread(function()if not Z:validateDimension(q._availableCustomDimensions03,c)then f:w("\067\111\117\108\100 \110\111\116 \115\101\116 \099\117\115\116\111\109\048\051 \100\105\109\101\110\115\105\111\110 \118\097\108\117\101 \116\111 \'"..(c.."\'\046 \086\097\108\117\101 \110\111\116 \102\111\117\110\100 \105\110 \097\118\097\105\108\097\098\108\101 \099\117\115\116\111\109\048\051 \100\105\109\101\110\115\105\111\110 \118\097\108\117\101\115"))return end if not N({playerId=j;needsInitialized=true,shouldWarn=true;message="\067\111\117\108\100 \110\111\116 \115\101\116 \099\117\115\116\111\109\048\051 \100\105\109\101\110\115\105\111\110"})then return end q:setCustomDimension03(j,c)end)end function b.setEnabledReportErrors(c,j)E:performTaskOnGAThread(function()q.ReportErrors=j end)end function b.setEnabledAutomaticSendBusinessEvents(c,j)E:performTaskOnGAThread(function()q.AutomaticSendBusinessEvents=j end)end function b.addGameAnalyticsTeleportData(L,j,c)local b={}for j,c in ipairs(j)do local L=r:GetPlayerDataFromCache(c)L.PlayerTeleporting=true local f={SessionID=L.SessionID,Sessions=L.Sessions,SessionStart=L.SessionStart}b[tostring(c)]=f end c.gameanalyticsData=b return c end function b.getRemoteConfigsValueAsString(L,j,c)local b=c.key or""local f=c.defaultValue or nil return q:getRemoteConfigsStringValue(j,b,f)end function b.isRemoteConfigsReady(c,j)return q:isRemoteConfigsReady(j)end function b.getRemoteConfigsContentAsString(c,j)return q:getRemoteConfigsContentAsString(j)end function b.PlayerJoined(c,j)local L=j:GetJoinData()local E=L.TeleportData local Z=nil local o=r:GetPlayerData(j)if E then Z=E.gameanalyticsData and E.gameanalyticsData[tostring(j.UserId)]end local G=r:GetPlayerDataFromCache(j.UserId)if G then if Z then G.SessionID=Z.SessionID G.SessionStart=Z.SessionStart end G.PlayerTeleporting=false return end local u="\117\110\107\110\111\119\110"local H,M=p.InvokeClient("\103\101\116\080\108\097\116\102\111\114\109",j,5)if H then u=M end for j,c in pairs(r.BasePlayerData)do o[j]=o[j]or c end local a,A=pcall(function()return U:GetCountryRegionForPlayerAsync(j)end)if a then o.CountryCode=A end r.PlayerCache[j.UserId]=o o.Platform=u=="\067\111\110\115\111\108\101"and"\117\119\112\095\099\111\110\115\111\108\101"or u=="\077\111\098\105\108\101"and"\117\119\112\095\109\111\098\105\108\101"or u=="\068\101\115\107\116\111\112"and"\117\119\112\095\100\101\115\107\116\111\112"or"\117\119\112\095\100\101\115\107\116\111\112"o.OS=o.Platform.." \048\046\048\046\048"b:startNewSession(j,Z)z=z or t:WaitForChild("\079\110\080\108\097\121\101\114\082\101\097\100\121\069\118\101\110\116")z:Fire(j)if q.AutomaticSendBusinessEvents then if o.OwnedGamepasses==nil then o.OwnedGamepasses={}for c,L in ipairs(q._availableGamepasses)do if y:UserOwnsGamePassAsync(j.UserId,L)then table.insert(o.OwnedGamepasses,L)end end r.PlayerCache[j.UserId]=o r:SavePlayerData(j)else local c={}for L,b in ipairs(q._availableGamepasses)do if y:UserOwnsGamePassAsync(j.UserId,b)then table.insert(c,b)end end local L={}for j,c in ipairs(o.OwnedGamepasses)do L[c]=true end for c,f in ipairs(c)do if not L[f]then table.insert(o.OwnedGamepasses,f)local c=C[f]if not c then c=y:GetProductInfo(f,Enum.InfoType.GamePass)C[f]=c end b:addBusinessEvent(j.UserId,{amount=c.PriceInRobux;itemType="\071\097\109\101\112\097\115\115";itemId=b:filterForBusinessEvent(c.Name),cartType="\087\101\098\115\105\116\101"})end end r.PlayerCache[j.UserId]=o r:SavePlayerData(j)end end local V=B[j.UserId]if V then B[j.UserId]=nil for j,c in ipairs(V)do c.Func(unpack(c.Args))end f:i("\080\108\097\121\101\114 \105\110\105\116\105\097\108\105\122\097\116\105\111\110 \113\117\101\117\101 \099\097\108\108\101\100 \035"..(#V.." \101\118\101\110\116\115"))end end function b.PlayerRemoved(c,j)r:SavePlayerData(j)local L=r:GetPlayerDataFromCache(j.UserId)if L then if not L.PlayerTeleporting then b:endSession(j.UserId)else r.PlayerCache[j.UserId]=nil end end end function b.isPlayerReady(c,j)if r:GetPlayerDataFromCache(j)then return true else return false end end function b.ProcessReceiptCallback(c,j)local L=C[j.ProductId]if not L then L=y:GetProductInfo(j.ProductId,Enum.InfoType.Product)C[j.ProductId]=L end b:addBusinessEvent(j.PlayerId,{amount=j.CurrencySpent,itemType="\068\101\118\101\108\111\112\101\114\080\114\111\100\117\099\116",itemId=b:filterForBusinessEvent(L.Name)})end function b.GamepassPurchased(f,j,c,L)local E=C[c]if not E then E=y:GetProductInfo(c,Enum.InfoType.GamePass)C[c]=E end local q=0 local Z="\071\097\109\101\080\097\115\115"if L then q=L.PriceInRobux Z=L.Name elseif E then q=E.PriceInRobux Z=E.Name end b:addBusinessEvent(j.UserId,{amount=q or 0;itemType="\071\097\109\101\112\097\115\115",itemId=b:filterForBusinessEvent(Z);gamepassId=c})end local S={"\103\097\109\101\075\101\121","\115\101\099\114\101\116\075\101\121"}function b.initialize(c,j)E:performTaskOnGAThread(function()for c,L in ipairs(S)do if j[L]==nil then f:e("\073\110\105\116\105\097\108\105\122\101 \'"..(L.."\' \111\112\116\105\111\110 \109\105\115\115\105\110\103"))return end end if j.enableInfoLog~=nil and j.enableInfoLog then b:setEnabledInfoLog(j.enableInfoLog)end if j.enableVerboseLog~=nil and j.enableVerboseLog then b:setEnabledVerboseLog(j.enableVerboseLog)end if j.availableCustomDimensions01~=nil and#j.availableCustomDimensions01>0 then b:configureAvailableCustomDimensions01(j.availableCustomDimensions01)end if j.availableCustomDimensions02~=nil and#j.availableCustomDimensions02>0 then b:configureAvailableCustomDimensions02(j.availableCustomDimensions02)end if j.availableCustomDimensions03~=nil and#j.availableCustomDimensions03>0 then b:configureAvailableCustomDimensions03(j.availableCustomDimensions03)end if j.availableResourceCurrencies~=nil and#j.availableResourceCurrencies>0 then b:configureAvailableResourceCurrencies(j.availableResourceCurrencies)end if j.availableResourceItemTypes~=nil and#j.availableResourceItemTypes>0 then b:configureAvailableResourceItemTypes(j.availableResourceItemTypes)end if j.build~=nil and#j.build>0 then b:configureBuild(j.build)end if j.availableGamepasses~=nil and#j.availableGamepasses>0 then b:configureAvailableGamepasses(j.availableGamepasses)end if j.enableDebugLog~=nil then b:setEnabledDebugLog(j.enableDebugLog)end if j.automaticSendBusinessEvents~=nil then b:setEnabledAutomaticSendBusinessEvents(j.automaticSendBusinessEvents)end if j.reportErrors~=nil then b:setEnabledReportErrors(j.reportErrors)end if N({needsInitialized=true;shouldWarn=false})then f:w("\083\068\075 \097\108\114\101\097\100\121 \105\110\105\116\105\097\108\105\122\101\100\046 \067\097\110 \111\110\108\121 \098\101 \099\097\108\108\101\100 \111\110\099\101\046")return end local c=j.gameKey local L=j.secretKey if not Z:validateKeys(c,L)then f:w("\083\068\075 \102\097\105\108\101\100 \105\110\105\116\105\097\108\105\122\101\046 \071\097\109\101 \107\101\121 \111\114 \115\101\099\114\101\116 \107\101\121 \105\115 \105\110\118\097\108\105\100\046 \067\097\110 \111\110\108\121 \099\111\110\116\097\105\110 \099\104\097\114\097\099\116\101\114\115 \065-\122 \048-\057\044 \103\097\109\101\075\101\121 \105\115 \051\050 \108\101\110\103\116\104\044 \115\101\099\114\101\116\075\101\121 \105\115 \052\048 \108\101\110\103\116\104\046 \070\097\105\108\101\100 \107\101\121\115 - \103\097\109\101\075\101\121\058 "..(c..("\044 \115\101\099\114\101\116\075\101\121\058 "..L)))return end o.GameKey=c o.SecretKey=L q.Initialized=true G.PlayerAdded:Connect(function(j)b:PlayerJoined(j)end)G.PlayerRemoving:Connect(function(j)b:PlayerRemoved(j)end)for j,c in ipairs(G:GetPlayers())do(coroutine.wrap(b.PlayerJoined))(b,c)end for j,c in ipairs(P)do spawn(c.Func,unpack(c.Args))end f:i("\083\101\114\118\101\114 \105\110\105\116\105\097\108\105\122\097\116\105\111\110 \113\117\101\117\101 \099\097\108\108\101\100 \035"..(#P.." \101\118\101\110\116\115"))P=nil o:processEventQueue()end)end if not t:FindFirstChild("\071\097\109\101\065\110\097\108\121\116\105\099\115\082\101\109\111\116\101\067\111\110\102\105\103\115")then local j=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")j.Name="\071\097\109\101\065\110\097\108\121\116\105\099\115\082\101\109\111\116\101\067\111\110\102\105\103\115"j.Parent=t end if not t:FindFirstChild("\079\110\080\108\097\121\101\114\082\101\097\100\121\069\118\101\110\116")then local j=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")j.Name="\079\110\080\108\097\121\101\114\082\101\097\100\121\069\118\101\110\116"j.Parent=t end spawn(function()local j=math.floor(os.time()/3600)V=r:GetErrorDataStore(j)while wait(a)do j=math.floor(os.time()/3600)V=r:GetErrorDataStore(j)W={}K={}end end)spawn(function()while wait(r.AutoSaveData)do for j,c in pairs(K)do local L=W[c]local b=L.currentCount-L.countInDS W[c].countInDS=r:IncrementErrorCount(V,c,b)W[c].currentCount=W[c].countInDS end end end)local function w(j,c,L,f)if not q.ReportErrors then return end if not L then return end local E=nil local Z,r=pcall(function()E=L:GetFullName()end)if not Z then return end local o=E..("\058 \109\101\115\115\097\103\101\061"..(j..("\044 \116\114\097\099\101\061"..c)))if#o>8192 then o=string.sub(o,1,8192)end local G=nil if f then G=f.UserId o=o:gsub(f.Name,"\091\076\111\099\097\108\080\108\097\121\101\114\093")end local y=o if#y>50 then y=string.sub(y,1,50)end if W[y]==nil then K[#K+1]=y W[y]={}W[y].countInDS=0 W[y].currentCount=0 end if W[y].currentCount>A then return end b:addErrorEvent(G,{severity=b.EGAErrorSeverity.error;message=o})W[y].currentCount=W[y].currentCount+1 end H.Error:Connect(w)if not t:FindFirstChild("\071\097\109\101\065\110\097\108\121\116\105\099\115\069\114\114\111\114")then local j=Instance.new("\082\101\109\111\116\101\069\118\101\110\116")j.Name="\071\097\109\101\065\110\097\108\121\116\105\099\115\069\114\114\111\114"j.Parent=t end t.GameAnalyticsError.OnServerEvent:Connect(function(j,c,L,b)w(c,L,b,j)end)y.PromptGamePassPurchaseFinished:Connect(function(j,c,L)if not q.AutomaticSendBusinessEvents or not L then return end b:GamepassPurchased(j,c)end)return b
local GAResourceFlowType = require(script.GAResourceFlowType)
local GAProgressionStatus = require(script.GAProgressionStatus)
local GAErrorSeverity = require(script.GAErrorSeverity)

local ga = {
	EGAResourceFlowType = GAResourceFlowType,
	EGAProgressionStatus = GAProgressionStatus,
	EGAErrorSeverity = GAErrorSeverity,
}

local logger = require(script.Logger)
local threading = require(script.Threading)
local state = require(script.State)
local validation = require(script.Validation)
local store = require(script.Store)
local events = require(script.Events)
local Players = game:GetService('Players')
local MKT = game:GetService('MarketplaceService')
local RunService = game:GetService('RunService')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local LocalizationService = game:GetService('LocalizationService')
local ScriptContext = game:GetService('ScriptContext')
local Modules = ReplicatedStorage:WaitForChild('Modules')
local Postie = require(Modules.src.utils.Postie)

local OnPlayerReadyEvent
local ProductCache = {}
local ONE_HOUR_IN_SECONDS = 3600
local MaxErrorsPerHour = 10
local ErrorDS = {}
local errorCountCache = {}
local errorCountCacheKeys = {}

local InitializationQueue = {}
local InitializationQueueByUserId = {}

local function addToInitializationQueue(func, ...)
	if InitializationQueue ~= nil then
		table.insert(InitializationQueue, {
			Func = func,
			Args = { ... },
		})

		logger:i('Added event to initialization queue')
		--This should never happen
	else
		logger:w('Initialization queue already cleared.')
	end
end

local function addToInitializationQueueByUserId(userId, func, ...)
	if not ga:isPlayerReady(userId) then
		if InitializationQueueByUserId[userId] == nil then
			InitializationQueueByUserId[userId] = {}
		end

		table.insert(InitializationQueueByUserId[userId], {
			Func = func,
			Args = { ... },
		})

		logger:i('Added event to player initialization queue')
		--This should never happen
	else
		logger:w('Player initialization queue already cleared.')
	end
end

-- local functions
local function isSdkReady(options)
	local playerId = options['playerId'] or nil
	local needsInitialized = options['needsInitialized'] or true
	local shouldWarn = options['shouldWarn'] or false
	local message = options['message'] or ''

	-- Is SDK initialized
	if needsInitialized and not state.Initialized then
		if shouldWarn then
			logger:w(message .. ' SDK is not initialized')
		end

		return false
	end

	-- Is SDK enabled
	if needsInitialized and playerId and not state:isEnabled(playerId) then
		if shouldWarn then
			logger:w(message .. ' SDK is disabled')
		end

		return false
	end

	-- Is session started
	if needsInitialized and playerId and not state:sessionIsStarted(playerId) then
		if shouldWarn then
			logger:w(message .. ' Session has not started yet')
		end

		return false
	end

	return true
end

function ga:configureAvailableCustomDimensions01(customDimensions)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Available custom dimensions must be set before SDK is initialized')
		return
	end

	state:setAvailableCustomDimensions01(customDimensions)
end

function ga:configureAvailableCustomDimensions02(customDimensions)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Available custom dimensions must be set before SDK is initialized')
		return
	end

	state:setAvailableCustomDimensions02(customDimensions)
end

function ga:configureAvailableCustomDimensions03(customDimensions)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Available custom dimensions must be set before SDK is initialized')
		return
	end

	state:setAvailableCustomDimensions03(customDimensions)
end

function ga:configureAvailableResourceCurrencies(resourceCurrencies)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Available resource currencies must be set before SDK is initialized')
		return
	end

	events:setAvailableResourceCurrencies(resourceCurrencies)
end

function ga:configureAvailableResourceItemTypes(resourceItemTypes)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Available resource item types must be set before SDK is initialized')
		return
	end

	events:setAvailableResourceItemTypes(resourceItemTypes)
end

function ga:configureBuild(build)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Build version must be set before SDK is initialized.')
		return
	end

	events:setBuild(build)
end

function ga:configureAvailableGamepasses(availableGamepasses)
	if isSdkReady({
		needsInitialized = true,
		shouldWarn = false,
	}) then
		logger:w('Available gamepasses must be set before SDK is initialized.')
		return
	end

	state:setAvailableGamepasses(availableGamepasses)
end

function ga:startNewSession(player, gaData)
	threading:performTaskOnGAThread(function()
		if not state:isEventSubmissionEnabled() then return end

		if not state.Initialized then
			logger:w('Cannot start new session. SDK is not initialized yet.')
			return
		end

		state:startNewSession(player, gaData)
	end)
end

function ga:endSession(playerId)
	threading:performTaskOnGAThread(function()
		if not state:isEventSubmissionEnabled() then return end
		state:endSession(playerId)
	end)
end

function ga:filterForBusinessEvent(text)
	return string.gsub(text, '[^A-Za-z0-9%s%-_%.%(%)!%?]', '')
end

function ga:addBusinessEvent(playerId, options)
	threading:performTaskOnGAThread(function()

	-- Send to events
		if not state:isEventSubmissionEnabled() then return end
		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = false,
			message = 'Could not add business event',
		}) then
			if playerId then
				addToInitializationQueueByUserId(
					playerId,
					ga.addBusinessEvent,
					ga,
					playerId,
					options
				)
			else
				addToInitializationQueue(ga.addBusinessEvent, ga, playerId, options)
			end
			return
		end

		local amount = options['amount'] or 0
		local itemType = options['itemType'] or ''
		local itemId = options['itemId'] or ''
		local cartType = options['cartType'] or ''
		local USDSpent = math.floor((amount * 0.7) * 0.35)
		local gamepassId = options['gamepassId'] or nil

		events:addBusinessEvent(playerId, 'USD', USDSpent, itemType, itemId, cartType)

		if itemType == 'Gamepass' and cartType ~= 'Website' then
			local player = Players:GetPlayerByUserId(playerId)
			local playerData = store:GetPlayerData(player)
			if not playerData.OwnedGamepasses then
				playerData.OwnedGamepasses = {}
			end
			table.insert(playerData.OwnedGamepasses, gamepassId)
			store.PlayerCache[playerId] = playerData
			store:SavePlayerData(player)
		end
	end)
end

function ga:addResourceEvent(playerId, options)
	threading:performTaskOnGAThread(function()

	-- Send to events
		if not state:isEventSubmissionEnabled() then return end
		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = false,
			message = 'Could not add resource event',
		}) then
			if playerId then
				addToInitializationQueueByUserId(
					playerId,
					ga.addResourceEvent,
					ga,
					playerId,
					options
				)
			else
				addToInitializationQueue(ga.addResourceEvent, ga, playerId, options)
			end
			return
		end

		local flowType = options['flowType'] or 0
		local currency = options['currency'] or ''
		local amount = options['amount'] or 0
		local itemType = options['itemType'] or ''
		local itemId = options['itemId'] or ''

		events:addResourceEvent(playerId, flowType, currency, amount, itemType, itemId)
	end)
end

function ga:addProgressionEvent(playerId, options)
	threading:performTaskOnGAThread(function()

	-- Send to events
		if not state:isEventSubmissionEnabled() then return end
		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = false,
			message = 'Could not add progression event',
		}) then
			if playerId then
				addToInitializationQueueByUserId(
					playerId,
					ga.addProgressionEvent,
					ga,
					playerId,
					options
				)
			else
				addToInitializationQueue(ga.addProgressionEvent, ga, playerId, options)
			end
			return
		end

		local progressionStatus = options['progressionStatus'] or 0
		local progression01 = options['progression01'] or ''
		local progression02 = options['progression02'] or nil
		local progression03 = options['progression03'] or nil
		local score = options['score'] or nil

		events:addProgressionEvent(
			playerId,
			progressionStatus,
			progression01,
			progression02,
			progression03,
			score
		)
	end)
end

function ga:addDesignEvent(playerId, options)
	threading:performTaskOnGAThread(function()

	-- Send to events
		if not state:isEventSubmissionEnabled() then return end
		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = false,
			message = 'Could not add design event',
		}) then
			if playerId then
				addToInitializationQueueByUserId(playerId, ga.addDesignEvent, ga, playerId, options)
			else
				addToInitializationQueue(ga.addDesignEvent, ga, playerId, options)
			end
			return
		end

		local eventId = options['eventId'] or ''
		local value = options['value'] or nil

		events:addDesignEvent(playerId, eventId, value)
	end)
end

function ga:addErrorEvent(playerId, options)
	threading:performTaskOnGAThread(function()

	-- Send to events
		if not state:isEventSubmissionEnabled() then return end
		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = false,
			message = 'Could not add error event',
		}) then
			if playerId then
				addToInitializationQueueByUserId(playerId, ga.addErrorEvent, ga, playerId, options)
			else
				addToInitializationQueue(ga.addErrorEvent, ga, playerId, options)
			end
			return
		end

		local severity = options['severity'] or 0
		local message = options['message'] or ''

		events:addErrorEvent(playerId, severity, message)
	end)
end

function ga:setEnabledDebugLog(flag)
	if RunService:IsStudio() then
		if flag then
			logger:setDebugLog(flag)
			logger:i('Debug logging enabled')
		else
			logger:i('Debug logging disabled')
			logger:setDebugLog(flag)
		end
	else
		logger:i('setEnabledDebugLog can only be used in studio')
	end
end

function ga:setEnabledInfoLog(flag)
	if flag then
		logger:setInfoLog(flag)
		logger:i('Info logging enabled')
	else
		logger:i('Info logging disabled')
		logger:setInfoLog(flag)
	end
end

function ga:setEnabledVerboseLog(flag)
	if flag then
		logger:setVerboseLog(flag)
		logger:ii('Verbose logging enabled')
	else
		logger:ii('Verbose logging disabled')
		logger:setVerboseLog(flag)
	end
end

function ga:setEnabledEventSubmission(flag)
	threading:performTaskOnGAThread(function()
		if flag then
			state:setEventSubmission(flag)
			logger:i('Event submission enabled')
		else
			logger:i('Event submission disabled')
			state:setEventSubmission(flag)
		end
	end)
end

function ga:setCustomDimension01(playerId, dimension)
	threading:performTaskOnGAThread(function()
		if not validation:validateDimension(state._availableCustomDimensions01, dimension) then
			logger:w(
				"Could not set custom01 dimension value to '" .. dimension .. "'. Value not found in available custom01 dimension values"
			)
			return
		end

		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = true,
			message = 'Could not set custom01 dimension',
		}) then return
		end

		state:setCustomDimension01(playerId, dimension)
	end)
end

function ga:setCustomDimension02(playerId, dimension)
	threading:performTaskOnGAThread(function()
		if not validation:validateDimension(state._availableCustomDimensions02, dimension) then
			logger:w(
				"Could not set custom02 dimension value to '" .. dimension .. "'. Value not found in available custom02 dimension values"
			)
			return
		end

		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = true,
			message = 'Could not set custom02 dimension',
		}) then return
		end

		state:setCustomDimension02(playerId, dimension)
	end)
end

function ga:setCustomDimension03(playerId, dimension)
	threading:performTaskOnGAThread(function()
		if not validation:validateDimension(state._availableCustomDimensions03, dimension) then
			logger:w(
				"Could not set custom03 dimension value to '" .. dimension .. "'. Value not found in available custom03 dimension values"
			)
			return
		end

		if not isSdkReady({
			playerId = playerId,
			needsInitialized = true,
			shouldWarn = true,
			message = 'Could not set custom03 dimension',
		}) then return
		end

		state:setCustomDimension03(playerId, dimension)
	end)
end

function ga:setEnabledReportErrors(flag)
	threading:performTaskOnGAThread(function()
		state.ReportErrors = flag
	end)
end

function ga:setEnabledAutomaticSendBusinessEvents(flag)
	threading:performTaskOnGAThread(function()
		state.AutomaticSendBusinessEvents = flag
	end)
end

function ga:addGameAnalyticsTeleportData(playerIds, teleportData)
	local gameAnalyticsTeleportData = {}
	for _, playerId in ipairs(playerIds) do
		local PlayerData = store:GetPlayerDataFromCache(playerId)
		PlayerData.PlayerTeleporting = true
		local data = {
			SessionID = PlayerData.SessionID,
			Sessions = PlayerData.Sessions,
			SessionStart = PlayerData.SessionStart,
		}

		gameAnalyticsTeleportData[tostring(playerId)] = data
	end

	teleportData['gameanalyticsData'] = gameAnalyticsTeleportData

	return teleportData
end

function ga:getRemoteConfigsValueAsString(playerId, options)
	local key = options['key'] or ''
	local defaultValue = options['defaultValue'] or nil
	return state:getRemoteConfigsStringValue(playerId, key, defaultValue)
end

function ga:isRemoteConfigsReady(playerId)
	return state:isRemoteConfigsReady(playerId)
end

function ga:getRemoteConfigsContentAsString(playerId)
	return state:getRemoteConfigsContentAsString(playerId)
end

function ga:PlayerJoined(Player)
	local joinData = Player:GetJoinData()
	local teleportData = joinData.TeleportData
	local gaData = nil

	--Variables
	local PlayerData = store:GetPlayerData(Player)

	if teleportData then
		gaData =
			teleportData.gameanalyticsData and teleportData.gameanalyticsData[tostring(
				Player.UserId
			)]
	end

	local pd = store:GetPlayerDataFromCache(Player.UserId)
	if pd then
		if gaData then
			pd.SessionID = gaData.SessionID
			pd.SessionStart = gaData.SessionStart
		end
		pd.PlayerTeleporting = false
		return
	end

	local PlayerPlatform = 'unknown'
	local isSuccessful, platform = Postie.InvokeClient('getPlatform', Player, 5)
	if isSuccessful then
		PlayerPlatform = platform
	end

	--Fill Data
	for key, value in pairs(store.BasePlayerData) do
		PlayerData[key] = PlayerData[key] or value
	end

	local countryCodeResult, countryCode = pcall(function()
		return LocalizationService:GetCountryRegionForPlayerAsync(Player)
	end)

	if countryCodeResult then
		PlayerData.CountryCode = countryCode
	end

	store.PlayerCache[Player.UserId] = PlayerData

	PlayerData.Platform =
		(PlayerPlatform == 'Console' and 'uwp_console') or (PlayerPlatform == 'Mobile' and 'uwp_mobile') or (PlayerPlatform == 'Desktop' and 'uwp_desktop') or 'uwp_desktop'
	PlayerData.OS = PlayerData.Platform .. ' 0.0.0'

	ga:startNewSession(Player, gaData)

	OnPlayerReadyEvent = OnPlayerReadyEvent or ReplicatedStorage:WaitForChild('OnPlayerReadyEvent')
	OnPlayerReadyEvent:Fire(Player)

	--Validate
	if state.AutomaticSendBusinessEvents then
		--Website gamepasses
		if PlayerData.OwnedGamepasses == nil then --player is new (or is playing after SDK update)
			PlayerData.OwnedGamepasses = {}
			for _, id in ipairs(state._availableGamepasses) do
				if MKT:UserOwnsGamePassAsync(Player.UserId, id) then
					table.insert(PlayerData.OwnedGamepasses, id)
				end
			end
			--Player's data is now up to date. gamepass purchases on website can now be tracked in future visits
			store.PlayerCache[Player.UserId] = PlayerData
			store:SavePlayerData(Player)
			--build a list of the game passes a user owns

			--make a table so it's easier to compare to stored game passes

			--compare stored game passes to currently owned game passses

			--Cache
			--Get
		else
			local currentlyOwned = {}
			for _, id in ipairs(state._availableGamepasses) do
				if MKT:UserOwnsGamePassAsync(Player.UserId, id) then
					table.insert(currentlyOwned, id)
				end
			end

			local storedGamepassesTable = {}
			for _, id in ipairs(PlayerData.OwnedGamepasses) do
				storedGamepassesTable[id] = true
			end

			for _, id in ipairs(currentlyOwned) do
				if not storedGamepassesTable[id] then
					table.insert(PlayerData.OwnedGamepasses, id)

					local gamepassInfo = ProductCache[id]

					if not gamepassInfo then
						gamepassInfo = MKT:GetProductInfo(id, Enum.InfoType.GamePass)
						ProductCache[id] = gamepassInfo
					end

					ga:addBusinessEvent(Player.UserId, {
						amount = gamepassInfo.PriceInRobux,
						itemType = 'Gamepass',
						itemId = ga:filterForBusinessEvent(gamepassInfo.Name),
						cartType = 'Website',
					})
				end
			end

			store.PlayerCache[Player.UserId] = PlayerData

			store:SavePlayerData(Player)
		end
	end

	local playerEventQueue = InitializationQueueByUserId[Player.UserId]
	if playerEventQueue then
		InitializationQueueByUserId[Player.UserId] = nil
		for _, queuedFunction in ipairs(playerEventQueue) do
			queuedFunction.Func(unpack(queuedFunction.Args))
		end

		logger:i('Player initialization queue called #' .. #playerEventQueue .. ' events')
	end
end

function ga:PlayerRemoved(Player)
	--Save
	store:SavePlayerData(Player)

	local PlayerData = store:GetPlayerDataFromCache(Player.UserId)
	if PlayerData then
		if not PlayerData.PlayerTeleporting then
			ga:endSession(Player.UserId)
		else
			store.PlayerCache[Player.UserId] = nil
		end
	end
end

function ga:isPlayerReady(playerId)
	if store:GetPlayerDataFromCache(playerId) then
		return true
	else
		return false
	end
end

function ga:ProcessReceiptCallback(Info)
	--Variables
	local ProductInfo = ProductCache[Info.ProductId]

	--Cache
	if not ProductInfo then
		--Get
		ProductInfo = MKT:GetProductInfo(Info.ProductId, Enum.InfoType.Product)
		ProductCache[Info.ProductId] = ProductInfo
	end

	ga:addBusinessEvent(Info.PlayerId, {
		amount = Info.CurrencySpent,
		itemType = 'DeveloperProduct',
		itemId = ga:filterForBusinessEvent(ProductInfo.Name),
	})
end

--customGamepassInfo argument to optinaly provide our own name or price
function ga:GamepassPurchased(player, id, customGamepassInfo)
	local gamepassInfo = ProductCache[id]

	--Cache
	if not gamepassInfo then
		--Get
		gamepassInfo = MKT:GetProductInfo(id, Enum.InfoType.GamePass)
		ProductCache[id] = gamepassInfo
	end

	local amount = 0
	local itemId = 'GamePass'
	if customGamepassInfo then
		amount = customGamepassInfo.PriceInRobux
		itemId = customGamepassInfo.Name
	elseif gamepassInfo then
		amount = gamepassInfo.PriceInRobux
		itemId = gamepassInfo.Name
	end

	ga:addBusinessEvent(player.UserId, {
		amount = amount or 0,
		itemType = 'Gamepass',
		itemId = ga:filterForBusinessEvent(itemId),
		gamepassId = id,
	})
end

local requiredInitializationOptions = { 'gameKey', 'secretKey' }

function ga:initialize(options)
	threading:performTaskOnGAThread(function()

	-- New Players

	-- Players leaving

	-- Fire for players already in game
		for _, option in ipairs(requiredInitializationOptions) do
			if options[option] == nil then
				logger:e("Initialize '" .. option .. "' option missing")
				return
			end
		end
		if options.enableInfoLog ~= nil and options.enableInfoLog then
			ga:setEnabledInfoLog(options.enableInfoLog)
		end
		if options.enableVerboseLog ~= nil and options.enableVerboseLog then
			ga:setEnabledVerboseLog(options.enableVerboseLog)
		end
		if options.availableCustomDimensions01 ~= nil and #options.availableCustomDimensions01 > 0 then
			ga:configureAvailableCustomDimensions01(options.availableCustomDimensions01)
		end
		if options.availableCustomDimensions02 ~= nil and #options.availableCustomDimensions02 > 0 then
			ga:configureAvailableCustomDimensions02(options.availableCustomDimensions02)
		end
		if options.availableCustomDimensions03 ~= nil and #options.availableCustomDimensions03 > 0 then
			ga:configureAvailableCustomDimensions03(options.availableCustomDimensions03)
		end
		if options.availableResourceCurrencies ~= nil and #options.availableResourceCurrencies > 0 then
			ga:configureAvailableResourceCurrencies(options.availableResourceCurrencies)
		end
		if options.availableResourceItemTypes ~= nil and #options.availableResourceItemTypes > 0 then
			ga:configureAvailableResourceItemTypes(options.availableResourceItemTypes)
		end
		if options.build ~= nil and #options.build > 0 then
			ga:configureBuild(options.build)
		end
		if options.availableGamepasses ~= nil and #options.availableGamepasses > 0 then
			ga:configureAvailableGamepasses(options.availableGamepasses)
		end
		if options.enableDebugLog ~= nil then
			ga:setEnabledDebugLog(options.enableDebugLog)
		end

		if options.automaticSendBusinessEvents ~= nil then
			ga:setEnabledAutomaticSendBusinessEvents(options.automaticSendBusinessEvents)
		end
		if options.reportErrors ~= nil then
			ga:setEnabledReportErrors(options.reportErrors)
		end

		if isSdkReady({
			needsInitialized = true,
			shouldWarn = false,
		}) then
			logger:w('SDK already initialized. Can only be called once.')
			return
		end

		local gameKey = options['gameKey']
		local secretKey = options['secretKey']

		if not validation:validateKeys(gameKey, secretKey) then
			logger:w(
				'SDK failed initialize. Game key or secret key is invalid. Can only contain characters A-z 0-9, gameKey is 32 length, secretKey is 40 length. Failed keys - gameKey: ' .. gameKey .. ', secretKey: ' .. secretKey
			)
			return
		end

		events.GameKey = gameKey
		events.SecretKey = secretKey

		state.Initialized = true

		Players.PlayerAdded:Connect(function(Player)
			ga:PlayerJoined(Player)
		end)

		Players.PlayerRemoving:Connect(function(Player)
			ga:PlayerRemoved(Player)
		end)

		for _, Player in ipairs(Players:GetPlayers()) do
			coroutine.wrap(ga.PlayerJoined)(ga, Player)
		end

		for _, queuedFunction in ipairs(InitializationQueue) do
			spawn(queuedFunction.Func, unpack(queuedFunction.Args))
		end
		logger:i('Server initialization queue called #' .. #InitializationQueue .. ' events')
		InitializationQueue = nil

		events:processEventQueue()
	end)
end

if not ReplicatedStorage:FindFirstChild('GameAnalyticsRemoteConfigs') then
	--Create
	local f = Instance.new('RemoteEvent')
	f.Name = 'GameAnalyticsRemoteConfigs'
	f.Parent = ReplicatedStorage
end

if not ReplicatedStorage:FindFirstChild('OnPlayerReadyEvent') then
	--Create
	local f = Instance.new('BindableEvent')
	f.Name = 'OnPlayerReadyEvent'
	f.Parent = ReplicatedStorage
end

spawn(function()
	local currentHour = math.floor(os.time() / 3600)
	ErrorDS = store:GetErrorDataStore(currentHour)

	while wait(ONE_HOUR_IN_SECONDS) do
		currentHour = math.floor(os.time() / 3600)
		ErrorDS = store:GetErrorDataStore(currentHour)
		errorCountCache = {}
		errorCountCacheKeys = {}
	end
end)

spawn(function()
	while wait(store.AutoSaveData) do
		for _, key in pairs(errorCountCacheKeys) do
			local errorCount = errorCountCache[key]
			local step = errorCount.currentCount - errorCount.countInDS
			errorCountCache[key].countInDS = store:IncrementErrorCount(ErrorDS, key, step)
			errorCountCache[key].currentCount = errorCountCache[key].countInDS
		end
	end
end)

local function ErrorHandler(message, trace, Script, player)
	--Validate
	if not state.ReportErrors then return end

	if not Script then
		return -- don't remember if this check is necessary but must have added it for a reason
	end

	local scriptName = nil
	local ok, _ = pcall(function()
		scriptName = Script:GetFullName() -- CoreGui.RobloxGui.Modules.PlayerList error, can't get name because of security permission
	end)
	if not ok then return end

	local m = scriptName .. ': message=' .. message .. ', trace=' .. trace
	if #m > 8192 then
		m = string.sub(m, 1, 8192)
	end

	local userId = nil
	if player then
		userId = player.UserId
		m = m:gsub(player.Name, '[LocalPlayer]') -- so we don't flood the same errors with different player names
	end

	local key = m
	if #key > 50 then
		key = string.sub(key, 1, 50)
	end

	if errorCountCache[key] == nil then
		errorCountCacheKeys[#errorCountCacheKeys + 1] = key
		errorCountCache[key] = {}
		errorCountCache[key].countInDS = 0
		errorCountCache[key].currentCount = 0
	end

	-- don't report error if limit has been exceeded
	if errorCountCache[key].currentCount > MaxErrorsPerHour then return end

	ga:addErrorEvent(userId, {
		severity = ga.EGAErrorSeverity.error,
		message = m,
	})

	-- increment error count
	errorCountCache[key].currentCount = errorCountCache[key].currentCount + 1
end

--Error Logging
ScriptContext.Error:Connect(ErrorHandler)
if not ReplicatedStorage:FindFirstChild('GameAnalyticsError') then
	--Create
	local f = Instance.new('RemoteEvent')
	f.Name = 'GameAnalyticsError'
	f.Parent = ReplicatedStorage
end

ReplicatedStorage.GameAnalyticsError.OnServerEvent:Connect(function(player, message, trace, Script)
	ErrorHandler(message, trace, Script, player)
end)

--Record Gamepasses.
MKT.PromptGamePassPurchaseFinished:Connect(function(Player, ID, Purchased)

--Validate
	if not state.AutomaticSendBusinessEvents or not Purchased then return end

	ga:GamepassPurchased(Player, ID)
end)

return ga