Roblox's buggiest script - please help fix it

Note: A large amount of this code is AI generated, using extremely futuristic nextgen tech.

Issues

Animations (except shooting) work in studio, and I’m basically holding an invisible gun that I can’t shoot. It even reloads!

Spam equipping different guns with auto-clickers just completely breaks the system (fixable by clicking H, but that detail doesnt really matter. H is just the key that refreshes the client gun system)

Swapping side doesnt swap the hold animation instantly, instead I have to click V


Script located in ServerScriptService, and named 'GunServer'
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local Debris = game:GetService("Debris")

local ContentProvider = game:GetService("ContentProvider")

ContentProvider:PreloadAsync({
	--death anim
	
	"rbxassetid://136559111009274",
	"rbxassetid://124594580008089",
	"rbxassetid://136367278296323",
	"rbxassetid://91674262384181",
})

local shootGunRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("ShootGun")
local playGunEffectsRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("PlayGunEffects")
local updateAimRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("UpdateAim")
local changeShoulder = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("ChangeDirection")

local RAYCAST_DISTANCE = 788

local playerStates = {}

local MAX_AIM_ANGLE = math.rad(45)
local ARM_AIM_WEIGHT = 0.7
local HEAD_AIM_WEIGHT = 0.5

local SKELETON_TORSO_MESHES = {
	36424605,
	36780113,
	121760277323208,
}

local SKELETON_TORSO_VARIANTS = {
	121760277323208,
	124141239818905,
	121099867135122,
	82938465914213,
}

