Help with scripting a sword

I am currently working on a sword, and unfortunately for me I’m a bit of a “noob” at scripting. I have created a local script that runs animations perfectly fine, and now I am on to the server side scripting. First off, I have received advice to put my server script into ServerScriptService, however it seems it would be more convenient to place it as a child of the tool. Are there any advantages to using ServerScriptService? Second, I began to work on the script and I do not know how to check if what the tool touched was a player. (Current code below.) Third, I am unaware of how to make my sword FilteringEnabled compatible, and I also would like to be able to award the player with cash for every kill he gets.

local blade = script.Parent.SwordBlade
local CanAttack = true

blade.Touched:Connect(function(p)
	if CanAttack == true then
		CanAttack = false
		p.Parent.Humanoid:TakeDamage(37)
		script.Parent.Handle.Hit:Play()
		wait(0.5)
		CanAttack = true
	end
end)

I am not asking for scripts, just advice on how to approach doing the above things, such as what to use and where.
Thanks, toe_s :slightly_smiling_face:

9 Likes

Nevermind, figured it out on my own. It was easier than I thought to fix.

1 Like

In response to your question about using ServerScriptService, the big advantage to using that would be that you could use a single server script to handle the server side stuff needed for all the player’s in your game. Basically it doesn’t matter how many players are currently playing, you’d still only need one server script to handle all the swords. The other big advantage is if you make multiple swords that all use a similar server script then you can update all your swords at once on the server side by updating a single script.

1 Like

I will keep that in mind. I figured out my sword wasn’t completely finished yet, because I haven’t set it so that it doesn’t constantly deal idle damage. I have no idea on how to do this, and also my sword seems like it works on both client and server side without bugs. I am just really confused and have no clue as to where to go from here. I have everything inside of a tool and any help would be greatly appreciated.

Server Script:
local blade = script.Parent.SwordBlade
local CanAttack = true

blade.Touched:Connect(function(hit)
	if CanAttack == true and hit.Parent:FindFirstChild("Humanoid")~= nil then
		CanAttack = false
		hit.Parent.Humanoid:TakeDamage(37)
		script.Parent.Handle.Hit:Play()
		wait(0.5)
		CanAttack = true
	end
end)

Local script:
–//Idle Animation\–
script.Parent.Equipped:Connect(function()
local idle = script.Parent.Parent.Humanoid:LoadAnimation(script.Parent.Animations.Idle)
idle:Play()

	script.Parent.Unequipped:Connect(function()
		idle:Stop()
	end)
end)

--//Attacking Animations\\--
local UserInputService = game:GetService("UserInputService")
local Tool = script.Parent
local CanAttack = true
local Anim_Name = "Attack"
local Audio = script.Parent.Handle.Swing
local Attack_Animations = script.Parent:WaitForChild("Animations")
local Attack_Num = 1


UserInputService.InputBegan:Connect(function(InputObject)
	if InputObject.UserInputType == Enum.UserInputType.MouseButton1 then
		local Animation = script.Parent.Parent.Humanoid:LoadAnimation(Attack_Animations[Anim_Name..Attack_Num])
		if CanAttack == true then
			Animation:Play()
			Audio:Play()
			CanAttack = false
			wait(0.5)
			CanAttack = true
			if Attack_Num == 1 then
				Attack_Num = Attack_Num + 1
				else if Attack_Num == 2 then
					Attack_Num = 1
				end
			end
		end
	end
end)

How could I fix the idle damage bug and make it filtering enabled compatible?
Thanks, toe_s

3 Likes

Your code is already FE compatible, but I assume you mean for the fix of the idle damage to be FE enabled so I’ll answer with that assumption in mind.

Currently, you are running the attack animation on the client, as well as the cooldown for the attack, but the onTouched function is on the server.

In order to prevent the player from being damaged when the sword is idle, you will have to do the onTouched function on the client in the same script as your attack function.

script.Parent.Equipped:Connect(function()
    local idle = script.Parent.Parent.Humanoid:LoadAnimation(script.Parent.Animations.Idle)
    idle:Play()
    script.Parent.Unequipped:Connect(function()
		idle:Stop()
	end)
end)

