I’m currently testing out different approaches to combat, many people suggest client sided hitboxes with server validation for the best gameplay experience. However, im having trouble securing this system.
Here’s my current system:
Client input → Client fires to server to update a table that contains all info for the player’s current attack. this table has all valid info.
Most importantly, table contains the amount of times that attack is allowed to hit.
client starts animation and listens to animation markers → hitbox marker is reached.
Client spawns hitbox and gets PartsInPart → client invokes server and sends a list of victims.
Server validates hits
Client fires server when attack ends in order to clear the server table.
boom perfect
Now here’s the problem,
I understand that it’s virtually impossible to make a super secure game while keeping it very smooth for players but I dont think vulnerabilities like what im encountering should exist.
If an exploiter fires the Attack Start remote, they can then spawn a hitbox whenever, then they can fire the Attack End remote and repeat this sequence very quickly to basically have kill aura.
I’ve tried doing timing checks by storing the expected hitbox spawn timings in a config file but it’s difficult to accurately time things when a player has a little bit of lag. (or maybe i did it wrong). I’ve tried starting the animations on the server then invoking the client to spawn a hitbox and such but again, theres too much delay to go from server to client back to server.
Any help is greatly appreciated!
Here’s a bit of code for my serversided attack handler
CombatRemote.OnServerEvent:Connect(function(player, request, AttackType, ClientStartTime, values)
if request == "Attack Start" then
local DataStore = main.GetPlayerData(player)
local Style = DataStore.EquipWeapon
local Weapon = DataStore.EquipWeapon
if not main.CanAttack(player.Character, AttackType, nil) then warn("CanAttack = false") main.stopAction(player.Character.Humanoid) return end
if not values.Reference then warn("no reference values passed in") return end
main.Cooldown(AttackType, player.Character, math.clamp(0, 0.5, 0.5 - player:GetNetworkPing()))
local AttackingValue = main.effectscreate("NumberValue","Attacking",0, player.Character.Data.Effects)
local Servertime = workspace:GetServerTimeNow()
local AttackTable:AttackTable = {
["AttackType"] = AttackType,
["Exemptions"] = nil,
["Attacking"] = true,
["ClientStart"] = ClientStartTime,
["ServerStart"] = Servertime,
["Value"] = AttackingValue,
["Possible Events"] = Config[AttackType][Style][values.Reference]["Animation Events"],
["Used Events"] = {},
["AttackProperties"] = Config[AttackType][Style][values.Reference]["AttackProperties"],
["HitboxProperties"] = Config[AttackType][Style][values.Reference]["HitboxProperties"],
["Misc"] = values
}
if AttackType == "M1" and not AttackTable.HitboxProperties.Instance then
AttackTable.HitboxProperties.Instance = Config.Weapons["Dawncleaver"].Hitbox
end
CombatStates.AddAttack(player, AttackTable, AttackType)
print("Attack started:", CombatStates.StateTable[player])
elseif request == "Attack End" then
if not CombatStates.FindCurrentAttack(player) then return end
CombatStates.RemoveAttack(player, AttackType)
print("attack end")
end
end)