local function randomizeSkeletonMesh(character)
	if not character then
		return
	end

	local torso = character:FindFirstChild("Skeleton Torso")
	if not torso or not torso:IsA("CharacterMesh") then
		return
	end

	local valid = false

	for _, meshId in ipairs(SKELETON_TORSO_MESHES) do
		if torso.MeshId == meshId then
			valid = true
			break
		end
	end

	if valid then
		torso.MeshId = SKELETON_TORSO_VARIANTS[math.random(1, #SKELETON_TORSO_VARIANTS)]
	end
end

local function applyBleed(targetHumanoid, bleedAmount)
	if bleedAmount <= 0 then return end

	task.spawn(function()
		local ticks = 0
		local maxTicks = 5
		local damagePerTick = bleedAmount / maxTicks

		while targetHumanoid and targetHumanoid.Health > 0 and ticks < maxTicks do
			task.wait(1)
			ticks += 1
			if targetHumanoid and targetHumanoid.Health > 0 then
				targetHumanoid:TakeDamage(damagePerTick)
			end
		end
	end)
end

local function hitEffect(humanoid: Humanoid, position: Vector3)
	pcall(function()
		if humanoid and position then
			local blood = ReplicatedStorage:FindFirstChild("Assets"):FindFirstChild("Replicated"):FindFirstChild("Blood1"):Clone()
			blood.Parent = workspace
			blood.CFrame = CFrame.new(position)
			blood:FindFirstChild("ZombieTransformation1").Enabled = true

			task.delay(0.2, function()
				blood:FindFirstChild("ZombieTransformation1").Enabled = false
			end)
			Debris:AddItem(blood, 8)
		end
	end)
end

local function handleShot(player, direction)
	local state = playerStates[player]
	if not state then return end
	if not state.currentTool then return end
	if not state.currentSettingz then return end

	local character = player.Character
	if not character then return end

	local model = character:FindFirstChild(state.currentTool.Name:lower() .. "Model")
	if not model then return end

	local point = model:FindFirstChild("Point")
	if not point then return end

	local origin = point.WorldPosition
	local dir = direction.Unit

	local rayParams = RaycastParams.new()
	rayParams.FilterType = Enum.RaycastFilterType.Exclude
	rayParams.FilterDescendantsInstances = {character}

	local result = workspace:Raycast(origin, dir * RAYCAST_DISTANCE, rayParams)
	if not result then return end
	local position = result.Position
	local hitPart: BasePart | Terrain = result.Instance
	
	local endAttachment = Instance.new("Attachment")
	endAttachment.WorldPosition = position
	endAttachment.Parent = workspace.Terrain
	
	local bulletHole = Instance.new("Part")
	bulletHole.Name = "BulletHole"
	bulletHole.Anchored = true
	bulletHole.CanCollide = false
	bulletHole.CanQuery = false
	bulletHole.CanTouch = false

	bulletHole.Size = Vector3.new(1,1,1)
	bulletHole.Material = Enum.Material.SmoothPlastic
	bulletHole.Color = hitPart.Color
	bulletHole.Transparency = 1
	
	local decal = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Bulletholes"):WaitForChild("Concrete"):Clone()
	decal.Parent = bulletHole
	decal.Color3 = hitPart.Color

	bulletHole.CFrame =
		CFrame.lookAt(
			position + result.Normal * 0.001,
			position + result.Normal
		)

	bulletHole.Parent = workspace

	local beam = Instance.new("Beam")
	beam.Attachment0 = point
	beam.Attachment1 = endAttachment
	beam.FaceCamera = true
	beam.Parent = point

	Debris:AddItem(endAttachment, 0.05)
	Debris:AddItem(beam, 0.05)

	local hitHumanoid: Humanoid = nil
	local hitCharacter: Model = hitPart.Parent
	while hitCharacter do
		hitHumanoid = hitCharacter:FindFirstChildOfClass("Humanoid")
		if hitHumanoid then break end
		hitCharacter = hitCharacter.Parent
	end

	pcall(randomizeSkeletonMesh, hitCharacter)

	if hitHumanoid and hitHumanoid.Health > 0 then
		local damage = state.currentSettingz.damage or 0
		local bleed = state.currentSettingz.bleed or 0

		if type(damage) ~= "number" then damage = 0 end
		if type(bleed) ~= "number" then bleed = 0 end

		hitHumanoid:TakeDamage(damage)
		applyBleed(hitHumanoid, bleed)
		hitEffect(hitHumanoid, position)
	elseif hitHumanoid then
		hitEffect(hitHumanoid, position)
	end
	task.delay(0.02, function()
		pcall(function()
			if hitCharacter and hitHumanoid and hitHumanoid:IsA("Humanoid") and hitHumanoid.Health <= 0 then
				if hitCharacter:FindFirstChild("EnemyStuff") then
					hitCharacter:FindFirstChild("EnemyStuff"):Destroy()
				end
				
				if hitCharacter:HasTag("Enemy") and not hitCharacter:HasTag("DeathAnim") then
					if true == false then
						--hitCharacter.HumanoidRootPart.Anchored = true
						for index,joint in pairs(hitCharacter:GetDescendants()) do
							if joint:IsA("Motor6D") then
								local socket = Instance.new("BallSocketConstraint")
								local a1 = Instance.new("Attachment")
								local a2 = Instance.new("Attachment")
								a1.Parent = joint.Part0
								a2.Parent = joint.Part1
								socket.Parent = joint.Parent
								socket.Attachment0 = a1
								socket.Attachment1 = a2
								a1.CFrame = joint.C0
								a2.CFrame = joint.C1
								socket.LimitsEnabled = true
								socket.TwistLimitsEnabled = true
								joint:Destroy()
							end
						end
					else
						hitCharacter.HumanoidRootPart.Anchored = true
						hitCharacter:AddTag("DeathAnim")

						local anim = Instance.new("Animation")
						anim.Parent = hitHumanoid

						if hitPart.Name == "Left Leg" or hitPart.Name == "Right Leg" then
							anim.AnimationId = "rbxassetid://106068709503023"
						else
							anim.AnimationId = math.random(1, 2) == 1
								and "rbxassetid://93827090845426"
								or "rbxassetid://123337930048143"

							if math.random(1, 2) == 1 then
								anim.AnimationId = "rbxassetid://104771509882318"
							end
						end

						local animator = hitHumanoid:FindFirstChildOfClass("Animator")
						if not animator then
							return
						end

						local track = animator:LoadAnimation(anim)
						track:Play()

						task.spawn(function()
							while track.Length <= 0 do
								task.wait()
							end

							task.wait(math.max(0, track.Length - 0.1))

							if not hitCharacter.Parent then
								return
							end

							for _, v in ipairs(hitCharacter:GetDescendants()) do
								if v:IsA("BasePart") then
									v.Anchored = true
									v.CanCollide = false
									v.CanQuery = false
									v.CanTouch = false
								end
							end
						end)
					end
				end
			end
		end)
	end)
end

local function broadcastGunEffects(firingPlayer, toolName)
	local shooterCharacter = firingPlayer.Character
	if not shooterCharacter then return end
	local shooterPosition = shooterCharacter:GetPivot().Position

	for _, player in Players:GetPlayers() do
				if player == firingPlayer then continue end

		local character = player.Character
		if not character then continue end

		local distance = (character:GetPivot().Position - shooterPosition).Magnitude
				if distance >= 400 then continue end

		playGunEffectsRemote:FireClient(player, firingPlayer, toolName)
	end
end

shootGunRemote.OnServerEvent:Connect(function(firingPlayer, origin, direction)
	local state = playerStates[firingPlayer]
	if not state then return end
	if not state.currentTool then return end

	local character = firingPlayer.Character
	if not character then return end

	local model = character:FindFirstChild(state.currentTool.Name:lower() .. "Model")
	if not model then return end

	local point = model:FindFirstChild("Point")
	if not point then return end

		if typeof(origin) ~= "Vector3" then return end
	if typeof(direction) ~= "Vector3" then return end

	local mag = direction.Magnitude
	if mag < 0.001 then return end

		broadcastGunEffects(firingPlayer, state.currentTool.Name)

	handleShot(firingPlayer, direction / mag)
end)

local function disconnectActive(state)
	for _, c in ipairs(state.activeConnections) do
		c:Disconnect()
	end
	table.clear(state.activeConnections)
end

local function setupGun(player, character, tool)
	local state = playerStates[player]
	if not state then return end

	local weaponsFolder = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Weapons")
	local weaponModel = weaponsFolder:FindFirstChild(tool.Name:lower(), false)
	if not weaponModel then return end

	if not CollectionService:HasTag(tool, "Gun") then return end

	local settingz = require(weaponModel:WaitForChild("Settingz"))
	state.currentSettingz = settingz
	state.currentWeaponModel = weaponModel

	local existingModel = character:FindFirstChild(weaponModel.Name:lower() .. "Model")
	if existingModel then
		existingModel:Destroy()
	end

	local new = weaponModel:Clone()
	new.Name = weaponModel.Name .. "Model"
	new.Parent = character
	new.Anchored = false

	local settingsFolder = player:FindFirstChild("Settings")
	local leftValue = settingsFolder and settingsFolder:FindFirstChild("Left")
	local isLeft = leftValue and leftValue.Value

	local function getArm(useLeft)
		if useLeft then
			return character:FindFirstChild("Left Arm") or character:FindFirstChild("LeftHand")
		else
			return character:FindFirstChild("Right Arm") or character:FindFirstChild("RightHand")
		end
	end

	local arm = getArm(isLeft)

	if not arm then
		warn("no arm found")
		return
	end

	if state.currentMotor then
		state.currentMotor:Destroy()
		state.currentMotor = nil
	end

	local motor = Instance.new("Motor6D")
	motor.Part0 = arm
	motor.Part1 = new
	motor.Parent = arm
	state.currentMotor = motor

	if leftValue then
		local leftConn
		leftConn = leftValue:GetPropertyChangedSignal("Value"):Connect(function()
			if not state.currentMotor or state.currentMotor.Parent == nil then
				if leftConn then leftConn:Disconnect() end
				return
			end

			local newArm = getArm(leftValue.Value)
			if newArm and state.currentMotor then
				state.currentMotor.Part0 = newArm
				state.currentMotor.Parent = newArm
			end
		end)
		table.insert(state.activeConnections, leftConn)
	end

	tool.Unequipped:Connect(function()
		if state.currentMotor then
			state.currentMotor:Destroy()
			state.currentMotor = nil
		end
	end)

	local pos = settingz.position or Vector3.new(0, 0, 0)
	local rot = settingz.orientation or Vector3.new(0, 0, 0)

	motor.C0 = CFrame.new(pos)
		* CFrame.Angles(
			math.rad(rot.X),
			math.rad(rot.Y),
			math.rad(rot.Z)
		)
end

local function onEquipped(player, character, tool)
	local state = playerStates[player]
	if not state then return end

	tool.CanBeDropped = false
	state.currentTool = tool
	disconnectActive(state)
	setupGun(player, character, tool)
end

local function onUnequipped(player, character, tool)
	local state = playerStates[player]
	if not state then return end

	if tool ~= state.currentTool then return end

	state.currentSettingz = nil
	state.currentWeaponModel = nil
	disconnectActive(state)

	local model = character:FindFirstChild(tool.Name:lower() .. "Model")
	if model then model:Destroy() end

	state.currentTool = nil
end

local function hookTool(player, character, tool)
	local state = playerStates[player]
	if not state then return end

	table.insert(state.toolConnections, tool.Equipped:Connect(function()
		onEquipped(player, character, tool)
	end))

	table.insert(state.toolConnections, tool.Unequipped:Connect(function()
		onUnequipped(player, character, tool)
	end))
end

local function cleanupState(state)
	if not state then return end
	disconnectActive(state)
	for _, c in ipairs(state.toolConnections) do
		c:Disconnect()
	end
	table.clear(state.toolConnections)
end

local function setupCharacter(player, character)
	local state = playerStates[player]
	if not state then return end

		cleanupState(state)

		state.currentTool = nil
	state.currentSettingz = nil
	state.currentWeaponModel = nil
	state.currentMotor = nil
	state.originalShoulderC0 = nil
	state.originalNeckC0 = nil

	character.ChildAdded:Connect(function(child)
		if child:IsA("Tool") then
			hookTool(player, character, child)
		end
	end)

	for _, tool in ipairs(character:GetChildren()) do
		if tool:IsA("Tool") then
			hookTool(player, character, tool)
		end
	end
end

updateAimRemote.OnServerEvent:Connect(function(player, pitchDeg, yawDeg)
	local state = playerStates[player]
	if not state then return end

		if typeof(pitchDeg) ~= "number" or typeof(yawDeg) ~= "number" then return end

		local pitch = math.clamp(math.rad(pitchDeg), -MAX_AIM_ANGLE, MAX_AIM_ANGLE)
	local yaw = math.clamp(math.rad(yawDeg), -MAX_AIM_ANGLE, MAX_AIM_ANGLE)

		local character = player.Character
	if not character then return end

	local torso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
	local head = character:FindFirstChild("Head")
	local settingsFolder = player:FindFirstChild("Settings")
	local leftValue = settingsFolder and settingsFolder:FindFirstChild("Left")
	local isLeft = leftValue and leftValue.Value

	if not torso or not head then return end

		local shoulderName = isLeft and "LeftShoulder" or "RightShoulder"
	local shoulder = torso:FindFirstChild(shoulderName)
	if not shoulder then
		shoulderName = isLeft and "Left Shoulder" or "Right Shoulder"
		shoulder = torso:FindFirstChild(shoulderName)
	end
	local neck = torso:FindFirstChild("Neck")

		if shoulder and shoulder:IsA("Motor6D") and not state.originalShoulderC0 then
		state.originalShoulderC0 = shoulder.C0
	end
	if neck and neck:IsA("Motor6D") and not state.originalNeckC0 then
		state.originalNeckC0 = neck.C0
	end

			if shoulder and shoulder:IsA("Motor6D") and state.originalShoulderC0 then
		local aimCFrame = CFrame.Angles(-pitch * ARM_AIM_WEIGHT, -yaw * ARM_AIM_WEIGHT, 0)
		shoulder.C0 = state.originalShoulderC0 * aimCFrame
	end

	if neck and neck:IsA("Motor6D") and state.originalNeckC0 then
		local headAimCFrame = CFrame.Angles(-pitch * HEAD_AIM_WEIGHT, -yaw * HEAD_AIM_WEIGHT, 0)
		neck.C0 = state.originalNeckC0 * headAimCFrame
	end
end)

local function onPlayerAdded(player)
	playerStates[player] = {
		currentTool = nil,
		activeConnections = {},
		toolConnections = {},
		currentMotor = nil,
		currentSettingz = nil,
		currentWeaponModel = nil,
		originalShoulderC0 = nil,
		originalNeckC0 = nil,
	}
	
	local settingz = Instance.new("Configuration")
	settingz.Parent = player
	settingz.Name = "Settings"

	local left = Instance.new("BoolValue")
	left.Parent = settingz
	left.Name = "Left"
	left.Value = false

	player.CharacterAdded:Connect(function(character)
		setupCharacter(player, character)
	end)

		if player.Character then
		setupCharacter(player, player.Character)
	end
end

local function onPlayerRemoving(player)
	local state = playerStates[player]
	cleanupState(state)
	playerStates[player] = nil
end

changeShoulder.OnServerEvent:Connect(function(player)
	local settings1 = player:FindFirstChild("Settings")
	if not settings1 then return end

	local left = settings1:FindFirstChild("Left")
	if not left then return end

	left.Value = not left.Value
end)

for _,v in ipairs(Players:GetPlayers()) do
	onPlayerAdded(v)
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)

for _, player in Players:GetPlayers() do
	onPlayerAdded(player)
end
LocalScript located in StarterCharacterScripts, and named 'Weapon'
task.wait(0.01)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

local player: Player = Players.LocalPlayer
local mouse = player:GetMouse()
local character: Model = script.Parent
local humanoid: Humanoid = character:WaitForChild("Humanoid")
local animator: Animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)

