I need help for scripting

Hello, I’ve created a melee system but I’m having a couple of problems with it. I’d really appreciate some help.

Issues:

  1. The cooldown doesn’t work properly after an attack. For example, when I click, the animation plays and then waits for the cooldown. However, during that cooldown period, I can still spam left-click and it keeps damaging the target as if there’s no cooldown at all. This makes the opponent’s health drop way too fast.
  2. When I hit an NPC, sometimes it instantly dies. Occasionally, it applies the correct damage value that I set in my config, but after a few seconds, the NPC suddenly drops dead as if it took fatal damage.

I’ll include both my server script and module script below.

MODULE SCRIPT


local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local RagdollModule = require(script.Parent:WaitForChild("RigsModule"))

local MeleeSystem = {}

local PlayerCooldown = {}
local HumanoidCooldown = {}

local function isCrit(chance)
	return math.random() < (chance or 0)
end

local function tagHumanoid(h, p)
	local tag = Instance.new("ObjectValue")
	tag.Name = "creator"
	tag.Value = p
	tag.Parent = h
	Debris:AddItem(tag, 2)
end

function MeleeSystem.StartAttack(player, tool)
	local config = require(tool:FindFirstChild("Config"))
	local now = tick()

	if PlayerCooldown[player] and now - PlayerCooldown[player] < config.HitCooldown then
		return
	end

	PlayerCooldown[player] = now

	local char = player.Character
	local hum = char and char:FindFirstChild("Humanoid")
	local animator = hum and hum:FindFirstChild("Animator")
	local anims = tool:FindFirstChild("Animations")

	
	if animator and anims then
		local swing = anims:FindFirstChild("swing2") or anims:FindFirstChild("Swing")
		if swing then
			local track = animator:LoadAnimation(swing)
			track:Play()
		end
	end

	local sfx = tool:FindFirstChild("sfx")
	if sfx and sfx:FindFirstChild("swing") then
		sfx.swing:Play()
	end

	local handle = tool:FindFirstChild("Handle")
	if not handle then return end

	local alreadyHit = {}
	local conn
	conn = handle.Touched:Connect(function(part)
		local targetChar = part:FindFirstAncestorOfClass("Model")
		if not targetChar or targetChar == char then return end

		local targetHum = targetChar:FindFirstChildOfClass("Humanoid")
		if not targetHum or targetHum.Health <= 0 or alreadyHit[targetHum] then return end

		local last = HumanoidCooldown[targetHum]
		if last and now - last < config.HitCooldown then return end
		HumanoidCooldown[targetHum] = now

		alreadyHit[targetHum] = true

		local damage = config.Damage or 25
		local crit = isCrit(config.CritChance)
		if crit then damage *= (config.CritMultiplier or 2) end

		targetHum:TakeDamage(damage)
		tagHumanoid(targetHum, player)

		if sfx and sfx:FindFirstChild("hitmaker") then
			local sound = sfx.hitmaker:Clone()
			sound.Parent = targetChar:FindFirstChild("HumanoidRootPart") or handle
			sound:Play()
			Debris:AddItem(sound, 2)
		end

		if crit and config.RagdollOnCrit then
			local motors = RagdollModule.CreateJoints(targetChar)
			RagdollModule.Ragdoll(targetChar)
			task.delay(3, function()
				if targetHum and targetHum.Health > 0 then
					RagdollModule.DestroyJoints(targetChar)
					RagdollModule.SetMotorsEnabled(motors, true)
					RagdollModule.UnRagdoll(targetChar)
				end
			end)
		end
	end)

	task.delay(config.SwingCooldown or 0.5, function()
		if conn then conn:Disconnect() end
	end)
end

function MeleeSystem.OnToolEquipped(player, tool)
	local char = player.Character
	local humanoid = char and char:FindFirstChild("Humanoid")
	local animator = humanoid and humanoid:FindFirstChild("Animator")
	local anims = tool:FindFirstChild("Animations")

	if not (animator and anims) then return end

	local equip = anims:FindFirstChild("equip")
	local idle = anims:FindFirstChild("Idle")

	if equip then
		local equipTrack = animator:LoadAnimation(equip)
		equipTrack:Play()

		-- equip bitince idle başlat
		if idle then
			equipTrack.Stopped:Connect(function()
				local idleTrack = animator:LoadAnimation(idle)
				idleTrack.Looped = true
				idleTrack:Play()
			end)
		end
	elseif idle then
		-- Eğer equip yoksa direk idle başlat
		local idleTrack = animator:LoadAnimation(idle)
		idleTrack.Looped = true
		idleTrack:Play()
	end

	local sfx = tool:FindFirstChild("sfx")
	if sfx and sfx:FindFirstChild("equip") then
		sfx.equip:Play()
	end
