Anyone getting reports of random data being given to players?

I recently released my new game Selfie Simulator which has a significant presence of data. One of the main things that’s been going on is people have been reporting to me that when they join the game, they’re given random pets. I’ve gone through the flow chart and triple checked what happens when the player joins the game.

Player Joins > Data is retrieved or default data is created > Data is returned to playerJoin script >
leaderstats are created and gamepass items are given > function checks if player has at least one pet equipped > if so the retrieved data is sent to a pet module > pet module loops through equipped pets, spawns them into the workspace and tells the client to start moving them.

Something is going wrong and I’m not sure what. If you need any examples of my code just let me know. Thanks!

3 Likes

I doubt this is a Roblox bug so you’ll need to show us your code.

1 Like

What I suspect the problem behind this could be is possibly when multiple players are active in one server. Ever tried this?

This
A little tricky to play with, it opens multiple Studio windows with one for server and each and one of the rest for each client in the server.

Also assume some IDs of in-game objects are incorrectly indexed. Maybe check that.

I’ve used that many times. I store everyone’s data in there own global table. It’s removed when they leave as well. I’ll provide some code.

1 Like

This is the datastore module. This is what retrieves and saves the data. As you can see is uses _G[player] to store the players data. Pet module is after this code.

function dataStoreService:getLocalData(player)
	local data = _G[player]
	
	if data then
		return data
	else
		local data = dataStoreService:loadData()
	end
end

function dataStoreService:saveData(player, data, isPlayerExiting)
	local key = "player-"..player.UserId
	
	--//Clone data
	local dataToSave = {}

	for statName, statValue in pairs(data) do
		dataToSave[statName] = statValue
	end
	
	--//Remove temporary stats
	for statName, statValue in pairs(_G.unsavedStats) do
		if dataToSave[statName] then
			dataToSave[statName] = nil
		end
	end
	
	--//Save data
	local success, err = pcall(function()
		mainDataStore:SetAsync(key, dataToSave)
	end)
	
	if success then
		if isPlayerExiting then
			_G[player] = nil
		end
		print(player.Name.."'s data was saved.")
	else
		warn(err)
		warn(player.Name.."'s data could NOT be saved")
	end
	return
end

function dataStoreService:createData(player)
	--//Create new data entry
	local data = {}
		
	--//Add data values into data so we create a seperate table
	for newStatName, newStatValue in pairs(_G.defaultData) do
		data[newStatName] = newStatValue
	end
	
	--//Insert temporary stats
	for statName, statValue in pairs(_G.unsavedStats) do
		data[statName] = statValue
	end
	
	_G[player] = data
	return data	
end

function dataStoreService:loadData(player)
	local key = "player-"..player.UserId
	local data
	
	local success, err = pcall(function()
		data = mainDataStore:GetAsync(key)
	end)
	
	if success then
		--//Datastores working fine
		
		if data then
			--//Data exists
			print(player.Name.."'s data was found.")
			
			--//Add temporary stats
			for statName, statValue in pairs(_G.unsavedStats) do
				data[statName] = statValue
			end
			
			--//Add new data nodes
			for statName, statValue in pairs(_G.defaultData) do
				if data[statName] == nil then
					data[statName] = statValue
				end
			end
			
			--//set global table
			_G[player] = data
			return data
		else
			--//Data does not exist
			
			--//Create new data
			data = dataStoreService:createData(player)
			
			--//Wait to save data
			coroutine.wrap(function()
				dataStoreService:saveData(player, data)
			end)()
			
			return data
		end
		
	else
		--//Datastores are broken
		warn("Could not retrieve any data for "..player.Name)
		player:Kick("Datastores are down. You were kicked to prevent data overwriting!")
		
	end
end

When the player joins the game, the data is retrieved like this

local localData = dataModule:loadData(player)

repeat wait() until localData ~= nil

Once the data is loaded, pets are equipped like this

if #localData.currentPets > 0 then

petModule:equipPets(player, localData)

end

The equip function looks like this