local camera = workspace.CurrentCamera
camera.FieldOfView = 72

local UserGameSettings = UserSettings():GetService("UserGameSettings")

local activeConnections = {}
local currentTracks = {}

local reloadCooldown: boolean = false
local isReloading: boolean = false
local left: boolean = false
local firing: boolean = false
local canShoot = {}
local isAlt = false

local equipId = 0

local isHolstering: boolean = false
local autoShootCooldown: boolean = false
local isAiming: boolean = false
local aimFOV: number = 40
local normalFOV: number = 72
local aimSpeed: number = 0.15

local shootGunRemote: RemoteEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("ShootGun")
local changeDirectionRemote: RemoteEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("ChangeDirection")
local playGunEffectsRemote: RemoteEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("PlayGunEffects")
local updateAimRemote: RemoteEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("UpdateAim")
local leftValue: RemoteEvent = player:WaitForChild("Settings"):WaitForChild("Left")

local currentTool = nil
local currentAnimations = nil


pcall(function()
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	UserInputService.MouseIconEnabled = true
	player:WaitForChild("PlayerGui"):WaitForChild("MainGui"):WaitForChild("Cursors").Stroke.Visible = false
end)

local function swapDirection()
	if not currentTool or currentTool.Parent ~= character then return end
	if not currentAnimations then return end

	stopAllAnimations()

	if currentTool then
		local autoTrackKey = "AutoTrack_" .. currentTool.Name
		if _G[autoTrackKey] then
			_G[autoTrackKey]:Stop()
			_G[autoTrackKey]:Destroy()
			_G[autoTrackKey] = nil
		end
	end

	local holdAnimId = left and currentAnimations.LeftHold or currentAnimations.RightHold
	if holdAnimId then
		local anim = Instance.new("Animation")
		anim.AnimationId = holdAnimId
		local track = animator:LoadAnimation(anim)
		track.Looped = true
		track.Priority = Enum.AnimationPriority.Idle
		track:Play()
		table.insert(currentTracks, track)
	end
	
	--stopAllAnimations()

	_G.GunOffset = left and Vector3.new(-2,0,0) or Vector3.new(2,0,0)
