local y=game:GetService("\068\097\116\097\083\116\111\114\101\083\101\114\118\105\099\101")local g=game:GetService("\080\108\097\121\101\114\115")local H=game:GetService("\082\117\110\083\101\114\118\105\099\101")local k=game:GetService("\083\101\114\118\101\114\083\116\111\114\097\103\101")local o=require(script.SavingMethods)local T=require(script.TableUtil)local w=require(script.Verifier)local d=k:FindFirstChild("\083\097\118\101\073\110\083\116\117\100\105\111")local z=d and d.Value local function X(y)if typeof(y)=="\116\097\098\108\101"then return T.clone(y)else return y end end local D={}function D.Debug(y,...)if y.debug then print(...)end end function D._GetRaw(y)if not y.getQueue then y.getQueue=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")end if y.getting then y: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")y.getQueue.Event:wait()y:Debug("\065\097\097\110\100 \119\101\'\114\101 \098\097\099\107")return end y.getting=true local g,H=y.savingMethod:Get()y.getting=false if not g then error(tostring(H))end y.value=H y:Debug("\118\097\108\117\101 \114\101\099\101\105\118\101\100")y.getQueue:Fire()y.haveValue=true end function D._Update(g,y)if not y then for y,H in pairs(g.callbacks)do H(g.value,g)end end g.haveValue=true g.valueUpdated=true end function D.Get(H,y,g)if g then return H.value end local k=0 if not H.haveValue then while not H.haveValue do local y,g=pcall(H._GetRaw,H)if not y then if H.backupRetries then k=k+1 if k>=H.backupRetries then H.backup=true H.haveValue=true H.value=H.backupValue break end end H:Debug("\071\101\116 \114\101\116\117\114\110\101\100 \101\114\114\111\114\058",g)end end if H.value~=nil then for y,g in pairs(H.beforeInitialGet)do H.value=g(H.value,H)end end end local o if H.value==nil and y~=nil then o=y else o=H.value end o=X(o)H.value=o return o end function D.GetTable(g,y,...)assert(y~=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 H=g:Get(y,...)local k=false assert(typeof(H)=="\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 y,g in pairs(y)do if H[y]==nil then H[y]=g k=true end end if k then g:Set(H)end return H end function D.Set(H,y,g)H.value=X(y)H:_Update(g)end function D.Update(g,y)g.value=y(g.value)g:_Update()end function D.Increment(H,y,g)H:Set(H:Get(g)+y)end function D.OnUpdate(g,y)table.insert(g.callbacks,y)end function D.BeforeInitialGet(g,y)table.insert(g.beforeInitialGet,y)end function D.BeforeSave(g,y)g.beforeSave=y end function D.AfterSave(g,y)table.insert(g.afterSave,y)end function D.SetBackup(H,y,g)H.backupRetries=y H.backupValue=g end function D.ClearBackup(y)y.backup=nil y.haveValue=false y.value=nil end function D.IsBackup(y)return y.backup~=nil end function D.Save(y)if not y.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(y.Name))return end if H:IsStudio()and not z 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(y.Name))if not d 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 y.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 y.value~=nil then local g=X(y.value)if y.beforeSave then local H,k=pcall(y.beforeSave,g,y)if H then g=k else warn("\069\114\114\111\114 \111\110 \066\101\102\111\114\101\083\097\118\101\058 "..k)return end end if not w.warnIfInvalid(g)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 H,k=y.savingMethod:Set(g)if not H then error("\115\097\118\101 \101\114\114\111\114\033 "..tostring(k))end for H,k in pairs(y.afterSave)do local o,T=pcall(k,g,y)if not o then warn("\069\114\114\111\114 \111\110 \065\102\116\101\114\083\097\118\101\058 "..T)end end print("\115\097\118\101\100 "..y.Name)end end function D.SaveAsync(y)(coroutine.wrap(D.Save))(y)end function D.BindToClose(g,y)table.insert(g.bindToClose,y)end function D.GetKeyValue(g,y)return(g.value or{})[y]end function D.SetKeyValue(H,y,g)if not H.value then H.value=H:Get({})end H.value[y]=g end local m={}do function m.BeforeInitialGet(g,y)g.combinedBeforeInitialGet=y end function m.BeforeSave(g,y)g.combinedBeforeSave=y end function m.Get(H,y,g)local k=H.combinedStore:Get({})local o=k[H.combinedName]if not g then if o==nil then o=y else if H.combinedBeforeInitialGet and not H.combinedInitialGot then o=H.combinedBeforeInitialGet(o)end end end H.combinedInitialGot=true k[H.combinedName]=X(o)H.combinedStore:Set(k,true)return o end function m.Set(H,y,g)local k=H.combinedStore:GetTable({})k[H.combinedName]=y H.combinedStore:Set(k,g)H:_Update(g)end function m.Update(g,y)g:Set(y(g:Get()))g:_Update()end function m.OnUpdate(g,y)if not g.onUpdateCallbacks then g.onUpdateCallbacks={y}else g.onUpdateCallbacks[#g.onUpdateCallbacks+1]=y end end function m._Update(g,y)if not y then for y,H in pairs(g.onUpdateCallbacks or{})do H(g:Get(),g)end end g.combinedStore:_Update(true)end function m.SetBackup(g,y)g.combinedStore:SetBackup(y)end end local b={}b.__index=D local n={}local p={}local e={}function p.Combine(y,...)for g,H in pairs({...})do e[H]=y end end function p.ClearCache()n={}end function p.__call(H,y,g)assert(typeof(y)=="\115\116\114\105\110\103"and typeof(g)=="\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(y),typeof(g)))if n[g]and n[g][y]then return n[g][y]elseif e[y]then local H=p(e[y],g)H:BeforeSave(function(y)for H in pairs(y)do if e[H]then local k=p(H,g)local o=k:Get(nil,true)if o~=nil then if k.combinedBeforeSave then o=k.combinedBeforeSave(X(o))end y[H]=o end end end return y end)local k=setmetatable({combinedName=y;combinedStore=H},{__index=function(y,g)return m[g]or H[g]end})if not n[g]then n[g]={}end n[g][y]=k return k end local k={}k.Name=y k.UserId=g.UserId k.callbacks={}k.beforeInitialGet={}k.afterSave={}k.bindToClose={}k.savingMethod=o.OrderedBackups.new(k)setmetatable(k,b)local T,w=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116"),false game:BindToClose(function()if not w then T.Event:wait()end local y=k:Get(nil,true)for H,k in pairs(k.bindToClose)do k(g,y)end end)local d d=g.AncestryChanged:Connect(function()if g:IsDescendantOf(game)then return end d:Disconnect()k:Save()T:Fire()w=true delay(40,function()n[g]=nil end)end)if not n[g]then n[g]={}end n[g][y]=k return k end return setmetatable(p,p)
--[[
	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()
	- SaveAsync()
	- 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()
--]]