function petModule:equipPets(player, localData)
	local petsToReturn = {}
	
	for _, petEquipped in pairs(localData.currentPets) do
		local petName = petEquipped.name
		local uuid = petEquipped.uuid
		
		if petModule:checkOwnership(localData, petEquipped) then
			local newPet = script.Parent.Parent.Objects.Pets:FindFirstChild(petName):Clone()
			newPet.Parent = workspace.Pets
			newPet.Name = player.Name..uuid
			
			table.insert(petsToReturn, #petsToReturn+1, {name = petName, uuid = uuid, object = newPet})
		else
			updatePlayerUi:FireClient(player, {"Notification"}, {{"Pet not owned!","You must own the pet to equip it!"}}) 
		end
	end
	return updatePlayerUi:FireClient(player, {"PetsOwned", "MovePet"}, {{localData.petsOwned, localData.currentPets}, {petsToReturn}})		
end

The client then handles the movement of the pets like so

local petsToMove = {}

runService.RenderStepped:Connect(function()	
	for i, petTable in pairs(petsToMove) do		
		local pet = petTable.object

		local CharPos = CFrame.new(player.Character.PrimaryPart.CFrame.p)
		local Rot = CFrame.Angles(0,math.rad(45*i),0)
		local MakeRotation = CharPos * Rot
		local SetPosition = MakeRotation * CFrame.new(-5,0,0).p
		
		if player.Character.Humanoid.MoveDirection == Vector3.new(0,0,0) then
			pet:SetPrimaryPartCFrame(CFrame.new(SetPosition, player.Character.Head.CFrame.p))
		else
			pet:SetPrimaryPartCFrame(CFrame.new(SetPosition, player.Character.PrimaryPart.CFrame.p + player.Character.PrimaryPart.CFrame.LookVector * 20))
		end
	end										
end)

local function movePet(pets)
	for a,b in pairs(pets) do
		table.insert(petsToMove, #petsToMove+1, b)
	end
end

I hope this provides all the info needed. I’ve never made a pet system before, so this could probably be optimized more. I’ve also been at this bug for a few days, so I decided to ask around. Thanks!

Code is listed in another response below :slight_smile:

I don’t see where in your local script the function movePet is called. Is it possible everything else is correct but it’s moving the wrong pets? And wouldn’t network ownership need to be set for other players to see each others pets? Do other players see the affected player has having the wrong pets?

Your players are sharing the data.currentPets table, as all your data.currentPets tables are all pointing to the same spot in memory the table is stored.

An example:

local a = {1,"2",true,{4}} 
local b = {} 
for k,v in pairs(a) do 
   b[k] = v 
end 
b[1] = 5 
b[2] = "6"
b[3] = false 
b[4][1] = 7 
print(a[1]) -- outputs 1 as numbers don't share spot in memory
print(a[2]) -- outputs "2" as strings don't share either
print(a[3]) -- outputs true as booleans don't share either
print(a[4][1]) -- outputs 7, because a[4] and b[4] are the same table

To fix this issue, when you are creating new data from the template data use this function instead of the for loop in your dataStoreService:createData() function.

function deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

local data = deepcopy(_G.defaultData)

This will guarantee all data is separate between data and _G.defaultData, so that modifications made to one table will not effect the other.

3 Likes

Each player has his or her own _G table. All of them are separate.

I will for sure be adding this as soon as I can.

You can replicate this bug with your game by following these steps, so you can understand what is happening.

  1. Start a Test Server with 1 player.
  2. Have that player buy/equip a pet or however that works.
  3. Start another player client, so that there is now two players in the server (you can do this by setting Local Server to None, and Players to “1 Player”)
  4. The new player will have the same pet bought/equipped that the first player has.

This is because when a brand new player joins (i.e, doesn’t use datastores to retrieve data), it copies the _G.defaultData. However, your method of copying the _G.defaultData is incorrect, and so the currentPets table is being shared with the _G.defaultData.currentPets table. So when a new player buys/equips a pet, and makes changes to his _G[player].currentPets table, this change is also applied to the _G.defaultData.currentPets table. Which is what the steps shows, and the reason the new player has the pet equipped/bought, because it is copying the changes that the first player made to the _G.defaultData.currentPets table.

2 Likes

That makes so much sense. Thank you!