You mean that, when server shutdown happens PlayerRemoving is not firing?
Yup. Everyone leaves without firing The player removing function
I’m more use to SetAsync, So lets try it.
local success, err = pcall(function()
playerDataStore:SetAsync(key, data)
end)
if not success then
warn("Failed to save "..player.Name.."'s data! (" ..tostring(player.UserId).. ")")
else
print("Saved Data!")
end
print(data)
end
forget to remove the extra end, you should be good now
I just made a test in a real server, using PlayerRemoving event, and from the website, shutdown all server, PlayerRemoving fired saving the DSS
Tried it, unfortunately still not working. I tested it without the BindToClose function and it still didn’t save, which is leading me to believe that it is not the source of the problem. Maybe its the data im trying to store? I am storing multiple tables inside tables, idk if that makes a difference or not.
try to use the datastore2, How to use DataStore2 - Data Store caching and data loss prevention it is very simple and easy to use and you dont need to code all datastore systems, all is done just use module functions from the datastore2
I agree with POG about using SetAsync, Im more used to it.
I was playing with your code a little, this code probably is not friendly with other systems you could have, I deleted the leader board and stuff that was not needed for the test, I was playing… so I added a repeat function instead of repeat return etc. And modified keys in your tables.
Its working fine for me, when I place a model rig into the player folder, and leave/close studio. It saves normally, when joining again its printing what its saved.
Obviously it has an issue trying to save twice when in a real server, it shutdown. Cause apparently PlayerRemoving and BindToClose can fire both when a real server shutdown. I just added a InStudio check to not fire it from studio.
Give it a try, dont forget to add your leaderstats and stuff.
local plrs = game:GetService("Players")
local DSS = game:GetService("DataStoreService")
local playerDataStore = DSS:GetDataStore("PlayerData")
local RS = game:GetService("RunService")
local cs = game:GetService("CollectionService")
local function retry(func, attempt)
if not attempt then
attempt = 0
end
local s, m = pcall(function()
return func()
end)
if not s then
attempt = attempt + 1
--print(attempt)
--warn(m, "Attempt:", attempt)
task.wait(2)
return retry(func, attempt)
end
if attempt == 5 and not s then
warn('Error' .. tostring(m))
return false
end
return s, m
end
local function loadData(player:Player)
local plrNPCs = Instance.new("Folder")
plrNPCs.Parent = player
plrNPCs.Name = "PlayerNPCFolder"
local deceasedPlrNPCs = Instance.new("Folder")
deceasedPlrNPCs.Parent = plrNPCs
deceasedPlrNPCs.Name = ("DeceasedNPCs")
local s, m = retry(function()
return playerDataStore:GetAsync(player.UserId)
end)
if s then
warn("DSS connection success")
if m then
warn("data found for:", player)
print(m)
for _, items in pairs(m) do
--print(items)
end
else
warn("player has not data")
end
else
warn("DSS connection failed after retries", player)
end
end
function saveData(player)
local data = {}
for _, item in pairs(player:WaitForChild("PlayerNPCFolder"):GetChildren()) do
if item:IsA("Model") then
local bodycolors = {}
local gender
local accessory = false
for _, bodypart in pairs(item:GetChildren()) do
if bodypart.Name == "Head" or bodypart:IsA("MeshPart") then
local colors = {
R = math.floor(bodypart.Color.R*255),
G = math.floor(bodypart.Color.G*255),
B = math.floor(bodypart.Color.B*255)
}
bodycolors[bodypart.Name] = colors
elseif bodypart:IsA("Accessory") then
accessory = bodypart.Name
end
end
for _, tag in pairs(cs:GetTags(item)) do
if tag == "Male" or tag == "Female" then
gender = tag
end
end
table.insert(data, {
NameNPC = item.Name,
Accessory = accessory,
BodyColors = bodycolors,
Gender = gender
})
end
end
print(#data)
if #data > 0 then
warn("saving")
local s, m = retry(function()
return playerDataStore:SetAsync(player.UserId, data)
end)
if s then
warn("data saved for:", player)
else
warn("data not saved for:", player)
end
end
end
plrs.PlayerAdded:Connect(loadData)
plrs.PlayerRemoving:Connect(saveData)
game:BindToClose(function()
if (RS:IsStudio()) then
warn("BindToClose in Studio should do nothing")
--task.wait(1)
else
-- Shutdown in a real Server
-- In a real server when it shutdown, its trying to save twice.
-- One from PlayerRemoving event and one from this BindToClose
for _, plr in pairs(plrs:GetPlayers()) do
task.spawn(function()
saveData(plr)
end)
end
end
end)
Developer shut downs are only one. And often the most protected from data. Crashing, etc. Also PlayerRemoving doesn’t have the 30 seconds, rather player removing runs while the server is still alive. While It can save a couple in time , provided that it does fire, it won’t say a full server of people like how bind to close will. Also it doesn’t run most of the time. This is something that has been debated, and most developers like using it. I use it myself, and it improved a lot of my data lost, after updates
I’m going to try this out. I’ll let you know if it works.
I agree, that PlayerRemoving is not consistent when a server shutdown, thats why would be important to use BindToClose to save the data of players when its about to shutdown.
I was just pointing out that it needs some debounces, cause it could try to save twice for some players.
Not really important, cause the data is surely saved once at least, trying to save twice it will only “clog” the datastore for that player.
Yup. The only thing is those limits of Set/Updating.
I don’t make my data stores, rather I use profile service for it. But it was useful when making my own, and I didn’t have too many issues
Just tried this out - everything fires like it should but when I rejoin it says I have no data.
Sorry, as I said, I was playing with your code I changed many stuff from your original script, I changed the key of the player when saving into datastore too. Make sure to check it carefully. I will check it again and send a video, Im sure its working for me
Double checked and I realized there was an error being produced:
I have no idea what this means or how to fix it. Any ideas?
Edit - Here is my adapted version of your code, just for reference:
-- Services
local dss = game:GetService("DataStoreService")
local playerDataStore = dss:GetDataStore("PlayerData")
local plrs = game:GetService("Players")
local wrkspce = game:GetService("Workspace")
local cs = game:GetService("CollectionService")
local runservice = game:GetService("RunService")
-- Functions
local function FolderMaker(Name, Parent)
local Folder = Instance.new("Folder", Parent)
Folder.Name = Name
end
local function retry(func, attempt)
if not attempt then
attempt = 0
end
local success, err = pcall(function()
return func()
end)
if not success then
attempt = attempt + 1
--print(attempt)
warn(err, "Attempt:", attempt)
task.wait(2)
return retry(func, attempt)
end
if attempt == 5 and not success then
warn('Error' .. tostring(err))
return false
end
return success, err
end
local function saveData(player: Player)
local key = ("Player: " ..tostring(player.UserId))
local data = {}
for _, item in pairs(player:WaitForChild("PlayerNPCFolder"):GetChildren()) do
if item:IsA("Model") then
local bodycolors = {}
local gender
local accessory = nil
for _, bodypart in pairs(item:GetChildren()) do
if bodypart.Name == "Head" or bodypart:IsA("MeshPart") then
local colors = {
R = math.floor(bodypart.Color.R*255),
G = math.floor(bodypart.Color.G*255),
B = math.floor(bodypart.Color.B*255)
}
bodycolors[bodypart.Name] = colors
elseif bodypart:IsA("Accessory") then
accessory = bodypart.Name
end
end
for _, tag in pairs(cs:GetTags(item)) do
if tag == "Male" or tag == "Female" then
gender = tag
end
end
table.insert(data, {
item.Name,
item:FindFirstChildWhichIsA("Accessory"),
bodycolors,
gender
})
end
end
print(#data)
if #data > 0 then
print(data)
warn("saving")
local success, err = retry(function()
return playerDataStore:SetAsync(player.UserId, data)
end)
if success then
warn("data saved for: ", player)
else
warn("data not saved for: ", player)
end
end
end
local function loadData(player: Player)
-- Game Folder
FolderMaker(player.Name, wrkspce.CurrentNPCs)
-- Player Folders
FolderMaker("leaderstats", player)
FolderMaker("PlayerNPCFolder", player)
FolderMaker(player.Name.. "'s Deceased NPCs", player:WaitForChild("PlayerNPCFolder"))
local success, err = retry(function()
return playerDataStore:GetAsync(player.UserId)
end)
if success then
warn("DSS connection success")
if err then
warn("data found for:", player)
print(err)
for _, items in pairs(err) do
--print(items)
end
else
warn("player has not data")
end
else
warn("DSS connection failed after retries", player)
end
end
-- Events
plrs.PlayerAdded:Connect(loadData)
plrs.PlayerRemoving:Connect(saveData)
game:BindToClose(function()
if (runservice:IsStudio()) then
warn("BindToClose in Studio should do nothing")
--task.wait(1)
else
-- Shutdown in a real Server
-- In a real server when it shutdown, its trying to save twice.
-- One from PlayerRemoving event and one from this BindToClose
for _, plr in pairs(plrs:GetPlayers()) do
task.spawn(function()
saveData(plr)
end)
end
end
end)
You are trying my code? Cause that should not happen with what I sent. All values to be saved are safe
Because this line:
item:FindFirstChildWhichIsA("Accessory"),
I already supplied a workaround to save the Name of the Accessory that the NPC is using. Its not possible to save an accessory, you can just save the name for reference. Plus, you need to check if theres any before trying to save into table
The code I supplied does this:
table.insert(data, {
NameNPC = item.Name,
Accessory = accessory,
BodyColors = bodycolors,
Gender = gender
})
instead than this:
table.insert(data, {
item.Name,
item:FindFirstChildWhichIsA("Accessory"),
bodycolors,
gender
})
And talking about that. You dont want to save only one accessory as you are doing, create a subtable with all the accessories the NPC has
Save function should look like this:
Save data function:
function saveData(player)
local key = ("Player: "..tostring(player.UserId))
local data = {}
for _, item in pairs(player:WaitForChild("PlayerNPCFolder"):GetChildren()) do
if item:IsA("Model") then
local bodycolors = {}
local gender
local accessory = false
for _, bodypart in pairs(item:GetChildren()) do
if bodypart.Name == "Head" or bodypart:IsA("MeshPart") then
local colors = {
R = math.floor(bodypart.Color.R*255),
G = math.floor(bodypart.Color.G*255),
B = math.floor(bodypart.Color.B*255)
}
bodycolors[bodypart.Name] = colors
elseif bodypart:IsA("Accessory") then
accessory = bodypart.Name
end
end
for _, tag in pairs(cs:GetTags(item)) do
if tag == "Male" or tag == "Female" then
gender = tag
end
end
table.insert(data, {
NameNPC = item.Name,
Accessory = accessory,
BodyColors = bodycolors,
Gender = gender
})
end
end
print(#data)
if #data > 0 then
warn("saving")
local s, m = retry(function()
return playerDataStore:SetAsync(key, data) -- You forgot to use youe key instead of mine, which is based only in UserId
end)
if s then
warn("data saved for:", player)
else
warn("data not saved for:", player)
end
end
end
And again, same error on this line 109, you are not using your key, you are still using mine:
playerDataStore:GetAsync(player.UserId)
Load data function:
local function loadData(player: Player)
local key = ("Player: "..tostring(player.UserId))
-- Game Folder
FolderMaker(player.Name, wrkspce.CurrentNPCs)
-- Player Folders
FolderMaker("leaderstats", player)
FolderMaker("PlayerNPCFolder", player)
FolderMaker(player.Name.. "'s Deceased NPCs", player:WaitForChild("PlayerNPCFolder"))
local success, err = retry(function()
return playerDataStore:GetAsync(key)
end)
if success then
warn("DSS connection success")
if err then
warn("data found for:", player)
print(err)
for _, items in pairs(err) do
--print(items)
end
else
warn("player has not data")
end
else
warn("DSS connection failed after retries", player)
end
end
Thanks for pointing that out - didn’t catch it when I was checking. Everything works fine now, I just had one more question. Everytime I spawn a new NPC and then leave, all NPCs from the previous session are erased from the DataStore. Is there a way to add new data to old data rather than overwriting old data with new data?
EDIT - I realized that this problem will be solved once I start cloning NPCs
Thank you to everyone who replied. You were all a great help!
I understand what you mean, but SetAsync is exactly as useful as UpdateAsync.
Lets see, if a player leaves, and it has lets say 3 NPC in folder. When player leaves, all data about them is saved.
When player join again, you should populate those NPC into the player folder. Using the data from DataStore, create all NPC again, and place them in player folder, when player leaves, old NPC plus new ones will be saved into the DataStore.
Right now, with your code, you are doing nothing when player joins, datastore readed, and then nothing, you should create the old NPC again, based on the player’s datastore. When player leaves, all NPC will be saved again, old ones and new ones, cause all are in the player’s folder