Gun Ammo System

I have made a gun using raycasting but now I need to make an ammo System. Because without it players can easily abuse it and it shoots forever. I have tried adding a number value on the Handle of the gun and set it to 40 to make it the default amount but if the player shoots it reduces by 1. But the problem is it’s not working. Here is the server script inside my gun:

local DEBUG = false					
local BULLET_SPEED = 520							
local BULLET_MAXDIST = 1000							
local BULLET_GRAVITY = Vector3.new(0, 1, 0)		
local MIN_BULLET_SPREAD_ANGLE = 1					
local MAX_BULLET_SPREAD_ANGLE = 1.1		
local FIRE_DELAY = 0.1							
local BULLETS_PER_SHOT = 1							
local PIERCE_DEMO = false						

-- Local Variables

local Tool = script.Parent
local Handle = Tool.Handle
local MouseEvent = Tool.MouseEvent
local FirePointObject = Handle.GunFirePoint
local FastCast = require(Tool.FastCastRedux)
local FireSound = Handle.Fire
local ImpactParticle = Handle.ImpactParticle
local Debris = game:GetService("Debris")
local table = require(Tool.FastCastRedux.Table)
local PartCacheModule = require(Tool.PartCache)
local CanFire = true								
local ammo = script.Parent.Handle.Ammo
local RNG = Random.new()						
local TAU = math.pi * 2							
FastCast.DebugLogging = DEBUG
FastCast.VisualizeCasts = DEBUG

--Cast Objects

