Answered by
6 years ago Edited 6 years ago
You are going over the DataStore
request limit by attempting to save whenever a value changes.
There are four times that we should be saving to a DataStore
:
When the player leaves the game. This should work most of the time.
When the server is about to shut down. We can't guarantee this will work, but we can try.
Auto-saving every few minutes. Attempting to save when the server shuts down or player leaves might fail for some reason, so we want to make sure the player only loses a few minutes of data at most.
After a player makes a R$ purchase, we should save immediately before accepting the purchase. This is very important. Players should never lose data after spending money.
You should wrap your DataStore
requests in a pcall
, since they can error.
Additionally, we should be saving and loading a dictionary as our player data. This is because it allows us to easily add and remove what we want to save at any time. Do this always, even if you only have one value you want to save at the time. It'll make your life easier, trust me!
Something I see a lot is that people forget to check if the attempt to load a player's data failed or not before saving. This is a must, because you can overwrite a player's data if you don't do this.
We can also avoid wasting DataStore
requests on guests by checking if we're not in Studio's Test Server mode and checking their Player.UserId
. Though guests have been officially removed from the platform, they're still around, and players can still join as them. The reason to check if the game is running in Test Server mode is that players all get negative user IDs in it.
Let's try something like this:
02 | DataStore = game:GetService( "DataStoreService" ), |
03 | Players = game:GetService( "Players" ), |
04 | Run = game:GetService( "RunService" ) |
07 | local PlayerDataStore = Services.DataStore:GetDataStore( "PlayerDataStore" ) |
09 | local LoadedDataPlayers = { } |
11 | local function IsPlayerGuest(Player) |
12 | return Services.Run:IsStudio() and Player.UserId < 1 |
15 | local function GetPlayerDataToSave() |
19 | FavoriteColor = "Red" , |
28 | local function OnPlayerAdded(Player) |
29 | if LoadedDataPlayers [ Player.UserId ] then |
31 | Player:Kick( "Data loading error: Please rejoin the game" ) |
35 | if not IsPlayerGuest(Player) then |
36 | local IsLoadSuccess, LoadedData = pcall ( function () |
37 | return PlayerDataStore:GetAsync(Player.UserId) |
42 | LoadedDataPlayers [ Player.UserId ] = true |
45 | if IsLoadSuccess and LoadedData then |
53 | local function OnPlayerRemoving(Player) |
54 | if not LoadedDataPlayers [ Player.UserId ] then |
58 | local PlayerDataToSave = GetPlayerDataToSave() |
61 | PlayerDataStore:SetAsync(Player.UserId, PlayerDataToSave) |
64 | LoadedDataPlayers [ Player.UserId ] = nil |
67 | local function OnGameShutdown() |
69 | local Players = Services.Players:GetPlayers() |
71 | for Index = 1 , #Players do |
72 | local Player = Players [ Index ] |
74 | OnPlayerRemoving(Player) |
78 | Services.Players.PlayerAdded:Connect(OnPlayerAdded) |
79 | Services.Players.PlayerRemoving:Connect(OnPlayerRemoving) |
81 | game:BindToClose(OnGameShutdown) |
You may see a warning message about your data request being throttled when testing the game in Studio, because the game will both attempt to save the data when the player leaves, and right after when the game shuts down. It's fine to ignore this.
Additionally, your AddCash
RemoteEvent
is very insecure. Exploiters can fire it and get unlimited amounts of cash. The client should never be in the business of ordering the server to perform actions. They should never have access to a RemoteEvent
that adds cash like that.