That was actually my mistake, and I do see it and can point it out if you would like, but given your other statement I’ll point out some things I do differently than you.
Firstly, I set all my constants and global values as variables at the very beginning of the script. Constants being anything that does not change and global values being anything that will always apply to everything in the document (Any values you’re currently typing over and over again). This includes services (The ones you commonly use). I also like to create my “data” variable at the very top.
Therefore if I were you I’d start my entire script like this:
local DataStoreService = game:GetService("DataStoreService")
local myDataStore = DataStoreService:GetDataStore("myData")
--Here I'll place the services I notice you'll need throughout your script.
local players = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
--That were the only two
local data
local Checkpoints = game.Workspace.Checkpoints
local rebirthEvent = RS.RebirthEvent
Now instead of typing all of your PlayerAdded and PlayerRemoved content directly under an anonymous function, being as to how WE KNOW PlayerAdded and PlayerRemoving takes in plr as a passed value, I assign those to a defined function with the given name and call that function when PlayerAdded or PlayerRemoving happens at the very bottom. And because we created a global players variable for the players service, you don’t have to keep typing game.Players and can just type the variable. I also see you have the CharacterAdded event connected to an anonymous function as well, so I’d suggest making a defined function for when the character is added as well. New Code:
local DataStoreService = game:GetService("DataStoreService")
local myDataStore = DataStoreService:GetDataStore("myData")
local players = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local data
local Checkpoints = game.Workspace.Checkpoints
local rebirthEvent = RS.RebirthEvent
local function onCharacterAdded(char, Stage) -- Just added
wait()
if Stage.Value > 1 then
char:MoveTo(Checkpoints:FindFirstChild(Stage.Value - 1).Position)
end
end
local function onPlayerAdded(plr) -- Just added
local userId = plr.UserId
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = plr
local Stage = Instance.new ("IntValue", leaderstats)
Stage.Name = "Stage"
Stage.Value = 1
Stage.Parent = leaderstats
local Rebirths = Instance.new("IntValue")
Rebirths.Name = "Rebirths"
Rebirths.Value = 0
Rebirths.Parent = leaderstats
plr.CharacterAdded:Connect(function()
onCharacterAdded(plr.Character, Stage)
end)
rebirthEvent.OnServerEvent:Connect(function()
Rebirths.Value += 1
plr.Character:WaitForChild("HumanoidRootPart").CFrame = game.Workspace.SpawnLocation.CFrame + Vector3.new(0, 3, 0)
Stage.Value = 1
print("Player has not reached the max stage")
end)
end
local function onPlayerRemoving(plr) -- Just added
end
players.PlayerAdded:Connect(onPlayerAdded) -- Just Added
players.PlayerRemoving:Connect(onPlayerRemoving) -- Just Added
Now, you want the stored data to be loaded into the player the second they joined. So you want to check for success with the pcall as well as apply that data if it is successful INSIDE the onPlayerAdded function. So modify your onPlayerAdded function to look like the following:
local function onPlayerAdded(plr)
local userId = plr.UserId -- Always assign variables for constant values instead of typing the definition over and over again. So now whenever you need the User Id you just have to type userId instead of plr.UserId
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = plr
local Stage = Instance.new ("IntValue", leaderstats)
Stage.Name = "Stage"
Stage.Value = 1
Stage.Parent = leaderstats
local Rebirths = Instance.new("IntValue")
Rebirths.Name = "Rebirths"
Rebirths.Value = 0
Rebirths.Parent = leaderstats
plr.CharacterAdded:Connect(function()
onCharacterAdded(plr.Character, Stage)
end)
rebirthEvent.OnServerEvent:Connect(function()
Rebirths.Value += 1
plr.Character:WaitForChild("HumanoidRootPart").CFrame = game.Workspace.SpawnLocation.CFrame + Vector3.new(0, 3, 0)
Stage.Value = 1
print("Player has not reached the max stage")
end)
local success, errormsg = pcall(function() -- Just Added
data = myDataStore:GetAsync(userId) -- Here is where we assign the data
end
if success and data ~= nil then -- If retrieving the data is successful and there is actually value in the data, load it.
--The stage value will never be 0 as you literally set it to 1 before you let any of this happen, and therefore that if statement is not needed.
plr.leaderstats.Stage.Value = data.stageValue
plr.leaderstats.Rebirths.Value = data.rebirthsValue
print(plr.Name.."'s data was received from the data store")
else -- Otherwise
if errormsg then
warn(errormsg)
end
if data == nil then
print(plr.Name.."'s has no previously saved data")
end
end
end
That should be it for the onPlayerAdded function, now for removing. For the most part, it’s fine. But a major problem is that it doesn’t check for if the server is closing, meaning if you were to ever shut down the server while there are players, they will lose their data. It also means that the last player is not always guaranteed to have their data save if the server happens to shut down before it finishes saving data for that last player. So I suggest:
local function onPlayerRemoving(plr, closing) -- Notice we added a new value to be passed in, it is a boolean value.
local success = nil -- Define this variable up here but give it a value of nil
local errormsg = nil -- This one too
local userId = plr.UserId -- let's not forget to assign a variable
local data = {
stageValue = plr.leaderstats.Stage.Value;
rebirthValue = plr.leaderstats.Rebirths.Value;
}
if closing == true then -- Now, if we're closing, we want to return the userId and data to be used in the function binded to the game's closing (Below)
return userId, data
else
success, errormsg = pcall(function()
myDataStore:SetAsync(userId, data) -- If you end up with a problem where the data doesn't save (At any point at all, even if it's not now), add a wait(7) before this line.
end)
end
if success then
print("Successfully saved"..plr.Name.."'s data.")
else
warn(errormsg)
end
end
game:BindToClose(function() -- This saves data if the game closes (Or server shuts down) while there are still several players in it.
for _, Player in pairs(game.Players:GetPlayers()) do
print("Saving data before server closes")
local userId, data = onPlayerRemoving(Player, true)
myDataStore:SetAsync(userId, data)
wait(.7)
end
end)
I think this should be just about everything. So you will need to include that for loop you have at the bottom. Which turns your final script into:
local DataStoreService = game:GetService("DataStoreService")
local myDataStore = DataStoreService:GetDataStore("myData")
local players = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local data
local Checkpoints = game.Workspace.Checkpoints
local rebirthEvent = RS.RebirthEvent
local function onCharacterAdded(char, Stage) -- Just added
wait()
if Stage.Value > 1 then
char:MoveTo(Checkpoints:FindFirstChild(Stage.Value - 1).Position)
end
end
local function onPlayerAdded(plr)
local userId = plr.UserId -- Always assign variables for constant values instead of typing the definition over and over again. So now whenever you need the User Id you just have to type userId instead of plr.UserId
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = plr
local Stage = Instance.new ("IntValue", leaderstats)
Stage.Name = "Stage"
Stage.Value = 1
Stage.Parent = leaderstats
local Rebirths = Instance.new("IntValue")
Rebirths.Name = "Rebirths"
Rebirths.Value = 0
Rebirths.Parent = leaderstats
plr.CharacterAdded:Connect(function()
onCharacterAdded(plr.Character, Stage)
end)
rebirthEvent.OnServerEvent:Connect(function()
Rebirths.Value += 1
plr.Character:WaitForChild("HumanoidRootPart").CFrame = game.Workspace.SpawnLocation.CFrame + Vector3.new(0, 3, 0)
Stage.Value = 1
print("Player has not reached the max stage")
end)
local success, errormsg = pcall(function() -- Just Added
data = myDataStore:GetAsync(userId) -- Here is where we assign the data
end
if success and data ~= nil then -- If retrieving the data is successful and there is actually value in the data, load it.
--The stage value will never be 0 as you literally set it to 1 before you let any of this happen, and therefore that if statement is not needed.
plr.leaderstats.Stage.Value = data.stageValue
plr.leaderstats.Rebirths.Value = data.rebirthsValue
print(plr.Name.."'s data was received from the data store")
else -- Otherwise
if errormsg then
warn(errormsg)
end
if data == nil then
print(plr.Name.."'s has no previously saved data")
end
end
end
local function onPlayerRemoving(plr, closing) -- Notice we added a new value to be passed in, it is a boolean value.
local success = nil -- Define this variable up here but give it a value of nil
local errormsg = nil -- This one too
local userId = plr.UserId -- let's not forget to assign a variable
local data = {
stageValue = plr.leaderstats.Stage.Value;
rebirthValue = plr.leaderstats.Rebirths.Value;
}
if closing == true then -- Now, if we're closing, we want to return the userId and data to be used in the function binded to the game's closing (Below)
return userId, data
else
success, errormsg = pcall(function()
myDataStore:SetAsync(userId, data) -- If you end up with a problem where the data doesn't save (At any point at all, even if it's not now), add a wait(7) before this line.
end)
end
if success then
print("Successfully saved"..plr.Name.."'s data.")
else
warn(errormsg)
end
end
game:BindToClose(function() -- This saves data if the game closes (Or server shuts down) while there are still several players in it.
for _, Player in pairs(game.Players:GetPlayers()) do
print("Saving data before server closes")
local userId, data = onPlayerRemoving(Player, true)
myDataStore:SetAsync(userId, data)
wait(.7)
end
end)
players.PlayerAdded:Connect(onPlayerAdded)
players.PlayerRemoving:Connect(onPlayerRemoving)
--Stage value is increased
for i, checkpoint in pairs(Checkpoints:GetChildren()) do
checkpoint.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild("Humanoid") then
local char = hit.Parent
local plr = game.Players:GetPlayerFromCharacter(char)
if tonumber(checkpoint.Name) == plr.leaderstats.Stage.Value then
plr.leaderstats.Stage.Value += 1
end
end
end)
end
Tell me how this goes! If it gives you the same error "attempt to index number with ‘stageValue’, then that means the variable “data” was set to a number instead of the table at some point in the script. It could be that data still holds some previous value, so before asking me for help, try changing the data store’s name so a different data store will be utilized.