Strange issue with my game

So I’ve discovered a bug with my game that if left unfixed would make my game practically unplayable.
I have a simple fireball tool that will fire a remote event to a server script inside starter character scripts to launch a fireball. It works perfectly, however if you use it enough the fireball will start to not launch at all and keep you permastunned. Here are my scripts:

Server side (StarterCharacterScripts):

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local SkillEvent = ReplicatedStorage.FireSkillUsed
local SkillEndedEvent = ReplicatedStorage.FireSkillEnded
local assets = ReplicatedStorage.Assets.Fire
local MouseRequest = ReplicatedStorage:WaitForChild("MouseRequest") 
local MouseReply = ReplicatedStorage:WaitForChild("MouseReply")

local Players = game:GetService("Players")
local myCharacter = script.Parent
local myPlayer = Players:GetPlayerFromCharacter(myCharacter)
local Humanoid = myCharacter.Humanoid

local SecureData = require(ServerScriptService:WaitForChild("SecureData"))
local Stun = require(ReplicatedStorage:WaitForChild("StunManager"))

local AnimID1 = "rbxassetid://132127577863396"
local Move1CD = 3
local Move1OnCd = false

local AnimID2 = "rbxassetid://132127577863396"
local Move2CD = 3
local Move2OnCd = false


local AnimID3 = "rbxassetid://132127577863396"
local Move3CD = 3
local Move3OnCd = false


local AnimID4 = "rbxassetid://132127577863396"
local Move4CD = 3
local Move4OnCd = false

SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
	if Player ~= myPlayer then
		return
	end
	if Move1OnCd == false then
		Move1OnCd = true
		task.delay(Move1CD - 0.01, function()
			Move1OnCd = false
		end)
		local Values = Player.Character:FindFirstChild("Values")
		if Values.Stunned.Value == false then
		if Values.MoveStunned.Value == false then
		
		Stun.StartMoveStun(Player.Character)

	if SkillName == "Fireball" then
		local VFX = assets.FireballModel.Explosion
		local Projectile = assets.FireballModel
		local anim = Instance.new("Animation")
		anim.AnimationId = AnimID1
		local track = Humanoid:LoadAnimation(anim)
		track:Play()
		Humanoid.WalkSpeed = 5
		local ProjectileClone = Projectile:Clone()
		
		local stats = SecureData[Player.UserId]
		if stats then
			if stats.Level <= 999 then
			stats.XP = (stats.XP or 0) + 150
			else
				
			end
		end
		
		local Hitbox = ProjectileClone.Hitbox
		Hitbox.Sender.Value = Player.Name
		Hitbox.Damage.Value = 10 + ((SecureData[Player.UserId].Level) * 0.1)

		task.wait(1.4)
		
		ProjectileClone.Parent = workspace.VFX
		ProjectileClone.CFrame = myCharacter["Right Arm"].CFrame
		
		Hitbox.Parent = ReplicatedStorage
		
		MouseRequest:FireClient(myPlayer)
		
		MouseReply.OnServerEvent:Once(function(returningPlayer, mousePosition) 
			if returningPlayer ~= myPlayer then 
				return 
			end
			

			local origin = myCharacter["Right Arm"].Position
			local direction = (mousePosition - origin).Unit * 100

			local raycastParams = RaycastParams.new()
			raycastParams.FilterDescendantsInstances = {
				game.Workspace.TestPart,
				ProjectileClone,
				myCharacter["Right Arm"],
				myCharacter["Left Arm"],
				myCharacter["Right Leg"],
				myCharacter["Left Leg"],
				myCharacter.Head,
				myCharacter.HumanoidRootPart
			}
			raycastParams.FilterType = Enum.RaycastFilterType.Exclude
			raycastParams.IgnoreWater = true

			local raycastResult = workspace:Raycast(origin, direction, raycastParams)

			local hitPos = raycastResult and raycastResult.Position or (origin + direction)
			
			local distance = (hitPos - origin).Magnitude
			local speed = 100
			local duration = distance / speed

			local tween = game:GetService("TweenService"):Create(
				ProjectileClone, 
				TweenInfo.new(duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
				{Position = hitPos}
			)
			tween:Play()
			tween.Completed:Once(function()
				ProjectileClone.FireballVFX.BallTrail.Enabled = false
				ProjectileClone.FireballVFX.Embers.Enabled = false
				ProjectileClone.FireballVFX.Main.Enabled = false
				ProjectileClone.Explosion:Emit()
				Hitbox.Parent = workspace.Hitboxes
				Hitbox.Position = ProjectileClone.Position
				task.delay(0.1, function()
					Hitbox.Position = Vector3.new(-999999999, 999999999, 999999999)
				end)
				game.Debris:AddItem(Hitbox, 5)
			end)
				SkillEndedEvent:FireClient(Player, SkillName)
				Player:SetAttribute("IFrames", false)
				Stun.EndMoveStun(Player.Character)
				Humanoid.WalkSpeed = 16
		end)
	else
		warn("Skill "..SkillName.." is invalid.")
				end
			end
		end
	end
end)

