Point counter playing for everyone?

Hello! Recently, I have created a client-sided hitbox system that works quite well but there are a few things that I need to have brushed up and cannot quite figure out the solution for

The way that it works is that the client sends a request to the server to fire an ability, the ability then fires from the server to all clients, the client hitbox returns a table of characters it hit to the server to validate them and then back to the client to process the effects on those hit characters.

As I stated earlier, this works great! But there are a few hiccups.


Firstly, whenever a player begins to get knocked away in any direction and the point counter appears, it shows for EVERYONE in the game, not just the person who scored the hit.

Second, I see that it says, “Hit” twice in the output which means the for loop is running more times than it should on each client. I tested this actually and it runs the loop on each client an extra time for each player that’s in the server so that I need some help on as well.

I’m sure, once again, it’s just needing to shuffle around a few things to get it to function in the way I intend it to! I will leave the scripts bellow. (I use a configuration library for my variables so config. is effectively the same as a variable call)

Server Script: (Ability remote call and validation)

config.abilityRemote.OnServerEvent:Connect(function(plr, remote)
	
	remote:FireAllClients(plr)
	print("Ability Recieved!")
	
end)

--Gravity Attributes
config.gravityValidator.OnServerEvent:Connect(function(plr, pChar, vTable)

	print("Processing hit.")

	local validHit = validationModule.HitValidation(plr, pChar, vTable, 3, 25, 100) --Validation is sent to a module script
	config.playGravityVFX:FireAllClients(validHit)
	print(validHit)
	if #validHit >= 1 then

		local pHum = pChar:FindFirstChild("Humanoid")
		local hrp = pChar:FindFirstChild("HumanoidRootPart")
		local pDirection = hrp.CFrame.LookVector

		print(hrp)
		for _, vChar in pairs (validHit) do

			local vrp = vChar:FindFirstChild("HumanoidRootPart")

			task.delay(1.5, function()
				knockbackModule.knockbackAttributes(plr, hrp, vChar, vrp, -40, 0, 50, 0) --Knockback attributes of ability
			end)
		end
		print("Hit Validated")
	else
		return
	end
end)

Local Script (VFX Handler. This gets replicated to everyone on the server)

--Gravity VFX
config.gravityVFX.OnClientEvent:Connect(function(plr)

	print("Connected to VFX Handler!")

	local pChar = plr.Character
	local gravAttack  = pChar.Humanoid.Animator:LoadAnimation(config.gravityFire)
	
	for _, effect in pairs (config.gravityHand:GetChildren()) do

		local effectClone = effect:Clone()
		effectClone.Parent = pChar:WaitForChild("LeftHand")
		effectClone:Emit(5)

		game.Debris:AddItem(effectClone, 1)
	end
	
	gravAttack:Play()
	
	gravAttack:GetMarkerReachedSignal("GravEffect"):Connect(function()
		
		config.gravitySnap:Play()
			
		local vTable = hitboxModule.CreateHitbox(pChar, 0, 0, -20, 8, 8, 8)
		
		config.gravityValidator:FireServer(pChar, vTable)
		
		local snapPart = Instance.new("Part")
		snapPart.Anchored = true
		snapPart.CanCollide = false
		snapPart.CanQuery = false
		snapPart.CanTouch = false
		snapPart.Transparency = 1
		snapPart.Size = Vector3.new(1,1,1)

		local hitmarkerClone = config.gravityHitmarker:Clone()
		hitmarkerClone.Parent = snapPart
		snapPart.CFrame = hitboxModule.pos
		snapPart.Parent = workspace
		hitmarkerClone:Emit(20)

		local victimVFX = config.playGravityVFX.OnClientEvent:Connect(function(validHit)
			
			if #validHit == 0 then --If nobody was hit this will fire and then stop the script
				print("Nobody was hit!")
				return
			else
				config.gravityCatch:Play()
				for _, vChar in pairs (validHit) do --Any victim related effects go here
					
					print("Hit")
					
					local vTorso = vChar:WaitForChild("LowerTorso")

					local victim = vChar.Humanoid.Animator:LoadAnimation(config.gravityVictim)
					local swirlClone = config.gravitySwirl:Clone()
					victim:Play()
					swirlClone.Parent = vTorso
					swirlClone:Emit(5)
					
					task.delay(1.5, function()
						swirlClone:Destroy()
						config.gravityRelease:Play()
					end)
				end
			end	
		end)

		task.delay(2, function()
			snapPart:Destroy()
			victimVFX:Disconnect()
		end)
		
	end)

end)

I know the point counter has something to do with shuffling around where the event fires back to the server, but the amount of loops depending on how many players are in the server does not make sense to me.

If you need any more information let me know and thanks!

4 Likes

