Hitbox only hitting one at a time?

Hello! Recently, I have been working on a new hitbox system that allows me to have a bit more control over what I can do with it and it’s been going great, I’m near the end of implementation! There’s one problem standing in my way for the time being and that is my hitbox only hitting one player at a time. Well, not really, “hitting” them one at a time, the way my system works is that it creates the hitbox locally, then sends a validation check to the server and then sends it to a VFX handler, but it seems like the validation check is only running once.


There’s quite a few moving parts going on in making this, but I do believe it is something at fault with the validator check being a boolean statement instead of a table like what I used to run it as before. Since I do not fully understand the problem, I will send what I believe is needed and if more information is required, (such as the hitbox module) I would be happy to provide it.
Ability Activation: (Local Script)

local UIS = game:GetService("UserInputService")
local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local PLRS = game:GetService("Players")

local hitboxModule = require(RS:WaitForChild("HitboxCreator"))

local remotes = RS:WaitForChild("Remotes")
local slamRemotes = remotes:WaitForChild("Slam")
local slamValidator = slamRemotes:WaitForChild("SlamValidator")
local slamVFX = slamRemotes:WaitForChild("SlamVFX")

local plr = PLRS.LocalPlayer
local pChar = plr.Character
local pHum = pChar:WaitForChild("Humanoid")
local hrp = pChar:WaitForChild("HumanoidRootPart")

local cooldown = 1
local canUseAbility = true

UIS.InputBegan:Connect(function(input, gameProcessed)

	if gameProcessed then return end

	if input.KeyCode == Enum.KeyCode.Q then

		slamVFX:FireServer(plr)

		task.delay(1.2, function()
			
			local hitbox = hitboxModule.CreateHitbox() 
			hitbox.HitboxMode = "Constant"
			hitbox.Size = Vector3.new(10, 10, 10)
			hitbox.Offset = CFrame.new(0, 0, 0)
			hitbox.CFrame = hrp.CFrame
			hitbox.Transparency = 0.5

			hitbox:StartHitbox()	

			hitbox.HitPlayer:Connect(function(hitPart, vHum)

				slamValidator:FireServer(hitPart, vHum)

			end)

			task.delay(1, function()
				hitbox:DestroyHitbox()
			end)
			
		end)

	end
end)

Ability Validator: (Server Script)

--Begins VFX for Slam and validates the hit
config.slamVFX.OnServerEvent:Connect(function(plr)

	local pChar = plr.Character
	config.slamVFX:FireAllClients(plr)

	--If the hit is valid it gets sent to the VFX handler
	local validator = config.slamValidator.OnServerEvent:Connect(function(plr, hitPart, vHum)

		print("Checking hit")
		
		local validHit = validationModule.HitValidation(plr, hitPart, vHum, 1.5, 10, 360)
		--config.slamValidator:FireAllClients(validHit)

		if validHit == true then	
			
			local pChar = plr.Character
			local hrp = pChar:WaitForChild("HumanoidRootPart")

			local vChar = vHum.Parent
			local vrp = vChar:FindFirstChild("HumanoidRootPart")
			local vPos = vrp.Position

			task.delay(0, function()
				knockbackModule.knockbackAttributes(plr, hrp, vChar, vrp, 10, 0, 40, 0)
				print("Awarding points to "..plr.Name)
			end)
		end
	end)
	task.delay(2, function()
		validator:Disconnect()
	end)
end)

Validator Module: (Module Script)

local module = {}

local debounce = {}

local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local PLRS = game:GetService("Players")

function module.HitValidation(plr, hitPart, vHum, c, m, a)
	
	local pChar = plr.Character
	
	warn(pChar.Name.. " has been sent to the hit validator along with the table.")

	local cooldown = c
	local validHit = false

	if plr then
		
		if table.find(debounce, plr.Name) ~= nil then --Debounce cooldown so exploiters cannot spam the server with requests
			print("Under debounce!")
		else
			print("Ability casted!")
			table.insert(debounce, plr.Name)
			
			local hrp = pChar:FindFirstChild("HumanoidRootPart")
			local pos = hrp.Position
			local pDirection = hrp.CFrame.LookVector

			local vChar = vHum.Parent
			local vrp = vChar:FindFirstChild("HumanoidRootPart")
			local vPos = vrp.Position

			local magCheck = (vPos - pos)
			local distance = magCheck.Magnitude --Gets distance between the players
			local normalizedDist = magCheck.Unit --Normalizes that distance number

			local dot = pDirection:Dot(normalizedDist)  --Normalizes look vector
			local radAngle = math.acos(dot) --Finds the angle between the vrp and player look direction
			local degAngle = math.deg(radAngle) --Converts above into an angle
				
			if distance <= m and degAngle <= a and vChar then --Validation check
				vChar.Humanoid:TakeDamage(10)
				validHit = true
			else 
				validHit = false
			end	

			--[[for _, vChar in pairs (vTable) do --Checks if hit was actually valid

				local vrp = vChar:FindFirstChild("HumanoidRootPart")
				local vPos = vrp.Position

				local magCheck = (vPos - pos)
				local distance = magCheck.Magnitude --Gets distance between the players
				local normalizedDist = magCheck.Unit --Normalizes that distance number

				local dot = pDirection:Dot(normalizedDist)  --Normalizes look vector
				local radAngle = math.acos(dot) --Finds the angle between the vrp and player look direction
				local degAngle = math.deg(radAngle) --Converts above into an angle

				if distance <= m and degAngle <= a and vChar and not table.find(validHit, vChar) then --Validation check
					table.insert(validHit, vChar) --Validates what character was hit
					vChar.Humanoid:TakeDamage(10)
				else 

				end	
			end]]

			task.delay(cooldown, function() --Removes player from debounce table after cooldown
				debounce[table.find(debounce, plr.Name)] = nil
			end)

		end
		return validHit --Returns valid hits bool to the server for processing
	end