Client side (Fireball tool):

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SkillEvent = ReplicatedStorage.FireSkillUsed
local Skill = script.Parent
local players = game:GetService("Players")
local Player = players.LocalPlayer
local Character = Player.Character
local Root = Character.HumanoidRootPart
local SkillEndedEvent = ReplicatedStorage.FireSkillEnded

local CD = 3
local OnCD = false

local Mouse = Player:GetMouse()

local RunService = game:GetService("RunService")

local UsingMove = false

Skill.Activated:Connect(function()
	if OnCD == false then
		if Character:WaitForChild("Values").Stunned.Value == false then
			if Character:WaitForChild("Values").MoveStunned.Value == false then
	Character.Humanoid.AutoRotate = false
	OnCD = true
	SkillEvent:FireServer(Skill.Name)
	UsingMove = true
	wait(CD)
	OnCD = false
			end
		end
	end
end)

RunService.RenderStepped:Connect(function()
	if UsingMove == true then
	local MousePos = Mouse.Hit.Position
	local RootPos, MousePos = Root.Position, Mouse.Hit.Position
	Root.CFrame = CFrame.new(RootPos, Vector3.new(MousePos.X, RootPos.Y, MousePos.Z))
	end
end)

SkillEndedEvent.OnClientEvent:Connect(function(SkillName)
	if SkillName == Skill.Name then
			UsingMove = false
			Character.Humanoid.AutoRotate = true
	end
end)

It seems that every time i throw one there’s a small chance for this to happen. Any and all help is appreciated. Thank you!

you’re using MouseReply.OnServerEvent:Once inside your fireball handler, so after the first shot the callback never re-registers, meaning you never fire the skill ended event or call Stun.EndMoveStun, leaving the player permastunned, move your reply listener outside
or use :Connect + explicit disconnect so it runs every time you launch a fireball.

Wouldn’t that cause memory leaks? If not then how would I implement this?

:Once only disconnects after it fires if the client never replies,
those listeners pile up until GC causing a mild leak.
I guess you could send the mouse position in your original FireServer call instead of doing a request.

What do you mean by that and what would I change?

you’d grab the mouse hit locally and send it with your fire event (Client)

local mousePos = Mouse.Hit.Position  
SkillEvent:FireServer("Fireball", mousePos)

and update your listener to accept that extra argument and remove (Server)

SkillEvent.OnServerEvent:Connect(function(player, skillName, mousePos)
-- spawn fireball toward mousePos
end)

Yeah but I only want to get the mouse’s position when the raycast actually fires

ok then use a single permanent MouseReply listener plus a unique shot ID to pair each fireball request with its fresh mouse-position callback, so you never spawn one off listeners that could leak

what do you mean? could you please give me an example?

I guess I can spoonfeed you a little :tongue:, keep one permanent MouseReply listener on the server and tag each fireball request with a unique ID so the clients reply invokes and immediately removes just that one callback, giving you new mouse data without ever leaking listeners.

-- Server
local HttpService = game:GetService("HttpService")
local pending = {}
ReplicatedStorage.MouseReply.OnServerEvent:Connect(function(_, pos, id)
    pending[id](pos); pending[id]=nil
end)
-- when firing
local id = HttpService:GenerateGUID(false)
pending[id] = function(pos)
-- spawn/tween fireball toward pos
end
ReplicatedStorage.MouseRequest:FireClient(player, id)
-- Client // top of your tool script
local Mouse = game.Players.LocalPlayer:GetMouse()
ReplicatedStorage.MouseRequest.OnClientEvent:Connect(function(id)
    ReplicatedStorage.MouseReply:FireServer(Mouse.Hit.Position, id)
end)

