Need help making a LocalScript that cant be easily exploited

Before anything, i’d like to say that i’ve been reading about exploiters for a few hours and i still haven’t found a foolproof solution to the problem. The general consensus seems to be that you shouldn’t trust your client at all, making the server do sanity checks whenever you get info from the client. With that in mind, i’ve attempted to create a simple script (with as little involvement with the client as possible) that makes the Player Character play a punch animation, and create a hitbox to detect hits, them damage anyone who might have been struck.

However i’ve run into a few roadblocks in the way, those being the fact that trying to do animations and hitboxes in the server isn’t really optimal, the animation are often late and the hitbox lags behind the character. So i’ve decided that i’d keep my original script, but change only those two parts to the client side.

I’m fully aware that i might having the wrong approach to the problem, so if you have a better suggestion, i’m willing to change.

--I removed most of the script that was irrelevant
local Cooldown
local CombatTool = script.Parent
local Player = CombatTool.Parent.Parent
local Character = Player.Character
local Animator = Character.Humanoid.Animator
-- Let's pretend the PlayerDamage is 10
local Damage = 10

local RayParams = RaycastParams.new()
RayParams.FilterDescendantsInstances = {Character}
RayParams.FilterType = Enum.RaycastFilterType.Blacklist

local Clicked = function()
			
	if not Cooldown then
		
		Cooldown = true

      --Firing Remote Function that plays the animation in the client, it works as intended
		AnimationClient:FireClient(Player,Animator,script.Animation)
      --Another Remote Function that activates the Hitbox in the Client
		RayHitBoxClient:FireClient(Player,Character.RightHand,1,RayParams,Damage)
		
		wait(1)
		Cooldown = false
		
	end
	
end

CombatTool.Activated:Connect(Clicked)

-- Local script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Player = game:GetService("Players").LocalPlayer
-- I'm using Raycast Hitbox V4.01 for this, it works as intended
local RayHitbox = require(ReplicatedStorage.Modules.RaycastHitboxV4)

ReplicatedStorage.CheckHitbox.OnClientEvent:Connect(function(Part,Duration,Param,Damage)
	
	local NewHitbox = RayHitBox.new(Part)
	NewHitbox.RaycastParams = Param
	
	NewHitbox.OnHit:Connect(function(Hit,Humanoid)

		if Human then
			-- I was thinking of Firing a remote event  to the server, something like
			ReplicatedStorage.DealDamage:FireServer(Hit,Humanoid,Damage)
		end

	end)
	
	NewHitbox:HitStart()
	wait(Duration)
	NewHitbox:HitStop()
	
end)

-- Final Server Script

