local p=game:GetService("\080\108\097\121\101\114\115")local d=game:GetService("\068\097\116\097\083\116\111\114\101\083\101\114\118\105\099\101")local L=require(script.table)local J=false local W=300 local F=(game:GetService("\083\101\114\118\101\114\083\116\111\114\097\103\101")):FindFirstChild("\083\097\118\101\073\110\083\116\117\100\105\111")local O=F and F.Value local x=true local u={}function u.typeValid(p)return type(p)~="\117\115\101\114\100\097\116\097",typeof(p)end function u.scanValidity(p,d,L)if type(p)~="\116\097\098\108\101"then return u.scanValidity({input=p},{},{})end d,L=d or{},L or{"\105\110\112\117\116"}d[p]=true local J do local d,L=next(p)if type(d)=="\110\117\109\098\101\114"then J="\065\114\114\097\121"else J="\068\105\099\116\105\111\110\097\114\121"end end local W=0 for p,F in next,p do L[#L+1]=tostring(p)if type(p)=="\110\117\109\098\101\114"then if J=="\068\105\099\116\105\111\110\097\114\121"then return false,L,"\077\105\120\101\100 \065\114\114\097\121\047\068\105\099\116\105\111\110\097\114\121"elseif p%1~=0 then return false,L,"\078\111\110-\105\110\116\101\103\101\114 \105\110\100\101\120"elseif p==math.huge or p==-math.huge then return false,L,"\040-\041\073\110\102\105\110\105\116\121 \105\110\100\101\120"end elseif type(p)~="\115\116\114\105\110\103"then return false,L,"\078\111\110-\115\116\114\105\110\103 \107\101\121",typeof(p)elseif J=="\065\114\114\097\121"then return false,L,"\077\105\120\101\100 \065\114\114\097\121\047\068\105\099\116\105\111\110\097\114\121"end if J=="\065\114\114\097\121"then if W~=p-1 then return false,L,"\065\114\114\097\121 \119\105\116\104 \110\111\110-\115\101\113\117\101\110\116\105\097\108 \105\110\100\101\120\101\115"end W=p end local O,x=u.typeValid(F)if not O then return false,L,"\073\110\118\097\108\105\100 \116\121\112\101",x end if type(F)=="\116\097\098\108\101"then if d[F]then return false,L,"\067\121\099\108\105\099"end local p,J,W,O=u.scanValidity(F,d,L)if not p then return p,J,W,O end end L[#L]=nil end d[p]=nil return true end function u.getStringPath(p)return L.concat(p,"\046")end function u.warnIfInvalid(p)local d,L,J,W=u.scanValidity(p)if not d then if W then warn("\073\110\118\097\108\105\100 \097\116 "..(u.getStringPath(L)..(" \098\101\099\097\117\115\101\058 "..(J..(" \040"..(tostring(W).."\041"))))))else warn("\073\110\118\097\108\105\100 \097\116 "..(u.getStringPath(L)..(" \098\101\099\097\117\115\101\058 "..J)))end end return d end local function e(p)if typeof(p)=="\116\097\098\108\101"then return L.deep(p)else return p end end local o={}function o.Debug(p,...)if x then print(...)end end function o._GetRaw(p)if not p.getQueue then p.getQueue={}end if p.getting then p:Debug("\065 \095\071\101\116\082\097\119 \105\115 \097\108\114\101\097\100\121 \105\110 \109\111\116\105\111\110\044 \106\117\115\116 \119\097\105\116 \117\110\116\105\108 \105\116\'\115 \100\111\110\101")local d=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")p.getQueue[#p.getQueue+1]=d d.Event:wait()p:Debug("\065\097\097\110\100 \119\101\'\114\101 \098\097\099\107")return end p.getting=true local d,L=pcall(function()return((p.orderedDataStore:GetSortedAsync(false,1)):GetCurrentPage())[1]end)if not d then p.getting=false error(L)end if L then local d=L.value p:Debug("\109\111\115\116 \114\101\099\101\110\116 \107\101\121",L)p.mostRecentKey=d local J,W=pcall(function()return p.dataStore:GetAsync(d)end)if not J then p.getting=false error(W)end p.value=W else p:Debug("\110\111 \114\101\099\101\110\116 \107\101\121")p.value=nil end for d,L in pairs(p.getQueue)do p:Debug("\114\101\115\117\109\105\110\103 \105\110 \113\117\101\117\101",L)L:Fire()end p.getQueue={}p.haveValue=true p.getting=false end function o._Update(d,p)if not p then for p,L in pairs(d.callbacks)do L(d.value,d)end end d.haveValue=true d.valueUpdated=true end function o.Get(L,p,d)if d then return L.value end local J=0 if not L.haveValue then while not L.haveValue and not pcall(L._GetRaw,L)do if L.backupRetries then J=J+1 if J>=L.backupRetries then L.backup=true L.haveValue=true L.value=L.backupValue break end end end print("\098\111\110\101\122\111\110\101")if L.value~=nil then for p,d in pairs(L.beforeInitialGet)do L.value=d(L.value,L)end end end local W if L.value==nil and p~=nil then W=p else W=L.value end W=e(W)L.value=W return W end function o.GetTable(d,p,...)assert(p~=nil,"\089\111\117 \109\117\115\116 \112\114\111\118\105\100\101 \097 \100\101\102\097\117\108\116 \118\097\108\117\101 \119\105\116\104 \058\071\101\116\084\097\098\108\101\046")local L=d:Get(p,...)local J=false assert(typeof(L)=="\116\097\098\108\101","\058\071\101\116\084\097\098\108\101 \119\097\115 \117\115\101\100 \119\104\101\110 \116\104\101 \118\097\108\117\101 \105\110 \116\104\101 \100\097\116\097 \115\116\111\114\101 \105\115\110\'\116 \097 \116\097\098\108\101\046")for p,d in pairs(p)do if L[p]==nil then L[p]=d J=true end end if J then d:Set(L)end return L end function o.Set(L,p,d)L.value=e(p)L:_Update(d)end function o.Update(d,p)d.value=p(d.value)d:_Update()end function o.Increment(L,p,d)L:Set(L:Get(d)+p)end function o.OnUpdate(d,p)L.insert(d.callbacks,p)end function o.BeforeInitialGet(d,p)L.insert(d.beforeInitialGet,p)end function o.BeforeSave(d,p)d.beforeSave=p end function o.AfterSave(d,p)L.insert(d.afterSave,p)end function o.SetBackup(L,p,d)L.backupRetries=p L.backupValue=d end function o.ClearBackup(p)p.backup=nil p.haveValue=false p.value=nil end function o.IsBackup(p)return p.backup~=nil end function o.Save(p)if not p.valueUpdated then warn(("\068\097\116\097 \115\116\111\114\101 \037\115 \119\097\115 \110\111\116 \115\097\118\101\100 \097\115 \105\116 \119\097\115 \110\111\116 \117\112\100\097\116\101\100\046"):format(p.name))return end if(game:GetService("\082\117\110\083\101\114\118\105\099\101")):IsStudio()and not O then warn(("\068\097\116\097 \115\116\111\114\101 \037\115 \097\116\116\101\109\112\116\101\100 \116\111 \115\097\118\101 \105\110 \115\116\117\100\105\111 \119\104\105\108\101 \083\097\118\101\073\110\083\116\117\100\105\111 \105\115 \102\097\108\115\101\046"):format(p.name))if not F then warn("\089\111\117 \099\097\110 \115\101\116 \116\104\101 \118\097\108\117\101 \111\102 \116\104\105\115 \098\121 \099\114\101\097\116\105\110\103 \097 \066\111\111\108\086\097\108\117\101 \110\097\109\101\100 \083\097\118\101\073\110\083\116\117\100\105\111 \105\110 \083\101\114\118\101\114\083\116\111\114\097\103\101\046")end return end if p.backup then warn("\084\104\105\115 \100\097\116\097 \115\116\111\114\101 \105\115 \097 \098\097\099\107\117\112 \115\116\111\114\101\044 \097\110\100 \116\104\117\115 \119\105\108\108 \110\111\116 \098\101 \115\097\118\101\100\046")return end if p.value~=nil then local d=e(p.value)if p.beforeSave then local L,J=pcall(p.beforeSave,d,p)if L then d=J else warn("\069\114\114\111\114 \111\110 \066\101\102\111\114\101\083\097\118\101\058 "..J)return end end if not u.warnIfInvalid(d)then return warn("\073\110\118\097\108\105\100 \100\097\116\097 \119\104\105\108\101 \115\097\118\105\110\103")end local L=(p.mostRecentKey or 0)+1 p.dataStore:SetAsync(L,d)p.orderedDataStore:SetAsync(L,L)p.mostRecentKey=L for L,J in pairs(p.afterSave)do local W,F=pcall(J,d,p)if not W then warn("\069\114\114\111\114 \111\110 \065\102\116\101\114\083\097\118\101\058 "..F)end end print(("\083\097\118\101\100 \037\115 \102\111\114 \037\115"):format(p.name,tostring(p.player)))end end function o.BindToClose(d,p)L.insert(d.bindToClose,p)end function o.GetKeyValue(d,p)return(d.value or{})[p]end function o.SetKeyValue(L,p,d)if not L.value then L.value=L:Get({})end L.value[p]=d end local r={}do function r.BeforeInitialGet(d,p)d.combinedBeforeInitialGet=p end function r.BeforeSave(d,p)d.combinedBeforeSave=p end function r.Get(L,p,d)local J=L.combinedStore:Get({})local W=J[L.combinedName]if not d then if W==nil then W=p else if L.combinedBeforeInitialGet and not L.combinedInitialGot then W=L.combinedBeforeInitialGet(W)end end end L.combinedInitialGot=true J[L.combinedName]=e(W)L.combinedStore:Set(J,true)return W end function r.Set(L,p,d)local J=L.combinedStore:GetTable({})J[L.combinedName]=p L.combinedStore:Set(J,d)L:_Update(d)end function r.Update(d,p)d:Set(p(d:Get()))d:_Update()end function r.OnUpdate(d,p)if not d.onUpdateCallbacks then d.onUpdateCallbacks={p}else d.onUpdateCallbacks[#d.onUpdateCallbacks+1]=p end end function r._Update(d,p)if not p then for p,L in pairs(d.onUpdateCallbacks or{})do L(d:Get(),d)end end d.combinedStore:_Update(true)end function r.SetBackup(d,p)d.combinedStore:SetBackup(p)end end local g={}g.__index=o local U={}local j={}local v={}function j.Combine(p,...)for d,L in pairs({...})do v[L]=p end end function j.ClearCache()U={}end function j.__call(W,L,J)assert(typeof(L)=="\115\116\114\105\110\103"and typeof(J)=="\073\110\115\116\097\110\099\101",("\068\097\116\097\083\116\111\114\101\050\040\041 \065\080\073 \099\097\108\108 \101\120\112\101\099\116\101\100 \123\115\116\114\105\110\103 \100\097\116\097\083\116\111\114\101\078\097\109\101\044 \073\110\115\116\097\110\099\101 \112\108\097\121\101\114\125\044 \103\111\116 \123\037\115\044 \037\115\125"):format(typeof(L),typeof(J)))if U[J]and U[J][L]then return U[J][L]elseif v[L]then local p=j(v[L],J)p:BeforeSave(function(p)for d in pairs(p)do if v[d]then local L=j(d,J)local W=L:Get(nil,true)if W~=nil then if L.combinedBeforeSave then W=L.combinedBeforeSave(e(W))end p[d]=W end end end return p end)local d=setmetatable({combinedName=L;combinedStore=p},{__index=function(d,L)return r[L]or p[L]end})if not U[J]then U[J]={}end U[J][L]=d return d end local F={}local O=L..("\047"..J.UserId)F.dataStore=d:GetDataStore(O)F.orderedDataStore=d:GetOrderedDataStore(O)F.name=L F.player=J F.callbacks={}F.beforeInitialGet={}F.afterSave={}F.bindToClose={}setmetatable(F,g)local x,u=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116"),false game:BindToClose(function()if not u then x.Event:wait()end local p=F:Get(nil,true)for d,L in pairs(F.bindToClose)do L(J,p)end end)p.PlayerRemoving:connect(function(p)if p==J then F:Save()x:Fire()u=true delay(40,function()U[p]=nil end)end end)if not U[J]then U[J]={}end U[J][L]=F return F end return setmetatable(j,j)
--[[
	DataStore2: A wrapper for data stores that caches, saves player's data, and uses berezaa's method of saving data.
	Use require(1936396537) to have an updated version of DataStore2.

	DataStore2(dataStoreName, player) - Returns a DataStore2 DataStore

	DataStore2 DataStore:
	- Get([defaultValue])
	- Set(value)
	- Update(updateFunc)
	- Increment(value, defaultValue)
	- BeforeInitialGet(modifier)
	- BeforeSave(modifier)
	- Save()
	- OnUpdate(callback)
	- BindToClose(callback)

	local coinStore = DataStore2("Coins", player)

	To give a player coins:

	coinStore:Increment(50)

	To get the current player's coins:

	coinStore:Get()
--]]

--[[
	berezaa's method of saving data (from the dev forum):

	What I do and this might seem a little over-the-top but it's fine as long as you're not using datastores excessively elsewhere is have a datastore and an ordereddatastore for each player. When you perform a save, add a key (can be anything) with the value of os.time() to the ordereddatastore and save a key with the os.time() and the value of the player's data to the regular datastore. Then, when loading data, get the highest number from the ordered data store (most recent save) and load the data with that as a key.

	Ever since I implemented this, pretty much no one has ever lost data. There's no caches to worry about either because you're never overriding any keys. Plus, it has the added benefit of allowing you to restore lost data, since every save doubles as a backup which can be easily found with the ordereddatastore

	edit: while there's no official comment on this, many developers including myself have noticed really bad cache times and issues with using the same datastore keys to save data across multiple places in the same game. With this method, data is almost always instantly accessible immediately after a player teleports, making it useful for multi-place games.
--]]

--Required components
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local table = require(script.table) --If you're using this from GitHub, this is the table in the boilerplate folder.
local RegularSave = false
local RegularSaveNum = 300
local SaveInStudioObject = game:GetService("ServerStorage"):FindFirstChild("SaveInStudio")
local SaveInStudio = SaveInStudioObject and SaveInStudioObject.Value
local Debug = true

local Verifier = {}

function Verifier.typeValid(data)
	return type(data) ~= "userdata", typeof(data)
end

function Verifier.scanValidity(tbl, passed, path)
	if type(tbl) ~= "table" then
		return Verifier.scanValidity({input = tbl}, {}, {})
	end
	passed, path = passed or {}, path or {"input"}
	passed[tbl] = true
	local tblType
	do
		local key, value = next(tbl)
		if type(key) == "number" then
			tblType = "Array"
		else
			tblType = "Dictionary"
		end
	end
	local last = 0
	for key, value in next, tbl do
		path[#path + 1] = tostring(key)
		if type(key) == "number" then
			if tblType == "Dictionary" then
				return false, path, "Mixed Array/Dictionary"
			elseif key%1 ~= 0 then  -- if not an integer
				return false, path, "Non-integer index"
			elseif key == math.huge or key == -math.huge then
				return false, path, "(-)Infinity index"
			end
		elseif type(key) ~= "string" then
			return false, path, "Non-string key", typeof(key)
		elseif tblType == "Array" then
			return false, path, "Mixed Array/Dictionary"
		end
		if tblType == "Array" then
			if last ~= key - 1 then
				return false, path, "Array with non-sequential indexes"
			end
			last = key
		end
		local isTypeValid, valueType = Verifier.typeValid(value)
		if not isTypeValid then
			return false, path, "Invalid type", valueType
		end
		if type(value) == "table" then
			if passed[value] then
				return false, path, "Cyclic"
			end
			local isValid, keyPath, reason, extra = Verifier.scanValidity(value, passed, path)
			if not isValid then
				return isValid, keyPath, reason, extra
			end
		end
		path[#path] = nil
	end
	passed[tbl] = nil
	return true
end

function Verifier.getStringPath(path)
	return table.concat(path, ".")
end

function Verifier.warnIfInvalid(input)
	local isValid, keyPath, reason, extra = Verifier.scanValidity(input)
	if not isValid then
		if extra then
			warn("Invalid at "..Verifier.getStringPath(keyPath).." because: "..reason.." ("..tostring(extra)..")")
		else
			warn("Invalid at "..Verifier.getStringPath(keyPath).." because: "..reason)
		end
	end

	return isValid
end

local function clone(value)
	if typeof(value) == "table" then
		return table.deep(value)
	else
		return value
	end
end

--DataStore object
local DataStore = {}

--Internal functions
function DataStore:Debug(...)
	if Debug then
		print(...)
	end
end

function DataStore:_GetRaw()
	if not self.getQueue then
		self.getQueue = {}
	end

	if self.getting then
		self:Debug("A _GetRaw is already in motion, just wait until it's done")
		local event = Instance.new("BindableEvent")
		self.getQueue[#self.getQueue + 1] = event
		event.Event:wait()
		self:Debug("Aaand we're back")
		return
	end

	self.getting = true

	local success, mostRecentKeyPage = pcall(function()
		return self.orderedDataStore:GetSortedAsync(false, 1):GetCurrentPage()[1]
	end)

	if not success then
		self.getting = false
		error(mostRecentKeyPage)
	end

	if mostRecentKeyPage then
		local recentKey = mostRecentKeyPage.value
		self:Debug("most recent key", mostRecentKeyPage)
		self.mostRecentKey = recentKey
		local success, value = pcall(function()
			return self.dataStore:GetAsync(recentKey)
		end)
		if not success then
			self.getting = false
			error(value)
		end
		self.value = value
	else
		self:Debug("no recent key")
		self.value = nil
	end

	for _, waiting in pairs(self.getQueue) do
		self:Debug("resuming in queue", waiting)
		waiting:Fire()
	end

	self.getQueue = {}
	self.haveValue = true
	self.getting = false
end

function DataStore:_Update(dontCallOnUpdate)
	if not dontCallOnUpdate then
		for _,callback in pairs(self.callbacks) do
			callback(self.value, self)
		end
	end

	self.haveValue = true
	self.valueUpdated = true
end

--Public functions

--[[**
	<description>
	Gets the result from the data store. Will yield the first time it is called.
	</description>

	<parameter name = "defaultValue">
	The default result if there is no result in the data store.
	</parameter>

	<parameter name = "dontAttemptGet">
	If there is no cached result, just return nil.
	</parameter>

	<returns>
	The value in the data store if there is no cached result. The cached result otherwise.
	</returns>
**--]]
function DataStore:Get(defaultValue, dontAttemptGet)
	if dontAttemptGet then
		return self.value
	end

	local backupCount = 0

	if not self.haveValue then
		while not self.haveValue and not pcall(self._GetRaw, self) do
			if self.backupRetries then
				backupCount = backupCount + 1

				if backupCount >= self.backupRetries then
					self.backup = true
					self.haveValue = true
					self.value = self.backupValue
					break
				end
			end
		end
		print("bonezone")

		if self.value ~= nil then
			for _,modifier in pairs(self.beforeInitialGet) do
				self.value = modifier(self.value, self)
			end
		end
	end

	local value

	if self.value == nil and defaultValue ~= nil then --not using "not" because false is a possible value
		value = defaultValue
	else
		value = self.value
	end

	value = clone(value)

	self.value = value

	return value
end

--[[**
	<description>
	The same as :Get only it'll check to make sure all keys in the default data provided
	exist. If not, will pass in the default value only for that key.
	This is recommended for tables in case you want to add new entries to the table.
	Note this is not required for tables, it only provides an extra functionality.
	</description>

	<parameter name = "defaultValue">
	A table that will have its keys compared to that of the actual data received.
	</parameter>

	<returns>
	The value in the data store will all keys from the default value provided.
	</returns>
**--]]
function DataStore:GetTable(default, ...)
	assert(default ~= nil, "You must provide a default value with :GetTable.")

	local result = self:Get(default, ...)
	local changed = false

	assert(typeof(result) == "table", ":GetTable was used when the value in the data store isn't a table.")

	for defaultKey, defaultValue in pairs(default) do
		if result[defaultKey] == nil then
			result[defaultKey] = defaultValue
			changed = true
		end
	end

	if changed then
		self:Set(result)
	end

	return result
end

--[[**
	<description>
	Sets the cached result to the value provided
	</description>

	<parameter name = "value">
	The value
	</parameter>
**--]]
function DataStore:Set(value, _dontCallOnUpdate)
	self.value = clone(value)
	self:_Update(_dontCallOnUpdate)
end

--[[**
	<description>
	Calls the function provided and sets the cached result.
	</description>

	<parameter name = "updateFunc">
	The function
	</parameter>
**--]]
function DataStore:Update(updateFunc)
	self.value = updateFunc(self.value)
	self:_Update()
end

--[[**
	<description>
	Increment the cached result by value.
	</description>

	<parameter name = "value">
	The value to increment by.
	</parameter>

	<parameter name = "defaultValue">
	If there is no cached result, set it to this before incrementing.
	</parameter>
**--]]
function DataStore:Increment(value, defaultValue)
	self:Set(self:Get(defaultValue) + value)
end

--[[**
	<description>
	Takes a function to be called whenever the cached result updates.
	</description>

	<parameter name = "callback">
	The function to call.
	</parameter>
**--]]
function DataStore:OnUpdate(callback)
	table.insert(self.callbacks, callback)
end

--[[**
	<description>
	Takes a function to be called when :Get() is first called and there is a value in the data store. This function must return a value to set to. Used for deserializing.
	</description>

	<parameter name = "modifier">
	The modifier function.
	</parameter>
**--]]
function DataStore:BeforeInitialGet(modifier)
	table.insert(self.beforeInitialGet, modifier)
end

--[[**
	<description>
	Takes a function to be called before :Save(). This function must return a value that will be saved in the data store. Used for serializing.
	</description>

	<parameter name = "modifier">
	The modifier function.
	</parameter>
**--]]
function DataStore:BeforeSave(modifier)
	self.beforeSave = modifier
end

--[[**
	<description>
	Takes a function to be called after :Save().
	</description>

	<parameter name = "callback">
	The callback function.
	</parameter>
**--]]
function DataStore:AfterSave(callback)
	table.insert(self.afterSave, callback)
end

--[[**
	<description>
	Adds a backup to the data store if :Get() fails a specified amount of times.
	Will return the value provided (if the value is nil, then the default value of :Get() will be returned)
	and mark the data store as a backup store, and attempts to :Save() will not truly save.
	</description>

	<parameter name = "retries">
	Number of retries before the backup will be used.
	</parameter>

	<parameter name = "value">
	The value to return to :Get() in the case of a failure.
	You can keep this blank and the default value you provided with :Get() will be used instead.
	</parameter>
**--]]
function DataStore:SetBackup(retries, value)
	self.backupRetries = retries
	self.backupValue = value
end

--[[**
	<description>
	Unmark the data store as a backup data store and tell :Get() and reset values to nil.
	</description>
**--]]
function DataStore:ClearBackup()
	self.backup = nil
	self.haveValue = false
	self.value = nil
end

--[[**
	<returns>
	Whether or not the data store is a backup data store and thus won't save during :Save() or call :AfterSave().
	</returns>
**--]]
function DataStore:IsBackup()
	return self.backup ~= nil --some people haven't learned if x then yet, and will do if x == false then.
end

--[[**
	<description>
	Saves the data to the data store. Called when a player leaves.
	</description>
**--]]
function DataStore:Save()
	if not self.valueUpdated then
		warn(("Data store %s was not saved as it was not updated."):format(self.name))
		return
	end

	if game:GetService("RunService"):IsStudio() and not SaveInStudio then
		warn(("Data store %s attempted to save in studio while SaveInStudio is false."):format(self.name))
		if not SaveInStudioObject then
			warn("You can set the value of this by creating a BoolValue named SaveInStudio in ServerStorage.")
		end
		return
	end

	if self.backup then
		warn("This data store is a backup store, and thus will not be saved.")
		return
	end

	if self.value ~= nil then
		local save = clone(self.value)

		if self.beforeSave then
			local success, newSave = pcall(self.beforeSave, save, self)

			if success then
				save = newSave
			else
				warn("Error on BeforeSave: "..newSave)
				return
			end
		end

		if not Verifier.warnIfInvalid(save) then return warn("Invalid data while saving") end

		local key = (self.mostRecentKey or 0) + 1
		self.dataStore:SetAsync(key, save)
		self.orderedDataStore:SetAsync(key, key)
		self.mostRecentKey = key

		for _, afterSave in pairs(self.afterSave) do
			local success, err = pcall(afterSave, save, self)

			if not success then
				warn("Error on AfterSave: "..err)
			end
		end

		print(("Saved %s for %s"):format(self.name,tostring(self.player)))
	end
end

--[[**
	<description>
	Add a function to be called before the game closes. Fired with the player and value of the data store.
	</description>

	<parameter name = "callback">
	The callback function.
	</parameter>
**--]]
function DataStore:BindToClose(callback)
	table.insert(self.bindToClose, callback)
end

--[[**
	<description>
	Gets the value of the cached result indexed by key. Does not attempt to get the current value in the data store.
	</description>

	<parameter name = "key">
	The key you're indexing by.
	</parameter>

	<returns>
	The value indexed.
	</returns>
**--]]
function DataStore:GetKeyValue(key)
	return (self.value or {})[key]
end

--[[**
	<description>
	Sets the value of the result in the database with the key and the new value. Attempts to get the value from the data store. Does not call functions fired on update.
	</description>

	<parameter name = "key">
	The key to set.
	</parameter>

	<parameter name = "newValue">
	The value to set.
	</parameter>
**--]]
function DataStore:SetKeyValue(key, newValue)
	if not self.value then
		self.value = self:Get({})
	end

	self.value[key] = newValue
end

local CombinedDataStore = {}

do
	function CombinedDataStore:BeforeInitialGet(modifier)
		self.combinedBeforeInitialGet = modifier
	end

	function CombinedDataStore:BeforeSave(modifier)
		self.combinedBeforeSave = modifier
	end

	function CombinedDataStore:Get(defaultValue, dontAttemptGet)
		local tableResult = self.combinedStore:Get({})
		local tableValue = tableResult[self.combinedName]

		if not dontAttemptGet then
			if tableValue == nil then
				tableValue = defaultValue
			else
				if self.combinedBeforeInitialGet and not self.combinedInitialGot then
					tableValue = self.combinedBeforeInitialGet(tableValue)
				end
			end
		end

		self.combinedInitialGot = true
		tableResult[self.combinedName] = clone(tableValue)
		self.combinedStore:Set(tableResult, true)
		return tableValue
	end

	function CombinedDataStore:Set(value, dontCallOnUpdate)
		local tableResult = self.combinedStore:GetTable({})
		tableResult[self.combinedName] = value
		self.combinedStore:Set(tableResult, dontCallOnUpdate)
		self:_Update(dontCallOnUpdate)
	end

	function CombinedDataStore:Update(updateFunc)
		self:Set(updateFunc(self:Get()))
		self:_Update()
	end

	function CombinedDataStore:OnUpdate(callback)
		if not self.onUpdateCallbacks then
			self.onUpdateCallbacks = { callback }
		else
			self.onUpdateCallbacks[#self.onUpdateCallbacks + 1] = callback
		end
	end

	function CombinedDataStore:_Update(dontCallOnUpdate)
		if not dontCallOnUpdate then
			for _, callback in pairs(self.onUpdateCallbacks or {}) do
				callback(self:Get(), self)
			end
		end

		self.combinedStore:_Update(true)
	end

	function CombinedDataStore:SetBackup(retries)
		self.combinedStore:SetBackup(retries)
	end
end

local DataStoreMetatable = {}

DataStoreMetatable.__index = DataStore

--Library
local DataStoreCache = {}

local DataStore2 = {}
local combinedDataStoreInfo = {}

--[[**
	<description>
	Run this once to combine all keys provided into one "main key".
	Internally, this means that data will be stored in a table with the key mainKey.
	This is used to get around the 2-DataStore2 reliability caveat.
	</description>

	<parameter name = "mainKey">
	The key that will be used to house the table.
	</parameter>

	<parameter name = "...">
	All the keys to combine under one table.
	</parameter>
**--]]
function DataStore2.Combine(mainKey, ...)
	for _, name in pairs({...}) do
		combinedDataStoreInfo[name] = mainKey
	end
end

function DataStore2.ClearCache()
	DataStoreCache = {}
end

function DataStore2:__call(dataStoreName, player)
	assert(typeof(dataStoreName) == "string" and typeof(player) == "Instance", ("DataStore2() API call expected {string dataStoreName, Instance player}, got {%s, %s}"):format(typeof(dataStoreName), typeof(player)))
	if DataStoreCache[player] and DataStoreCache[player][dataStoreName] then
		return DataStoreCache[player][dataStoreName]
	elseif combinedDataStoreInfo[dataStoreName] then
		local dataStore = DataStore2(combinedDataStoreInfo[dataStoreName], player)

		dataStore:BeforeSave(function(combinedData)
			for key in pairs(combinedData) do
				if combinedDataStoreInfo[key] then
					local combinedStore = DataStore2(key, player)
					local value = combinedStore:Get(nil, true)
					if value ~= nil then
						if combinedStore.combinedBeforeSave then
							value = combinedStore.combinedBeforeSave(clone(value))
						end
						combinedData[key] = value
					end
				end
			end

			return combinedData
		end)

		local combinedStore = setmetatable({
			combinedName = dataStoreName,
			combinedStore = dataStore
		}, {
			__index = function(self, key)
				return CombinedDataStore[key] or dataStore[key]
			end
		})

		if not DataStoreCache[player] then
			DataStoreCache[player] = {}
		end

		DataStoreCache[player][dataStoreName] = combinedStore
		return combinedStore
	end

	local dataStore = {}
	local dataStoreKey = dataStoreName .. "/" .. player.UserId

	dataStore.dataStore = DataStoreService:GetDataStore(dataStoreKey)
	dataStore.orderedDataStore = DataStoreService:GetOrderedDataStore(dataStoreKey)
	dataStore.name = dataStoreName
	dataStore.player = player
	dataStore.callbacks = {}
	dataStore.beforeInitialGet = {}
	dataStore.afterSave = {}
	dataStore.bindToClose = {}

	setmetatable(dataStore, DataStoreMetatable)

	local event, fired = Instance.new("BindableEvent"), false

	game:BindToClose(function()
		if not fired then
			event.Event:wait()
		end

		local value = dataStore:Get(nil, true)

		for _, bindToClose in pairs(dataStore.bindToClose) do
			bindToClose(player, value)
		end
	end)

	Players.PlayerRemoving:connect(function(playerLeaving)
		if playerLeaving == player then
			dataStore:Save()
			event:Fire()
			fired = true

			delay(40, function() --Give a long delay for people who haven't figured out the cache :^(
				DataStoreCache[playerLeaving] = nil
			end)
		end
	end)

	if not DataStoreCache[player] then
		DataStoreCache[player] = {}
	end

	DataStoreCache[player][dataStoreName] = dataStore

	return dataStore
end

return setmetatable(DataStore2, DataStore2)