just one server listener plus a tiny pending[id] callback per shot, so there’s zero leak risk

Thanks! Would this be the proper way to do it?

--variables

local HttpService = game:GetService("HttpService")
local pending = {}
MouseReply.OnServerEvent:Connect(function(_, pos, id)
	pending[id](pos); pending[id] = nil
end)

SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
	if Player ~= myPlayer then
		return
	end
	if Move1OnCd == false then
		Move1OnCd = true
		task.delay(Move1CD - 0.01, function()
			Move1OnCd = false
		end)
		local Values = Player.Character:FindFirstChild("Values")
		if Values.Stunned.Value == false then
		if Values.MoveStunned.Value == false then
		
		Stun.StartMoveStun(Player.Character)

	if SkillName == "Fireball" then
		local Projectile = assets.FireballModel
		local anim = Instance.new("Animation")
		anim.AnimationId = AnimID1
		local track = Humanoid:LoadAnimation(anim)
		track:Play()
		Humanoid.WalkSpeed = 5
		local ProjectileClone = Projectile:Clone()
		
		local stats = SecureData[Player.UserId]
		if stats then
			if stats.Level <= 999 then
			stats.XP = (stats.XP or 0) + 150
			else
				
			end
		end
		
		local Hitbox = ProjectileClone.Hitbox
		Hitbox.Sender.Value = Player.Name
		Hitbox.Damage.Value = 10 + ((SecureData[Player.UserId].Level) * 0.1)
		
		local Charge = assets.GeneralSFX.FireballCharge:Clone()
		Charge.Parent = workspace.SFX
		Charge.Position = Player.Character:FindFirstChild("HumanoidRootPart").Position
		Charge.SoundEffect:Play()
		
		Charge.SoundEffect.Ended:Connect(function()
			Charge:Destroy()
		end)

		local HandVFX = assets.GeneralVFX.HandFireFX:Clone()
		HandVFX.Parent = Player.Character["Right Arm"]
		HandVFX.CFrame = Player.Character["Right Arm"].CFrame
		HandVFX.RightArmWeld.Part0 = Player.Character["Right Arm"]
		
		
		task.wait(1.4)
		
		ProjectileClone.Parent = workspace.VFX
		ProjectileClone.CFrame = myCharacter["Right Arm"].CFrame
		
		Hitbox.Parent = ReplicatedStorage
		
		local id = HttpService:GenerateGUID(false)
		pending[id] = function(pos)
		end
		MouseRequest:FireClient(Player, id)
			MouseReply.OnServerEvent:Once(function(returningPlayer, mousePosition) 
				if returningPlayer ~= myPlayer then 
					return 
				end
--rest of script

I’m asking this because it works but i get an error “Workspace.jilly3678.FireManager:38: attempt to call a nil value” so I don’t know

you’re on the right track but the error happens because you’re using :Once, which we want to avoid, as it only listens once, causing the callback to be unavailable after the first fireball,

-- //Server//
local HttpService = game:GetService("HttpService")
local pending = {}

MouseReply.OnServerEvent:Connect(function(_, pos, id)
    if pending[id] then
        pending[id](pos)
        pending[id] = nil
    end
end)

-- when firing
SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
    if SkillName == "Fireball" then
        local id = HttpService:GenerateGUID(false)
        pending[id] = function(pos) 
-- handle mouse pos here
        end
        MouseRequest:FireClient(Player, id)
    end
end)

the listener works for every fireball and prevents errors or memory leaks :tongue:

I changed the :Once to :Connect and it still has the same error

Edit: Never mind I don’t get the error, but multiple fireballs shoot with one use.

It’s likely that the callback isn’t cleared after being used, add pending[id] = nil after the callback is triggered to prevent multiple fireballs

MouseReply.OnServerEvent:Connect(function(_, pos, id)
    if pending[id] then
        pending[id](pos)
        pending[id] = nil
    end
end)

should stop multiple fireballs
from being fired on one use

This is my script now:

--Variables
MouseReply.OnServerEvent:Connect(function(_, pos, id)
    if pending[id] then
        pending[id](pos)
        pending[id] = nil
    end
end)
SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
	if Player ~= myPlayer then
		return
	end
	if Move1OnCd == false then
		Move1OnCd = true
		task.delay(Move1CD - 0.01, function()
			Move1OnCd = false
		end)
		local Values = Player.Character:FindFirstChild("Values")
		if Values.Stunned.Value == false then
		if Values.MoveStunned.Value == false then
		
		Stun.StartMoveStun(Player.Character)

	if SkillName == "Fireball" then
		local Projectile = assets.FireballModel
		local anim = Instance.new("Animation")
		anim.AnimationId = AnimID1
		local track = Humanoid:LoadAnimation(anim)
		track:Play()
		Humanoid.WalkSpeed = 5
		local ProjectileClone = Projectile:Clone()
		
		local stats = SecureData[Player.UserId]
		if stats then
			if stats.Level <= 999 then
			stats.XP = (stats.XP or 0) + 150
			else
				
			end
		end
		
		local Hitbox = ProjectileClone.Hitbox
		Hitbox.Sender.Value = Player.Name
		Hitbox.Damage.Value = 10 + ((SecureData[Player.UserId].Level) * 0.1)
		
		local Charge = assets.GeneralSFX.FireballCharge:Clone()
		Charge.Parent = workspace.SFX
		Charge.Position = Player.Character:FindFirstChild("HumanoidRootPart").Position
		Charge.SoundEffect:Play()
		
		Charge.SoundEffect.Ended:Connect(function()
			Charge:Destroy()
		end)

		local HandVFX = assets.GeneralVFX.HandFireFX:Clone()
		HandVFX.Parent = Player.Character["Right Arm"]
		HandVFX.CFrame = Player.Character["Right Arm"].CFrame
		HandVFX.RightArmWeld.Part0 = Player.Character["Right Arm"]
		
		
		task.wait(1.4)
		
		ProjectileClone.Parent = workspace.VFX
		ProjectileClone.CFrame = myCharacter["Right Arm"].CFrame
		
		Hitbox.Parent = ReplicatedStorage
		
		local id = HttpService:GenerateGUID(false)
		pending[id] = function(mousePosition)
		end
		MouseRequest:FireClient(Player, id)
			MouseReply.OnServerEvent:Connect(function(_, mousePosition, id)
				if pending[id] then
					pending[id](mousePosition)
					pending[id] = nil
				end
			end)

local origin = myCharacter["Right Arm"].Position
			local direction = (mousePosition - origin).Unit * 200 --there are red lines under mousePosition here
--Rest of script

–EDIT: I have removed the end) and it works a little bit now. It still shoots multiple at one time and now theres another error: The Parent property of Hitbox is locked, current parent: NULL, new parent Hitboxes

that’s cause you’re trying to parent a Hitbox that was either destroyed or not cloned properly, newly clone the Hitbox each time you need it.

local id = HttpService:GenerateGUID(false)
pending[id] = function(mousePosition)
    local Hitbox = ProjectileClone.Hitbox:Clone()
    Hitbox.Sender.Value = Player.Name
    Hitbox.Damage.Value = 10 + (SecureData[Player.UserId].Level * 0.1)
    Hitbox.Parent = ReplicatedStorage
end
MouseRequest:FireClient(Player, id)

Thanks. I changed where i stored the hitbox and added this to the tween:completed function:

local Hitbox = assets.FireballHitbox:Clone()
				Hitbox.Sender.Value = Player.Name
				Hitbox.Damage.Value = 10 + (SecureData[Player.UserId].Level * 0.1)
				Hitbox.Parent = workspace.Hitboxes
				Hitbox.Position = ProjectileClone.Position

but it’s still shooting multiple fireballs at one time. Every time i throw a fireball it adds another fireball to the amount of fireballs the next throw is going to shoot.

Heres my entire script so far. Let me know what i need to change:

--Note that I added sound effects and other things since the first time I posted the full script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local SkillEvent = ReplicatedStorage.FireSkillUsed
local SkillEndedEvent = ReplicatedStorage.FireSkillEnded
local assets = ReplicatedStorage.Assets.Fire
local MouseRequest = ReplicatedStorage:WaitForChild("MouseRequest") 
local MouseReply = ReplicatedStorage:WaitForChild("MouseReply")

local Players = game:GetService("Players")
local myCharacter = script.Parent
local myPlayer = Players:GetPlayerFromCharacter(myCharacter)
local Humanoid = myCharacter.Humanoid

