How to check if Data from PlayerService has changed

Hello,

I am wondering how I can check if someone’s data has changed with PlayerService. I am pretty new to using it and I am more fond of DataStore2. Is it even possible?

Thanks!

1 Like

Data, as in the players leaderstats, or a property of the player?

1 Like

Their data is stored within a table using PlayerService. I know with DataStore2 you can check if the Data has changed but with PlayerService I am unaware as I can’t find anything about it.

Can you show me the script? I’m not completely understanding what you mean.

1 Like

I don’t see what you don’t understand, it makes perfect sense.

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServiceScriptService = game:GetService("ServerScriptService")

local Knit = require(ReplicatedStorage.Packages.Knit)
local Signal = require(Knit.Util.Signal)
local ProfileService = require(ServiceScriptService.ProfileService)

local ServiceName = script.Name
local Service = Knit.CreateService {
	Name = ServiceName,
	Client = {
		DataChanged = Knit.CreateSignal()
	},
	ProfileTemplate = {
		SaleTimer = 0,
		Cash = 2000,
		LogInTimes = 0,
		PurchasedItems = {

		}
	},
	Profiles = {}, -- [player] = profile
	joinTime = {},
}
Service.DataChanged = Signal.new()

local ProfileStore = ProfileService.GetProfileStore(
	"PlayerDataV3",
	Service.ProfileTemplate
)

local function handleJoinData(profile)
	if os.time() - profile.Data.SaleTimer >= 86400 then
		profile.Data.SaleTimer = os.time()
	end
end

function Service:PlayerAdded(player : Player)
	if not player then return end
	
	self.joinTime[player] = tick()

	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()
			self.Profiles[player] = nil
			-- The profile could've been loaded on another Roblox server:
			player:Kick()
		end)
		if player:IsDescendantOf(Players) then
			self.Profiles[player] = profile
			
			-- A profile has been successfully loaded:
			handleJoinData(profile)
			
			local Profile = self.Profiles[player]
			
		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()
	end
end

-- In case Players have joined the server earlier than this script ran:
for _, player in pairs(Players:GetPlayers()) do
	coroutine.wrap(function()
		Service:PlayerAdded(player)
	end)()
end
Players.PlayerAdded:Connect(function(...)
	Service:PlayerAdded(...)
end)

function Service:PlayerRemoving(player : Player)
	local profile = self.Profiles[player]
	if profile ~= nil then
		for itemName, itemData in profile.Data.PurchasedItems do
			itemData.TimeLeft -= tick() - math.max(self.joinTime[player], itemData.TimeBought)
		end

		self.joinTime[player] = nil

		profile:Release()
	end
end
Players.PlayerRemoving:Connect(function(...)
	Service:PlayerRemoving(...)
end)

function Service:GetPlayerData(player : Player)
	if not player or not player:IsDescendantOf(Players) then return end
	
	repeat
		task.wait()
	until self.Profiles[player]
	
	return self.Profiles[player].Data
end
function Service.Client:GetData(...)
	return self.Server:GetPlayerData(...)
end

function Service:KnitStart()
	self.DataChanged:Connect(function(...)
		print(...)
		self.Client.DataChanged:Fire(...)
	end)
end

return Service

Ah, I’ve never used knit tbh,


-- Check if the player is a descendant of Players
if player:IsDescendantOf(game.Players) then
    -- Get the player's data using the Service:GetPlayerData function
    local playerData = Service:GetPlayerData(player)

    if playerData then
        -- Now you can access the player's data
        print("Player Cash:", playerData.Cash)
        print("LogIn Times:", playerData.LogInTimes)
        -- You can access other data fields in a similar way
    end
end```

assuming it's a local script I guess you could do this to get the players data.
and do something like this to check if the players data has changed

```function Service:CheckDataChange(player)
    local previousData = self.Profiles[player].Data
    local currentData = self:GetPlayerData(player)

    if previousData and currentData then
        if previousData.Cash ~= currentData.Cash or previousData.LogInTimes ~= currentData.LogInTimes then
            -- Data has changed, trigger an event or take any other action here
            self.DataChanged:Fire(player, currentData)
        end
    end