end

leftValue:GetPropertyChangedSignal("Value"):Connect(function()
	left = leftValue.Value
	swapDirection()
end)



local function disconnectActive()
	for _, c in ipairs(activeConnections) do
		c:Disconnect()
	end
	table.clear(activeConnections)
end

function stopAllAnimations()
	for _, track in ipairs(currentTracks) do
		if track.IsPlaying then
			track:Stop(0)
		end
		track:Destroy()
	end
	table.clear(currentTracks)
end

local function setupGun(tool: Tool)
	equipId += 1
	local thisEquip = equipId

	task.wait(0.01)

	if thisEquip ~= equipId then
		return
	end
	
	if currentTool == tool then
		return
	end
	
	if tool.Parent ~= character then
		return
	end
	
	currentTool = tool
	disconnectActive()
	stopAllAnimations()

	local weaponsFolder = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Weapons")
	local weaponModel = weaponsFolder:FindFirstChild(tool.Name:lower())
	if not weaponModel then return end

	local animations = require(weaponModel:WaitForChild("Animations"))
	local settingz = require(weaponModel:WaitForChild("Settingz"))
	local firemode = settingz.firemode
	
	local cursorStroke = player.PlayerGui:WaitForChild("MainGui"):WaitForChild("Cursors"):WaitForChild("Stroke")
	local cursorStroke2 = cursorStroke:FindFirstChildOfClass("UIStroke")
	local cursorScale = cursorStroke:FindFirstChildOfClass("UIScale")

	local baseSpread = settingz.spread or 0
	local targetScale = 1
	local currentScale = 1

	local function holster(state)
		isHolstering = state
		
		local TweenService = game:GetService("TweenService")

		TweenService:Create(
			cursorStroke2,
			TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
			{
				Transparency = state and 1 or 0.64
			}
		):Play()

		stopAllAnimations()

		local animId

		if state then
			if left == false then
				animId = animations.LeftHoldDown or "rbxassetid://79857257731361"
			else
				animId = animations.RightHoldDown or "rbxassetid://114485290440409"
			end
		else
			if left == false then
				animId = animations.RightHold
			else
				animId = animations.LeftHold
			end
		end

		if not animId then return end

		local anim = Instance.new("Animation")
		anim.AnimationId = animId

		local track = animator:LoadAnimation(anim)
		track.Looped = true
		track.Priority = state and Enum.AnimationPriority.Action or Enum.AnimationPriority.Idle
		track:Play()

		table.insert(currentTracks, track)

		if state then
			firing = false
			humanoid:SetAttribute("IsFiring", false)
			humanoid:SetAttribute("IsFiring", false)

			local autoTrackKey = "AutoTrack_" .. tool.Name
			if _G[autoTrackKey] then
				_G[autoTrackKey]:Stop()
				_G[autoTrackKey]:Destroy()
				_G[autoTrackKey] = nil
			end
		end
	end

	local function playAnim(animId, looped, priority, reload)
		local success, track = pcall(function()
			local anim = Instance.new("Animation")
			anim.AnimationId = animId

			local track = animator:LoadAnimation(anim)
			track.Looped = looped or false
			track.Priority = priority or Enum.AnimationPriority.Action
			track:Play(0)

			table.insert(currentTracks, track)

			--track.Stopped:Connect(function()
			--	if reload then
			--		isReloading = false
			--		humanoid.JumpPower = 34
			--	end
			--end)
			
			task.delay(track.Length, function()
				if reload then
					isReloading = false
					humanoid:SetAttribute("IsReloading", false)
					humanoid.JumpPower = 34
				end
			end)

			return track
		end)

		if success then
			return track
		end
	end

	if left == false then
		local holdTrack = playAnim(animations.RightHold, true, Enum.AnimationPriority.Idle, false)
	else
		local holdTrack = playAnim(animations.LeftHold, true, Enum.AnimationPriority.Idle, false)
	end

	local function fireAnim()
		local weaponId = tool
		if isHolstering or isReloading or canShoot[weaponId] == false then
			return
		end

		canShoot[weaponId] = false
		humanoid:SetAttribute("IsFiring", true)

		local camera = workspace.CurrentCamera
		local lookDir = camera.CFrame.LookVector
		local spread = settingz.spread or 0
		local multishot = settingz.multishot or 1

		for _ = 1, multishot do
			local dir = lookDir

			if spread > 0 then
				dir = (CFrame.new(Vector3.zero, lookDir)
					* CFrame.Angles(
						math.rad((math.random() - 0.5) * spread * 2),
						math.rad((math.random() - 0.5) * spread * 2),
						0
					)).LookVector
			end

			local origin
			local direction

			if UserInputService.MouseEnabled and not UserInputService.TouchEnabled and not UserInputService.VREnabled then
				local ray = camera:ScreenPointToRay(mouse.X, mouse.Y)
				origin = ray.Origin
				direction = ray.Direction
			else
				local viewport = camera.ViewportSize
				local ray = camera:ViewportPointToRay(viewport.X / 2, viewport.Y / 2)
				origin = ray.Origin
				direction = ray.Direction
			end

			shootGunRemote:FireServer(origin, direction)
		end

		local model = nil
		pcall(function()
			model = character:FindFirstChild(tool.Name:lower().."Model")
		end)
		if not model then return end
		local point = model:FindFirstChild("Point")
		local fireSound = model:FindFirstChild("FireSounds") and model.FireSounds:FindFirstChild("Fire")

		local animId
		if left == false then
			if player.AccountAge < 1500 and tool.Name:lower() == "rifle" then
				animId = animations.NoobRightFire
			else
				animId = animations.RightFire
			end
		else
			if player.AccountAge <= 1500 and tool.Name:lower() == "rifle" then
				animId = animations.NoobLeftFire
			else
				animId = animations.LeftFire
			end
		end

		if firemode == "Auto" or firemode == "Automatic" then
			local autoTrackKey = "AutoTrack_" .. tool.Name
			if not _G[autoTrackKey] or not _G[autoTrackKey].IsPlaying then
				local anim = Instance.new("Animation")
				anim.AnimationId = animId
				
				local track = animator:LoadAnimation(anim)
				track.Looped = true
				track.Priority = Enum.AnimationPriority.Action
				track:Play()
				
				table.insert(currentTracks, track)
				_G[autoTrackKey] = track
			end
		else
			local anim = Instance.new("Animation")
			anim.AnimationId = animId
			
			local track = animator:LoadAnimation(anim)
			track.Priority = Enum.AnimationPriority.Action
			track:Play()
			
			table.insert(currentTracks, track)
		end

		if point and point:FindFirstChild("Fire") then
			local fire = point.Fire
			if fire:FindFirstChild("Flame") then fire.Flame:Emit(3) end
			if fire:FindFirstChild("Embers") then fire.Embers:Emit(3) end
			if point:FindFirstChild("Smoke") then
				task.delay(0.4, function()
					pcall(function()
						local smoke = point.Smoke:Clone()
						smoke.Parent = point
						smoke.Enabled = true
						task.delay(0.23, function()
							task.delay(7, function() if smoke then smoke:Destroy() end end)
							smoke.Enabled = false
						end)
					end)
				end)
			end
		end

		if fireSound then
			if firemode == "Auto" then
				fireSound.Looped = true
				fireSound:Play()
			else
				fireSound.Looped = false
				fireSound:Play()
			end
		end

		if firemode == "Single" then
			task.delay(settingz.cooldown or 1.5, function()
				canShoot[weaponId] = true
			end)
		else
			canShoot[weaponId] = true
		end

		task.delay(0.3, function()
			if not firing then
				humanoid:SetAttribute("IsFiring", false)
			end
		end)
	end

	local function stopFire()
		autoShootCooldown = true
		for i = #currentTracks, 1, -1 do
			local track = currentTracks[i]
			if track and track.IsPlaying then
				track:Stop(0)
				track:Destroy()
				table.remove(currentTracks, i)
			end
		end
		local model = character:FindFirstChild(tool.Name:lower().."Model")
		local fireSound = model and model:FindFirstChild("FireSounds") and model.FireSounds:FindFirstChild("Fire")
		if fireSound then
			fireSound:Stop()
		end
		firing = false

		local autoTrackKey = "AutoTrack_" .. tool.Name
		if _G[autoTrackKey] then
			_G[autoTrackKey] = nil
		end

		local animations = require(ReplicatedStorage.Assets.Weapons:FindFirstChild(tool.Name:lower()):WaitForChild("Animations"))

		local holdAnimId
		if left == false then
			holdAnimId = animations.RightHold
		else
			holdAnimId = animations.LeftHold
		end

		local anim = Instance.new("Animation")
		anim.AnimationId = holdAnimId
		local track = animator:LoadAnimation(anim)
		track.Looped = true
		track.Priority = Enum.AnimationPriority.Idle
		track:Play()

		table.insert(currentTracks, track)
		task.delay(0.23, function()
			autoShootCooldown = false
		end)
	end

	local function reloadAnim()
		reloadCooldown = true
		isReloading = true
		humanoid:SetAttribute("IsReloading", true)
		humanoid:SetAttribute("IsFiring", false)
		if character:FindFirstChild("Head") and character.Head:FindFirstChild("Spit") and character.Head.Spit:FindFirstChildOfClass("ParticleEmitter") then
			character.Head.Spit:FindFirstChildOfClass("ParticleEmitter"):Emit(3)
		end
		humanoid.JumpPower = 0
		humanoid.JumpHeight = 0

		task.delay(5, function()
			reloadCooldown = false 
		end)

		if left == false then
			if (player.AccountAge < 654 or tostring(player.UserId):find("6164")) and tool.Name:lower() == "rifle" then
				playAnim(animations.NoobRightReload, false, Enum.AnimationPriority.Action, true)
			else
				playAnim(animations.RightReload, false, Enum.AnimationPriority.Action, true)
			end
		else
			if (player.AccountAge < 654 or tostring(player.UserId):find("6164")) and tool.Name:lower() == "rifle" then
				playAnim(animations.NoobLeftReload, false, Enum.AnimationPriority.Action, true)
			else
				playAnim(animations.LeftReload, false, Enum.AnimationPriority.Action, true)
			end
		end
	end

	local function requestFire()
		fireAnim()
	end

	if firemode == "Auto" then
		table.insert(activeConnections, tool.Activated:Connect(function()
			if autoShootCooldown == true then return end
			firing = true
			task.spawn(function()
				local model = character:FindFirstChild(tool.Name:lower().."Model")
				local fireSound = model and model:FindFirstChild("FireSounds") and model.FireSounds:FindFirstChild("Fire")
				if fireSound then
					fireSound.Looped = true
					fireSound:Play()
				end

				while firing and humanoid.Health > 0 do
					fireAnim()
					task.wait(settingz.cooldown or 0.1)
				end
			end)
		end))
		table.insert(activeConnections, tool.Deactivated:Connect(stopFire))
	else
		if UserInputService.TouchEnabled then
			local fireButton = player.PlayerGui.MainGui:WaitForChild("FireButton1")
			if fireButton then
				table.insert(activeConnections, fireButton.MouseButton1Down:Connect(function()
					if UserInputService.TouchEnabled then
						requestFire()
					end
				end))
			end
		else
			table.insert(activeConnections, tool.Activated:Connect(requestFire))
		end
	end

	table.insert(activeConnections, UserInputService.InputBegan:Connect(function(input, gpe)
		if gpe then return end

		if input.KeyCode == Enum.KeyCode.R and not reloadCooldown and not isHolstering then
			reloadAnim()
		elseif input.KeyCode == Enum.KeyCode.V then
			holster(true)
		elseif input.KeyCode == Enum.KeyCode.LeftAlt then
			isAlt = true
		elseif input.KeyCode == Enum.KeyCode.Q then
			changeDirectionRemote:FireServer()
			humanoid:UnequipTools()
			humanoid:EquipTool(currentTool)
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			isAiming = true
		elseif input.KeyCode == Enum.KeyCode.H then
			stopAllAnimations()
			stopFire()
			humanoid:UnequipTools()
			UserInputService.MouseIconEnabled = true
			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
			
			task.delay(0.1, function()
				local s = script:Clone()
				s.Parent = script.Parent
				task.delay(0.01, function()
					script:Destroy()
				end)
			end)
		end
	end))

	table.insert(activeConnections, UserInputService.InputEnded:Connect(function(input, gpe)
		if input.KeyCode == Enum.KeyCode.V then
			holster(false)
		elseif input.KeyCode == Enum.KeyCode.LeftAlt then
			isAlt = false
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			isAiming = false
	humanoid:SetAttribute("IsReloading", false)
	humanoid:SetAttribute("IsFiring", false)
		end
	end))

	local aimButton = player.PlayerGui:FindFirstChild("MainGui") and player.PlayerGui.MainGui:FindFirstChild("AimButton1")
	if aimButton then
		table.insert(activeConnections, aimButton.MouseEnter:Connect(function()
			isAiming = true
		end))
		table.insert(activeConnections, aimButton.MouseLeave:Connect(function()
			isAiming = false
		end))
	end

	table.insert(activeConnections, RunService.RenderStepped:Connect(function()
		local spreadScale = 1 + (baseSpread * 0.02)

		if isAiming then
			targetScale = math.max(0.4, spreadScale * 0.5)
		else
			targetScale = spreadScale
		end

		currentScale += (targetScale - currentScale) * 0.15

		if cursorScale then
			cursorScale.Scale = currentScale
		end
		local velocity = humanoid.MoveDirection.Magnitude
		spreadScale += velocity * 0.4
		
		local character = player.Character
		if not character then return end
		local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter

		local camera = workspace.CurrentCamera
		if camera then
			local targetFOV = isAiming and aimFOV or normalFOV
			camera.FieldOfView = camera.FieldOfView + (targetFOV - camera.FieldOfView) * aimSpeed
		end

		if humanoidRootPart and humanoid and humanoid.Health > 0 then
			--if isHolstering or isAlt then
			--	UserGameSettings.RotationType = Enum.RotationType.MovementRelative
			--	humanoid.AutoRotate = true
			--else
			--	UserGameSettings.RotationType = Enum.RotationType.CameraRelative
			--	humanoid.AutoRotate = true
			--end
			UserGameSettings.RotationType = Enum.RotationType.CameraRelative
			humanoid.AutoRotate = true
		end
	end))
