Help with some DataStore2 problems on newly released game

I recently released a game with skin packs for the weapons. However, there appear to be some players losing their items once they buy them, and I’m wondering if anyone is able to look over my DataStore2 script to try and figure out why this may be? Here is the script - I warn you it’s a bit long! I would be grateful for any assistance as I am new to DataStore2 (hoping I didn’t make the wrong decision choosing it over regular DataStores lol)

-------------------------------------------------------------------------------------
local MainKey = "PlayerData1"
DataStore2.Combine(MainKey, "Inventory")

local function CreateDataTable() -- function to get the table
	local PlayerData = {
		KOWeaponPacks = {
			["StarterPack"] = true;
			["BronzePack"] = false;
			["SilverPack"] = false;
			["GoldPack"] = false;
			["PlatinumPack"] = false;
			["TitaniumPack"] = false;
			["NaturePack"] = false;
			["LavaPack"] = false;
			["EnergyPack"] = false;
		};

		PremiumWeaponPacks = {
			["GhostlyPack"] = false;
            ["AncientPack"] = false;
			["DirtPack"] = false;
			["MysticPack"] = false;
			["PolishedPack"] = false;
			["NeonPack"] = false;
            ["HotCoalPack"] = false;
			["ForcefieldPack"] = false;
			["TwistedWoodPack"] = false;
			["IciclePack"] = false;
			["MythrilPack"] = false;
			["CyberPack"] = false;
			["PurityPack"] = false;		
			["AquaPack"] = false;
			["BrickPack"] = false;
			["OverseerPack"] = false;
			["ShadowPack"] = false;
			["RainbowPack"] = false;	
		};

		EquippedItems = {
            ["Weapon1"] = "StarterPack";
			["Weapon2"] = "StarterPack";
			["Weapon3"] = "StarterPack";
			["Weapon4"] = "StarterPack";
			["Weapon5"] = "StarterPack";
			["Weapon6"] = "StarterPack";
		};
	}
	return PlayerData -- Returns the table when function is called
