Advice on how to validate attacks on the server?

I’m working on a fighting game and I’m a bit lost on how I want to structure the networking for it. Currently, the client does everything so that it can feel responsive to the player, but when having to communicate with the server for validation is where I start to get lost. I’ve been looking into client prediction and server reconciliation and I kind of understand it and kind of don’t so although I can’t implement it I wanna try and use similar concepts.

Anyways here’s what I have hoped I can get some suggestions primarily for the server code since the client I feel is alright.

local function hitDetection()
	for _, hitboxObj in ipairs(Hitbox.activeHitboxes) do
		param.FilterDescendantsInstances = {Character:getCharacter()}

		local hitboxPart = hitboxObj.hitbox    
		local results = workspace:GetPartsInPart(hitboxPart, param)

		for _, part in ipairs(results) do
			local parentModel = part.Parent
			if parentModel:IsA("Model") and parentModel:FindFirstChildOfClass("Humanoid") then
				if not hitboxObj.hits[parentModel] then
					print("Hit",parentModel)
					hitboxObj.hits[parentModel] = true
					--  damage, vfx, sounds, etc could go here
					ReplicatedStorage.Assets.Remotes.RemoteEvent:FireServer(parentModel)
				end
			end
		end
	end
end

local function handleInput()
	if Input:pressed(inputButtons.lightAttackButton) then
		fsm:attacking()

		local limb = Character:getCharacter()["Right Leg"]
		local hitboxSize = Vector3.new(2, 2, 2)
		local hitboxCFrame = limb.CFrame
		local hitbox = Hitbox.new(hitboxSize, hitboxCFrame, Animation:getLength("lightAttack"), limb)
		
		Animation:playAnimation("lightAttack", false, Enum.AnimationPriority.Action, function()
			fsm:idle()
		end)
	end
end

local function update(dt)
	handleInput()
	hitDetection()
end
local function validateHit(attacker)
	local limb = attacker["Right Leg"]
	local hitboxSize = Vector3.new(2, 2, 2)
	local hitboxCFrame = limb.CFrame
	local hitbox = Hitbox.new(hitboxSize, hitboxCFrame, 0.583, limb)
	
	for _, hitboxObj in ipairs(Hitbox.activeHitboxes) do
		param.FilterDescendantsInstances = {attacker}

		local hitboxPart = hitboxObj.hitbox    
		local results = workspace:GetPartsInPart(hitboxPart, param)

		for _, part in ipairs(results) do
			local parentModel = part.Parent
			if parentModel:IsA("Model") and parentModel:FindFirstChildOfClass("Humanoid") then
				if not hitboxObj.hits[parentModel] then
					print("Hit",parentModel)
					hitboxObj.hits[parentModel] = true
				end
			end
		end
	end
end

--- @param attacker model 
--- @param target model
local function isNear(attacker, target)
	local distance = (attacker.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude
    return distance <= 10
end

--- @param attacker player instance
--- @param target model
local function onHit(attacker, target)
	print("Hit Received")
	
	if validateHit(attacker.Character) then
		print("hit seems legit lol")
	end
end

Your scripts seem functional so far.

For the server, you probably shouldn’t loop through Hitbox.activeHitboxes; workspace:GetPartsInPart() is considered a costly function. You should send the hitbox the client activated over to the server and just verify that singular hitbox. If the hitbox fails to replicate between the client-server boundary (aka hitbox == nil on the server) make sure your hitboxes are server-sided. The exception being the client-sided hitbox instantiated before the animation is played; this should pose no issues.

You should also pass in the target as a parameter in the server validateHit and verify that the humanoid the attacker is hitting does belong to their target.

Also, if you want further optimization, consider using :GetPartBoundsInBox() instead of :GetPartsInPart(). Instead of storing hitbox information as parts (which take up more memory), you can store hitboxes in an array (or dictionary for ease of access) that contains their CFrames and sizes.

If you have further questions lmk!
Hope this helps! :slight_smile:

3 Likes