Player's stats break in multiplayer match

Hello!

I want to track the number of times a player dies in a match.

When there’s only one player in the match, my code works as expected, however once you add other players into the mix my code starts generating strange results. Death counts change unpredictably, and the totals at the end are wildly off-base.

I suspect the issue has something to do with multiple simultaneous calls to the death count update code, or variable crosstalk somewhere, but I’m not familiar enough with having all these asynchronous events and function calls flying everywhere to know where to start debugging.

I’ve tried adding local LocalPlayer = Character.Parent just after Player.CharacterAdded:Connect(function(Character), but that causes crashes all over the place.

Here’s my current death tally update code:

local function PreparePlayer(Player)
	
	-- Zero out the player's death tally for the new match
	UpdatePlayerDeathTally(Player, function(_) return 0 end)
	
	Player.CharacterAdded:Connect(function(Character)
		
		ForceFieldHandler.Create(Player, GameSettings.FORCE_FIELD_DURATION)
		
		task.spawn(function()
			
			local Humanoid = Character:WaitForChild("Humanoid")
			
			Humanoid.Died:Connect(function()
				
				UpdatePlayerDeathTally(Player, function(CurrentDeaths)
					CurrentDeaths = CurrentDeaths or 1
					return CurrentDeaths + 1
				end)
				
				Humanoid.JumpPower = GameSettings.DEFAULT_JUMP_POWER
				Humanoid.WalkSpeed = GameSettings.DEFAULT_WALK_SPEED
				
				PlayerData.UpdateValue(Player, JUMP_BONUS_KEY, function() return 0 end)
				PlayerData.UpdateValue(Player, SPEED_BONUS_KEY, function() return 0 end)
				PlayerData.UpdateValue(Player, GREED_LEVEL_KEY, function() return 0 end)
				
			end)
			
		end)
		
	end)
	
end

And

local function UpdatePlayerDeathTally(Player, UpdateFunction)

	local NewDeathTally = PlayerData.UpdateValue(Player, DEATH_TALLY_KEY, UpdateFunction)
	
	LeaderboardHandler.SetStat(Player, DEATH_TALLY_KEY, NewDeathTally)

        -- Updates death tally in player GUI
	UpdateDeathsRemote:FireClient(Player, NewDeathTally)
	
end
1 Like

Their isn’t any reason to use a task.spawn ? And maybe replace connect with Once

1 Like

Tried it, still having the same issue… and I can’t identify a pattern in how the death tallies are assigned. If other players die, then you die, your count jumps up by more than one, but it’s not a summation of previous deaths, or the previous number of deaths from another player. It’s weird.


local function UpdatePlayerDeathTally(Player, UpdateFunction)

	local NewDeathTally = PlayerData.UpdateValue(Player, DEATH_TALLY_KEY, UpdateFunction)
	
	LeaderboardHandler.SetStat(Player, DEATH_TALLY_KEY, NewDeathTally)
	UpdatePlayerHUD:FireClient(Player, NewDeathTally)
	
end


local function PreparePlayer(Player)
	
	-- Zero out the player's death tally for the new match
	UpdatePlayerDeathTally(Player, function(_) return 0 end)
	
	Player.CharacterAdded:Connect(function(Character)
		
		--ForceFieldHandler.Create(Player, GameSettings.FORCE_FIELD_DURATION)

		local Humanoid = Character:WaitForChild("Humanoid")
		
		Humanoid.Died:Once(function()
			
			UpdatePlayerDeathTally(Player, function(CurrentDeaths)
				CurrentDeaths = CurrentDeaths or 1
				return CurrentDeaths + 1
			end)
			
			Humanoid.JumpPower = GameSettings.DEFAULT_JUMP_POWER
			Humanoid.WalkSpeed = GameSettings.DEFAULT_WALK_SPEED
			
			PlayerData.UpdateValue(Player, JUMP_BONUS_KEY, function() return 0 end)
			PlayerData.UpdateValue(Player, SPEED_BONUS_KEY, function() return 0 end)
			PlayerData.UpdateValue(Player, GREED_LEVEL_KEY, function() return 0 end)
			
		end)
		
	end)
	
end


local function onPlayerAdded(Player)
	
	PreparePlayer(Player)
	
end

Could the currentDeaths parameter in the UpdatePlayerDeathTally be the issue?

I get a bit crosseyed when it comes to passing anonymous functions, my understanding is that:

In PreparePlayer(): when UpdatePlayerDeathTally() is called, in its arguments I’m declaring an anonymous function with arguement CurrentDeaths, could I have a residual value hanging around from previous calls?

That anonymous function gets passed to UpdatePlayerDeathTally(), which in turn passes it to PlayerData.UpdateValue(), which just uses it to derive an updated death tally.


function PlayerData.UpdateValue(Player, Key, UpdateFunction)
	local Data = GetData(Player)
	local OldValue = Data[Key]
	local NewValue = UpdateFunction(OldValue)
	
	Data[Key] = NewValue
	return NewValue
end

So, working the other way, UpdateValue() calls the anonymous function (locally called UpdateFunction()), and passes OldValue (which is the player’s previous death tally) which is what gets plugged into CurrentDeaths where we started.

I think… :sweat_smile:

Ok, I think I have the issue tracked down to my PlayerData module.

SOMEHOW DEFAULT_PLAYER_DATA is being overwritten, THAT variable is being incremented and transferred over to playerData. Erm… wut


local PlayerData = {}

--
-- CONSTANTS
PlayerData.DEATH_TALLY_KEY_NAME = "Deaths"
PlayerData.JUMP_BONUS_KEY_NAME = "Jump"
PlayerData.SPEED_BONUS_KEY_NAME = "Speed"
PlayerData.GREED_LEVEL_KEY_NAME = "Greed"


--
-- VARIABLES


local playerData = {
  --[[
    [userId: string] = {
      ["Deaths"] = DeathTally: number
      ["Jump"] = JumpBonus: number
      ["Speed"] = SpeedBonus: number
      ["Greed"] = GreedTally: number
    }
  ]]
}


local DEFAULT_PLAYER_DATA = {
	[PlayerData.DEATH_TALLY_KEY_NAME] = 0,
	[PlayerData.JUMP_BONUS_KEY_NAME] = 0,
	[PlayerData.SPEED_BONUS_KEY_NAME] = 0,
	[PlayerData.GREED_LEVEL_KEY_NAME] = 0
}


--
-- LOCAL FUNCTIONS


local function GetData(Player)
	local Data = playerData[tostring(Player.UserId)] or DEFAULT_PLAYER_DATA
	playerData[tostring(Player.UserId)] = Data
	return Data
end


--
-- MODULE FUNCTIONS

function PlayerData.GetValue(Player, Key)
	return GetData(Player)[Key]
end


function PlayerData.UpdateValue(Player, Key, UpdateFunction)
	local Data = GetData(Player)
	local OldValue = Data[Key]
	local NewValue = UpdateFunction(OldValue)
	Data[Key] = NewValue
	return NewValue
end


return PlayerData

In the end it was an issue with my PlayerData module, so I scrapped it and made a new one based off RoBoPoJu’s example here:

https://devforum.roblox.com/t/how-to-use-module-scripts-for-player-data/710941/4