if script.Parent.Handle.Ammo.Value <= 40 then
	local CosmeticBulletsFolder = workspace:FindFirstChild("CosmeticBulletsFolder") or Instance.new("Folder", workspace)
	CosmeticBulletsFolder.Name = "CosmeticBulletsFolder"


	local Caster = FastCast.new() 


	local CosmeticBullet = Instance.new("Part")
	CosmeticBullet.Material = Enum.Material.Neon
	CosmeticBullet.Color = Color3.fromRGB(255, 255, 0)
	CosmeticBullet.CanCollide = false
	CosmeticBullet.Anchored = true
	CosmeticBullet.Size = Vector3.new(0.2, 0.2, 2.4)


	local CastParams = RaycastParams.new()
	CastParams.IgnoreWater = true
	CastParams.FilterType = Enum.RaycastFilterType.Blacklist
	CastParams.FilterDescendantsInstances = {}

	local CosmeticPartProvider = PartCacheModule.new(CosmeticBullet, 100, CosmeticBulletsFolder)

	local CastBehavior = FastCast.newBehavior()
	CastBehavior.RaycastParams = CastParams
	CastBehavior.MaxDistance = BULLET_MAXDIST
	CastBehavior.HighFidelityBehavior = FastCast.HighFidelityBehavior.Default

	CastBehavior.CosmeticBulletProvider = CosmeticPartProvider 

	CastBehavior.CosmeticBulletContainer = CosmeticBulletsFolder
	CastBehavior.Acceleration = BULLET_GRAVITY
	CastBehavior.AutoIgnoreContainer = false

	function PlayFireSound()
		local NewSound = FireSound:Clone()
		NewSound.Parent = Handle
		NewSound:Play()
		Debris:AddItem(NewSound, NewSound.TimeLength)
	end

	-- Create the spark effect for the bullet impact
	function MakeParticleFX(position, normal)
		local attachment = Instance.new("Attachment")
		attachment.CFrame = CFrame.new(position, position + normal)
		attachment.Parent = workspace.Terrain
		local particle = ImpactParticle:Clone()
		particle.Parent = attachment
		Debris:AddItem(attachment, particle.Lifetime.Max)

		
		particle.Enabled = true
		wait(0.05)
		particle.Enabled = false
	end


	local function Reflect(surfaceNormal, bulletNormal)
		return bulletNormal - (2 * bulletNormal:Dot(surfaceNormal) * surfaceNormal)
	end


	function CanRayPierce(cast, rayResult, segmentVelocity)

		local hits = cast.UserData.Hits
		if (hits == nil) then
			cast.UserData.Hits = 1
		else
			cast.UserData.Hits += 1
		end

		if (cast.UserData.Hits > 3) then
			return false
		end

		local hitPart = rayResult.Instance
		if hitPart ~= nil and hitPart.Parent ~= nil then
			local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")
			if humanoid then
				humanoid:TakeDamage(20) -- Damage.
			end
		end

		return true

	end

	function Fire(direction)
		if Tool.Parent:IsA("Backpack") then return end 
		ammo = (ammo-1)
		local directionalCF = CFrame.new(Vector3.new(), direction)
		local direction = (directionalCF * CFrame.fromOrientation(0, 0, RNG:NextNumber(0, TAU)) * CFrame.fromOrientation(math.rad(RNG:NextNumber(MIN_BULLET_SPREAD_ANGLE, MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector

		local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1)
		local myMovementSpeed = humanoidRootPart.Velocity						
		local modifiedBulletSpeed = (direction * BULLET_SPEED)

		if PIERCE_DEMO then
			CastBehavior.CanPierceFunction = CanRayPierce
		end

		local simBullet = Caster:Fire(FirePointObject.WorldPosition, direction, modifiedBulletSpeed, CastBehavior)

		PlayFireSound()
	end

	function OnRayHit(cast, raycastResult, segmentVelocity, cosmeticBulletObject)
		local hitPart = raycastResult.Instance
		local hitPoint = raycastResult.Position
		local normal = raycastResult.Normal
		if hitPart ~= nil and hitPart.Parent ~= nil then
			local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")
			if humanoid then
				humanoid:TakeDamage(15)
			end
			MakeParticleFX(hitPoint, normal) 
		end
	end

	function OnRayPierced(cast, raycastResult, segmentVelocity, cosmeticBulletObject)
		local position = raycastResult.Position
		local normal = raycastResult.Normal

		local newNormal = Reflect(normal, segmentVelocity.Unit)
		cast:SetVelocity(newNormal * segmentVelocity.Magnitude)

		cast:SetPosition(position)

	end

	function OnRayUpdated(cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject)

		if cosmeticBulletObject == nil then return end
		local bulletLength = cosmeticBulletObject.Size.Z / 2 
		local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
		cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
	end

	function OnRayTerminated(cast)
		local cosmeticBullet = cast.RayInfo.CosmeticBulletObject
		if cosmeticBullet ~= nil then
			if CastBehavior.CosmeticBulletProvider ~= nil then
				CastBehavior.CosmeticBulletProvider:ReturnPart(cosmeticBullet)
			else
				cosmeticBullet:Destroy()
			end
		end
	end

	MouseEvent.OnServerEvent:Connect(function (clientThatFired, mousePoint)
		if not CanFire then
			return
		end
		CanFire = false
		local mouseDirection = (mousePoint - FirePointObject.WorldPosition).Unit
		for i = 1, BULLETS_PER_SHOT do
			Fire(mouseDirection)
		end
		if FIRE_DELAY > 0.03 then wait(FIRE_DELAY) end
		CanFire = true
	end)

	Caster.RayHit:Connect(OnRayHit)
	Caster.RayPierced:Connect(OnRayPierced)
	Caster.LengthChanged:Connect(OnRayUpdated)
	Caster.CastTerminating:Connect(OnRayTerminated)

	Tool.Equipped:Connect(function ()
		CastParams.FilterDescendantsInstances = {Tool.Parent, CosmeticBulletsFolder}
	end)


	assert(MAX_BULLET_SPREAD_ANGLE >= MIN_BULLET_SPREAD_ANGLE, "Error: MAX_BULLET_SPREAD_ANGLE cannot be less than MIN_BULLET_SPREAD_ANGLE!")
	if (MAX_BULLET_SPREAD_ANGLE > 180) then
		warn("Warning: MAX_BULLET_SPREAD_ANGLE is over 180! This will not pose any extra angular randomization. The value has been changed to 180 as a result of this.")
		MAX_BULLET_SPREAD_ANGLE = 180
	end
elseif script.Parent.Handle.Ammo.Value == 0 then return end

on the fire function, it reduces the ammo by 1. But it does not shoot even if the NumberValue Ammo is equal to or lower then 40

You accidentally used the wrong sign. Instead of using <= use >=. It’s such an early mistake in your code that it affects everything.

if ammo.Value >= 40 then -- replaced <= with >= and used the earlier defined 'ammo' variable.


Also, I noticed in the Fire function you had

ammo = (ammo-1)

This will error because ammo is an object, not a number. You should replace it with this.

ammo.Value -= 1
1 Like

Strange. The guns seems to not shoot even with 40 ammo.
And after trying to shoot I get an error:

Remote event invocation queue exhausted for Workspace.ForgottenDogYT.AK47Ammo.MouseEvent; did you forget to implement OnServerEvent? (256 events dropped)

WAIT. I’m an idiot. I should have done this instead.

if ammo.Value >= 1 then

Sorry. My previous response only included a half-formed idea.

Also, I can’t help you with the remote event that much because you didn’t provide the code for it. I am assuming that the remote event being fired is called from the client because it says you aren’t using RemoteEvent | Roblox Creator Documentation. To handle this error you need to have the remote event in question referenced with a variable on the server script and then connected to a function, even if the function is empty. This can be done on a server script by doing something like this:

local ExampleRemote = ExamplePathToRemoteEvent

-- To connect the remote event you must do this.
ExampleRemote.OnServerEvent:Connect(function(...)
    print(...) -- This will print any values passed through the remote event
end)
1 Like