Need help creating a non-exploitable progress system

Hey there!

I need help with my progress system.

So, I already have one and am pretty happy with how it functions for the most part, however the issue is that it replicates itself to the client side and uses RemoteEvents to determine how much progress is being made. The downside to this is that the player can just duplicate the progress part, or they can fire the RemoteEvent and give themselves infinite progress.

Excuse my horrible naming haha. These scripts are replicated into each progress block.

local part = script.Parent -- Part the will activate when touched

part.Touched:Connect(function(hit)
	local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
	if humanoid then
		local char = hit.Parent
		local player = game.Players[char.Name] -- This gets the player in game.Players by getting the name from its character
		local plrid = player.UserId
				
		local rs = game:GetService("ReplicatedStorage")
		
		local ap = rs:WaitForChild("AddProgress"..plrid) -- the exploitable event
		
		if script.Parent.Parent.Name == "Progress"..plrid then
			ap:Fire(0.00512820512) -- amount of progress to give the player
			
			script:Destroy() -- destroys the script for that individual user
		end
	end
end)
1 Like

You should handle everything on Server and do not trust Client with things such as this! You can use Module Scripts to get basically the same effect, but safe.

1 Like

You should handle this on the server, we can achieve this by have a script that manages all the parts you want to give progress. You should start by instancing a Folder into the workspace and then adding the parts into the folder.

You then want to place an IntValue into the part so you can easily set the progress each part will give. In the server script we will have a loop that add the part to an empty table, then we can set this part equal to an empty table, which will give you a dictionary. The instance is your key and your table is the value for the corresponding key.

We then can add the names of the player who has touched the part, and if you find the name inside of the table if they touch the part again it will not execute.


If you were to continue to use the client, which I highly recommend you don’t you should keep a table of value which have a key of the instance, the value of the key will be the amount of progress. You would only send the part inside of the parameters for your event.

local ProgressTable = {
	[workspace.Part] = 10,
	[workspace.Part]  = 25,
	[workspace.Part]  = 50,
}

local ProgressParts = -- Path to folder
local PartTable = {}

-- Setup table
for index , part in ipairs(ProgressParts:GetChildren()) do
	if part:IsA("BasePart") then
		table.insert(PartTable,part) -- Adds instance to table
		PartTable[part] = {} -- Sets key to blank table
	end
end

for index , part in ipairs(PartTable) do
	part.Touched:Connect(function(Hit)
		local Player = game:GetService("Players"):GetPlayerFromCharacter(Hit.Parent)
		local ProgressValue = part:FindFirstChild("NAME_OF_VALUE") -- IntValue for progress
		
		if Player then
			if not table.find(PartTable[part],Player.Name) then
				table.insert(PartTable[part],Player.Name) -- Adds name into table
				print(table.unpack(PartTable[part])) -- For debugging, this just prints all contents of the table
				-- Add progress here
			end
		end
	end)
end

To be fair you’re not wrong about using a module, but I would suggest to just have a table with the set values or my method with IntValues which are checked on the server, this way you can have one script handling the values, the touched event, and data.

There will be no need to require a module nor risk their table being tampered with if he had the module inside replicated storage.

1 Like

Was thinking of using a table actually, wasn’t sure how to go about it though. Could I just put this in each part and then give each part its own table instead, and then check if the player has touched it? I feel like it would take up a lot of bandwidth though. What do you think?

I feel like it would be easier to manage for myself. Also, I could just do like 1 / #ProgressFolder:GetChildren() to determine how much progress to give, as that’s basically the formula I was using before anyway. I’d use your method but maybe modified a bit as well.