Okay! I believe I’m in the home stretch of making this client-sided hitbox system! I have the local ability script which houses the hitbox and gets sent to a server script, the server script then sends the validation values (magnitude and angle) to a module script to verify it and then if it’s verified the victim VFX plays to those players on all clients on a VFX handler local script.
This is all well, but there’s one last point I’m struggling with, the player animation begins on the ability script and the client-sided hitbox is bound to a flag in the animation track. My issue is, I don’t know how to make a cooldown for the CLIENT that’s SAFE so the animation isn’t being spammed (Even though the actual hitbox isn’t doing anything because of server debounce and validation checks)
I may be overthinking this, or I may be right there at the end and just need a bit more of a push, I’ve been getting a lot of help with this recently and I appreciate it! This would be the final hurdle and it feels like it may be either something small or I’ll need to completely rearrange my scripts.
Here are the scripts in order and what each one does:
Ability (Local Script) Initializes the ability and the client-sided hitbox. Player animation plays here because everyone can see it.
local function hitboxEffects() --Function that creates hitbox and fires server for hit validation
local hrp = pChar:FindFirstChild("HumanoidRootPart")
local hitbox = Instance.new("Part")
local pos = hrp.Position
local hitCharacters = {}
local parameters = OverlapParams.new()
hitbox.Shape = Enum.PartType.Ball
hitbox.Size = Vector3.new(10, 10, 10)
hitbox.CanCollide = false
hitbox.CanTouch = false
hitbox.CanQuery = false
hitbox.Anchored = true
hitbox.Transparency = 0.5
local cf = hrp.CFrame
local size = hitbox.Size
parameters.FilterDescendantsInstances = {pChar}
local hitboxPart = workspace:GetPartBoundsInBox(cf, 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
hitbox.Position = pos
hitbox.Parent = workspace
abilityRemote:FireServer(vTable)
game.Debris:AddItem(hitbox, 0.1)
end
--Hitbox is created on the client for WYSIWYG flow. We will need checks on the server side for exploit purposes
UIS.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then
return
end
if input.KeyCode == Enum.KeyCode.Q then -- POST QUESTION: How do I make a safe cooldown for this?
gravity:Play()
end
end)
gravity:GetMarkerReachedSignal("GravEffect"):Connect(hitboxEffects) --Displays the hitbox and effects whenever the animation reaches this point
Ability Validation (Server Script) Sends information to the validation module
local validationModule = require(SS:WaitForChild("HitValidation"))
abilityRemote.OnServerEvent:Connect(function(player, vTable)
validationModule.HitValidation(player, vTable, abilityVFX, 3, 6, 75) --Validation is sent to a module script
end)
Hit Validation (Module Script) Checks if the hit was actually valid
local module = {}
local RS = game:GetService("ReplicatedStorage")
local abilityEvents = RS:WaitForChild("Ability")
local abilityRemote = abilityEvents:WaitForChild("AbilityRemote")
local debounce = {}
function module.HitValidation(player, vTable, abilityVFX, c, m, a)
local cooldown = c
local hitChars = {}
local pChar = player.Character
local hrp = pChar:FindFirstChild("HumanoidRootPart")
local pos = hrp.Position
local pDirection = hrp.CFrame.LookVector
if table.find(debounce, player.Name) ~= nil then --Debounce cooldown so exploiters cannot spam the server with requests
print("Under debounce!")
else
print("Ability casted!")
table.insert(debounce, player.Name)
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(hitChars, vChar) then --Validation check
print("Hit")
table.insert(hitChars, vChar) --Adds player to table for VFX purposes
vChar.Humanoid:TakeDamage(10)
end
end
abilityVFX:FireAllClients(pChar, hitChars) --VFX
task.wait(cooldown)
debounce[table.find(debounce, player.Name)] = nil
end
end
return module
VFX Handler (Local Script) Plays VFX for all validated players that were in the hitbox
local RS = game:GetService("ReplicatedStorage")
local abilityEvents = RS:WaitForChild("Ability")
local abilityVFX = abilityEvents:WaitForChild("AbilityVFX")
local abilityAnims = RS:WaitForChild("AbilityAnims")
local victimAnim = abilityAnims:WaitForChild("Victim")
abilityVFX.OnClientEvent:Connect(function(pChar, hitChars)
if #hitChars == 0 then --If nobody was hit this will fire and then stop the script
print("Nobody was hit!")
return
end
if #hitChars >= 1 then --If there was a hit then run this
for _, victim in pairs (hitChars) do
local animate = victim.Humanoid:LoadAnimation(victimAnim)
animate:Play()
end
print(hitChars)
end
end)
I guess this functions as a sort of code review as well, so go ahead and give me all the pointers and advice you got! But the main goal is making a way so that the initial animation cooldown isn’t spammable.