Wacky FPS gun module behavior

Hey people,
I’m working on a game right now, sorta like a classic-esque roblox FPS, but I’m having an issue with my gun system.

If you pay attention to the HUD on the bottom right, you’ll see that when reloading the gun with a higher maxAmmo will override the gun with a lower maxAmmo after reloading. I have been at this for over an hour, and not even ChatGPT can help me.

My first thought was that I was setting the module’s ammo to my local ammo when I equipped the gun after reloading, but it wasn’t that. I added a check to detect whether or not it was the first time equipping the gun to prevent this, but it didn’t work, or wasn’t the issue.

I don’t know what’s wrong and can’t really provide a lot of context, but I think the issue is on line’s 68-77 in my tool’s local script, inside the onEquip() function when I initialize all the values.

Suspected Section
-- Initialize gun stats (could be loaded from some config per gun)
	gunStats.maxAmmo = 8
	if setAmmo then
		ammo = gunStats.maxAmmo
		setAmmo = false
	end
	gunStats.ammo = ammo
	gunStats.damage = 45
	gunStats.firerate = 13
	gunStats.isSecondary = true
	gunStats.isAutomatic = false
local
local tool = script.Parent
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
local replicatedStorage = game:GetService("ReplicatedStorage")
local userInputService = game:GetService("UserInputService")
local arms = workspace:WaitForChild("arms")
local shootEvent = replicatedStorage:WaitForChild("shootEvent")
local stateProperties = require(replicatedStorage:WaitForChild("stateProperties"))

local flashEffect = replicatedStorage:WaitForChild("gunFlash")

local gunStats = stateProperties.gunStats

local setAmmo = true

local mouseDown = false
local canShoot = true
local ammo = 0
local tracks = {}
local gunModel = nil
local animator = nil

local function stopAllAnimations()
	for _, animation in pairs(tracks) do
		animation:Stop()
	end
end

local function doRecoil(recoil, recoverySpeed)
	local rotationOffset = CFrame.Angles(math.rad(recoil), 0, 0)
	camera.CFrame *= rotationOffset
	for i = 1, recoil do
		task.wait(recoverySpeed / recoil)
		camera.CFrame *= CFrame.Angles(math.rad(-1), 0, 0)
	end
end

local function onEquip()
	-- Clear previous states if any
	stopAllAnimations()
	mouseDown = false
	canShoot = true

	-- Get animations from tool each time equipped
	local animations = tool:WaitForChild("animations")

	-- Set gunName based on tool name or a value (you might want to store it in a StringValue inside the tool)
	local gunName = tool.Name
	local gunModels = replicatedStorage:WaitForChild("gunModels")
	gunModel = gunModels:WaitForChild(gunName):Clone()
	gunModel.Parent = arms
	arms.head.grip.Part1 = gunModel.base
	arms.head.grip.C1 = CFrame.Angles(0, math.rad(180), 0)

	-- Setup animator fresh each equip
	animator = arms:WaitForChild("AnimationController"):WaitForChild("Animator")

	-- Load animations fresh each equip
	tracks = {
		equip = animator:LoadAnimation(animations.equip),
		hold = animator:LoadAnimation(animations.hold),
		shoot = animator:LoadAnimation(animations.shoot),
		reload = animator:LoadAnimation(animations:WaitForChild("reload"))
	}

	-- Initialize gun stats (could be loaded from some config per gun)
	gunStats.maxAmmo = 8
	if setAmmo then
		ammo = gunStats.maxAmmo
		setAmmo = false
	end
	gunStats.ammo = ammo
	gunStats.damage = 45
	gunStats.firerate = 13
	gunStats.isSecondary = true
	gunStats.isAutomatic = false

	stateProperties.equipped = true

	stopAllAnimations()
	tracks.equip:Play()
	task.wait(tracks.equip.Length)
	if stateProperties.equipped then
		tracks.hold:Play()
	end
end

local function onDequip()
	stateProperties.equipped = false
	if gunModel then
		gunModel.Parent = replicatedStorage
		arms.head.grip.Part1 = nil
	end
	stopAllAnimations()
	mouseDown = false