local SecureData = require(ServerScriptService:WaitForChild("SecureData"))
local Stun = require(ReplicatedStorage:WaitForChild("StunManager"))

local AnimID1 = "rbxassetid://132127577863396"
local Move1CD = 3
local Move1OnCd = false

local AnimID2 = "rbxassetid://132127577863396"
local Move2CD = 3
local Move2OnCd = false


local AnimID3 = "rbxassetid://132127577863396"
local Move3CD = 3
local Move3OnCd = false


local AnimID4 = "rbxassetid://132127577863396"
local Move4CD = 3
local Move4OnCd = false

local HttpService = game:GetService("HttpService")
local pending = {}

MouseReply.OnServerEvent:Connect(function(_, pos, id)
    if pending[id] then
        pending[id](pos)
        pending[id] = nil
    end
end)
SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
	if Player ~= myPlayer then
		return
	end
	if Move1OnCd == false then
		Move1OnCd = true
		task.delay(Move1CD - 0.01, function()
			Move1OnCd = false
		end)
		local Values = Player.Character:FindFirstChild("Values")
		if Values.Stunned.Value == false then
		if Values.MoveStunned.Value == false then
		
		Stun.StartMoveStun(Player.Character)

	if SkillName == "Fireball" then
		local Projectile = assets.FireballModel
		local anim = Instance.new("Animation")
		anim.AnimationId = AnimID1
		local track = Humanoid:LoadAnimation(anim)
		track:Play()
		Humanoid.WalkSpeed = 5
		local ProjectileClone = Projectile:Clone()
		
		local stats = SecureData[Player.UserId]
		if stats then
			if stats.Level <= 999 then
			stats.XP = (stats.XP or 0) + 150
			else
				
			end
		end
		
		local Charge = assets.GeneralSFX.FireballCharge:Clone()
		Charge.Parent = workspace.SFX
		Charge.Position = Player.Character:FindFirstChild("HumanoidRootPart").Position
		Charge.SoundEffect:Play()
		
		Charge.SoundEffect.Ended:Connect(function()
			Charge:Destroy()
		end)

		local HandVFX = assets.GeneralVFX.HandFireFX:Clone()
		HandVFX.Parent = Player.Character["Right Arm"]
		HandVFX.CFrame = Player.Character["Right Arm"].CFrame
		HandVFX.RightArmWeld.Part0 = Player.Character["Right Arm"]
		
		
		task.wait(1.4)
		
		ProjectileClone.Parent = workspace.VFX
		ProjectileClone.CFrame = myCharacter["Right Arm"].CFrame
		
		local id = HttpService:GenerateGUID(false)
		pending[id] = function(mousePosition)
			end
		MouseRequest:FireClient(Player, id)
			MouseReply.OnServerEvent:Connect(function(_, mousePosition, id)
				if pending[id] then
					pending[id](mousePosition)
					pending[id] = nil
				end
			
			local origin = myCharacter["Right Arm"].Position
			local direction = (mousePosition - origin).Unit * 200

			local raycastParams = RaycastParams.new()
			raycastParams.FilterDescendantsInstances = {
				game.Workspace.TestPart,
				ProjectileClone,
				myCharacter["Right Arm"],
				myCharacter["Left Arm"],
				myCharacter["Right Leg"],
				myCharacter["Left Leg"],
				myCharacter.Head,
				myCharacter.HumanoidRootPart
			}
			raycastParams.FilterType = Enum.RaycastFilterType.Exclude
			raycastParams.IgnoreWater = true

			local raycastResult = workspace:Raycast(origin, direction, raycastParams)

			local hitPos = raycastResult and raycastResult.Position or (origin + direction)
			
			local distance = (hitPos - origin).Magnitude
			local speed = 100
			local duration = distance / speed

			local tween = game:GetService("TweenService"):Create(
				ProjectileClone, 
				TweenInfo.new(duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
				{Position = hitPos}
			)
			tween:Play()
			
			ProjectileClone.FireballLaunch:Play()
			ProjectileClone.FireballCrackle:Play()
			
			local HandFX = HandVFX:GetChildren()
				for i, v in pairs(HandFX) do
					if v:IsA("ParticleEmitter") then
						v.Enabled = false
					end
				end
			game.Debris:AddItem(HandVFX, 3)
			
			tween.Completed:Connect(function()
				
			local FireballVFX = ProjectileClone.FireballVFX:GetChildren()
				for i, v in pairs(FireballVFX) do
					if v:IsA("ParticleEmitter") then
						v.Enabled = false
					end
				end
				
			local ExplosionVFX = ProjectileClone.ExplosionVFX:GetChildren()
				for i, v in pairs(ExplosionVFX) do
					if v:IsA("ParticleEmitter") then
						v:Emit(v.Rate)
					end
				end
				
				ProjectileClone.FireballCrackle:Stop()
				ProjectileClone.ExplosionSound:Play()
				
				local Hitbox = assets.FireballHitbox:Clone()
				Hitbox.Sender.Value = Player.Name
				Hitbox.Damage.Value = 10 + (SecureData[Player.UserId].Level * 0.1)
				Hitbox.Parent = workspace.Hitboxes
				Hitbox.Position = ProjectileClone.Position
				
				task.delay(0.1, function()
					Hitbox.Position = Vector3.new(-999999999, 999999999, 999999999)
				end)
				game.Debris:AddItem(Hitbox, 5)
			end)
				SkillEndedEvent:FireClient(Player, SkillName)
				Player:SetAttribute("IFrames", false)
				Stun.EndMoveStun(Player.Character)
				Humanoid.WalkSpeed = 16
		end)
	else
		warn("Skill "..SkillName.." is invalid.")
				end
			end
		end
	end
end)