end
-------------------------------------------------------------------------------------	
Players.PlayerAdded:Connect(function(plr)
	DataStore2("PlayerData", plr):SetBackup(3, DefaultKOValue)
	local PlayerData1 = DataStore2("PlayerData", plr):Get(CreateDataTable())
	if DataStore2("PlayerData", plr):IsBackup() then
		plr:Kick("Error loading save data, please rejoin.")
	end
	repeat wait() until ServerStorage:FindFirstChild(plr.Name) -- Wait until player data directory created by PlayerData script
    local PlayerDataDIR = ServerStorage:WaitForChild(plr.Name)
    local KOWeaponPackDIR = Instance.new("Folder", PlayerDataDIR) -- Create a new folder in the player's ServerStorage folder that stores KO weapon pack data
    KOWeaponPackDIR.Name = "KOWeaponPacks"
    local EquippedFolder = PlayerDataDIR:WaitForChild("EquippedData")
    local CoinWeaponPackDIR = Instance.new("Folder", PlayerDataDIR) -- Create a new folder in the player's ServerStorage folder that stores premium weapon pack data
    CoinWeaponPackDIR.Name = "CoinWeaponPacks"
  
    for i, v in pairs (PlayerData1["KOWeaponPacks"]) do  -- Index retrieved player data KO packs and add a BoolValue to the player's ServerStorage directory
	    print(tostring(i), v)
	    local NewStat = Instance.new("BoolValue")
	    NewStat.Parent = KOWeaponPackDIR
	    NewStat.Name = tostring(i)
	    NewStat.Value = tostring(v)
    end

    for i, v in pairs (PlayerData1["PremiumWeaponPacks"]) do -- Index retrieved player data premium weapon packs and add a BoolValue to the player's ServerStorage directory
	    print(tostring(i), v)
	    local NewStat = Instance.new("BoolValue")
	    NewStat.Parent = CoinWeaponPackDIR
	    NewStat.Name = tostring(i)
	    NewStat.Value = tostring(v)
    end

    for i, v in pairs (PlayerData1["EquippedItems"]) do -- Index retrieved equipped weapon packs and add a BoolValue to the player's ServerStorage directory
        print(tostring(i), v)
	    local NewStat = Instance.new("StringValue")
	    NewStat.Parent = EquippedFolder
	    NewStat.Name = tostring(i)
	    NewStat.Value = tostring(v)
    end

    for i, v in pairs (PlayerData1["KOWeaponPacks"]) do -- Set the boolvalue's value according to whether the PlayerData dictionary is true/false
	    if tostring(i) ~= "StarterPack" then
	    	if KOWeaponPackDIR:FindFirstChild(tostring(i)) then
		    	print("GOTCHA")
		    	local Value = KOWeaponPackDIR:WaitForChild(tostring(i))
		    	print("GOTCHA2")
		    	Value.Value = DataStore2("PlayerData", plr):Get(CreateDataTable())["KOWeaponPacks"][tostring(i)] -- Sets boolvalue
		    	print("GOTCHA3")
		   		Button:FireClient(plr, tostring(i), v) -- Fires purchase button in the shop (if the value is true the button will be disabled client side)
		    	print("GOTCHA4")
		    end
	    end
	end
    print("Fired ButtonStatus for "..plr.Name)

    for i, v in pairs (PlayerData1["PremiumWeaponPacks"]) do -- Set the boolvalue's value according to whether the PlayerData dictionary is true/false
	    if CoinWeaponPackDIR:FindFirstChild(tostring(i)) then
		    print("GOTCHA")
		    local Value = CoinWeaponPackDIR:WaitForChild(tostring(i))
		    print("GOTCHA2")
		    Value.Value = DataStore2("PlayerData", plr):Get(CreateDataTable())["PremiumWeaponPacks"][tostring(i)]
		    print("GOTCHA3")
		   	Button2:FireClient(plr, tostring(i), v)
		    print("GOTCHA4")
		end
	end
    print("Fired ButtonStatus for "..plr.Name)

	for i, v in pairs(EquippedFolder:GetChildren()) do -- Checks for when the equipped values change in ServerStorage, and sets them on the DataStore. 
	    v.Changed:Connect(function()
		PlayerData1["EquippedItems"][v.Name] = v.Value
		DataStore2("PlayerData", plr):Set(PlayerData1)
	end)

	local function UpdateAllStats(UpdatedStats)
        for i, v in pairs (PlayerData1["KOWeaponPacks"]) do
	        if PlayerDataDIR:FindFirstChild(tostring(i)) then
		        print("GOTCHA")
		        local Value = PlayerDataDIR:WaitForChild(tostring(i))
		        print("GOTCHA2")
		        Value.Value = DataStore2("PlayerData", plr):Get(UpdatedStats)["KOWeaponPacks"][tostring(i)]
		        print("GOTCHA3")
	        end
	    end
        for i, v in pairs (PlayerData1["PremiumWeaponPacks"]) do
	        if PlayerDataDIR:FindFirstChild(tostring(i)) then
		        print("GOTCHA")
		        local Value = PlayerDataDIR:WaitForChild(tostring(i))
		        print("GOTCHA2")
		        Value.Value = DataStore2("PlayerData", plr):Get(UpdatedStats)["PremiumWeaponPacks"][tostring(i)]
		        print("GOTCHA3")
	        end
	    end
	end
	DataStore2("PlayerData", plr):OnUpdate(UpdateAllStats)
	end
end)