end

local function flash()
	local flashClone = flashEffect:Clone()
	flashClone.Parent = gunModel.shootPart
	flashClone:Emit(4)
end

local function onActivate()
	local gunStats = stateProperties.gunStats
	if not gunStats.isAutomatic then
		if canShoot and ammo > 0 then
			canShoot = false
			shootEvent:FireServer(gunModel.base.Position, gunModel.shootPart.CFrame.LookVector * 500, gunStats.damage)
			ammo = ammo - 1
			gunStats.ammo = ammo
			flash()
			doRecoil(5, 0.3)
			task.wait(1 / gunStats.firerate)
			canShoot = true
		end
	end
end

tool.Equipped:Connect(onEquip)
tool.Unequipped:Connect(onDequip)
tool.Activated:Connect(onActivate)

mouse.Button1Down:Connect(function()
	mouseDown = true
	local gunStats = stateProperties.gunStats

	if gunStats.isAutomatic then
		while mouseDown do
			if stateProperties.equipped and not stateProperties.reloading and not stateProperties.sprinting and canShoot and ammo > 0 then
				canShoot = false
				shootEvent:FireServer(gunModel.base.Position, gunModel.shootPart.CFrame.LookVector * 500, gunStats.damage)
				ammo = ammo - 1
				gunStats.ammo = ammo
				flash()
				doRecoil(5, 0.3)
			end
			task.wait(1 / gunStats.firerate)
			canShoot = true
		end
	end
end)

mouse.Button1Up:Connect(function()
	mouseDown = false
end)

userInputService.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.R then
		local gunStats = stateProperties.gunStats
		if stateProperties.equipped and ammo < gunStats.maxAmmo and not stateProperties.reloading and not stateProperties.sprinting and not stateProperties.aiming then
			tool.reload.TimePosition = 0
			tool.reload:Play()
			stateProperties.reloading = true
			game:GetService("StarterGui"):SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, false)
			tracks.reload:Play()
			task.wait(tracks.reload.Length)
			tracks.reload:Stop()
			ammo = gunStats.maxAmmo
			gunStats.ammo = ammo
			stateProperties.reloading = false
			game:GetService("StarterGui"):SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, true)
		end
	end
end)

Here’s the moduleScript I store the value’s in.

stateProperties
local stateProperties = {
	equipped = false,
	aiming = false,
	sprinting = false,
	reloading = false,
	
	gunStats = {
		ammo = 0,
		maxAmmo = 0,
		damage = 0,
		isSecondary = false,
		isAutomatic = false,
		firerate = 0
	}
}

return stateProperties

Any help would be extremely appreciated. If any more details are necessary, I’ll provide them.

1 Like

I do have a feeling it’s the section you’re referring to. Is there a config file for that gun you’re using in the video and can you show it to me?

I can’t see where you reset setAmmo and so I believe it’s setting the maxAmmo to the correct value, but then never resetting it because setAmmo is never set to true anywhere else.

1 Like

Hey there! Sorry for the confusion, I should’ve mentioned- someone DM’d me the issue, and helped me solve it. I ended up rewriting the moduleScript that contained all the info to instead have a list of multiple defaulted tables each referring to a separate gun, so that I wouldn’t end up mixing around the values when I altered different guns. I appreciate the drive to help though! I’m gonna delete this post.

function createWeaponState(gunName)
	local gunState = {
		ammo = 0,
		maxAmmo = 0,
		damage = 0,
		isSecondary = false,
		isAutomatic = false,
		firerate = 0,
		recoil = 0,
		recoilRecovery = 0,
	}
	return gunState
end

local stateProperties = {
	equipped = false,
	aiming = false,
	sprinting = false,
	reloading = false,

	gunStates = {
		["PA-63"] = createWeaponState("PA-63"),
		["M34"] = createWeaponState("M34"),
		["P38"] = createWeaponState("P38")
	}
}

return stateProperties

Do not delete the post, other people having the same issue as you might come across this and solve it for themselves!

1 Like

Ok then. I’ll leave this up :+1:

1 Like