Tool with durability stops working after breaking

I have a weapon tool with durability and everything works as intended, but for some reason when the tool breaks and I grab another one it stops working. As in it appears the scripts inside the tool stop working and it can no longer be used. Just to note, I’m having no problems with the GUI, just the scripting. (Also the reason for the incredibly low durability is just so that I could test it faster.)

Server script in tool:

local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Players = game:GetService("Players")

local Functions = ServerScriptService:WaitForChild("Functions")
local Modules = ServerScriptService:WaitForChild("Modules")
local CModules = ReplicatedStorage:WaitForChild("Modules")

local RaycastHitbox = require(CModules:WaitForChild("RaycastHitboxV4"))
local Utility = require(CModules:WaitForChild("Utility"))

local GlobalCooldown = require(Modules:WaitForChild("GlobalCooldown"))
local Engine = require(Modules:WaitForChild("Engine"))

local Tool = script.Parent
local Handle = Tool:WaitForChild("Weapon")
local Remote = Tool:WaitForChild("Remote")
local Animation = Tool:WaitForChild("Animation")
local Weapon = Tool.Weapon
local DURABILITY = Instance.new("IntValue", Weapon)
DURABILITY.Name = "Durability"
DURABILITY.Value = 5

local isEquipped

local function getOwner(): Player?
	if isEquipped then
		return Players:GetPlayerFromCharacter(Tool.Parent) 
	else
		local P = Tool.Parent ~= nil and Tool.Parent.Parent or nil
		return P and P:IsA("Player") and P or nil
	end
end

Tool.Equipped:Connect(function()
	isEquipped = true
end)

Tool.Unequipped:Connect(function()
	isEquipped = false


end)

local RNG = Random.new()

local SwingSound = Handle:WaitForChild("Swing")
local BreakSound = Handle:WaitForChild("Break")
local HitSound = script:WaitForChild("Hit")

local AttackAnimation = Animation:WaitForChild("Attack")

Tool.Equipped:Connect(function()
	local Character = Tool.Parent

	local Torso = Character:FindFirstChild("Torso")

	local motor = Torso:FindFirstChild(Tool.Name) or Instance.new("Motor6D")
	motor.Part0 = Torso
	motor.Part1 = Handle
	motor.Name = Tool.Name
	motor.Parent = Torso
end)

local MIN_DAMAGE, MAX_DAMAGE = 15, 20

local hitList: { Player } = {}
local canHit

Remote.RequestAttack.OnServerInvoke = function(player: Player)
	if player ~= getOwner() or GlobalCooldown.IsOnCooldown(player) then return false end


	local character = player.Character
	local animationLength = Utility.GetAnimationLength(AttackAnimation)

	SwingSound:Play()
	GlobalCooldown.AddCooldown(player, animationLength + 2.6)

	canHit = true
	task.delay(animationLength, function()
		hitList = {}
		canHit = nil
	end)

	return true
end