end

local function onEquipped(tool)
	if tool:HasTag("Gun") or tool:HasTag("Melee") then
		setupGun(tool)
		_G.GunOffset = left and Vector3.new(-2,0,0) or Vector3.new(2,0,0)
		UserGameSettings.RotationType = Enum.RotationType.CameraRelative
		humanoid.AutoRotate = true
		UserInputService.MouseIconEnabled = false
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
		player.PlayerGui.MainGui.Cursors.Stroke.Visible = true
		player.PlayerGui.MainGui.FireButton1.Visible = UserInputService.TouchEnabled
		local aimButton = player.PlayerGui.MainGui:FindFirstChild("AimButton1")
		if aimButton then
			aimButton.Visible = UserInputService.TouchEnabled
		end
	end
end

local function onUnequipped(tool)
	isHolstering = false
	isAlt = false
	if tool then
		local autoTrackKey = "AutoTrack_" .. tool.Name
		if _G[autoTrackKey] then
			_G[autoTrackKey] = nil
		end
	end

	currentTool = nil
	disconnectActive()
	stopAllAnimations()
	isAiming = false
	
	local camera = workspace.CurrentCamera
	if camera then
		camera.FieldOfView = normalFOV
	end
	
	_G.GunOffset = Vector3.new(0,0,0)
	
	UserGameSettings.RotationType = Enum.RotationType.MovementRelative
	humanoid.AutoRotate = true
	
	UserInputService.MouseIconEnabled = true
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	
	pcall(function()
		player.PlayerGui.MainGui.Cursors.Stroke.Visible = false
		player.PlayerGui.MainGui.FireButton1.Visible = false
		local aimButton = player.PlayerGui.MainGui:FindFirstChild("AimButton1")
		if aimButton then
			aimButton.Visible = false
		end
	end)
