Checkpoint buttons adding too much to stage number

So I am practicing my scripting skills by trying to make an obby. I am trying to make it so when you step on a part it adds +1 to your leaderstats, I found out how that works but my problem is that it gives alot because I am using a IV Pairs loop. Can anyone tell me if there is some sort of way that I will be able to only add 1 while still using a IV pairs loop?

Here is an example of my problem:
https://medal.tv/games/roblox-studio/clips/6BJkeHo1NTu6T/d13370pnyLj2?invite=cr-MSxERzYsMjUzMjEwMzQs

-- This is an example Lua code block

local DataStoreService = game:GetService("DataStoreService")
local MyDataStore = DataStoreService:GetDataStore("MyDataStore") --If we change the name it makes a whole new datastore

--:Code below: Gets a players data when they join
game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")--:All this script makes a folder for all the data to be in
	leaderstats.Name = "leaderstats"          --:Sets the name of the folder
	leaderstats.Parent = player               --:Sets the parent of the folder the player

	local Stage = Instance.new("IntValue")--:Makes a value for cash in the leaderboard
	Stage.Name = "Stage"                   --:Names the cash
	Stage.Parent = leaderstats            --:Gives the cash a parent which is the datastore folder

	--local Cash = Instance.new("IntValue")
	--Cash.Name = "Cash"                   
	--Cash.Parent = leaderstats

	local playerUserId = "Player_"..player.UserId --Saves the players Id so it keeps his data after he leave

	local data
	local success, errormessage = pcall(function() --Finds out if a player has played the game
		data = MyDataStore:GetAsync(playerUserId) --If they have it gets their data
	end)

	if success then
		Stage.Value = data.Stage
		--Wins.Value = data.Wins
		--Sets our data equal to the current cash
	end
	
	local CheckPoints = game.Workspace:FindFirstChild("CheckPoints")
	
	for i, CheckPoints in pairs(CheckPoints:GetChildren()) do
		CheckPoints.Touched:Connect(function(hit)	
			
			player.leaderstats.Stage.Value = player.leaderstats.Stage.Value + 1
		end)
	end
end)

--Code below: Saves the players data when they leave
game.Players.PlayerRemoving:Connect(function(player) --You need player so we can find out who is leaving
	local playerUserId = "Player_"..player.UserId

	local data = {
		Stage = player.leaderstats.Stage.Value;
		--Wins = player.leaderstats.Cash.Value;
	}

	local success, errormessage = pcall(function() --Makes a pcall to find out if it was success or error
		MyDataStore:SetAsync(playerUserId, data) --Saves the data to the PlayerId when they leave
	end)
	if success then
		print("Data Successfully saved")
	else
		print("There was an error when saving data")
		warn(errormessage)
	end
end)

1 Like

This is not actually because you’re using a for i,v in pairs() loop. Using this type of loop is essentially just listening for a Touched event on every child of the CheckPoints folder. Your problem is that the checkpoints are being touched more than once, meaning the Touched event is being fired multiple times as a result.

You will need to add a check whenever a player touches a checkpoint that they haven’t already touched the stage. You will need to give each checkpoint a unique name, and I recommend numbering them to make it easier.

For example, your CheckPoints folder may look like this:
image

You could also name each one “Stage1”, “Stage2”, etc, but you would have to split the name string later in the code.

Your code will look something like this:

---> services
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local MyDataStore = DataStoreService:GetDataStore("MyDataStore")

-- get a player's data when they join
Players.PlayerAdded:Connect(function(player)
    local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

    local Stage = Instance.new("IntValue")
	Stage.Name = "Stage"
	Stage.Parent = leaderstats

    local playerUserId = "Player_"..player.UserId
    local data
    local success,errorMessage = pcall(function()
        data = MyDataStore:GetAsync(playerUserId)
    end)

    if success then
        Stage.Value = data.Stage
    end
end)

-- checkpoints
local CheckPoints = game.Workspace:WaitForChild("CheckPoints")
for i,checkpoint in pairs(CheckPoints:GetChildren()) do
    checkpoint.Touched:Connect(function(hit)
        if hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid") then
            local player = Players:GetPlayerFromCharacter(hit.Parent)
            if player then
                if player.leaderstats.Stage.Value == (tonumber(checkpoint.Name) - 1) then
                   player.leaderstats.Stage.Value += 1
                end
            end
        end
    end)
end

-- save the player's data when they leave
game.Players.PlayerRemoving:Connect(function(player) --You need player so we can find out who is leaving
	local playerUserId = "Player_"..player.UserId

	local data = {
		Stage = player.leaderstats.Stage.Value;
		--Wins = player.leaderstats.Cash.Value;
	}

	local success, errormessage = pcall(function() --Makes a pcall to find out if it was success or error
		MyDataStore:SetAsync(playerUserId, data) --Saves the data to the PlayerId when they leave
	end)
	if success then
		print("Data Successfully saved")
	else
		print("There was an error when saving data")
		warn(errormessage)
	end
end)

I also do not recommend doing the for loop every single time a player joins, so the function is removed from the function and a new check to see if a player is touching it is above.

Also, understand why we check that the current value of the stage is 1 less than the checkpoint they are touching - it should prevent people being able to exploit right to the end and get stages, as their current stage is not 1 less than the stage they are touching.

You can create a block list table in which you insert a player when it touches the part. Then, use TouchEnded event to remove the player from block list.

Thank you, but now when I step on the part it gives me an error:

ServerScriptService.DataStoreHandler:39: attempt to perform arithmetic (sub) on nil and number

I’ve only been scripting for around 1 week now so don’t really know how to fix this.

Did you use the exact script above? It’s working properly for me in game. Also, which line is line 39?

I am using the excact script only just that I added game.Players at line 37 in so it wouldn’t give me an error. This is line 37: local player = game.Players:GetPlayerFromCharacter(hit.Parent)

Also this is line 39: if player.leaderstats.Stage.Value == (tonumber(checkpoint.Name) - 1) then

Nevermind my comment before, I think it’s actually because of the names of the CheckPoints. Have you named them by number?

Thank you, I had mine named Stage1, Stage2… it works now, thank you.

1 Like