Remote.OnHit.OnServerEvent:Connect(function(player, part: Instance, position: Vector3)
	if not canHit or player ~= getOwner() then return end

	local targetPlayer = Players:GetPlayerFromCharacter(part.Parent)
	local targetCharacter = targetPlayer and targetPlayer.Character or nil

	if not targetPlayer or table.find(hitList, targetPlayer) or not targetCharacter or targetCharacter:FindFirstChildOfClass("ForceField") then return end
	if not Engine:CanInteract(player, targetPlayer) then return end

	local character = player.Character
	local targetHumanoid = targetCharacter:FindFirstChildOfClass("Humanoid")
	if not character or not targetHumanoid then return end

	local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
	local targetHumanoidRootPart = targetCharacter:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart or not targetHumanoidRootPart then return end

	local distance = math.abs((humanoidRootPart.Position - targetHumanoidRootPart.Position).Magnitude)
	if distance > 10 then return end

	local raycastParams = RaycastParams.new()
	raycastParams.IgnoreWater = true
	raycastParams.FilterDescendantsInstances = Utility.GetAllPlayerCharacter()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	local castResult = workspace:Raycast(humanoidRootPart.Position, CFrame.lookAt(humanoidRootPart.Position, targetHumanoidRootPart.Position).LookVector * Vector3.new(distance, distance, distance), raycastParams)
	if castResult then return end

	table.insert(hitList, targetPlayer)

	local damage = RNG:NextInteger(MIN_DAMAGE, MAX_DAMAGE)
	targetHumanoid:TakeDamage(damage)

	task.spawn(function()
		local EffectPart = Instance.new("Part")
		EffectPart.Anchored = true
		EffectPart.Position = position
		EffectPart.CanQuery = false
		EffectPart.CanTouch = false
		EffectPart.CanCollide = false
		EffectPart.Shape = Enum.PartType.Ball
		EffectPart.Size = Vector3.new(1, 1, 1)
		EffectPart.Color = Color3.fromRGB(255, 255, 255)
		EffectPart.Material = Enum.Material.Neon
		EffectPart.Transparency = .5
		EffectPart.Parent = workspace

		Functions:WaitForChild("ClientTween"):Invoke(EffectPart, {.75,Enum.EasingStyle.Linear,Enum.EasingDirection.InOut}, {Size = Vector3.new(5,5,5),Transparency= 1}, true)
		EffectPart:Destroy()
	end)

	task.spawn(function()
		local soundPart = Instance.new("Part")
		soundPart.Anchored = true
		soundPart.Position = position
		soundPart.CanQuery = false
		soundPart.CanTouch = false
		soundPart.CanCollide = false
		soundPart.Transparency = 1
		soundPart.Parent = workspace

		local hitSound: Sound = HitSound:Clone()
		hitSound.Parent = soundPart

		hitSound:Play()
		hitSound.Ended:Wait()
		soundPart:Destroy()
	end)

	DURABILITY.Value -= 1
	if 0 >= DURABILITY.Value then
		player.PlayerGui.Cooldown:Destroy()
		player.PlayerGui.Durability:Destroy()
		BreakSound.PlayOnRemove = true
		character:WaitForChild("Humanoid"):UnequipTools()
		task.wait()
		Tool:Destroy()
	end
end)

Local script in tool:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local TS = game:GetService("TweenService")
local CModules = ReplicatedStorage:WaitForChild('Modules')
local RaycastHitbox = require(CModules:WaitForChild("RaycastHitboxV4"))
local Tool = script.Parent
local Handle = Tool:WaitForChild("Weapon")
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Weapon = script.Parent.Weapon
local Durability = Weapon:WaitForChild("Durability")
local MaxDurability = 5

local GUI = script:WaitForChild("Cooldown")
local DGUI = script:WaitForChild("Durability")


local Animation = Tool:WaitForChild("Animation")
local Remote = Tool:WaitForChild("Remote")

local I = Character:WaitForChild("Humanoid"):WaitForChild("Animator"):LoadAnimation(Animation:WaitForChild("Idle")) :: AnimationTrack
local A = Character:WaitForChild("Humanoid"):WaitForChild("Animator"):LoadAnimation(Animation:WaitForChild("Attack")) :: AnimationTrack

local Params = RaycastParams.new()
Params.FilterDescendantsInstances = { Character }
Params.FilterType = Enum.RaycastFilterType.Blacklist

local Hitbox = RaycastHitbox.new(Handle)
Hitbox.RaycastParams = Params

Tool.Equipped:Connect(function()
	GUI.Parent = Player.PlayerGui
	DGUI.Parent = Player.PlayerGui
	I:Play()
end)

Tool.Unequipped:Connect(function()
	GUI.Enabled = false
	GUI.MainBar.Bar.Size = UDim2.new(1,0,1,0)
	GUI.Parent = script
	DGUI.Parent = script
	A:Stop()
	I:Stop()
end)

