Right off the bat, you can instead send the tool instance as a variable to the remote function. For example, a localscript in the sword could parse itself to the RemoteEvent like so:
local tool = script.Parent
local attackEvent = game:GetService("ReplicatedStorage"):WaitForChild("Attack")
tool.Activated:Connect(function()
attackEvent:FireServer(tool)
end)
And your server script would look something like this:
game:GetService("ReplicatedStorage"):WaitForChild("Attack").OnServerEvent:Connect(function(player, tool)
if tool.Name == "MiniSword" then
-- Code goes here
end
end)
This removes one of the for loops in your script already, making it cheaper by a power of n.
This seems like an incredibly inefficient way to do it, and there are a number of alternatives.
You could create a Region3 around the player when they activate the weapon, use workspace:FindPartsInRegion3(yourRegion)
to find all the BaseParts in that Region3 and then check if their parents are Characters, however this would limit you to a square attack area.
You could alternatively use a hitbox - simply create a Part of the desired size and shape and keep it in either ReplicatedStorage or ServerStorage, and then when the weapon is activated :Clone()
it and center it on the player. Then, use hitbox:GetTouchingParts()
and again check if their parents are Characters.
I think this latter option is more expensive, however it isn’t unreasonably so and allows finer control over the hitbox size and shape. Some example code:
Weapon:
local RS = game:GetService("ReplicatedStorage")
local attackEvent = RS:WaitForChild("Attack")
local tool = script.Parent
tool.Activated:Connect(function()
attackEvent:FireServer(tool)
end)
Server script:
local RS = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local attackEvent = RS:WaitForChild("Attack")
local weaponHitbox = RS:WaitForChild("MiniSwordHitbox")
local weaponName = "MiniSword"
attackEvent.OnServerEvent:Connect(function(player, tool)
if tool.Name == weaponName then
local hitbox = weaponHitbox:Clone()
hitbox.CFrame = player.character.CFrame
hitbox.Parent = workspace
local parts = hitbox:GetTouchingParts()
for _,v in ipairs(parts) do
local targetPlayer = Players:FindFirstChild(v.Parent.Name)
if targetPlayer then
-- Damage code or whatever
print("Killing "..targetPlayer.Name.."!")
end
end
end
end)
I wrote this up in the post editor at 1am so it might not work perfectly, but give it a go and PM me if you need help debugging it.
From a design perspective, this code is relatively secure, however the feedback to the client is pretty awful, as they don’t know if they’ve hit until the server does, introducing some real potential latency issues - this would be resolved by having both the client and the server doing the check, but only actually damaging the other player once the server has confirmed there is a hit.