I don’t think you’ll be able to - I’ve seen these errors too. If you think that every Roblox log error is safe from humanly mistakes made by engine developers… Then yeah just return Enum.ProductPurchaseDecision.PurchaseGranted to ProcessReciept callback immediately and you won’t have any errors.
I personally interpreted the ProcessReciept system differently.
Im experiencing an issue where a replica containing the player profile does not get received by the client.
Both the client and server uses modules.
Server code:
local function playerAdded(player)
local profile = profileStore:LoadProfileAsync("Player_"..player.UserId) -- get profile
if profile ~= nil then
profile:AddUserId(player.UserId) -- GDPR
profile:Reconcile() -- add missing stuff
profile:ListenToRelease(function() -- loaded on another server
profiles[player].Replica:Destroy()
profiles[player] = nil
player:Kick("Your data might have been loaded on another server, please rejoin")
end)
if player:IsDescendantOf(players) == true then -- loaded and player didnt leave
HandleData(player, profile) -- this just changes profile.Data before its sent to the client
-- create profile object with replica
local profileObject = {
Profile = profile,
Replica = replicaService.NewReplica({
ClassToken = profileToken,
Tags = {Player = player},
Data = profile.Data,
Replication = player,
}),
Player = player,
}
profiles[player] = profileObject
else -- left before loaded
profile:Release()
end
else -- could not load
player:Kick("Could not load data, please rejoin")
end
end
Client code:
local dataUtil = {}
local plr = game.Players.LocalPlayer
print(0)
local controller = require(game.ReplicatedStorage.UtilityModules.ReplicaService.ReplicaController)
print(.5)
controller.ReplicaOfClassCreated("PlayerProfile", function(replica)
print(3)
if replica.Tags.Player == plr then
print("Data has been received!")
end
end)
print(1)
controller.RequestData()
print(2)
return dataUtil
You can see I already added debug prints to the client side, and 0, 0.5, 1 and 2 get printed, but 3 does not. ([ReplicaController]: Initial data received does not get printed either)
What am I doing wrong here?
I tried making a repro for the issue but was unsuccessful.
EDIT: Looks like this was caused by mistakenly adding a ReplicaRemoteEvents folder into replicatedStorage beforehand, causing the module to make a duplicate. Whoops
Is it not possible to store instances inside tables, or what am I doing wrong?
I’m using a remoteFunction to get the data.ItemsSaved table into a local script, where I display the saved tools in a list and where I have the code to save the tools.
When saving a tool, the local script sends the Instance to the server script which is supposed to table.insert into the ItemsSaved the tool, but when doing that it seems it takes some time and then it errors, with the “Cannot store Dictionary in data store. Data stores can only accept valid UTF-8 characters.” error, and the game takes a long time to close too. I know the rest of the scripts are working because im also saving bank money and its working, even while studio testing the data stays (Studio api access is on) so what could be the issue here?
Yeah I realized some time after posting the comment, it makes sense for datastores to not store instances tbh, thankfully I can just save the data of the tools in some other format and then recreate the tools when reading the data from the profile.
Is there a event that you could possibly add that once the ProfileStore loads you can connect a event and execute some code when the ProfileStore is loaded?
Unless this is already achievable with some function or something. Please be sure to tell me.
I modified the basic module they provide to handle the profiles and let me know when a new profile has been loaded or unloaded in my game.
ProfileHandler:
local ProfileHandler = {}
-- https://madstudioroblox.github.io/ProfileService/
-- ProfileTemplate table is what empty profiles will default to.
-- Updating the template will not include missing template values
-- in existing player profiles!
local ProfileTemplate = {
Cash = 0,
Items = {},
LogInTimes = 0,
}
----- Loaded Modules -----
local ProfileService = require(game:GetService("ServerScriptService").ProfileService.ProfileService)
----- Private Variables -----
local Players = game:GetService("Players")
local ProfileStore = ProfileService.GetProfileStore(
"PlayerData",
ProfileTemplate
)
local Profiles = {} -- [player] = profile
local whenProfileLoadedConnections = {}
local whenProfileReleasingConnections = {}
----- Private Functions -----
local function DoSomethingWithALoadedProfile(player, profile)
if profile.Data.Money == nil then profile.Data.Money = 0 end
for _, func in pairs(whenProfileLoadedConnections) do
func(player, profile)
end
end
local function PlayerAdded(player)
local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId)
if profile ~= nil then
profile:AddUserId(player.UserId) -- GDPR compliance
profile:Reconcile() -- Fill in missing variables from ProfileTemplate (optional)
profile:ListenToRelease(function()
Profiles[player] = nil
-- The profile could've been loaded on another Roblox server:
player:Kick("Failed to load your data from datastore. To avoid data corruption we kicked you. Try again later.")
end)
if player:IsDescendantOf(Players) == true then
Profiles[player] = profile
-- A profile has been successfully loaded:
DoSomethingWithALoadedProfile(player, profile)
else
-- Player left before the profile loaded:
profile:Release()
end
else
-- The profile couldn't be loaded possibly due to other
-- Roblox servers trying to load this profile at the same time:
player:Kick("Failed to load your data from datastore. To avoid data corruption we kicked you. Try again later.")
end
end
----- Initialize -----
-- In case Players have joined the server earlier than this script ran:
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(PlayerAdded, player)
end
----- Connections -----
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(function(player)
local profile = Profiles[player]
if profile ~= nil then
for _, func in pairs(whenProfileReleasingConnections) do
func(player, profile)
end
profile:Release()
end
end)
function ProfileHandler:ConnectWhenProfileLoaded(name: string, func)
-- Use this in any other serverscript to listen when a profile has been loaded
-- The function sends the Player and the Profile as a parameter
-- ProfileHandler:ConnectWhenProfileLoaded("Something", function (player, profile) end)
if whenProfileLoadedConnections[name] ~= nil then error("Connection already defined. Disconnect it first.") return end
--print("New connection to when loaded", name)
whenProfileLoadedConnections[name] = func
end
function ProfileHandler:DisconnectWhenProfileLoaded(name: string, func)
-- Disconnects the funcion above
--print("New disconnection call to when loaded", name)
whenProfileLoadedConnections[name] = nil
end
function ProfileHandler:ConnectWhenProfileReleasing(name: string, func)
-- Use this in any other serverscript to listen when the player left the game
-- and is about to be released
-- The function sends the Player and the Profile as a parameter
-- ProfileHandler:ConnectWhenReleasing("Something", function (player, profile) end)
if whenProfileReleasingConnections[name] ~= nil then error("Connection already defined. Disconnect it first.") return end
--print("New connection to when releasing", name)
whenProfileReleasingConnections[name] = func
end
function ProfileHandler:DisconnectWhenProfileReleasing(name: string, func)
-- Disconnects the funcion above
--print("New disconnection call to when profile releasing", name)
whenProfileReleasingConnections[name] = nil
end
return ProfileHandler
Then from any other script I can do:
For example a cash Leaderstats
local ProfileHandler = require(game.ServerScriptService.ProfileHandler)
local UNIQUE_IDENTITY = "LeaderstatsCash"
-- will fire each time ProfileService loads a profile
ProfileHandler:ConnectWhenProfileLoaded(UNIQUE_IDENTITY, function (player, profile)
-- loads from the profile to the leaderstats
player.leaderstats.Cash.Value = profile.Data.Cash
end)
-- will fire just before ProfileService unloads a profile
ProfileHandler:DisconnectWhenProfileReleasing(UNIQUE_IDENTITY, function (player, profile)
-- saves from the leaderstats to the profile
profile.Data.Cash = player.leaderstats.Cash.Value
end)
@loleris
Is there a way to control new datastore saves?
In my old script
local Datastore = game:GetService("DataStoreService"):GetDataStore("0.00.15.6")
I was able to do something like this where by increasing the “version” ("0.00.15.6") I was able to reset all data. However in this system you did something…different. I’m not too advanced yet so I kinda need help with it
My game’s players report that there seems to have some data returned to the old version randomly. I’ve followed all your steps and checked if there is any problem with data loss.
(Average data loading time is 0.5 and all under 1 sec)
Codes followed
-- Initialize
local ProfileTemplate = {
Coin = 0,
Stage = 0,
CurrentDied = "湲곕낯",
Died = {
"湲곕낯"
}
}
local ProfileService = require(script.ProfileService)
local TableToString = require(script.TableToString)
local Players = game:GetService("Players")
local ProfileStore = ProfileService.GetProfileStore(
"WayToSchoolInOhioDataStore",
ProfileTemplate
)
_G.Profiles = {}
-- resolve method
local function OnCharacterAdded(char)
game:GetService("RunService").Stepped:Wait()
local plr = game.Players:GetPlayerFromCharacter(char)
char:WaitForChild("HumanoidRootPart").CFrame = workspace.Checkpoints[tostring(plr.TeleportedStage.Value)].CFrame + Vector3.new(0,3.25,0)
end
function OnPlayerAdded(plr)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
stats.Parent = plr
local stage = Instance.new("IntValue")
stage.Name = "Stage"
stage.Value = _G.Profiles[plr].Data.Stage
stage.Parent = stats
local coin = Instance.new("IntValue")
coin.Name = "Coin"
coin.Value = _G.Profiles[plr].Data.Coin
coin.Parent = stats
local TeleStage = Instance.new("IntValue")
TeleStage.Name = "TeleportedStage"
TeleStage.Value = stage.Value
TeleStage.Parent = plr
local DiedFolder = Instance.new("Folder", plr)
DiedFolder.Name = "DiedFolder"
for i,v in pairs(_G.Profiles[plr].Data.Died) do
local diedvalue = Instance.new("BoolValue", DiedFolder)
diedvalue.Name = v
end
local DiedEffect = Instance.new("StringValue", plr)
DiedEffect.Name = "DiedEffect"
DiedEffect.Value = _G.Profiles[plr].Data.CurrentDied
stats.Stage:GetPropertyChangedSignal("Value"):Connect(function()
_G.Profiles[plr].Data.Stage = stats.Stage.Value
end)
stats.Coin:GetPropertyChangedSignal("Value"):Connect(function()
_G.Profiles[plr].Data.Coin = stats.Coin.Value
end)
if plr.Character then OnCharacterAdded(plr.Character) end
plr.CharacterAdded:Connect(OnCharacterAdded)
while plr.Parent and wait(5) do
coin.Value += 1
end
end
-- load
local function PlayerAdded(player)
local elapsed = os.clock()
local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId)
if profile ~= nil then
print("[DataSaver] "..player.Name.."'s data successfully loaded, is taken " .. math.floor((os.clock() - elapsed) * 10) / 10 .." seconds, and is \n", TableToString(profile.Data))
local update = true
profile:AddUserId(player.UserId)
profile:Reconcile()
profile:ListenToRelease(function()
print("[DataSaver] ".."Release "..player.Name.."'s data as \n", TableToString(profile.Data))
_G.Profiles[player] = nil
player:Kick("Released your data")
end)
if player:IsDescendantOf(Players) == true then
_G.Profiles[player] = profile
task.spawn(OnPlayerAdded, player)
else
profile:Release()
end
else
player:Kick("[DataSaver] DataStore loading failure, It may because of roblox server down")
end
end
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(PlayerAdded, player)
end
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(function(player)
local profile = _G.Profiles[player]
if profile ~= nil then
profile:Release()
end
end)
Absolutely lovely resource! Have been using it for years now. One question however, is it fine to have 2 separate profiles per player to separate game save data and monetary save data? I do frequent wipes on players’ save data as the game moves to different stages (pre-alpha, alpha, beta, etc.), however, I do not want to wipe monetary data to maintain purchases, for instance tracking dev product purchases. Best example I can give is my game’s bank system: users can purchase up to 20 additional bank pages to expand their bank space beyond what is naturally obtainable, however, to track this, I have to store it in the player’s data. Is my approach viable or should I look at implementing a refactoring system that will allow me to wipe certain parts of the player’s data and maintain the monetary data? I just wanna know if 2 profiles per player is fine or if it’s better to go with the refactoring system that I mentioned above.
even if only 1 profile is recommended 2 profile will just consume x2 request. I have also 2 profiles on my game and i never had problem even on test with 20 people