Tool.Activated:Connect(function()
	local canHit = Remote.RequestAttack:InvokeServer()
	if not canHit then return end
	GUI.Enabled = true
	GUI.MainBar.Bar.Size = UDim2.new(1,0,1,0)
	local Tween = TS:Create(GUI.MainBar.Bar,TweenInfo.new(3.6,Enum.EasingStyle.Quad,Enum.EasingDirection.InOut) , {Size = UDim2.fromScale(0,1)})
	Tween:Play()
	Hitbox:HitStart()
	A:Play()
	A.Stopped:Wait()
	Hitbox:HitStop()
	Tween.Completed:Wait()
	GUI.Enabled = false
end)

Hitbox.OnHit:Connect(function(_, humanoid, result)
	Remote.OnHit:FireServer(humanoid, result.Position)
end)

local function UpdateDurability()

	local CurrentValue = Durability.Value
	local Formula = math.clamp(CurrentValue/MaxDurability, 0, 1)
	DGUI.MainBar.Bar:TweenSize(UDim2.new(Formula, 0, 1, 0), Enum.EasingDirection.Out, Enum.EasingStyle.Linear, 0.15, false)

end

UpdateDurability()
Durability.Changed:Connect(function()
	UpdateDurability()
end)
1 Like

I believe there could be a potential issue with the Destroy() line at the end, is it possible that’s what’s causing it to break?

By not working, do you mean there are certain parts of the script that do not run? Or the entire script does not run whatsoever? Have you tried printing something at the top of each script to see if it prints?

1 Like

Confirmed it, it seems like the server script in it stops working after the tool runs out of durability and is destroyed. I’m really not sure what’s going on.

1 Like

If the server script is the one that stops working perhaps it’s how you’re parenting the new tool to the player’s backpack? Do you parent the tool to the backpack on the server?

1 Like

It seems like that should be the case, here’s the collection service script just in case.

local ServerScriptService = game:GetService("ServerScriptService")
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local RemoteEvents = ReplicatedStorage:WaitForChild("RemoteEvents")
local Tool = ServerStorage:WaitForChild("Tools")

local Connection = {}

function applyCode(Inst)
	if not Inst:IsA("BasePart") then return end
	Connection[Inst] = {}
	local ProximityPrompt = Instance.new("ProximityPrompt")
	ProximityPrompt.Style = "Custom"
	ProximityPrompt.ActionText = Inst:GetAttribute("ActionText")
	ProximityPrompt.ObjectText = Inst:GetAttribute("ObjectText")
	ProximityPrompt.Parent = Inst
	table.insert(Connection[Inst], ProximityPrompt.Triggered:Connect(function(Player)
		if not Tool:FindFirstChild(Inst:GetAttribute("ItemName")) then return end

		if Player.Backpack:FindFirstChild(Inst:GetAttribute("ItemName")) and Player.Backpack[Inst:GetAttribute("ItemName")]:IsA("Tool") or Player.Character:FindFirstChild(Inst:GetAttribute("ItemName")) and Player.Character[Inst:GetAttribute("ItemName")]:IsA("Tool") then
			RemoteEvents:WaitForChild("Typewriter"):FireClient(Player, {Text="I already have this.", TextColor3=Color3.fromRGB(206, 206, 206), Duration = 3})
			return
		end
		local Sound = Instance.new("Sound")
		Sound.SoundId = "rbxassetid://4458750140"
		Sound.Parent = Inst
		Sound:Play()
		Tool[Inst:GetAttribute("ItemName")]:Clone().Parent = Player.Backpack
		Sound.Ended:Wait()
		Sound:Destroy()
	end))
end

task.spawn(function()
	local Tagged = CollectionService:GetTagged("PickItem")
	for count=1, #Tagged do
		local Inst = Tagged[count]
		task.spawn(function()
			applyCode(Inst)
		end)
	end
end)

