TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 14 Nov 2016 02:04 PM |
Datastores are easy. Don't overcomplicate it. These are the steps towards saving data in a game...
1. When the player enters, get the saved data (if any) and load in the values. 2. Generate the folders inside the player based on their data 3. When the player leaves, get the data and values from inside the player's stats folders, and save it to a table 4. When the data is set, save the table to the users userid 5. You should be using FE or data can be compromised. 6. VALUES MUST BE SET FROM THE SERVER. NEVER LET A CLIENT HAVE POWER TO CHANGE THEIR DATA 7. All of the data is handled with a module script!!! 8. Enjoy!
I have explained each step below...
--SERVER SCRIPT
local DataHandler = require(game.ServerStorage.Modules.DataHandler) --we require the module script
game.Players.PlayerAdded:connect(function(player) --when a player enters load their data DataHandler.LoadData(player) end)
game.Players.PlayerRemoving:connect(function(player) --when a player leaves, save their data DataHandler.SaveData(player) end)
game.OnClose = function() --if the server crashes for any reason at all, we can make sure the data isn't compromised. for i,v in next, game.Players:GetPlayers() do DataHandler.SaveData(v) --We can exploit the fact that a reference to the player will still be available end end
--MODULE SCRIPT
local PlayerData = game:GetService('Data StoreService'):GetDataStore('TestData') --Get the data store
local DefaultStats = { --These are the default stats the user gets if they play for the first time Stats = { --Each leaderstat/folders that you want the player to spawn in with is added here Powerups = { HealthBoost = true, RandomValue = 1, }, Kills = 0, Deaths = 0, Points = 0, }, --Inventory = {}, --optional --Character = {}, --optional }
local PlayerStats = {} --This is where each players data will be saved. --You can optionally use this to generate the stats in serverstorage
local DataHandler = {}
LoadStats = function(Player) local UserId = Player.UserId local PlayerStats = PlayerStats[UserId] for stats,data in next, PlayerStats do --stats,inventory,character,etc. local NewStat = Instance.new('Folder',Player) NewStat.Name = stats --new folder with the name of the stat for key,val in next, data do --loop through the data in each folder if type(val) == 'table' then --if its a table, then make a new folder local NewFolder = Instance.new('Folder',NewStat) NewFolder.Name = key --set the name of the folder to the container for key,v in next, val do --loop through the table if key == 'HealthBoost' then --We can choose what kind of value it will be local NewVal = Instance.new('BoolValue',NewFolder) NewVal.Name = key NewVal.Value = v else --otherwise it will be an intvalue by default local NewVal = Instance.new('IntValue',NewFolder) NewVal.Name = key NewVal.Value = v end end else --we can directly create the value inside the stat folder local NewVal = Instance.new('IntValue',NewStat) NewVal.Name = key NewVal.Value = val end end end end
SaveStats = function(Player) local UserId = Player.UserId --we use userid to make sure we don't overlap data local Stats = Player:WaitForChild('Stats') --Stats is in our table for i,val in next, Stats:GetChildren() do --Get each value inside the stat folder if val:IsA('Folder') then --we treat folders as tables (data containers) like before for i,v in next, val:GetChildren() do --get the data inside the folder and set to the table PlayerStats[UserId][Stats.Name][val.Name][v.Name] = v.Value end else PlayerStats[UserId][Stats.Name][val.Name] = val.Value -- set the data to the stat folder end end --if we want to verify that the correct values are set... for i,v in next, PlayerStats[UserId] do print(i) --Stats for j,k in next, v do if type(k) == 'table' then print(j) for key,val in next, k do print(key,val) end else print(j,k) end end end end
DataHandler.LoadData = function(Player) local UserId = Player.UserId local Data pcall(function() Data = PlayerData:GetAsync(UserId) end) if Data then --load in previous data PlayerStats[UserId] = Data else --otherwise we load in the default values and save the default stats local s,e = pcall(function() --Wrap with pcall to prevent an error PlayerData:SetAsync(UserId,DefaultStats) --save the default stats end) if not s then warn(e) end --alert us if an error occured PlayerStats[UserId] = DefaultStats end LoadStats(Player) --load in the default values end
DataHandler.SaveData = function(Player) local UserId = Player.UserId SaveStats(Player) --save the default values inside the player --After all the data is loaded into the table, we can save it local s,e = pcall(function() --Wrap with pcall to prevent an error PlayerData:SetAsync(UserId,PlayerStats[UserId]) --We save the player's individual data end) if not s then warn(e) end --alert us if an error occured end
return DataHandler
|
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
| |
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
| |
|
|
| 15 Nov 2016 09:28 AM |
| Use this: http://wiki.roblox.com/index.php?title=API:Class/DataModel/BindToClose |
|
|
| Report Abuse |
|
|
|
| 15 Nov 2016 09:29 AM |
Why not letting a client do the data Store? must you use a module script or is that your choice? |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 09:32 AM |
You should be using a module script so the main server script isnt cluttered.
@foreverdev
what the difference between
game.OnClose
and
BindToClose?
Aren't those the exact same thing?
|
|
|
| Report Abuse |
|
|
|
| 15 Nov 2016 09:33 AM |
| A question. Why playerdata[payer.UserId] and not just playerdata? What are the differences? |
|
|
| Report Abuse |
|
|
| |
|
|
| 15 Nov 2016 09:33 AM |
"Why not letting a client do the data Store?" Is the reason not obvious? Exploiters.
"must you use a module script or is that your choice?" Of course you don't have to.
Also "5. You should be using FE or data can be compromised." No. |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 09:33 AM |
Oh I just saw its deprecated. Thanks for the heads up!
|
|
|
| Report Abuse |
|
|
|
| 15 Nov 2016 09:34 AM |
@TimeTicks BindToClose allows multiple functions to be bound to the closing of the game, in case any other scripts are binding a close function. |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 09:34 AM |
playerdata[userid] ensures that the data is not overlapped with another players data.
|
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 09:35 AM |
@Forever, they should be using fe regardless.
|
|
|
| Report Abuse |
|
|
|
| 15 Nov 2016 09:37 AM |
| So if the player has the user I'd 123456 it would be playerdata.123456? |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 09:40 AM |
This is what the table will look like with multiple ids.
PlayerStats = { 123456 = { --userid so we can prevent overlapping Stats = { Kills = 10, Deaths = 0, Points = 100, }, }, 654321 = { --userid Stats = { Kills = 10, Deaths = 0, Points = 100, }, }, },
|
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 09:41 AM |
And to index the data simply just do
local Data = PlayerData[UserId]
to get the users individual data
|
|
|
| Report Abuse |
|
|
0_1195
|
  |
| Joined: 21 Dec 2011 |
| Total Posts: 268 |
|
|
| 15 Nov 2016 10:07 AM |
When you create a new folder parented to the player, doesn't that mean the client can edit the data?
local NewStat = Instance.new('Folder',Player)
\_(siggy)_/ |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 15 Nov 2016 02:58 PM |
Yes. However, with FE, client changes will not affect the value. Only the server can do this. This is why this method is more or less safe. But like I said, if you are paranoid about the integrity of the data, you can store the values in serverstorage, and update the client's stats from there.
|
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
| |
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
| |
|
Dralian
|
  |
| Joined: 21 Mar 2013 |
| Total Posts: 7624 |
|
|
| 19 Nov 2016 09:10 PM |
| actually pretty neat, thank you. haha. |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
| |
|
|
| 22 Nov 2016 01:07 PM |
| thx, but can you also show a example of a script that saves the players inventory or backback when they leave? even when the server is closing? |
|
|
| Report Abuse |
|
|
TimeTicks
|
  |
| Joined: 27 Apr 2011 |
| Total Posts: 27115 |
|
|
| 22 Nov 2016 01:47 PM |
The example is right there. You literally just grab the items from their inventory just like you would like any other data. And game:BindToClose() is used to save when the server shuts down.
|
|
|
| Report Abuse |
|
|
|
| 22 Nov 2016 02:18 PM |
Grabbing this out of my main script:
game.OnClose = function() for i, v in pairs(playerdata) do datastore:SetAsync(i, v) end end
Do I write that the same way using BindToClose? |
|
|
| Report Abuse |
|
|