There’s some extra information that I need to add to this, specifically WHERE the points are coming from! They are housed inside of this module script called knockbackAttributes:

function module.knockbackAttributes(player, hrp, victimCharacter, vrp, power, xVelocity, yVelocity, zVelocity)
	
	local victimPlayer = players:GetPlayerFromCharacter(victimCharacter)
	local victimHumanoid = victimCharacter:FindFirstChild("Humanoid")
	
	victimHumanoid.PlatformStand = true
	
	local attatchment = Instance.new("Attachment")
	attatchment.Parent = vrp
	
	local linearVelocity = Instance.new("LinearVelocity")
	linearVelocity.Parent = attatchment
	local angularVelocity = Instance.new("AngularVelocity")
	angularVelocity.Parent = attatchment
	
	linearVelocity.MaxForce = math.huge
	linearVelocity.VectorVelocity = hrp.CFrame.lookVector * power + Vector3.new(xVelocity, yVelocity, zVelocity)
	linearVelocity.Attachment0 = attatchment

	angularVelocity.MaxTorque = math.huge
	angularVelocity.AngularVelocity = Vector3.new(math.random(-30, 30), math.random(-30, 30), math.random(-30, 30))
	angularVelocity.Attachment0 = attatchment
	
	game.Debris:AddItem(attatchment, 0.5)
	
	if victimPlayer then
		if victimPlayer:FindFirstChild("Giant") then
			if victimPlayer.Giant.Value == true then
				victimHumanoid:TakeDamage(math.random(10,15))
				player.leaderstats.Nuts.Value += math.random(1,10)
				print("Giggle")
				pointsEvent:FireClient(player, victimCharacter)
				victimHumanoid.PlatformStand = false
				angularVelocity.Parent = nil
			end
			return
		end
	end

	print("Hit")
	local addingPoints = player:WaitForChild("AddingPoints")
	local distanceTraveled = Instance.new("IntValue")
	distanceTraveled.Name = "Distance"
	distanceTraveled.Parent = vrp
	
	local function checkPoints()
		
		repeat
			task.wait()
		until vrp.Velocity.Magnitude >= 0.1
		
		if victimHumanoid.PlatformStand == true and vrp.Velocity.Magnitude >= 0.1 then
			
			pointsEvent:FireClient(player, victimCharacter)
			addingPoints.Value = true
			
			repeat
				
				distanceTraveled.Value = (hrp.Position - vrp.Position).Magnitude
	
				task.wait()
			until vrp.Velocity.Magnitude < 0.1 or vrp.Position.Y < -490
				
			victimHumanoid.PlatformStand = false
			addingPoints.Value = false
			
			print("Player has stopped moving")
			player.leaderstats.Score.Value += distanceTraveled.Value
			distanceTraveled:Destroy()
		end
	end
	checkPoints()
end

return module

There’s also the hit validation module, but I don’t believe the issue occurs there:

local module = {}

local debounce = {}

function module.HitValidation(plr, pChar, vTable, c, m, a)
	
	print("Printing")

	local cooldown = c
	local validHit = {}

	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

			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
					print("Hit")
					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 table to the server for processing
	end
end

return module

Hope that clears things up!

I’ve noticed something, the points on the leaderboard increases for everyone, not just the attacker. Is that intended?

2 Likes

No, that is an issue that should be resolved alongside the point counter being fixed by only displaying for the person that initiated the hit because the point scoring is also tied to the knockbackAttributes module.

3 Likes

Update: I believe I have been able to pinpoint the problem to a specific block of code.

config.gravityValidator:FireServer(pChar, vTable) --Sends the player character and all characters that were hit to processing.

When this is called, EVERYONE fires it to the server for validation and when it gets returned here…