CollectionService:GetInstanceAddedSignal("PickItem"):Connect(function(Inst)
	task.spawn(function()
		applyCode(Inst)
	end)
end)

CollectionService:GetInstanceRemovedSignal("PickItem"):Connect(function(Inst)
	if not Connection[Inst] then return end
	for count=1, #Connection[Inst] do
		local con = Connection[count]
		con:Disconnect()
	end
	Connection[Inst] = nil
end)

Yes, you are parenting it on the server. Without a doubt. If you don’t mind, can you send a place file to me through messages with the issue? All parts of your code – at first glance – look fine and it’s hard for me to narrow the issue if I can’t replicate it myself.

1 Like

Sorry it took a while, here it is. It should be R6 but in studio it keeps changing back to R15, so hopefully that won’t happen in game.

Hey! Sorry for taking so long. The issue stemmed from the GlobalCooldown module. I noticed that GlobalCooldown.IsOnCooldown always returned true after the first tool was broken. So I checked what was inside of the module and low and behold, it became clear. Here’s the issue: Threads that are created in a script are automatically cancelled whenever the script is destroyed. In your case, task.delay() is what is cancelled:

local function CancellableDelay(duration: number, func: (...any) -> any)
	local isCanncelled
	
	task.delay(duration, function() -- If the script is destroyed before this runs, then this thread will be cancelled
		if isCanncelled then return end
		func()
	end)
	
	return function()
		isCanncelled = true
	end
end

CooldownData[player] never actually gets set back to nil and therefore:

Remote.RequestAttack.OnServerInvoke = function(player: Player)
	--print(player ~= getOwner(), GlobalCooldown.IsOnCooldown(player))
	if player ~= getOwner() or GlobalCooldown.IsOnCooldown(player) then return false end -- Will return false since IsOnCooldown returns true

will always return false. An easy workaround would be to just use the Tool instance as the key instead of the player. When you destroy the tool, just set the index’s value to nil as well:

-- Updated functions
function GlobalCooldown.AddCooldown(player: Player, tool: Tool, duration: number)
	if CooldownData[player] and CooldownData[player][tool] then CooldownData[player][tool]() end
	
	CooldownData[player] = {
		[tool] = CancellableDelay(duration, function()
			CooldownData[player][tool] = nil
		end)
	}
end

function GlobalCooldown.IsOnCooldown(player: Player, tool: Tool)
	return CooldownData[player] ~= nil and CooldownData[player][tool] ~= nil
end

function GlobalCooldown.removeKey(player: Player, tool: Tool)
    if CooldownData[player] ~= nil then CooldownData[player][tool] = nil end
end

Also, just so you know, parenting an instance on the client is only replicated for that particular client:

-- Inside ur local script:
Tool.Equipped:Connect(function()
	print("equipped")
	GUI.Parent = Player.PlayerGui
	DGUI.Parent = Player.PlayerGui
	I:Play()
end)

It works fine the first time because you have these ScreenGUIs inside of StarterGui. It will error after the first time:

	if 0 >= DURABILITY.Value then
		print("it broke")
		player.PlayerGui.Cooldown:Destroy() -- Errors and prevents tool from being destroyed
		player.PlayerGui.Durability:Destroy()
		BreakSound.PlayOnRemove = true
		character:WaitForChild("Humanoid"):UnequipTools()
		task.wait()
		Tool:Destroy()
	end
1 Like

That fixed my first issue but now it appears the cooldown is a bit broken and the player can swing constantly lol.

Are you passing tool through the function?

— Assume these variables are all defined

— Checking the cooldown:
if GlobalCooldown.IsOnCooldown(player, tool) then
end

— Adding cooldown:
GlobalCooldown.AddCooldown(player, tool, duration)
1 Like

It really was that simple of a mistake. Thank you for all the help, I really appreciate it!

1 Like

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