--Required components
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ServerStorage = game:GetService("ServerStorage")

local SavingMethods = require(script.SavingMethods)
local TableUtil = require(script.TableUtil)
local Verifier = require(script.Verifier)

local SaveInStudioObject = ServerStorage:FindFirstChild("SaveInStudio")
local SaveInStudio = SaveInStudioObject and SaveInStudioObject.Value

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

--DataStore object
local DataStore = {}

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

function DataStore:_GetRaw()
	if not self.getQueue then
		self.getQueue = Instance.new("BindableEvent")
	end

	if self.getting then
		self:Debug("A _GetRaw is already in motion, just wait until it's done")
		self.getQueue.Event:wait()
		self:Debug("Aaand we're back")
		return
	end

	self.getting = true

	local success, value = self.savingMethod:Get()

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

	self.value = value

	self:Debug("value received")
	self.getQueue:Fire()

	self.haveValue = true
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 do
			local success, error = pcall(self._GetRaw, self)

			if not success then
				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

				self:Debug("Get returned error:", error)
			end
		end

		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 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 success, problem = self.savingMethod:Set(save)

		if not success then
			-- TODO: Something more robust than this
			error("save error! " .. tostring(problem))
		end

		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 "..self.Name)
	end
end

--[[**
	<description>
	Asynchronously saves the data to the data store.
	</description>
**--]]
function DataStore:SaveAsync()
	coroutine.wrap(DataStore.Save)(self)
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 = {}

	dataStore.Name = dataStoreName
	dataStore.UserId = player.UserId

	dataStore.callbacks = {}
	dataStore.beforeInitialGet = {}
	dataStore.afterSave = {}
	dataStore.bindToClose = {}
	dataStore.savingMethod = SavingMethods.OrderedBackups.new(dataStore)

	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)

	local playerLeavingConnection
	playerLeavingConnection = player.AncestryChanged:Connect(function()
		if player:IsDescendantOf(game) then return end
		playerLeavingConnection:Disconnect()
		dataStore:Save()
		event:Fire()
		fired = true

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

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

	DataStoreCache[player][dataStoreName] = dataStore

	return dataStore
end

return setmetatable(DataStore2, DataStore2)
