You are right about that.
I already managed to handle the seller taking it off sale before the purchase registers, but the other issues persist.
I guess it will have to be made in the way you are describing.
But wouldn’t holding seller’s profile for some time after player leaves help at least a bit? The fact that the seller won’t be able to join back until after a few seconds isn’t such a big deal.
Is there more potential for error in doing that than it would solve?
You can try doing that in hopes to prevent leave duping - in this case you’ll need to customize ProfileService not_released_handler to wait for as long as possible for the last session to be released without triggering a remote release (e.g. Return “Repeat” 5 times and then return “ForceLoad” for the last time)
Okay, great to know. Thanks a lot!
Hey, I read that you can use ProfileService for things that are detached from Players like groups, but I can’t find any examples.
How could you do something like a group chat/wall? Or some other data that multiple players in different servers can edit at the same time?
The only solutions I can think of are:
- To juggle the profile lock between servers so that as long as atleast 1 member is online, the profile will be loaded in and listening to/acting on global updates.
Or move the lock to the server where a player wants to send a message from every time. - To store things like a group chat/wall separately from data that can only be edited by the owner (so you never need to lock it from multiple servers), but then you still can’t use ProfileService for that data.
- To just use
:OverwriteAsync()
and hope for the best.
But none of these seem like good solutions to me.
ProfileService is for one server access implementations only
Are there any plans to make this compatible with LuaU’s type system/!strict mode?
Hey there! Me again.
I’ve been having problems with Profile:Reconcile()
. Basically, whenever I add/remove values from the template, all other values end up being reset. Is this intentional or?
Edit:
I forgot I was testing on a local place and data didn’t save.
You might be trying to reconcile arrays - the reconcile function is designed for dictionaries only. Reconciliation can be a very specialised thing and that’s why you have to personally understand how Profile:Reconcile()
works from the reconciliation code piece in the PS wiki.
There’s no single function that can understand the user’s intention on reconciliation.
Alright, thanks for clearing that up. But I have another question. How does the AddUserIds
function work exactly? Does it allow other accounts to access the data?
If I wanted to manipulate the profiles in some way before they’re unloaded due to the game closing, how could I do that?
Hey! Im using profile service, and im not sure if im doing something wrong. In my game, data saves and loads fine until there are like 12 people in a server, then it spams this message in output logs:
As to how i handle my data, i use a table and alter the values from there. the only way i can see these request queuing, from what i can understand from the module, is if i was manually calling a save function every second or so, but that isnt the case.
Am i altering the tables values to fast?
My main data script: PlayerDataService - Pastebin.com
Example on how I alter data : PlayerData.Stats.Strength += 1 (PlayerData being PlayerDataService.new() )
I should specify that it spams this (for every player or so) every second
This also stops some things in my game from working (for example, someone purchases gems with Robux, takes the Robux because the purchase isnt the issue but doesnt update the data sometimes due to a filled queue I presume. This request gets spammed a ton per second)
Hey there!
I’ve been considering trying to migrate my game’s normal DataStoreService to use ProfileService as this seems like an amazing and much more viable solution considering we’ve had problems with data loss in the past
However, I need to ask if it’s possible to migrate without deleting player data on accident? I was never good with Data Saving, and I have the data of over 100 different players over the last two years saved using the normal DataStoreService. If I wanted to migrate to ProfileService, would I really have to wipe it out??
Why is ProfileService not loading in my game (a sfoth iv remake)???
I have all things DataStore related like API Services enabled in the game settings but it just doesn’t load.
The script is a ModuleScript called ProfileManager in serverscriptservice and this is the code
local ProfileService = require(script.ProfileService)
local Players = game:GetService("Players")
local BadgeService = game:GetService("BadgeService")
local MarketplaceService = game:GetService("MarketplaceService")
local Profiles = {}
local DataManager = {}
local saveStructure = {
Main = {
{
KOs = 0;
Wipeouts = 0;
}
},
Badges = {
Welcome = 2130186399;
Kills5 = 2130186410;
Kills20 = 2130186426;
Kills97 = 2130186461;
LovedOfMuses = 2130186563;
},
Gamepasses = {
},
Products = { -- developer_product_id = function(profile)
},
PurchaseIdLog = 50,
}
----------------------------------- PROFILE STORES
local PlayerProfileStore = ProfileService.GetProfileStore("Player", saveStructure.Main)
----------------------------------- PRIVATE FUNCTIONS
local function createLeaderstats(player)
local profile = Profiles[player]
local folder = Instance.new("Folder")
folder.Name = "leaderstats"
folder.Parent = player
local KOs = Instance.new("IntValue")
KOs.Name = "KOs"
KOs.Value = profile.Data.KOs
KOs.Parent = folder
local Wipeouts = Instance.new("IntValue")
Wipeouts.Name = "Wipeouts"
Wipeouts.Value = profile.Data.Wipeouts
Wipeouts.Parent = folder
spawn(function()
while true do
local profile = Profiles[player]
if profile ~= nil then
KOs.Value = profile.Data.KOs
Wipeouts.Value = profile.Data.Wipeouts
else
break
end
wait(0.1)
end
end)
end
local function handleLockedUpdate(globalUpdates, update)
local id = update[1]
local data = update[2]
if data.UpdateType == "This" then
end
globalUpdates:ClearLockedUpdate(id)
end
local function onPlayerAdded(player)
local profile = PlayerProfileStore:LoadProfileAsync(
"Player".. player.UserId,
"ForceLoad"
)
if profile then
profile:ListenToRelease(function()
Profiles[player] = nil
player:Kick()
end)
if player:IsDescendantOf(Players) then
Profiles[player] = profile
local globalUpdates = profile.GlobalUpdates
for index, update in pairs(globalUpdates:GetActiveUpdates()) do
globalUpdates:LockActiveUpdate(update[1])
end
for index, update in pairs(globalUpdates:GetLockedUpdates()) do
handleLockedUpdate(globalUpdates, update)
end
globalUpdates:ListenToNewActiveUpdate(function(id, data)
globalUpdates:LockActiveUpdate(id)
end)
globalUpdates:ListenToNewLockedUpdate(function(id, data)
handleLockedUpdate(globalUpdates, {id, data})
end)
wait(0.5)
createLeaderstats(player)
else
profile:Release()
end
else
player:Kick("Issue with data loading. Rejoin.")
end
if not BadgeService:UserHasBadge(player.UserId, saveStructure.Badges.Welcome) then
BadgeService:AwardBadge(player.UserId, saveStructure.Badges.Welcome)
local badgeUI = require(game.ServerScriptService.BadgeNotification)
badgeUI.OpenUI(player.UserId, saveStructure.Badges.Welcome, 1)
end
local function CharacterAdded(char)
local humanoid = char:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
local playerProfile = Profiles[player]
if playerProfile ~= nil then
playerProfile.Data.Wipeouts += 1
-- if not BadgeService:UserHasBadge(player.UserId, saveStructure.Badges.Death) then
-- BadgeService:AwardBadge(player.UserId, saveStructure.Badges.Death)
-- local badgeUI = require(game.ServerScriptService.BadgeNotification)
-- badgeUI.OpenUI(player.UserId, saveStructure.Badges.Death, 1)
-- end
end
local killerValue = humanoid:FindFirstChild("creator")
if killerValue ~= nil then
local killer = killerValue.Value
local killerProfile = Profiles[killer]
if killerProfile ~= nil then
killerProfile.Data.KOs += 1
if killerProfile.Data.KOs == 5 then
if not BadgeService:UserHasBadge(killer.UserId, saveStructure.Badges.Kills5) then
BadgeService:AwardBadge(killer.UserId, saveStructure.Badges.Kills5)
local badgeUI = require(game.ServerScriptService.BadgeNotification)
badgeUI.OpenUI(killer.UserId, saveStructure.Badges.Kills5, 1)
end
elseif killerProfile.Data.KOs == 20 then
if not BadgeService:UserHasBadge(killer.UserId, saveStructure.Badges.Kills20) then
BadgeService:AwardBadge(killer.UserId, saveStructure.Badges.Kills20)
local badgeUI = require(game.ServerScriptService.BadgeNotification)
badgeUI.OpenUI(killer.UserId, saveStructure.Badges.Kills20, 1)
end
elseif killerProfile.Data.KOs == 97 then
if not BadgeService:UserHasBadge(killer.UserId, saveStructure.Badges.Kills97) then
BadgeService:AwardBadge(killer.UserId, saveStructure.Badges.Kills97)
local badgeUI = require(game.ServerScriptService.BadgeNotification)
badgeUI.OpenUI(killer.UserId, saveStructure.Badges.Kills97, 2)
end
end
end
end
end)
end
if player.Character ~= nil then
CharacterAdded(player.Character)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)
local function onPlayerRemoving(player)
local profile = Profiles[player]
if profile then
profile:Release()
end
end
Players.PlayerRemoving:Connect(onPlayerRemoving)
workspace.JohnLovedOfMuses.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild("HumanoidRootPart") then
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
--local profile = Profiles[player]
if not BadgeService:UserHasBadge(player.UserId, saveStructure.Badges.LovedOfMuses) then
BadgeService:AwardBadge(player.UserId, saveStructure.Badges.LovedOfMuses)
local badgeUI = require(game.ServerScriptService.BadgeNotification)
badgeUI.OpenUI(player.UserId, saveStructure.Badges.LovedOfMuses, 2)
end
end
end)
----------------------------------- PRODUCT FUNCTIONS
function PurchaseIdCheckAsync(profile, purchase_id, grant_product_callback) --> Enum.ProductPurchaseDecision
-- Yields until the purchase_id is confirmed to be saved to the profile or the profile is released
if profile:IsActive() ~= true then
return Enum.ProductPurchaseDecision.NotProcessedYet
else
local meta_data = profile.MetaData
local local_purchase_ids = meta_data.MetaTags.ProfilePurchaseIds
if local_purchase_ids == nil then
local_purchase_ids = {}
meta_data.MetaTags.ProfilePurchaseIds = local_purchase_ids
end
-- Granting product if not received:
if table.find(local_purchase_ids, purchase_id) == nil then
while #local_purchase_ids >= saveStructure.PurchaseIdLog do
table.remove(local_purchase_ids, 1)
end
table.insert(local_purchase_ids, purchase_id)
task.spawn(grant_product_callback)
end
-- Waiting until the purchase is confirmed to be saved:
local result = nil
local function check_latest_meta_tags()
local saved_purchase_ids = meta_data.MetaTagsLatest.ProfilePurchaseIds
if saved_purchase_ids ~= nil and table.find(saved_purchase_ids, purchase_id) ~= nil then
result = Enum.ProductPurchaseDecision.PurchaseGranted
end
end
check_latest_meta_tags()
local meta_tags_connection = profile.MetaTagsUpdated:Connect(function()
check_latest_meta_tags()
-- When MetaTagsUpdated fires after profile release:
if profile:IsActive() == false and result == nil then
result = Enum.ProductPurchaseDecision.NotProcessedYet
end
end)
while result == nil do
task.wait()
end
meta_tags_connection:Disconnect()
return result
end
end
local function GetPlayerProfileAsync(player) --> [Profile] / nil
-- Yields until a Profile linked to a player is loaded or the player leaves
local profile = Profiles[player]
while profile == nil and player:IsDescendantOf(Players) == true do
task.wait()
profile = Profiles[player]
end
return profile
end
local function GrantProduct(player, product_id)
-- We shouldn't yield during the product granting process!
local profile = Profiles[player]
local product_function = saveStructure.Products[product_id]
if product_function ~= nil then
product_function(profile)
else
warn("ProductId " .. tostring(product_id) .. " has not been defined in Products table")
end
end
local function ProcessReceipt(receipt_info)
local player = Players:GetPlayerByUserId(receipt_info.PlayerId)
if player == nil then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local profile = GetPlayerProfileAsync(player)
if profile ~= nil then
return PurchaseIdCheckAsync(
profile,
receipt_info.PurchaseId,
function()
GrantProduct(player, receipt_info.ProductId)
end
)
else
return Enum.ProductPurchaseDecision.NotProcessedYet
end
end
----- Initialize -----
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(onPlayerAdded, player)
end
MarketplaceService.ProcessReceipt = ProcessReceipt
function DataManager:Get(player)
local profile = Profiles[player]
if profile then
return profile
end
end
function DataManager:GetProfileStore()
return PlayerProfileStore
end
return DataManager
There’s literally no error, and every time I try putting print() commands to see where the script stops (including one in the first line of code) (I removed them tho) they just don’t show up which proves the script is just not loading/running in the game…
I think I found the issue. I think ModuleScripts don’t run automatically anymore or something
MS never ran automatically, that’s why they’re a module.
They used to and actually still do in some of my older games that used them for ProfileService
If you look in essentially every guide/tutorial video on ProfileService (such as the ones linked in the main post itself) you can see people use ProfileService with modules usually named stuff like “ProfileCacher” or “ProfileManager”, etc)) and they’ve always functioned like they would on a serverscript.
So even if they’re not supposed to run automatically, why remove the function for that if it doesn’t make anything worse, but in fact makes it easier to use this kind of stuff as you can just Module:Get() player data and use ProfileService in several scripts rather than managing everything in the same script
Hello, I’ve got a problem and probably the first one to encounter this problem…
I’ve got about 4-5 values inside ProfileTemplate and only the last one seems to not create it? I don’t exactly know how this works but however whenever I try adding a number to it it just says attempted to add number to nil or something like that? I’ve followed everything I found however I didn’t find much.
Could you show us your template please?