I am making a Datastore system for pets in my game it works well however there’s one problem.
When you trigger the prompt in the video it spawns a pet, I only triggered it once and it creates a StringValue in the data folder, however when I rejoined studio the string value duplicated itself and then when I rejoined again there were multiple pets and multiple string values that duplicated again.
Datastore Code:
local DataHandler = {}
local DataStore2 = require(script.DataStore2)
function DataHandler:LoadData(Player)
local PlayerData = DataStore2("PlayerData", Player)
local Data = PlayerData:GetTable({
PetData = {}
});
local PlayerData = Instance.new("Folder")
PlayerData.Name = Player.Name.."'s PetData"
PlayerData.Parent = Player
if #Data.PetData > 0 then
for i = 1, #Data.PetData do
local Setting = Instance.new("StringValue")
Setting.Name = Data.PetData[i]
Setting.Parent = PlayerData
end
end
end
function DataHandler:SaveData(Player)
local NewPetData = {}
local PlayerData = Player:FindFirstChild(Player.Name.."'s PetData")
if PlayerData then
for _, SettingValue in pairs(PlayerData:GetChildren()) do
warn(SettingValue.Name)
table.insert(NewPetData, SettingValue.Name)
end
local PlayerData = DataStore2("PlayerData", Player)
PlayerData:Set({
PetData = NewPetData
})
end
end
return DataHandler
I highly believe it is caused by this line of code:
if #Data.PetData > 0 then
for i = 1, #Data.PetData do
local Setting = Instance.new("StringValue")
Setting.Name = Data.PetData[i]
Setting.Parent = PlayerData
end
end
The problem is that you’re saving data that has already been loaded. It basically means you’re saving data twice, which is why the pets duplicate themselves.
Two solutions I can think of (though I’ve never worked with DataStores before):
Before saving data, check if it already exists.
Split the data that is already saved, and the data that will be saved.
I, personally, would prefer the first option. It is cleaner (at least theoretically).
So, here’s an implementation:
function DataHandler:SaveData(Player)
local NewPetData = {}
local PlayerData = Player:FindFirstChild(Player.Name.."'s PetData")
if PlayerData then
for _, SettingValue in pairs(PlayerData:GetChildren()) do
local entryAlreadyExists = table.find(NewPetData, SettingValue.Name)
if entryAlreadyExists == false then
table.insert(NewPetData, SettingValue.Name)
end
end
local PlayerData = DataStore2("PlayerData", Player)
PlayerData:Set({
PetData = NewPetData
})
end
end
Take it with a grain of salt, though. I’ve never worked with DataStores, much less DataStore2. (which, by the way, you should look for an alternative)
My bad, it is because I used table.find improperly. When table.find doesn’t find a match, it returns nil, not false. To fix this issue would be to change the if statement’s condition to entryAlreadyExists == nil. Or, if you’d like, not entryAlreadyExists (a little less readable).
Yes, unfortunately this was intentional in the code itself. ('tis but a feature, not a bug).
I was about to suggest you to use option 2, but shouldn’t DataStore2 provide some kind of Update function? The real fix would be for you to add new entries, not re-add existing entries.
A manual approach would look a like this:
function DataHandler:SaveData(Player, NewData: { StringValue })
local Data = {}
do
-- Will *remember* the existing data
local PlayerData = Player:FindFirstChild(Player.Name.."'s PetData")
if PlayerData then
for _, SettingValue in pairs(PlayerData:GetChildren()) do
warn(SettingValue.Name)
table.insert(Data, SettingValue.Name)
end
end
end
-- Will add the new data
for _, SettingValue in ipairs(NewData) do
table.insert(Data, SettingValue.Name)
end
-- Finally, save.
local PlayerData = DataStore2("PlayerData", Player)
PlayerData:Set({
PetData = NewPetData
})
end
You’d then have to call SaveData with the new data you want to save.
You could do this by also creating a NewData folder, and calling SaveData(player, NewDataFolder:GetChildren(). Furthermore, you would ALSO need to clear out this folder, otherwise, we will run into the same duplication issue.
Because I’m a bit of a perfectionist myself, I feel like this approach is overcomplicated, though it could be that I am inexperienced in the field. Hope this helps.
local Players = game:GetService("Players")
local DataHandler = require(script.DataHandler)
Players.PlayerAdded:Connect(function(Player)
DataHandler:LoadData(Player)
end)
Players.PlayerRemoving:Connect(function(Player)
DataHandler:SaveData(Player)
end)
What am I supposed to do with the save data part since it expects the new data
(Also, I assume that the NewPetData in the code is supposed to be replaced with Data at the start of the function)
As I said before, you should create a folder called NewData inside the player (you could do this inside the Players.PlayerAdded callback).
Whenever you want to add data belonging to the player (such as by triggering the prompt, as shown in the video), you’d add a new entry to NewData.
Finally, the Players.PlayerRemoving callback would look like this:
Players.PlayerRemoving:Connect(function(Player)
local NewData = Player:FindFirstChild("NewData")
if not NewData then
error("Player doesn't have a NewData folder? wtf")
end
DataHandler:SaveData(Player, NewData:GetChildren())
end)
I still think you shouldn’t do it like this (i.e., do at your own risk). Look for a better approach, and for a better DataStore module.
I have since solved this it was actually caused by one of the RemoteFunctions loading in the data as it was inserting StringValues into the folder (The data) then the game recognizes this and loads the data however this would duplicate string values when I loaded the pets in so what I did was just add a Boolean argument whether or not you want to add StringValues into the folder.