end

return module

What is your hitbox creator module script?

For sure! Here’s the main hitbox module:
This is my first attempt at using type checking as well, so I hope it isn’t too confusing.

--!nonstrict

local RS = game:GetService("ReplicatedStorage")
local RUN = game:GetService("RunService")
local PLRS = game:GetService("Players")
local HS = game:GetService("HttpService")


local GoodSignal = require(script.GoodSignal)

local clientBox = {}
clientBox.__index = clientBox

export type Attributes = {
	
	HitboxMode : ("Default" | "Constant"),
	Size : Vector3,
	CanCollide : boolean,
	CanTouch : boolean,
	CanQuery : boolean,
	Anchored : boolean,
	Transparency : number,
	CFrame : CFrame,
	Offset : CFrame,
	Active : number,
	OverlapParams : OverlapParams,
	PlayerKey : Player,
	Key : string,
	vTable : {Model}?,
	
	Connection : RBXScriptConnection,
	HitPlayer : GoodSignal.Signal<BasePart, Humanoid>
	
}

function clientBox.CreateHitbox() --Default hitbox settings, if nothing is changed these are the default values.
	
	local self = setmetatable({} :: Attributes, clientBox)
	
	self.HitboxMode = "Default"
	self.Size = Vector3.new(0, 0, 0)
	self.CanCollide = false
	self.CanTouch = false
	self.CanQuery = false
	self.Anchored = true
	self.Transparency = 0
	self.CFrame = CFrame.new(0, 0, 0)
	self.Offset = CFrame.new(0, 0, 0)
	self.Active = 1
	self.OverlapParams = OverlapParams.new()
	self.PlayerKey = PLRS.LocalPlayer
	self.vTable = {}
	
	self.HitPlayer = GoodSignal.new()
	
	--print(self)	
	
	return self --This info will return to EVERYTHING that calls it
end

function clientBox.StartHitbox(self : Attributes)
	
	local pChar = self.PlayerKey.Character
	local hrp = pChar:FindFirstChild("HumanoidRootPart")
	
	self.OverlapParams.FilterDescendantsInstances = {pChar}
	
	local mode = self.HitboxMode

	if mode == "Default" then
		print("Default Mode")
	elseif mode == "Constant" then
		print("Constant Mode")
	end
	
	task.spawn(function()
		self.Connection = RUN.Heartbeat:Connect(function()
			self:CastHitbox(hrp.CFrame) 
		end)
	end)
	
end

function clientBox.CastHitbox(self : Attributes, hrp, GoodSignal)

	if not self.Visualizer then
		
		local visualizer = Instance.new("Part")
		visualizer.Size = self.Size
		visualizer.CanCollide = self.CanCollide
		visualizer.CanTouch = self.CanTouch
		visualizer.CanQuery = self.CanQuery
		visualizer.Anchored = self.Anchored
		visualizer.Transparency = self.Transparency
		visualizer.CFrame = hrp * self.Offset
		visualizer.Parent = workspace
		self.Visualizer = visualizer
		
	else
		self.Visualizer.CFrame = hrp * self.Offset
	end

	local hitboxPart = workspace:GetPartBoundsInBox(hrp * self.Offset, self.Size, self.OverlapParams)
	
	for _, hitPart in pairs(hitboxPart) do
		local vHum = hitPart.Parent:FindFirstChild("Humanoid")
		if vHum and not table.find(self.vTable, hitPart.Parent) then --Only fires once per person that was hit.
			print("Target hit")
			table.insert(self.vTable, hitPart.Parent)
			
			self.HitPlayer:Fire(hitPart, vHum)
			
		end
	end
	
	print(self.vTable)
	
	return self.vTable
	
end

function clientBox.DestroyHitbox(self : Attributes)
	
	self.Visualizer:Destroy()
	self.Connection:Disconnect()
	
end

return clientBox

(There’s the signal script as well attached to this module, but I don’t think that has anything to do with it)

try changing overlapparams.maxparts

Is self.HitPlayer:Fire(hitPart, vHum) being sent three times for the three rigs?

From what I saw it seems that is is calling :FireServer for each humanoid, now this is an issue since you are running into the debounce.

It is printing “Under debounce!” for the other 2 humanoids resulting in the validation being false.

Now you have 2 options,
1: Change it so that instead of sending it for each individual humanoid, merge them together into the same request. (Slightly more optimized too)

2: Make each Humanoid have it’s own separate cooldown attached to the source player.

This one is most recommended if you are dealing with multihits that are slightly delayed between each dummy hit.
E.g:
A Raycast hitbox with a sword
or
A ring AOE that expands overtime dealing damage.

Additionally a tip:
I would recommend just doing the VFX on the client so they don’t feel any form of “lag”/“delay”.

You would then also have the client visually reduce the HP of the enemy and send the request to the server purely for the “damage check”.
If true then return to the client that it was a valid hit. (If the target’s HP reaches 0, don’t kill him on the client, wait till the server does it for you)
If false tell the client “This hit wasn’t valid, put the HP back up to where it should be” If you don’t put the HP back, it will be out of sync with the server, displaying the incorrect HP in the worst case scenario.

I tend to keep my studio on 0.4s Network Delay just to make sure that everything feels smooth no matter what you do.
Atleast that’s how I did my combat, people have their own frameworks to do predictive hits and have their own preference.

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