--//Attacking Animations\\--
local UserInputService = game:GetService("UserInputService")
local Tool = script.Parent
local CanAttack = true
local Anim_Name = "Attack"
local Audio = script.Parent.Handle.Swing
local Attack_Animations = script.Parent:WaitForChild("Animations")
local Attack_Num = 1

local blade = script.Parent.SwordBlade --(update this to where the sword is)

blade.Touched:Connect(function(hit)
	if CanAttack == false and hit.Parent:FindFirstChild("Humanoid")~= nil then -- this indicates that the attack is currently happening
		hit.Parent.Humanoid:TakeDamage(37) -- send a remote event to the server to deal this damage, make sure that user hasnt dealt damage to this player within the past 0.5 seconds in case multiple onTouched events are triggered in one swing, or if the user is hacking the remote event.
		script.Parent.Handle.Hit:Play() -- same as above, play this noise from the server when the damage is dealt
	end
end)

UserInputService.InputBegan:Connect(function(InputObject)
	if InputObject.UserInputType == Enum.UserInputType.MouseButton1 then
		local Animation = script.Parent.Parent.Humanoid:LoadAnimation(Attack_Animations[Anim_Name..Attack_Num])
		if CanAttack == true then
            CanAttack = false
			Animation:Play()
			Audio:Play()
			-- CanAttack = false (moved this to above the playing of the anims, to make sure it is already false when the onTouched is triggered
			wait(0.5)
			CanAttack = true
			if Attack_Num == 1 then
				Attack_Num = Attack_Num + 1
				else if Attack_Num == 2 then
					Attack_Num = 1
				end
			end
		end
	end
end)

Of course, with FE enabled you can’t deal damage from the client, or play the hit noise. So, you will need to send a remote event to the server to deal the damage and play the noise (as mentioned in the notes).

Remote events can be hacked, so you will probably want to do some security checks on the server to make sure:

  1. The users last attack was not within the past 0.5 seconds, or whatever your cooldown is going to be.
  2. The user is within a certain amount of studs of the character they’re attacking (probably something around 30 studs to account for any latency).

There are probably some other things you can check for as well, but those two should suffice for your average hacker.

2 Likes

How can I check to see if the last attack was not within the cooldown and check if it was in a 30 stud radius? The sword doesn’t have a debounce anymore for the damage function so it will deal damage to the player multiple times (basically instant kill). Starting to work on the remote event and will keep you updated on any other bugs.
Happy Thanksgiving! :turkey:
toe_s
EDIT: Added remote event that works, however the hit audio doesn’t play and you don’t have to be swinging the sword to deal damage. Is it because I added the debounce back?

You will have to show me your new code if you would like assistance with new problems.

Once you send that, I will also explain how to incorporate the 30 stud radius and cooldown factors.

Happy Thanksgiving!

Local script:
script.Parent.Equipped:Connect(function()
local idle = script.Parent.Parent.Humanoid:LoadAnimation(script.Parent.Animations.Idle)
idle:Play()
script.Parent.Unequipped:Connect(function()
idle:Stop()
end)
end)

--//Attacking Animations\\--
local UserInputService = game:GetService("UserInputService")
local Tool = script.Parent
local CanAttack = true
local Anim_Name = "Attack"
local Audio = script.Parent.Handle.Swing
local Attack_Animations = script.Parent:WaitForChild("Animations")
local Attack_Num = 1

local blade = script.Parent.SwordBlade

blade.Touched:Connect(function(hit)
	game.ReplicatedStorage.Damage:FireServer(hit)
end)

UserInputService.InputBegan:Connect(function(InputObject)
	if InputObject.UserInputType == Enum.UserInputType.MouseButton1 then
		local Animation = script.Parent.Parent.Humanoid:LoadAnimation(Attack_Animations[Anim_Name..Attack_Num])
		if CanAttack == true then
            CanAttack = false
			Animation:Play()
			Audio:Play()
			wait(0.5)
			CanAttack = true
			if Attack_Num == 1 then
				Attack_Num = Attack_Num + 1
				else if Attack_Num == 2 then
					Attack_Num = 1
				end
			end
		end
	end
end)

Server script:
local CanAttack = true

game.ReplicatedStorage.Damage.OnServerEvent:Connect(function(player, hit)
	if CanAttack == true and hit.Parent:FindFirstChild("Humanoid")~= nil then
		CanAttack = false
		hit.Parent.Humanoid:TakeDamage(37)
		game.StarterPack.Sword.Handle.Hit:Play()
		wait(0.5)
		CanAttack = true
	end
end)

RemoteEvent named damage is in ReplicatedStorage
Serverscript stored in ServerScriptService as “SwordServer”
Local Script stored in tool in starterpack as “Client”

In general, this is just a bad idea, because even with the debouce all it takes is an exploiter knowing what arguments to pass through the remote.

As I showed in my original response, you need to check that the CanAttack value is set to false in the Blade.Touched event in order to make sure the player is currently attacking. You also should check for the humanoid on the client as well, to avoid sending remote events that are unnecessary.

This will prevent the user from dealing damage when they aren’t attacking since the CanAttack value is only set to false while their attack animation is playing.

You should not be using a CanAttack Boolean on the server, because that is going to limit ALL players from dealing damage more often than every 0.5 seconds (ex. User 1 attacks, then user 2 attacks before 0.5 seconds have elapsed. Their attack would not count since the User 1 already triggered the CanAttack boolean to be false on the server.

Once you complete that, before dealing damage check the magnitude between the two characters. If it is less than 30, allow the attack. As for the timers on the server, you will need to create a table of the players, and each time they attack update the value to the current tick(). When they attack again, make sure the difference between the old tick() and the new tick() is greater than 0.5.

Exploiters can take advantage of any client to server interactivity whatsoever. That does not mean developers should not try to communicate to the client from the server, as we fundamentally have to in many cases.

There are many ways on the server side to make this “less” exploitable. Like I said in my post, two prime examples is checking how long ago their last attack was, as well as, checking their distance from the character they are attempting to damage. This will prevent most hackers from doing any serious damage to the game, and it will also not be something they can figure out easily since the code behind this check is running on the server (hackers have access to the client, not the server).

I mean I still see no point in relying on the client for a sword, being it’s something the server can easily handle.

His goal is to only deal damage while the sword is actually in the attacking state (playing the animation). You cannot get that information on the server.

How can you not? if they’re two separate animations you can loop over the humanoids current animation tracks looking for the hit animation. This can be done server sided as I do it in my game.

Animations are played on the client, due to the fact that you can not detect user input from the server. He can’t obtain whether it is currently playing from the server, without the usage of a remote event or function. It is most practical to check that from the same script as the play animation script so that there is no client to server (or vice versa) delay of detecting if the animation is currently playing.

You will always have delay when it comes to server and client sided things, however if you look at link sword scripts they rely more on the server than client, and animations are also loaded both server and client sided to handle “latency” type issues. However hit detection is Server based, which it should always be.

That is an opinion and not a matter of fact. Regardless, with the security checks I mentioned; it shouldn’t be much of an issue. I would personally write a line that kicks anyone who is spamming the remote event faster than possible.

Edit: Checking for the hit on the server will have less delay because it is happening simultaneously with the players attack. There will be no delay. The delay would be the damage dealt (edit: and the sound playing), which will be subject to the connection, but you won’t be hitting people that you weren’t close enough to, or be missing players that you clearly hit.

There can be delays between a remote event being fired, and the server recognizing it. So not only would that delay damage dealt, but if the client is remotely laggy they may miss hits they originally should have got, or hit when they shouldn’t have. Personally either way has it’s own pros and cons.

[quote=“vNotSiren, post:18, topic:396530, full:true”]… if the client is remotely laggy they may miss hits they originally should have got, or hit when they shouldn’t have…
[/quote]

They would not miss hits they should get, because they would get any hits that actually happened on their client (meaning anything they saw hit, would trigger a hit).

Now, for the enemy player, they may get hit when they think they shouldn’t have, because on the attacking users screen they may have been closer to the enemy than the enemy could see, because of the delay of the replication of the movement from client to server to client. But for the attacker it will always hit when it appears to hit. Which is ideal in most situations.

with your checks, by the time the server realizes a hit, if the person they’re hitting has a better connection and walks out of the radius they will miss that intended hit. Unless you make the radius a significant value for latency and then exploiters could just make attack auras.

Client > Server > Client is a good idea yes, that’s how guns would be handled.