end

function Service:CheckDataChangesPeriodically()
    while true do
        for player, _ in pairs(self.Profiles) do
            self:CheckDataChange(player)
        end

        wait(60) -- Adjust the interval as needed (e.g., check every 60 seconds)
    end
end

function Service:KnitStart()
    -- Start the data change checking loop
    coroutine.wrap(function()
        self:CheckDataChangesPeriodically()
    end)()

    self.DataChanged:Connect(function(player, newData)
        -- Handle data change event here
        print("Player data has changed for", player.Name)
        print("New data:", newData)
    end)
end```

again I've never used knit before, so if it works tell me, if not then I suck lol, if it does work feel free to set this as the solution!
2 Likes

Just looking at it this really isn’t what I’m talking about, I’m trying to see how I can check if it changes automatically, so if the money goes down, it’ll fire the signal. This should be all directly from ProfileService and Knit shouldn’t be used for this at all, I’m just using Knit for the Signals, etc. That would technically work, but it’s not automatically.

Hmm, Perhaps you could modify your profile template to include a Changed signal in each data field you want to track for changes, and your Service:PlayerAdded function to set up data change listeners for the fields you want to track, So when you want to detect changes in a player’s data, you can directly use the Changed signals you set up in the player’s profile data.

ProfileTemplate = {
    SaleTimer = 0,
    Cash = 2000,
    LogInTimes = 0,
    PurchasedItems = {},
    -- Add Changed signals for fields you want to track
    CashChanged = Signal.new(),
    LogInTimesChanged = Signal.new(),
},

function Service:PlayerAdded(player)
    -- ... (previous code)

    local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId)
    if profile then
        -- ... (previous code)

        -- Set up data change listeners for fields you want to track
        profile.Data.CashChanged:Connect(function(newCashValue)
            -- Handle cash value change here
            self.Client.DataChanged:Fire(player, "Cash", newCashValue)
        end)

        profile.Data.LogInTimesChanged:Connect(function(newLogInTimesValue)
            -- Handle login times value change here
            self.Client.DataChanged:Fire(player, "LogInTimes", newLogInTimesValue)
        end)

        -- ... (rest of the code)
    end
end

local player = game.Players.LocalPlayer -- Replace with the specific player you want to check

-- Check if the player is a descendant of Players
if player:IsDescendantOf(game.Players) then
    local profile = Service.Profiles[player]
    
    if profile then
        profile.Data.CashChanged:Connect(function(newCashValue)
            print("Cash has changed to:", newCashValue)
            -- Handle the change here
        end)
    end
end

Okay well how am I gonna fire those events if I don’t know when they’re being changed?

local function updatePlayerCash(player, newCashValue)
    local profile = Service.Profiles[player]
    if profile then
        profile.Data.Cash = newCashValue
        profile.Data.CashChanged:Fire(newCashValue) -- Fire the CashChanged event
    end
end

local function updatePlayerLogInTimes(player, newLogInTimesValue)
    local profile = Service.Profiles[player]
    if profile then
        profile.Data.LogInTimes = newLogInTimesValue
        profile.Data.LogInTimesChanged:Fire(newLogInTimesValue) -- Fire the LogInTimesChanged event
    end
end

Not by default. You can use ReplicaService to do this, or make your own setter functions that would also fire a .changed event.

1 Like

Why don’t you just create a NumberValue in the player called “Cash” or whatever your currency is, and update it when you update the Datastore. Then whatever function you need to run when it changes, just have it connected to the value instead.

You can create a BindableEvent as a connection for a data change event. If you have a wrapper for ProfileService, incorporate the bindable event right into the data changing functions and add the BindableEvent:Fire(...) function. Once fired, you’ll receive the DataChanged event through registering BindableEvent.Event:Connect(). When you fire the data changed event, you can pass in the key, the old value, the new value, and whatever else you need. Hope this helps!