Thanks for showing me the whole block.
It seems that you’re creating a new MouseReply.OnServerEvent listener every time the skill is used tho… move the listener definition outside the SkillEvent.OnServerEvent function so only one listener is active at all times

-- define listener at top
MouseReply.OnServerEvent:Connect(function(_, pos, id)
    if pending[id] then
        pending[id](pos)
        pending[id] = nil
    end
end)

SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
    if SkillName == "Fireball" then
        local id = HttpService:GenerateGUID(false)
        pending[id] = function(mousePosition)
-- handle fireball launch
        end
        MouseRequest:FireClient(Player, id)
    end
end)

I believe this should prevent multiple fireballs from firing.

Now it doesn’t even begin to fire. I think i did something wrong, I’ll send the new script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local SkillEvent = ReplicatedStorage.FireSkillUsed
local SkillEndedEvent = ReplicatedStorage.FireSkillEnded
local assets = ReplicatedStorage.Assets.Fire
local MouseRequest = ReplicatedStorage:WaitForChild("MouseRequest") 
local MouseReply = ReplicatedStorage:WaitForChild("MouseReply")

local Players = game:GetService("Players")
local myCharacter = script.Parent
local myPlayer = Players:GetPlayerFromCharacter(myCharacter)
local Humanoid = myCharacter.Humanoid

local SecureData = require(ServerScriptService:WaitForChild("SecureData"))
local Stun = require(ReplicatedStorage:WaitForChild("StunManager"))

local AnimID1 = "rbxassetid://132127577863396"
local Move1CD = 3
local Move1OnCd = false

local AnimID2 = "rbxassetid://132127577863396"
local Move2CD = 3
local Move2OnCd = false


local AnimID3 = "rbxassetid://132127577863396"
local Move3CD = 3
local Move3OnCd = false


local AnimID4 = "rbxassetid://132127577863396"
local Move4CD = 3
local Move4OnCd = false

local HttpService = game:GetService("HttpService")
local pending = {}

MouseReply.OnServerEvent:Connect(function(_, mousePosition, id)
	if pending[id] then
		pending[id](mousePosition)
		pending[id] = nil
	end
end)

SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
	if Player ~= myPlayer then
		return
	end
	local id = HttpService:GenerateGUID(false)
	pending[id] = function(mousePosition)
	if Move1OnCd == false then
		
		Move1OnCd = true
		task.delay(Move1CD - 0.01, function()
			Move1OnCd = false
		end)
		local Values = Player.Character:FindFirstChild("Values")
		if Values.Stunned.Value == false then
		if Values.MoveStunned.Value == false then
		
		Stun.StartMoveStun(Player.Character)

	if SkillName == "Fireball" then
		local Projectile = assets.FireballModel
		local anim = Instance.new("Animation")
		anim.AnimationId = AnimID1
		local track = Humanoid:LoadAnimation(anim)
		track:Play()
		Humanoid.WalkSpeed = 5
		local ProjectileClone = Projectile:Clone()
		
		local stats = SecureData[Player.UserId]
		if stats then
			if stats.Level <= 999 then
			stats.XP = (stats.XP or 0) + 150
			else
				
			end
		end
		
		local Charge = assets.GeneralSFX.FireballCharge:Clone()
		Charge.Parent = workspace.SFX
		Charge.Position = Player.Character:FindFirstChild("HumanoidRootPart").Position
		Charge.SoundEffect:Play()
		
		Charge.SoundEffect.Ended:Connect(function()
			Charge:Destroy()
		end)

		local HandVFX = assets.GeneralVFX.HandFireFX:Clone()
		HandVFX.Parent = Player.Character["Right Arm"]
		HandVFX.CFrame = Player.Character["Right Arm"].CFrame
		HandVFX.RightArmWeld.Part0 = Player.Character["Right Arm"]
		
		
		task.wait(1.4)
		
		ProjectileClone.Parent = workspace.VFX
		ProjectileClone.CFrame = myCharacter["Right Arm"].CFrame
		
			
			local origin = myCharacter["Right Arm"].Position
			local direction = (mousePosition - origin).Unit * 200

			local raycastParams = RaycastParams.new()
			raycastParams.FilterDescendantsInstances = {
				game.Workspace.TestPart,
				ProjectileClone,
				myCharacter["Right Arm"],
				myCharacter["Left Arm"],
				myCharacter["Right Leg"],
				myCharacter["Left Leg"],
				myCharacter.Head,
				myCharacter.HumanoidRootPart
			}
			raycastParams.FilterType = Enum.RaycastFilterType.Exclude
			raycastParams.IgnoreWater = true

			local raycastResult = workspace:Raycast(origin, direction, raycastParams)

			local hitPos = raycastResult and raycastResult.Position or (origin + direction)
			
			local distance = (hitPos - origin).Magnitude
			local speed = 100
			local duration = distance / speed

			local tween = game:GetService("TweenService"):Create(
				ProjectileClone, 
				TweenInfo.new(duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
				{Position = hitPos}
			)
			tween:Play()
			
			ProjectileClone.FireballLaunch:Play()
			ProjectileClone.FireballCrackle:Play()
			
			local HandFX = HandVFX:GetChildren()
				for i, v in pairs(HandFX) do
					if v:IsA("ParticleEmitter") then
						v.Enabled = false
					end
				end
			game.Debris:AddItem(HandVFX, 3)
			
			tween.Completed:Connect(function()
			local FireballVFX = ProjectileClone.FireballVFX:GetChildren()
				for i, v in pairs(FireballVFX) do
					if v:IsA("ParticleEmitter") then
						v.Enabled = false
					end
				end
				
			local ExplosionVFX = ProjectileClone.ExplosionVFX:GetChildren()
				for i, v in pairs(ExplosionVFX) do
					if v:IsA("ParticleEmitter") then
						v:Emit(v.Rate)
					end
				end
				
				ProjectileClone.FireballCrackle:Stop()
				ProjectileClone.ExplosionSound:Play()
				
				local Hitbox = assets.FireballHitbox:Clone()
				Hitbox.Sender.Value = Player.Name
				Hitbox.Damage.Value = 10 + (SecureData[Player.UserId].Level * 0.1)
				Hitbox.Parent = workspace.Hitboxes
				Hitbox.Position = ProjectileClone.Position
				
				task.delay(0.1, function()
					Hitbox.Position = Vector3.new(-999999999, 999999999, 999999999)
				end)
				game.Debris:AddItem(Hitbox, 5)
				SkillEndedEvent:FireClient(Player, SkillName)
				Player:SetAttribute("IFrames", false)
				Stun.EndMoveStun(Player.Character)
				Humanoid.WalkSpeed = 16
			end)
		end
		MouseRequest:FireClient(Player, id)
	else
		warn("Skill "..SkillName.." is invalid.")
				end
			end
		end
	end
end)