end

local equippedConnections = {}

character.ChildAdded:Connect(function(child)
	if child:IsA("Tool") and not equippedConnections[child] then
		equippedConnections[child] = {
			child.Equipped:Connect(function()
				onEquipped(child)
			end),

			child.Unequipped:Connect(function()
				onUnequipped(child)
			end)
		}
	end
end)

playGunEffectsRemote.OnClientEvent:Connect(function(shooterPlayer, toolName)
	local shooterCharacter = shooterPlayer.Character
	if not shooterCharacter then return end

	local modelName = toolName:lower().."Model"
	local model = shooterCharacter:FindFirstChild(modelName)
	if not model then return end

	local point = model:FindFirstChild("Point")
	if not point then return end

	if point:FindFirstChild("Fire") then
		local fire = point.Fire
		if fire:FindFirstChild("Flame") then fire.Flame:Emit(3) end
		if fire:FindFirstChild("Embers") then fire.Embers:Emit(3) end
		if point:FindFirstChild("Smoke") then
			local smoke = point.Smoke:Clone()
			smoke.Parent = point
			smoke.Enabled = true
			task.delay(0.23, function()
				task.delay(7, function() if smoke then smoke:Destroy() end end)
				smoke.Enabled = false
			end)
		end
	end

	local fireSound = model:FindFirstChild("FireSounds") and model.FireSounds:FindFirstChild("Fire")
	if fireSound then
		fireSound.Looped = false
		fireSound:Play()
	end
end)