KOPurchase.OnServerEvent:Connect(function(player, ProductName)
	repeat wait() until player ~= nil and ProductName ~= nil
	if db == false then
		db = true
	else
		repeat wait() until db == false
	end
	local PlayerData1 = DataStore2("PlayerData", player):Get(CreateDataTable())
    local PlayerDataDIR = ServerStorage:WaitForChild(player.Name)
    local KOWeaponPackDIR = PlayerDataDIR:WaitForChild("KOWeaponPacks")
	print("Started purchase")
    if not ProductDatabase[ProductName] then return end
	local KODataStore = DataStore2("KOs", player)
	local ProductPrice = ProductDatabase[ProductName]
	if CreateDataTable()["KOWeaponPacks"] ~= nil and PlayerData1["KOWeaponPacks"] == nil then
		PlayerData1["KOWeaponPacks"] = CreateDataTable()["KOWeaponPacks"]
	elseif CreateDataTable()["KOWeaponPacks"][ProductName] ~= nil and PlayerData1["KOWeaponPacks"][ProductName] == nil then
		PlayerData1["KOWeaponPacks"][ProductName] = CreateDataTable()["KOWeaponPacks"][ProductName]
	end
    if KODataStore:Get(DefaultKOValue) >= ProductPrice and PlayerData1["KOWeaponPacks"][ProductName] == false then
	    print("Redeeming product")
	    local Button = ProductPrice
	    local Result = 'Successful purchase'
	    PurchaseResult:FireClient(player, Result, Button)
	    PlayerData1["KOWeaponPacks"][ProductName] = true
	    KOWeaponPackDIR:FindFirstChild(ProductName).Value = true
	elseif KODataStore:Get(DefaultKOValue) < ProductPrice and PlayerData1["KOWeaponPacks"][ProductName] == false then
		print("Redemption failure")
	    local Button = ProductPrice
		local Result = 'Not enough coins'
		PurchaseResult:FireClient(player, Result, Button)
	elseif PlayerData1["KOWeaponPacks"][ProductName] == nil then
		print("Redemption failure (nil product)")
	    local Button = ProductPrice
		local Result = 'Not enough coins'
		PurchaseResult:FireClient(player, Result, Button)
	elseif PlayerData1["KOWeaponPacks"][ProductName] == true then
	    print("Data mismatch correction attempt")
	    local Button = ProductPrice
	    local Result = 'Successful purchase'
	    PurchaseResult:FireClient(player, Result, Button)
	    PlayerData1["KOWeaponPacks"][ProductName] = true
	    KOWeaponPackDIR:FindFirstChild(ProductName).Value = true
	end
	DataStore2("PlayerData", player):Set(PlayerData1)
    db = false
end)```
1 Like

One thing that I noticed straight off is this

	for i, v in pairs(EquippedFolder:GetChildren()) do -- Checks for when the equipped values change in ServerStorage, and sets them on the DataStore. 
	    v.Changed:Connect(function()
		PlayerData1["EquippedItems"][v.Name] = v.Value
		DataStore2("PlayerData", plr):Set(PlayerData1)
	end)

You are calling DataStore2:Set() multiple times, try moving it out of the for [...] do loop.

Here is the next thing:

for i, v in pairs (PlayerData1["PremiumWeaponPacks"]) do -- Set the boolvalue's value according to whether the PlayerData dictionary is true/false
	    if CoinWeaponPackDIR:FindFirstChild(tostring(i)) then
		    print("GOTCHA")
		    local Value = CoinWeaponPackDIR:WaitForChild(tostring(i))
		    print("GOTCHA2")
		    Value.Value = DataStore2("PlayerData", plr):Get(CreateDataTable())["PremiumWeaponPacks"][tostring(i)]
		    print("GOTCHA3")
		   	Button2:FireClient(plr, tostring(i), v)
		    print("GOTCHA4")
		end
	end

You are doing DataStore2:Get every time the code executes, and since you already retrieve it at the beginning of the script you can just replace this

Value.Value = DataStore2("PlayerData", plr):Get(CreateDataTable())["PremiumWeaponPacks"][tostring(i)]

to this

Value.Value = PlayerData1["PremiumWeaponPacks"][tostring(i)] 

Do the same for every other :Get call that can access PlayerData1.

Thank you for the help! I do wonder though if the normal DataStoreService budget applies to DataStore2 :Get() calls though? As it is cached and everything. I will definitely make the changes you suggested though as doing :Get() multiple times is definitely a bit dumb on my part!

I believe it only calls once, but DataStore2 does use the budget since in the end it also uses the DataStoreService.
Just try to call :Get() only once and that when the player joins, and then make it available to the server with _G, or a module. (And also save it within the PlayerAdded event so you can easily access it, and at the end of the event you want to make the data available to all other Server Scripts).

1 Like

Sorry for another reply lol, but I looked at the first suggestion you put. Isn’t it not possible to take the :Set() out of the for i, v loop, as then it would not be connected to v.Changed? Because I am trying to get it to :Set() the DataStore whenever a BoolValue in the folder changes.

I’d rather update all values first, and once you are done with that you can call :Set() so you only call once not X times.