end


function MeleeSystem.OnToolUnequipped(player)
	local char = player.Character
	local humanoid = char and char:FindFirstChild("Humanoid")
	local animator = humanoid and humanoid:FindFirstChild("Animator")

	if animator then
		for _, track in ipairs(animator:GetPlayingAnimationTracks()) do
			local name = track.Animation.Name:lower()
			if name == "equip" or name == "swing" or name == "swing2" or name == "idle" then
				track:Stop()
			end
		end
	end
end

return MeleeSystem

SERVER SCRIPT

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local MeleeSystem = require(ReplicatedStorage:WaitForChild("MeleeSystem"))
local MeleeRemote = ReplicatedStorage:WaitForChild("MeleeRemote")

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(char)
		char.ChildAdded:Connect(function(tool)
			if tool:IsA("Tool") then
				

				tool.Equipped:Connect(function()
					MeleeSystem.OnToolEquipped(player, tool)
				end)

				tool.Unequipped:Connect(function()
					if MeleeSystem.OnToolUnequipped then
						MeleeSystem.OnToolUnequipped(player)
					end
				end)
			end
		end)
	end)
end)

MeleeRemote.OnServerEvent:Connect(function(player, action)
	if action == "StartAttack" then
		local char = player.Character
		local tool = char and char:FindFirstChildOfClass("Tool")
		if tool then
			MeleeSystem.StartAttack(player, tool)
		end
	end
end)

  1. Cooldown bug (stale time): You capture now = tick() once at the start of StartAttack and reuse it inside the Touched callback. That means HumanoidCooldown compares against a frozen timestamp; the per-target cooldown won’t expire correctly until the next StartAttack call.
  2. Possible nil index on animation stop: In OnToolUnequipped, you do track.Animation.Name:lower(). track.Animation can be nil (depends on how/where it was loaded), causing a runtime error.
  3. I recommend doing the debounce/cooldown on server to avoid exploits It may not be to important now but its a good habit
  4. require(Config) fragility: You require(tool:FindFirstChild("Config")) without verifying it’s a ModuleScript. If it’s missing or the wrong class, it errors.
  5. Event lifecycle leak/race: The handle.Touched connection only disconnects after SwingCooldown. If the tool is unequipped/destroyed early, the connection can fire on stale state. Also not cleared on unequip.
  6. Name mismatches / typos: You look for anims:FindFirstChild("Idle") (capital I) but later stop tracks by comparing lowercase "idle". If the actual asset is named idle (lowercase), equip won’t find it. Similarly, SFX child named "hitmaker" may be a typo (“hitmarker”?).
  7. Module name mismatch risk: local RagdollModule = require(script.Parent:WaitForChild("RigsModule"))—variable says Ragdoll, path says RigsModule. Easy to point at the wrong module.
  8. Ragdoll flow inconsistency: Calling DestroyJoints and then SetMotorsEnabled(motors, true)/UnRagdoll suggests an out-of-order or conflicting API usage (depends on your module). Likely to break reattachment.
  9. Self-hits & multi-hits edge cases: handle.Touched is noisy; you filter with alreadyHit[targetHum], but without collision groups you can still get odd triggers (e.g., accessories). Not a crash, but causes flaky hits.
  10. Minor: Players is required but unused; just a warning, not a crash.

#1 #2 #3 are the most important the others are just extras I can edit it if you want also
If you fix #1 and #2 first, you’ll likely eliminate the “red” runtime errors. The rest are stability/consistency issues.

1 Like

little :nerd_face:y lol _________________

thanks for the comment if u edit it could be more understandable for me :folded_hands:

here tell me if it works or not I did not test

local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local RagdollModule = require(script.Parent:WaitForChild("RigsModule"))

local MeleeSystem = {}

-- Use stable keys to avoid leaks when players leave
local PlayerCooldown = {}      -- keyed by player.UserId
local HumanoidCooldown = {}    -- keyed by Humanoid instance

local function isCrit(chance)
	return math.random() < (chance or 0)
end

local function tagHumanoid(h, p)
	local tag = Instance.new("ObjectValue")
	tag.Name = "creator"
	tag.Value = p
	tag.Parent = h
	Debris:AddItem(tag, 2)
end

