Hello, I scripted a punching system that I would appreciate feedback for(is it bad, good, could use work, etc).
It involves GetPartsBoundinBox, and most of the work is done by the client e.g. creating a hitbox, determining the parts in the hit box, and detecting a humanoid “hit”(I’ll explain right underneath).
The server’s job is to validate if the “hit” is valid. What it does is it compares the properties of the AttackFunction from the serverside module script and the clientside script(both of which are pretty much contain the same information) to see if there were changes on the clientside(e.g. hitbox size change) to prevent exploiting. It then detects if the client’s Player’s HumanoidRootPart’s position of the given and the server/workspace Player’s HumanoidRootPart’s position differ to account for latency. It also compares the player’s position and the humanoid “hit” position and see if it is more than a certain value to prevent exploiting
Overall, I would like to know if my code could cause lag, be inefficient(is there a better way?), etc
Here is the client side code(it’s in StarterCharacterScripts)
--// Services //--
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService")
--// Variables //--
local AttackInformation = require(ReplicatedStorage:WaitForChild("AttackInformationClient"))
local Player = game.Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HRP = Character:WaitForChild("HumanoidRootPart")
local PunchEvent = ReplicatedStorage:WaitForChild("PunchEvent")
local MoveEquipped = false
--// Main //--
UIS.InputBegan:Connect(function(input, gameProcessed)
if Character.Humanoid:GetState() ~= Enum.HumanoidStateType.Dead then
if input.UserInputType == Enum.UserInputType.MouseButton1 and not gameProcessed and MoveEquipped == false then
local HitDetected = AttackInformation.AttackInitiated(AttackInformation.PunchAttack,HRP)
if HitDetected then
PunchEvent:FireServer(AttackInformation.PunchAttack, HRP, HitDetected)
end
end
end
end)
Serverside hit confirmation
--// Services //--
local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--// Variables //--
local AttackInformation = require(ServerScriptService.AttackInformationServer)
local PunchEvent = ReplicatedStorage:WaitForChild("PunchEvent")
local HitFail = false
local MaxLatencyDistanceHRP = 1 --maximum distance between client HRP and server HRP allowed due to latency
--// Main //--
PunchEvent.OnServerEvent:Connect(function(player, AttackUsed, ClientHRP, Target)
local ServerHRP = workspace:FindFirstChild(ClientHRP.Parent.Name):FindFirstChild("HumanoidRootPart")
local UserHRPDistances = (ClientHRP.Position - ServerHRP.Position).Magnitude
local MaxLatencyDistanceHitbox = AttackInformation.PunchAttack.MaxLatencyDistanceHitbox --changes between Attacks
local UserHRPTargetDistance = math.abs(UserHRPDistances - AttackInformation.PunchAttack.HitboxSize.Z) --changes between attacks
for i, v in pairs(AttackUsed) do
if v ~= AttackInformation.PunchAttack[i] then --changes between attacks
warn("AttackUsedValue differs. Possible exploiter")
HitFail = true
break
end
end
if not HitFail then
if UserHRPDistances < MaxLatencyDistanceHRP and UserHRPTargetDistance < MaxLatencyDistanceHitbox then
Target.Parent.Humanoid.Health = Target.Parent.Humanoid.Health - 10
end
else
HitFail = false
end
end)
clientside module script with attack properties
local Attack = {
PunchAttack = {
HitboxSize = Vector3.new(4, 4.998, 3.364),
HitboxOffset = Vector3.new(0,0,-2),
HitboxCanCollide = false,
HitboxAnchored = false,
HitboxTransparency = 1,
Cooldown = 1,
HitboxDespawn = 0.1,
HitboxParent = workspace
}
}
local AttackTemplate = {
HitboxSize = Vector3.new(4, 4.998, 3.364),
HitboxOffset = Vector3.new(0,0,-2),
HitboxCanCollide = false,
HitboxAnchored = false,
HitboxTransparency = 1,
Cooldown = 1,
HitboxDespawn = 0.1,
HitboxParent = workspace -- just to visualize the hitbox
}
function Attack.AttackInitiated(AttackName,HRP)
local debounce = false
local HitDetected = nil
local Target
local Attack = AttackName
local Hitbox = Instance.new("Part")
local HitboxDespawn = Attack.HitboxDespawn
local HitboxOffset = CFrame.new(Attack.HitboxOffset)
local Cooldown = Attack.Cooldown
local HitboxParams = OverlapParams.new()
HitboxParams.FilterDescendantsInstances = {HRP.Parent:GetChildren(), Hitbox}
HitboxParams.FilterType = Enum.RaycastFilterType.Exclude
Hitbox.Size = Attack.HitboxSize
Hitbox.CanCollide = Attack.HitboxCanCollide
Hitbox.Anchored = Attack.HitboxAnchored
Hitbox.Transparency = 0.5 --make sure to change
local Weld = Instance.new("Weld")
Weld.Part0 = HRP
Weld.Part1 = Hitbox
Weld.C1 = CFrame.new(0,0,2) --change
Weld.Parent = HRP
Hitbox.Parent = Attack.HitboxParent
if Hitbox then
while true do
local HitboxObjects = workspace:GetPartBoundsInBox(Hitbox.CFrame, Hitbox.Size, HitboxParams)
if #HitboxObjects ~= 0 then
for i, v in pairs (HitboxObjects) do
if v.Parent:FindFirstChild("Humanoid") and v.Parent:FindFirstChild("Humanoid").Health ~= 0 and debounce == false then
debounce = true
task.wait(HitboxDespawn)
Hitbox:Destroy()
debounce = false
HitDetected = true
Target = v.Parent:FindFirstChild("HumanoidRootPart")
break
else
task.wait(HitboxDespawn)
Hitbox:Destroy()
break
end
end
else
task.wait(HitboxDespawn)
Hitbox:Destroy()
end
task.wait(HitboxDespawn)
break
end
end
if HitDetected then
HitDetected = false
return Target
end
end
return Attack
serverside module script with attack properties
local Attack = {
PunchAttack = {
HitboxSize = Vector3.new(4, 4.998, 3.364),
HitboxOffset = Vector3.new(0,0,-2),
HitboxCanCollide = false,
HitboxAnchored = false,
HitboxTransparency = 1,
Cooldown = 1,
HitboxDespawn = 0.1,
MaxLatencyDistanceHitbox = 5,
HitboxParent = workspace
}
}
local AttackTemplate = {
HitboxSize = Vector3.new(4, 4.998, 3.364),
HitboxOffset = Vector3.new(0,0,-2),
HitboxCanCollide = false,
HitboxAnchored = false,
HitboxTransparency = 1,
Cooldown = 1,
HitboxDespawn = 0.1,
MaxLatencyDistanceHitbox = 5,
HitboxParent = workspace -- just to visualize the hitbox
}
return Attack