I made a gun script 3 months ago it was working perfectly. When i decided to add rocket launcher logic to my game, the script stopped working no bulletholes were created, no damage only the explosion caused damage. Only the rocket launcher did damage and create bulletholes. here is the script 3 months ago working fine:
--!nocheck
-- Services --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Variables --
local gunEvent = ReplicatedStorage.Remotes.GunEvent
local timeSinceLastShot = {}
local whoIsReloading = {}
-- Constants --
local MAX_DISCREPANCY_DIST = 10
-- Functions --
local function createSound(parent, soundProperties)
local sound = Instance.new("Sound")
for property,value in pairs(soundProperties) do
sound[property] = value
end
sound.Parent = parent
return sound
end
local function createMuzzleFlash(parent, lightProperties)
local light = Instance.new("PointLight")
for property, value in pairs(lightProperties) do
light[property] = value
end
light.Parent = parent
light.Enabled = true
return light
end
local function createBulletHole(properties, result)
if not result then return end
local bulletHole = ReplicatedStorage.Shared.Parts.BulletHole:Clone()
bulletHole.Anchored = false -- make sure to unachor the bullet hole
local weld = Instance.new("WeldConstraint")
weld.Part0 = bulletHole
weld.Part1 = result.Instance
weld.Parent = bulletHole
local offset = (result.Position - result.InstanceOriginPosition)
local serverCFrameOffset = CFrame.new(result.Instance.Position) * CFrame.new(offset)
bulletHole.Size = properties.BulletHoleSize
bulletHole.CFrame = CFrame.new(serverCFrameOffset.Position, serverCFrameOffset.Position+result.Normal) -- at the hit position, and facing the direction of the lookVector
bulletHole.Parent = workspace.FilteringFolder.BulletHoles
bulletHole:FindFirstChildWhichIsA("Decal").Color3 = result.Instance.Color
bulletHole.ParticleEmitter.Color = ColorSequence.new(result.Instance.Color)
bulletHole.ParticleEmitter:Emit(properties.BulletHoleParticleAmount)
Debris:AddItem(bulletHole, 0.5)
end
local function checkBulletRay(properties, result, player)
if not result then return end
local character = result.Instance.Parent
if not character:FindFirstChildWhichIsA("Humanoid") or Players:GetPlayerFromCharacter(character) then return end
local offset = (result.Position - result.InstanceOriginPosition)
local serverCFrameOffset = CFrame.new(result.Instance.Position) * CFrame.new(offset)
if (result.Position - serverCFrameOffset.Position).Magnitude > MAX_DISCREPANCY_DIST then return end
--[[ by the time this information reaches the server, if the position where the client said they hit is within
a reasonable distance of where the actual location of that hit is on the server, then probably no exploits
10 studs is a GENEROUS estimate for lag, etc..
A player COULD alter where the they say they hit the head, granting them double damage, even though they hit maybe like an arm.
to avoid this, you could instead have the client pass their direciton where they shot and calculate the raycast on the server...
BUT...
that introduces lag and could make it where a player sees that they hit a zombie but the server didn't register the shot due to lag,
which ruins the player experience
you could also just reduce the discrepancy distance down to like 2 studs instead to try and combat that possible issue!
]]
character.Humanoid:TakeDamage(properties.Damage * properties.BodyPartMultipliers[result.Instance.Name])
if character:FindFirstChild("WhoLastDamaged") then
character.WhoLastDamaged.Value = player
end
end
local function reload(player, gun, properties)
local ammoInMag = gun:GetAttribute("AmmoInMag")
local ammoReserve = gun:GetAttribute("AmmoReserve")
if ammoInMag == properties.MaxMagAmmo then return end
if ammoReserve <= 0 then return end
if timeSinceLastShot[player.Name][gun.Name] then
if (DateTime.now().UnixTimestampMillis - timeSinceLastShot[player.Name][gun.Name]) < properties.ReloadDuration then return end
-- the player somehow reloaded FASTER than the reload animation length (probably exploits)
end
if not whoIsReloading[player.Name] then
-- create a new table for the player if they don't have one
whoIsReloading[player.Name] = {[gun.Name] = true}
elseif whoIsReloading[player.Name][gun.Name] then
return
end
whoIsReloading[player.Name][gun.Name] = true
local ammoNeeded = math.min(properties.MaxMagAmmo - ammoInMag, ammoReserve)
gun:SetAttribute("AmmoInMag", ammoInMag+ammoNeeded)
gun:SetAttribute("AmmoReserve", ammoReserve-ammoNeeded)
whoIsReloading[player.Name][gun.Name] = false
end
local function shoot(player, gun, properties, result)
if gun:GetAttribute("AmmoInMag") == 0 then return end
if not timeSinceLastShot[player.Name] then
-- create a new table for the player if they don't have one
timeSinceLastShot[player.Name] = {[gun.Name] = 0}
end
if not timeSinceLastShot[player.Name][gun.Name] then
-- create a new key-value pair for a new gun
timeSinceLastShot[player.Name][gun.Name] = 0
end
if DateTime.now().UnixTimestampMillis - timeSinceLastShot[player.Name][gun.Name] < 60/properties.FireRate then return end
-- the player somehow shot FASTER than the cooldown for shots (probably exploits)
timeSinceLastShot[player.Name][gun.Name] = DateTime.now().UnixTimestampMillis
gun:SetAttribute("AmmoInMag", gun:GetAttribute("AmmoInMag") - 1)
-- make sure to remove ammo from gun
if gun:GetAttribute("GunType") == "Spread" then
if not result or #result == 0 then
-- all the pellets went into the air, hit nothing, but we still want to play sound & effects
local light = createMuzzleFlash(gun.Handle.Muzzle, properties.MuzzleFlashProperties)
task.spawn(function()
task.wait(0.15)
light:Destroy()
end)
gun.Handle.Muzzle.Flash:Emit(1)
local sound = createSound(gun.Handle.Muzzle, properties.ShootSoundProperties)
sound:Play()
sound.Ended:Connect(function() sound:Destroy() end)
return
end
if #result > properties.TotalPellets then return end
-- the client tried to fire more pellets than what is possible for their gun, which would indicate exploits
for i,pelletResult in ipairs(result) do
if i == 1 then
-- play sound & effects for only 1 pellet
local light = createMuzzleFlash(gun.Handle.Muzzle, properties.MuzzleFlashProperties)
task.spawn(function()
task.wait(0.15)
light:Destroy()
end)
gun.Handle.Muzzle.Flash:Emit(1)
local sound = createSound(gun.Handle.Muzzle, properties.ShootSoundProperties)
sound:Play()
sound.Ended:Connect(function() sound:Destroy() end)
end
checkBulletRay(properties, pelletResult, player)
createBulletHole(properties, pelletResult, player)
end
elseif gun:GetAttribute("GunType") == "Standard" then
local light = createMuzzleFlash(gun.Handle.Muzzle, properties.MuzzleFlashProperties)
task.spawn(function()
task.wait(0.15)
light:Destroy()
end)
gun.Handle.Muzzle.Flash:Emit(5)
local sound = createSound(gun.Handle.Muzzle, properties.ShootSoundProperties)
sound:Play()
sound.Ended:Connect(function() sound:Destroy() end)
checkBulletRay(properties, result, player)
createBulletHole(properties, result)
end
end
-- Event handlers --
gunEvent.OnServerEvent:Connect(function(player, action, ...)
if not player.Character then return end
local gun = player.Character:FindFirstChildWhichIsA("Tool")
if not gun:GetAttribute("GunType") then return end
local properties = require(gun.Properties)
if action == "ValidateShot" then
local args = {...}
if args[1] and args[2] and args[3] and args[4] then
-- these args are passed when a 'standard' gun fires the event
args = {["Instance"] = args[1], ["Position"] = args[2], ["Normal"] = args[3], ["InstanceOriginPosition"] = args[4]}
elseif args[1] then
-- this arg is passed when a shotgun fires the event
args = args[1]
else
-- the bullet hit nothing
args = nil
end
shoot(player, gun, properties, args)
elseif action == "ValidateReload" then
reload(player, gun, properties)
end
end)
Players.PlayerRemoving:Connect(function(player)
-- frees up memory
if timeSinceLastShot[player.Name] then
timeSinceLastShot[player.Name] = nil
end
if whoIsReloading[player.Name] then
whoIsReloading[player.Name] = nil
end
end)
the script with new added functionality:
--!nocheck
-- Services --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
-- Variables --
local gunEvent = ReplicatedStorage.Remotes.GunEvent
local timeSinceLastShot = {}
local whoIsReloading = {}
-- Constants --
local MAX_DISCREPANCY_DIST = 10
-- Functions --
local function createSound(parent, soundProperties)
local sound = Instance.new("Sound")
for property,value in pairs(soundProperties) do
sound[property] = value
end
sound.Parent = parent
return sound
end
local function createMuzzleFlash(parent, lightProperties)
local light = Instance.new("PointLight")
for property, value in pairs(lightProperties) do
light[property] = value
end
light.Parent = parent
light.Enabled = true
return light
end
local function createExplosion(explosionProperties)
local explosion = Instance.new("Explosion")
for property,value in pairs(explosionProperties) do
explosion[property] = value
end
-- don't parent explosion to workspace yet
return explosion
end
local function createBulletHole(properties, result, rocket)
if not result then return end
local bulletHole = ReplicatedStorage.Shared.Parts.BulletHole:Clone()
bulletHole.Anchored = false -- make sure to unachor the bullet hole
local weld = Instance.new("WeldConstraint")
weld.Part0 = bulletHole
weld.Part1 = result.Instance
weld.Parent = bulletHole
local offset = (result.Position - result.InstanceOriginPosition)
local serverCFrameOffset = CFrame.new(result.Instance.Position) * CFrame.new(offset)
bulletHole.Size = properties.BulletHoleSize
bulletHole.CFrame = CFrame.new(serverCFrameOffset.Position, serverCFrameOffset.Position+result.Normal) -- at the hit position, and facing the direction of the lookVector
bulletHole.Parent = workspace.FilteringFolder.BulletHoles
bulletHole:FindFirstChildWhichIsA("Decal").Color3 = result.Instance.Color
bulletHole.ParticleEmitter.Color = ColorSequence.new(result.Instance.Color)
bulletHole.ParticleEmitter:Emit(properties.BulletHoleParticleAmount)
if rocket ~= nil then
local explosion = createExplosion({["BlastPressure"] = 0, ["BlastRadius"] = 20, ["DestroyJointRadiusPercent"] = 0, ["ExplosionType"] = Enum.ExplosionType.NoCraters})
explosion.Position = bulletHole.Position
explosion.Parent = workspace:WaitForChild("FilteringFolder"):WaitForChild("BulletHoles")
explosion.Hit:Connect(function(hitPart, distance)
if hitPart.Name ~= "HumanoidRootPart" then return end
local humanoid = hitPart.Parent:FindFirstChildWhichIsA("Humanoid")
if not humanoid then return end
if Players:GetPlayerFromCharacter(hitPart.Parent) then return end -- for when update of players can not kill eachother
local damage = 12000
humanoid:TakeDamage(damage)
end)
end
Debris:AddItem(bulletHole, 0.5)
end
local function checkBulletRay(properties, result, player)
if not result then return end
local character = result.Instance.Parent
if not character:FindFirstChildWhichIsA("Humanoid") or Players:GetPlayerFromCharacter(character) then return end
local offset = (result.Position - result.InstanceOriginPosition)
local serverCFrameOffset = CFrame.new(result.Instance.Position) * CFrame.new(offset)
if (result.Position - serverCFrameOffset.Position).Magnitude > MAX_DISCREPANCY_DIST then return end
--[[ by the time this information reaches the server, if the position where the client said they hit is within
a reasonable distance of where the actual location of that hit is on the server, then probably no exploits
10 studs is a GENEROUS estimate for lag, etc..
A player COULD alter where the they say they hit the head, granting them double damage, even though they hit maybe like an arm.
to avoid this, you could instead have the client pass their direciton where they shot and calculate the raycast on the server...
BUT...
that introduces lag and could make it where a player sees that they hit a zombie but the server didn't register the shot due to lag,
which ruins the player experience
you could also just reduce the discrepancy distance down to like 2 studs instead to try and combat that possible issue!
]]
character.Humanoid:TakeDamage(properties.Damage * properties.BodyPartMultipliers[result.Instance.Name])
if character:FindFirstChild("WhoLastDamaged") then
character.WhoLastDamaged.Value = player
end
end
local function reload(player, gun, properties)
local ammoInMag = gun:GetAttribute("AmmoInMag")
local ammoReserve = gun:GetAttribute("AmmoReserve")
if ammoInMag == properties.MaxMagAmmo then return end
if ammoReserve <= 0 then return end
if timeSinceLastShot[player.Name][gun.Name] then
if (DateTime.now().UnixTimestampMillis - timeSinceLastShot[player.Name][gun.Name]) < properties.ReloadDuration then return end
-- the player somehow reloaded FASTER than the reload animation length (probably exploits)
end
if not whoIsReloading[player.Name] then
-- create a new table for the player if they don't have one
whoIsReloading[player.Name] = {[gun.Name] = true}
elseif whoIsReloading[player.Name][gun.Name] then
return
end
whoIsReloading[player.Name][gun.Name] = true
local ammoNeeded = math.min(properties.MaxMagAmmo - ammoInMag, ammoReserve)
gun:SetAttribute("AmmoInMag", ammoInMag+ammoNeeded)
gun:SetAttribute("AmmoReserve", ammoReserve-ammoNeeded)
whoIsReloading[player.Name][gun.Name] = false
end
local function shoot(player, gun, properties, result, rocket)
if gun:GetAttribute("AmmoInMag") == 0 then return end
if not timeSinceLastShot[player.Name] then
-- create a new table for the player if they don't have one
timeSinceLastShot[player.Name] = {[gun.Name] = 0}
end
if not timeSinceLastShot[player.Name][gun.Name] then
-- create a new key-value pair for a new gun
timeSinceLastShot[player.Name][gun.Name] = 0
end
if DateTime.now().UnixTimestampMillis - timeSinceLastShot[player.Name][gun.Name] < 60/properties.FireRate then return end
-- the player somehow shot FASTER than the cooldown for shots (probably exploits)
timeSinceLastShot[player.Name][gun.Name] = DateTime.now().UnixTimestampMillis
gun:SetAttribute("AmmoInMag", gun:GetAttribute("AmmoInMag") - 1)
-- make sure to remove ammo from gun
if gun:GetAttribute("GunType") == "Spread" then
if not result or #result == 0 then
-- all the pellets went into the air, hit nothing, but we still want to play sound & effects
local light = createMuzzleFlash(gun.Handle.Muzzle, properties.MuzzleFlashProperties)
task.spawn(function()
task.wait(0.15)
light:Destroy()
end)
gun.Handle.Muzzle.Flash:Emit(1)
local sound = createSound(gun.Handle.Muzzle, properties.ShootSoundProperties)
sound:Play()
sound.Ended:Connect(function() sound:Destroy() end)
return
end
if #result > properties.TotalPellets then return end
-- the client tried to fire more pellets than what is possible for their gun, which would indicate exploits
for i,pelletResult in ipairs(result) do
if i == 1 then
-- play sound & effects for only 1 pellet
local light = createMuzzleFlash(gun.Handle.Muzzle, properties.MuzzleFlashProperties)
task.spawn(function()
task.wait(0.15)
light:Destroy()
end)
gun.Handle.Muzzle.Flash:Emit(1)
local sound = createSound(gun.Handle.Muzzle, properties.ShootSoundProperties)
sound:Play()
sound.Ended:Connect(function() sound:Destroy() end)
end
checkBulletRay(properties, pelletResult, player)
createBulletHole(properties, pelletResult, player)
end
elseif gun:GetAttribute("GunType") == "Standard" then
local light = createMuzzleFlash(gun.Handle.Muzzle, properties.MuzzleFlashProperties)
task.spawn(function()
task.wait(0.15)
light:Destroy()
end)
gun.Handle.Muzzle.Flash:Emit(5)
local sound = createSound(gun.Handle.Muzzle, properties.ShootSoundProperties)
sound:Play()
sound.Ended:Connect(function() sound:Destroy() end)
checkBulletRay(properties, result, player)
if rocket == nil then
createBulletHole(properties, result)
else
createBulletHole(properties, result, rocket)
end
end
end
-- Event handlers --
gunEvent.OnServerEvent:Connect(function(player, action, rocket, ...)
if not player.Character then return end
local gun = player.Character:FindFirstChildWhichIsA("Tool")
if not gun:GetAttribute("GunType") then return end
local properties = require(gun.Properties)
if action == "ValidateShot" then
local args = {...}
if args[1] and args[2] and args[3] and args[4] then
-- these args are passed when a 'standard' gun fires the event
args = {["Instance"] = args[1], ["Position"] = args[2], ["Normal"] = args[3], ["InstanceOriginPosition"] = args[4]}
elseif args[1] then
-- this arg is passed when a shotgun fires the event
args = args[1]
else
-- the bullet hit nothing
args = nil
end
if rocket == nil then
shoot(player, gun, properties, args)
else
shoot(player, gun, properties, args, rocket)
end
elseif action == "ValidateReload" then
reload(player, gun, properties)
end
end)
Players.PlayerRemoving:Connect(function(player)
-- frees up memory
if timeSinceLastShot[player.Name] then
timeSinceLastShot[player.Name] = nil
end
if whoIsReloading[player.Name] then
whoIsReloading[player.Name] = nil
end
end)