function MeleeSystem.StartAttack(player, tool)
	local config = require(tool:FindFirstChild("Config"))

	-- Treat these as distinct knobs:
	--  - AttackCooldown: how often the player can start a new swing
	--  - HitCooldown: per-target i-frames to avoid multi-hits from one swing
	local AttackCooldown = config.SwingCooldown or config.HitCooldown or 0.5
	local HitCooldown    = config.HitCooldown or 0.25

	-- Player swing cooldown (use userId as key)
	local now = time()
	local pkey = player.UserId
	if PlayerCooldown[pkey] and (now - PlayerCooldown[pkey]) < AttackCooldown then
		return
	end
	PlayerCooldown[pkey] = now

	local char = player.Character
	local hum = char and char:FindFirstChildOfClass("Humanoid")
	local animator = hum and hum:FindFirstChildOfClass("Animator")
	local anims = tool:FindFirstChild("Animations")

	if animator and anims then
		local swing = anims:FindFirstChild("swing2") or anims:FindFirstChild("Swing")
		if swing then
			local track = animator:LoadAnimation(swing)
			track:Play()
		end
	end

	local sfx = tool:FindFirstChild("sfx")
	if sfx and sfx:FindFirstChild("swing") then
		sfx.swing:Play()
	end

	local handle = tool:FindFirstChild("Handle")
	if not handle then return end

	local alreadyHit = {}
	local conn
	conn = handle.Touched:Connect(function(part)
		local targetChar = part:FindFirstAncestorOfClass("Model")
		if not targetChar or targetChar == char then return end

		local targetHum = targetChar:FindFirstChildOfClass("Humanoid")
		if not targetHum or targetHum.Health <= 0 or alreadyHit[targetHum] then return end

		-- Per-target cooldown uses the CURRENT time, not the stale 'now'
		local t = time()
		local last = HumanoidCooldown[targetHum]
		if last and (t - last) < HitCooldown then return end
		HumanoidCooldown[targetHum] = t

		alreadyHit[targetHum] = true

		local damage = config.Damage or 25
		local crit = isCrit(config.CritChance)
		if crit then damage *= (config.CritMultiplier or 2) end

		targetHum:TakeDamage(damage)
		tagHumanoid(targetHum, player)

		if sfx and sfx:FindFirstChild("hitmaker") then
			local sound = sfx.hitmaker:Clone()
			sound.Parent = targetChar:FindFirstChild("HumanoidRootPart") or handle
			sound:Play()
			Debris:AddItem(sound, 2)
		end

		if crit and config.RagdollOnCrit then
			local motors = RagdollModule.CreateJoints(targetChar)
			RagdollModule.Ragdoll(targetChar)
			task.delay(3, function()
				if targetHum and targetHum.Health > 0 then
					RagdollModule.DestroyJoints(targetChar)
					RagdollModule.SetMotorsEnabled(motors, true)
					RagdollModule.UnRagdoll(targetChar)
				end
			end)
		end
	end)

	-- Disconnect the hitbox window after the swing cooldown window
	task.delay(AttackCooldown, function()
		if conn then conn:Disconnect() end
	end)
end

function MeleeSystem.OnToolEquipped(player, tool)
	local char = player.Character
	local humanoid = char and char:FindFirstChildOfClass("Humanoid")
	local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")
	local anims = tool:FindFirstChild("Animations")

	if not (animator and anims) then return end

	local equip = anims:FindFirstChild("equip")
	local idle = anims:FindFirstChild("Idle")

	if equip then
		local equipTrack = animator:LoadAnimation(equip)
		equipTrack:Play()

		if idle then
			equipTrack.Stopped:Connect(function()
				local idleTrack = animator:LoadAnimation(idle)
				idleTrack.Looped = true
				idleTrack:Play()
			end)
		end
	elseif idle then
		local idleTrack = animator:LoadAnimation(idle)
		idleTrack.Looped = true
		idleTrack:Play()
	end

	local sfx = tool:FindFirstChild("sfx")
	if sfx and sfx:FindFirstChild("equip") then
		sfx.equip:Play()
	end
end

function MeleeSystem.OnToolUnequipped(player)
	local char = player.Character
	local humanoid = char and char:FindFirstChildOfClass("Humanoid")
	local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")

	if animator then
		for _, track in ipairs(animator:GetPlayingAnimationTracks()) do
			local anim = track.Animation
			local name = (anim and anim.Name or ""):lower()
			if name == "equip" or name == "swing" or name == "swing2" or name == "idle" then
				track:Stop()
			end
		end
	end
end

return MeleeSystem

thanks its fixed but there is a problem about the damage it takes all health from the humanoid after the swing or hit i didnt get it