local victimVFX = config.playGravityVFX.OnClientEvent:Connect(function(pChar, validHit) --processed hits are returned here to play effects.

…It gets played based on how many people are in the server because it’s being called that many times to everyone’s client!

This is great to know, but now my question has shifted. How do I make only a single person fire back to the server from fire all clients?

1 Like

I’m not sure about the “multiple hits” bug, it was screwing me up yesterday.
But since the hitbox is linked to the VFX, maybe check on the client if they are the attacker, then do we create the hitbox, otherwise only do attack visuals without hitbox

would adding an if plr == Players.LocalPlayer work?

1 Like

I have rebuilt the system from the ground up! Here is the entire client-Sided detection set-up if anyone is interested.


Ability call: (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 hitValidator = RS:WaitForChild("HitValidator")
local vfxStarter = RS:WaitForChild("VFXStarter")

local plr = PLRS.LocalPlayer
local pChar = plr.Character

local cooldown = 1
local canUseAbility = true

UIS.InputBegan:Connect(function(input, gameProcessed)
	if gameProcessed then
		return
	end
	
	if input.KeyCode == Enum.KeyCode.Q and canUseAbility then
		
		canUseAbility = false
		
		vfxStarter:FireServer()
		
		task.delay(0.4, function()
			local vTable = hitboxModule.CreateHitbox(pChar, 0, 0, 0, 5, 5, 5)
			hitValidator:FireServer(vTable)
		end)

		task.delay(cooldown, function()
			canUseAbility = true
		end)
		
	end
end)

Ability Validation: (Server 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 validationModule = require(SS:WaitForChild("HitValidation"))
local knockbackModule = require(SS:WaitForChild("KnockbackAttributes"))

local hitValidator = RS:WaitForChild("HitValidator")
local vfxStarter = RS:WaitForChild("VFXStarter")

--Begins VFX for Ability and validates the hit
vfxStarter.OnServerEvent:Connect(function(plr)
	
	local pChar = plr.Character
	vfxStarter:FireAllClients(plr)

	--If the hit is valid it gets sent to the VFX handler
	local validator = hitValidator.OnServerEvent:Connect(function(plr, vTable)
		
		print("Checking hit")
		local validHit = validationModule.HitValidation(plr, vTable, 1, 5, 75)
		
		if #validHit >= 1 then	
			
			local pChar = plr.Character
			local hrp = pChar:WaitForChild("HumanoidRootPart")
			
			for _, vChar in pairs (validHit) do	
				
				local vrp = vChar:FindFirstChild("HumanoidRootPart")
				
				task.delay(1.5, function()
					knockbackModule.knockbackAttributes(plr, hrp, vChar, vrp, -40, 0, 50, 0)
					print("Awarding points to "..plr.Name)
				end)
				
			end
			hitValidator:FireAllClients(validHit)
		else
			print("No hit!")
		end
		print(vTable)	
	end)
	task.delay(1, function()
		validator:Disconnect()
	end)
end)

VFX Handler: (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 hitValidator = RS:WaitForChild("HitValidator")
local vfxStarter = RS:WaitForChild("VFXStarter")

local testAnim = RS:WaitForChild("PlayerAnim")
local testVictimAnim = RS:WaitForChild("VictimAnim")

--Ability VFX
vfxStarter.OnClientEvent:Connect(function(plr)
	print("Everyone is seeing the VFX!")
	
	local pChar = plr.Character
	local pHum = pChar:WaitForChild("Humanoid")
	local pAnim = pHum.Animator:LoadAnimation(testAnim)
	
	pAnim:Play()
	
	--Ability Victim VFX
	local validator = hitValidator.OnClientEvent:Connect(function(validHit)
		print("Effects for hit players!")

		for _, victim in pairs (validHit) do

			local vHum = victim:WaitForChild("Humanoid")
			local vAnim = vHum.Animator:LoadAnimation(testVictimAnim)

			vAnim:Play()
		end
	end)
	task.delay(1, function()
		validator:Disconnect()
	end)
end)

Hitbox Creator: (Module Script)

local module = {}

function module.CreateHitbox(pChar, posX, posY, posZ, sizeX, sizeY, sizeZ)
	
	local hrp = pChar:FindFirstChild("HumanoidRootPart")

	local hitbox = Instance.new("Part")
	local hitCharacters = {}
	local parameters = OverlapParams.new()
	hitbox.Shape = Enum.PartType.Block
	hitbox.CanCollide = false
	hitbox.CanTouch = false
	hitbox.CanQuery = false
	hitbox.Anchored = true
	hitbox.Transparency = 0.5

	module.pos = hrp.CFrame * CFrame.new(posX, posY, posZ) --Changes location of hitbox on creation
	local size = Vector3.new(sizeX, sizeY, sizeZ) --Changes size of hitbox on creation

	hitbox.CFrame = module.pos
	hitbox.Size = size
	hitbox.Parent = workspace

	local cf = module.pos
	local size = hitbox.Size

	parameters.FilterDescendantsInstances = {pChar}

	local hitboxPart = workspace:GetPartBoundsInBox(module.pos, size, parameters)

	local vTable = {}

	for _, hitPart in pairs(hitboxPart) do
		local vHum = hitPart.Parent:FindFirstChild("Humanoid")
		if vHum and not table.find(vTable, hitPart.Parent) then
			table.insert(vTable, hitPart.Parent)
		end
	end 
	
	game.Debris:AddItem(hitbox, 0.1)
	return vTable
end

return module

Hit Validator: (Module Script)

local module = {}

local debounce = {}

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

local hitValidator = RS:WaitForChild("HitValidator")


function module.HitValidation(plr, vTable, 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 = {}

	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

			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 table to the server for processing
	end
end

return module

Enjoy!

2 Likes

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