Oh boy, I did say for MouseReply.OnServerEvent to be defined only once at the top to prevent multiple listeners from accumulating

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local SkillEvent = ReplicatedStorage.FireSkillUsed
local SkillEndedEvent = ReplicatedStorage.FireSkillEnded
local assets = ReplicatedStorage.Assets.Fire
local MouseRequest = ReplicatedStorage:WaitForChild("MouseRequest")
local MouseReply = ReplicatedStorage:WaitForChild("MouseReply")
local Players = game:GetService("Players")
local myCharacter = script.Parent
local myPlayer = Players:GetPlayerFromCharacter(myCharacter)
local Humanoid = myCharacter.Humanoid
local SecureData = require(ServerScriptService:WaitForChild("SecureData"))
local Stun = require(ReplicatedStorage:WaitForChild("StunManager"))
local HttpService = game:GetService("HttpService")
local pending = {}

-- listener at the top of the script before skill event
MouseReply.OnServerEvent:Connect(function(_, mousePosition, id)
    if pending[id] then
        pending[id](mousePosition) -- trigger callback only once
        pending[id] = nil -- clear after use
    end
end)

-- skill event handler
SkillEvent.OnServerEvent:Connect(function(Player, SkillName)
    if Player ~= myPlayer then return end -- make sure skill is for correct player

    local id = HttpService:GenerateGUID(false)
    pending[id] = function(mousePosition)
        if Move1OnCd == false then
            Move1OnCd = true
            task.delay(Move1CD - 0.01, function() Move1OnCd = false end)

            local Values = Player.Character:FindFirstChild("Values")
            if Values.Stunned.Value == false and Values.MoveStunned.Value == false then
                Stun.StartMoveStun(Player.Character)

                if SkillName == "Fireball" then
-- fireball creation and animation logic
                    local Projectile = assets.FireballModel
                    local anim = Instance.new("Animation")
                    anim.AnimationId = AnimID1
                    local track = Humanoid:LoadAnimation(anim)
                    track:Play()
                    Humanoid.WalkSpeed = 5
                    local ProjectileClone = Projectile:Clone()

-- handle sound & VFX
                    local Charge = assets.GeneralSFX.FireballCharge:Clone()
                    Charge.Parent = workspace.SFX
                    Charge.Position = Player.Character:FindFirstChild("HumanoidRootPart").Position
                    Charge.SoundEffect:Play()

                    Charge.SoundEffect.Ended:Connect(function() Charge:Destroy() end)

                    local HandVFX = assets.GeneralVFX.HandFireFX:Clone()
                    HandVFX.Parent = Player.Character["Right Arm"]
                    HandVFX.CFrame = Player.Character["Right Arm"].CFrame
                    HandVFX.RightArmWeld.Part0 = Player.Character["Right Arm"]

                    task.wait(1.4)

                    ProjectileClone.Parent = workspace.VFX
                    ProjectileClone.CFrame = myCharacter["Right Arm"].CFrame

-- handle raycast & tween to target pos
                    local origin = myCharacter["Right Arm"].Position
                    local direction = (mousePosition - origin).Unit * 200
                    local raycastResult = workspace:Raycast(origin, direction, RaycastParams.new())
                    local hitPos = raycastResult and raycastResult.Position or (origin + direction)
                    local distance = (hitPos - origin).Magnitude
                    local duration = distance / 100
                    local tween = game:GetService("TweenService"):Create(
                        ProjectileClone, 
                        TweenInfo.new(duration, Enum.EasingStyle.Linear, Enum.EasingDirection.Out),
                        {Position = hitPos}
                    )
                    tween:Play()

                    tween.Completed:Connect(function()
-- handle explosion and hitbox
                        local Hitbox = assets.FireballHitbox:Clone()
                        Hitbox.Sender.Value = Player.Name
                        Hitbox.Damage.Value = 10 + (SecureData[Player.UserId].Level * 0.1)
                        Hitbox.Parent = workspace.Hitboxes
                        Hitbox.Position = ProjectileClone.Position

-- clean up
                        task.delay(0.1, function() Hitbox.Position = Vector3.new(-999999999, 999999999, 999999999) end)
                        game.Debris:AddItem(Hitbox, 5)
                    end)

-- fire skill client side
                    SkillEndedEvent:FireClient(Player, SkillName)
                    Player:SetAttribute("IFrames", false)
                    Stun.EndMoveStun(Player.Character)
                    Humanoid.WalkSpeed = 16
                end
            end
        end
    end

-- fire mouse req to get mouse pos
    MouseRequest:FireClient(Player, id)
end)

Let me know if it works now.