humanoid.Died:Connect(function()
	onUnequipped()
end)

(Private) Game: Skyfall | Play on Roblox

1 Like

are you sure your game can animations and gun mesh in runtime?

what


uhh okayy yeah i guess roblox studio has a different asset loading system than roblox

There are a few things I noticed right away

First currentAnimations is created as nil and swapDirection() depends on it

if not currentAnimations then return end

but in setupGun() you only do

local animations = require(weaponModel:WaitForChild("Animations"))

You never set

currentAnimations = animations

So when you change shoulder side, swapDirection() probably just returns and never plays the new hold animation. That would explain why swapping side doesnt update instantly.

I’d add this after requiring the animations

currentAnimations = animations

and clear it when unequipping

Another thing is that some of your equip/unequip logic can stack up if you spam tools. For example, on the server you already handle Unequipped through hookTool() but inside setupGun() you also create another tool.Unequipped connection that isnt stored or cleaned up. If tools are equipped a lot, old connections can keep firing later and mess with the current gun.

You also call onPlayerAdded() twice for players who are already in the server

for _,v in ipairs(Players:GetPlayers()) do
	onPlayerAdded(v)
end

Players.PlayerAdded:Connect(onPlayerAdded)

for _, player in Players:GetPlayers() do
	onPlayerAdded(player)
end

I’d remove one of those loops. Running setup twice can create duplicate folders/connections and make the system act really weird.

For the invisible gun / not shooting issue, check this part too

local point = model:FindFirstChild("Point")

If Point is nested inside the gun model, this will return nil. Try

local point = model:FindFirstChild("Point", true)

Same thing on the client when you look for the gun model/point. The model may also not have replicated to the client yet, so if you instantly shoot after equipping, the client code can return early because it cannot find the model

I’d try this

  1. Set currentAnimations = animations inside client setupGun()
  2. Clear currentAnimations on unequip
  3. Remove the duplicate onPlayerAdded() loop
  4. Remove or properly store the extra tool.Unequipped connection inside server setupGun()
  5. Use FindFirstChild("Point", true) if Point is inside the model
  6. Add a proper equip id/token on the server too, so old equip/unequip actions cannot affect the newest gun

Most of the bugs you listed sound like old state or old connections still running after the player switches guns quickly. I’d clean up the equip/unequip flow first before changing the shooting logic

1 Like