game:GetService("ReplicatedStorage").DealDamage.OnServerEvent:Connect(function(PLAYER,HIT,HUMAN,DAMAGE)
	--Uh oh
end
  • This is the dreaded moment that i’ve been trying to avoid, so i have a few questions:

  • Can the client lie about the PLAYER parameter? Pretending to be another player? If they can, is there a way to check if they’re lying?

  • I’m already aware that the parameter HIT and HUMANOID are unreliable, but, if i have a correct PLAYER (As mentioned above, i dont know if i do) then i can get the Player.Character ( and by extension, the Humanoid ) and check the distance between HIT and the player character to check if it’s legit.

  • However i dont have a way to know that the variable DAMAGE is legit. Yes i could calculate the damage in the third script, instead of the first, but the problem is that the damage formula would probably look something like (BaseDamage * PlayerStat * Multipliers), which means i need to have the correct BaseDamage, which i can only get on the first script, since it’s the punching script.

Sorry for so many probably dumb questions but there is some stuff here i cant wrap my head around and i’d appreciate the help.

1 Like
  • No you can’t use any exploit to change the player argument sent by FireServer

  • Do a hitbox calculation on both sides but account for a 200-400 ping threshold on the server side

  • Make a fourth module script only accessible by the server-sided scripts and store base damage and other player data in that

2 Likes

1: I dont understand how to do the hitbox on both sides, the server side would be completely non functional, what use is there to that? Could you link me to something explaining the ping threshold thing? I’m not familiar with it

2:About the fourth module script you suggested. Even if it holds the value of the base damage of a punch, how do i signal to the module script that i should deal that damage specifically?

(ServerTool > ClientRemote > ServerRemote > ServerModule)

I want ClientRemote to be used whenever i need hit detection on the client and ServerRemote to be used from the client whenever i need to damage a Character on the server. So each time i use them i need a reliable way to tell ServerRemote what’s the BaseDamage of the specific attack i’m using. So if i store the BaseDamage of the attacks that i plan on using on the ServerModule, how do i ask it for the BaseDamage for the attack that i want? All i can think of is passing down a parameter from the PunchTool script all the way to the ServerModule, but that isn’t safe since it goes through the client.

2 Likes

Don’t trust the client.

As a consenquence, you really shouldn’t be giving the client control over the amount of damage they do. If you need a good reference as to which attack does what damage, just store that information in something like a ModuleScript or a Script with a BindableEvent that the server can call in order to retrieve that information.

Like you said, server-side sanity checks are the way to run here.

2 Likes

I’m sorry if this is asking too much but i dont understand how i should use bindable functions here, could you give me a pratical example of what you mean? I’ve finished the code up to the point where i need help so you can see.

--I left off here, on the ClientRemoteEvent
NewHitbox.OnHit:Connect(function(Hit,Human)
		
	if Human then
		--Checking if its a valid target on the client just so i dont fire an unnecessary Server Remote event
		if Human.Health > 0 and Human.Parent ~= Character then
				
			RemoteEvent:FireServer(Hit,Human)
				
		end
			
	end
		
end)

--ServerRemoteEvent that was fired
local SSS = game:GetService("ServerScriptService")
--Module with the values of each attacks BaseDamages
local BaseDamages = require(SSS.Modules.BaseDamages)
local CheckChar = require(SSS.Modules.CheckCharacter)

game:GetService("ReplicatedStorage").RemoteEvents.RayHitboxClient.OnServerEvent:Connect(function(Player,Hit,Human)
	
	if Hit and Human then
		--Checking if its a valid target again, this time on server
		local Character = CheckChar.FromHitHuman(Player,Hit,Human)
		
		if Character then
			
			--Here is where i should deal damage to the Humanoid. The BaseDamage i want is in the variable
			BaseDamages.Punch
			--However this is just one of the many values i want to store on the BaseDamages module
			--I need a way for this RemoteEvent to know which one i'm looking for, in this case Punch
			--If you could explain to me how bindable events could be used for this I'd highly appreciate it
			
		end
		
	end
	
end)

Here’s what I meant for the BindableFunction example:

-- Script with information about attacks
local bind = <directory of BindableFunction>
local attackInformation = {
    ["punch"] = {damage = 10, cooldown = 2, ...},
    ["heavyPunch"] = {damage = 45, cooldown = 8, ...},
}
bind.OnInvoke = function() return attackInformation end
-- Script requesting information
local bind = <directory of BindableFunction>
local information = bind:Invoke()

print(information)
>>  
{
    ["heavyPunch"] = {
        ["cooldown"] = 8,
        ["damage"] = 45
    },
    ["punch"] = {
        ["cooldown"] = 2,
        ["damage"] = 10
    }
}

For the ModuleScript, just create a method that returns the table of information.

1 Like

It seems you didnt understand my problem though. I already have these values in a module script and they can be accessed by my Remote Event, thats not what i’m having trouble with, so i’ll try to make it clearer.

The order of the scripts goes like this:

Tool script(Server) > ClientRemoteEvent script > ServerRemoteEvent script

The Tool script already has all the information it needs, and it passes all the information the Client needs to the ClientRemoteEvent.

My problem is that the ServerRemoteEvent has only one trustworthy parameter(the player) and i have to figure out the other information on my own. With the player instance i can determine the Player Character and, knowing the Player Character, i can also find out if the Hit instance is a plausible target by comparing the distance between them.

--So my ServerRemoteEvent has almost all the information it needs
Player,PlayerCharacter,HitCharacter,HitHumanoid,etc.
--And i have a table of multiple Attacks, containing their BaseDamage, cooldowns, etc.
module.Attacks = 
	{
		["Punch"] = {damage = 5, cooldown = .5},
		["kick"] = {damage = 10, cooldown = 1}
	}
--All of this works as intended

Still tho, when it comes to dealing damage to the HitHumanoid, i don’t have a way for the ServerRemoteEvent to know the correct type of attack used on HitCharacter, Not because i dont have access to the table, but because i dont have a way to tell the ServerRemoteEvent that’s the type of attack that was used since only Tool script knows that it was a punch. Basically if i could pass down a string called “Punch” from the Tool script to the ServerRemote, that would solve my problem, but i can’t since it goes through the client and the client shouldn’t be trusted.

1 Like

Yes, this is a problem you’ll have to solve with server-side checks.

Again, the client could fake firing any weapon they want to if it’s a parameter in a RemoteEvent. A possible solution is making sure the client has access to the particular attack they are trying to use, making sure it’s not on a cooldown for that player, and making sure the client is legitimately attacking in the first place. Another solution involves ditching the one-tool-fits-all approach and using separate tools for each attack. Unfortunately, there’s no simple way to guarantee security in this regard.

Just follow this rule of thumb: ask the server instead of telling the server: Think “Can I attack X using Y?” instead of “I am going to attack X with Y.”

1 Like

What I meant by a hitbox check on both the server-side and client-side is that when any player is hit, you should use FireClient to send the player who has been hit and the damage type. Then, on the server, you should perform the hitbox check again to validate that request. However, on the server, you can be less strict to avoid false positives for players with high ping (200-400). Don’t forget to validate the damage type as well.

Regarding the module, you can create a table that contains the base damage values for every damage type, and you can access this information using the require function.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.