local UserInputService = game:GetService("UserInputService")
local RepStorage = game:GetService("ReplicatedStorage")
local idle = script.Parent.Anims.Idle
local reloadanim = script.Parent.Anims.Reload
local kickBack = script.Parent.Anims.Fire
local reloadTrack = nil
local idleTrack = nil
local kickTrack = nil
local equipped = false
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local tool = script.Parent
local gui = tool.gui
local max_ammo = 10
local current_ammo = max_ammo
local firemode = "Semi" -- Change this to "semi" or "auto" as needed
local reload_duration = 1
local cooldown_duration = 0.9
local cooldown = false
local firing = false
local reloading = false
-- Recoil
local recoilShakerMod = require(RepStorage.CameraShaker)
local camera = workspace.CurrentCamera
local cameraShake = recoilShakerMod.new(Enum.RenderPriority.Camera.Value, function(shakeCFrame)
camera.CFrame = camera.CFrame * shakeCFrame
end)
cameraShake:Start()
function recoil()
cameraShake:Shake(recoilShakerMod.Presets.Bump)
end
local function reload()
if reloading or current_ammo == max_ammo then return end
if equipped then
reloading = true
reloadTrack:Play()
tool.Sounds.Reload:Play()
tool.gunmodel.Ammo.Transparency = 0
gui.gun_framethingy.ammo.Text = "Reloading..."
task.wait(tool.Sounds.Reload.TimeLength)
tool.gunmodel.Ammo.Transparency = 1
current_ammo = max_ammo
gui.gun_framethingy.ammo.Text = current_ammo.."/"..max_ammo
reloading = false
end
end
local function fire()
if current_ammo <= 0 then
reload()
return
end
if reloading then return end
if cooldown then return end
current_ammo -= 1
gui.gun_framethingy.ammo.Text = current_ammo.."/"..max_ammo
tool.fire:FireServer(tool.Handle.muzzle.WorldPosition, mouse.Hit.Position)
kickTrack:Play() -- Ensure the kick animation plays each time
recoil()
cooldown = true
task.wait(cooldown_duration)
cooldown = false
if current_ammo <= 0 then
reload()
end
end
local function startFiring()
if reloading or cooldown then return end -- Prevent firing if reloading or cooldown is active
firing = true
if firemode == "Semi" then
fire()
elseif firemode == "Auto" then
while firing and current_ammo > 0 do
fire()
task.wait(cooldown_duration) -- Control auto fire rate
end
end
end
local function stopFiring()
firing = false
end
tool.Activated:Connect(startFiring)
tool.Deactivated:Connect(stopFiring)
UserInputService.InputBegan:Connect(function(input, processed)
if processed then return end
if input.KeyCode == Enum.KeyCode.R then
reload()
end
end)
UserInputService.TouchStarted:Connect(function(touch, processed)
if processed then return end
startFiring()
end)
UserInputService.TouchEnded:Connect(function(touch, processed)
if processed then return end
stopFiring()
end)
tool.Equipped:Connect(function()
gui.Parent = player.PlayerGui
mouse.Icon = "rbxassetid://12829852445"
gui.gun_framethingy.ammo.Text = current_ammo.."/"..max_ammo
local char = player.Character or player.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid")
kickTrack = hum.Animator:LoadAnimation(kickBack)
reloadTrack = hum.Animator:LoadAnimation(reloadanim)
idleTrack = hum.Animator:LoadAnimation(idle)
idleTrack:Play()
equipped = true
end)
tool.Unequipped:Connect(function()
gui.Parent = tool
idleTrack:Stop()
mouse.Icon = ""
equipped = false
end)
So that is my code, but every time I tried to make a hitmarker, it would not work. Can somebody please make a edited version of this code? I’ve kinda ran out of ideas! The code above has no hitmarker code, so feel free to edit it and place it below.
When you mean return, do you mean at the end of the function or somewhere in the functions
and also how would i go about using the remote function, do i get the function and invoke it? Then how would I check if it hit using true or false?
local RunService = game:GetService("RunService") -- used for updating the bullets
local tool = script.Parent
local bullets = {} -- table that holds all the active bullets
-- Penetration configuration
local maxPenetrationDepth = 10 -- Maximum penetration depth in studs
local materialPenetrationCosts = {
[Enum.Material.Plastic] = 1, -- Penetration cost per material
[Enum.Material.Wood] = 2,
[Enum.Material.Glass] = 0.5,
}
-- Materials that completely stop penetration
local nonPenetrableMaterials = {
[Enum.Material.Metal] = true,
[Enum.Material.Concrete] = true,
}
-- Gravity (studs per second squared)
local gravity = Vector3.new(0, -30, 0)
-- Function to handle impacts
local function impact(raycast)
local part = Instance.new("Part", workspace.Terrain)
local decal = Instance.new("Decal", part)
part.Size = Vector3.new(0.5, 0.5, 0)
part.Massless = true
part.Transparency = 1
part.CanCollide = false
part.CanQuery = false
part.CanTouch = false
part.CFrame = CFrame.new(raycast.Position, raycast.Position + raycast.Normal) * CFrame.Angles(0, 0, math.rad(math.random(0, 360)))
part.Color = Color3.new()
decal.Texture = "rbxassetid://12769915043"
local weld = Instance.new("WeldConstraint", part)
weld.Part0 = raycast.Instance
weld.Part1 = part
game.Debris:AddItem(part, 5)
end
tool.fire.OnServerEvent:Connect(function(player, origin, dir)
local sfx = tool.Sounds.Fire:Clone()
sfx.Parent = tool.Handle
sfx:Play()
game.Debris:AddItem(sfx, sfx.TimeLength)
tool.Handle.muzzle["FlashFX[Flash]"].Enabled = true
tool.Handle.muzzle.smoke.Enabled = true
tool.Handle.muzzle.FIRE.Enabled = true
tool.Handle.muzzle.SPECKS.Enabled = true
tool.Handle.muzzle.GLOW.Enabled = true
task.wait(0.2)
tool.Handle.muzzle["FlashFX[Flash]"].Enabled = false
tool.Handle.muzzle.smoke.Enabled = false
tool.Handle.muzzle.FIRE.Enabled = false
tool.Handle.muzzle.SPECKS.Enabled = false
tool.Handle.muzzle.GLOW.Enabled = false
local bullet = game.ReplicatedStorage.bullet:Clone()
bullet.Parent = workspace.Terrain
bullets[bullet] = {
origin = origin,
direction = CFrame.new(origin, dir).LookVector,
velocity = CFrame.new(origin, dir).LookVector * 1500, -- Initial velocity
lifetime = tick() + 2;
firedby = tool.Parent,
penetrationDepth = maxPenetrationDepth, -- Track remaining penetration depth
playerPenetrations = 0, -- Track the number of players headshot
}
end)
RunService.Heartbeat:Connect(function(dt)
for bullet, data in pairs(bullets) do
local params = RaycastParams.new()
params.RespectCanCollide = false
params.FilterDescendantsInstances = {data.firedby}
-- Update velocity with gravity
data.velocity += gravity * dt
local movement = data.velocity * dt
-- Raycast with the updated movement
local ray = workspace:Raycast(data.origin, movement, params)
if ray then
local material = ray.Instance.Material
-- Check if the material completely stops penetration
if nonPenetrableMaterials[material] then
impact(ray)
bullet:Destroy()
bullets[bullet] = nil
return
end
-- Handle damage and impact
local hum = ray.Instance.Parent:FindFirstChildOfClass("Humanoid")
local head = ray.Instance.Parent:FindFirstChild("Head")
if hum then
local isHeadshot = ray.Instance == head
if isHeadshot then
-- Apply headshot damage and track penetrations
hum:TakeDamage(165)
data.playerPenetrations += 1
-- Stop the bullet after 2 headshot penetrations
if data.playerPenetrations >= 2 then
bullet:Destroy()
bullets[bullet] = nil
return
end
else
-- Apply normal damage and stop the bullet
hum:TakeDamage(65)
bullet:Destroy()
bullets[bullet] = nil
return
end
end
impact(ray)
-- Reduce penetration depth for penetrable materials
local materialCost = materialPenetrationCosts[material] or 5
data.penetrationDepth -= materialCost
-- Check if the bullet can penetrate further
if data.penetrationDepth > 0 then
data.origin = ray.Position + (movement.Unit * 0.1) -- Move origin slightly past the impact point
else
bullet:Destroy()
bullets[bullet] = nil
end
else
data.origin += movement
bullet.CFrame = CFrame.new(data.origin, data.origin + data.velocity)
if tick() > data.lifetime then
bullet:Destroy()
bullets[bullet] = nil
end
end
end
end)
remote function works exactly like a function, but in this case you would call the function on the client and then the server would return a value
on the server you can do
remoteFunction.onServerInvoke = function(something) -- pass arguments from client here
return 5 -- this gets returned to the client
end
in your case you would replace tool.fire:FireServer with tool.fire:InvokeServer(tool.Handle.muzzle.WorldPosition, mouse.Hit.Position) and then keep the same code on the server but return if the hit is succsessful
RunService.Heartbeat:Connect(function(dt)
for bullet, data in pairs(bullets) do
local params = RaycastParams.new()
params.RespectCanCollide = false
params.FilterDescendantsInstances = {data.firedby}
-- Update velocity with gravity
data.velocity += gravity * dt
local movement = data.velocity * dt
-- Raycast with the updated movement
local ray = workspace:Raycast(data.origin, movement, params)
if ray then
local material = ray.Instance.Material
-- Check if the material completely stops penetration
if nonPenetrableMaterials[material] then
impact(ray)
bullet:Destroy()
bullets[bullet] = nil
return
end
-- Handle damage and impact
local hum = ray.Instance.Parent:FindFirstChildOfClass("Humanoid")
local head = ray.Instance.Parent:FindFirstChild("Head")
if hum then
local isHeadshot = ray.Instance == head
if isHeadshot then
-- Apply headshot damage and track penetrations
hum:TakeDamage(165)
data.playerPenetrations += 1
-- Stop the bullet after 2 headshot penetrations
if data.playerPenetrations >= 2 then
bullet:Destroy()
bullets[bullet] = nil
return
end
else
-- Apply normal damage and stop the bullet
hum:TakeDamage(65)
bullet:Destroy()
bullets[bullet] = nil
return
end
end
impact(ray)
-- Reduce penetration depth for penetrable materials
local materialCost = materialPenetrationCosts[material] or 5
data.penetrationDepth -= materialCost
-- Check if the bullet can penetrate further
if data.penetrationDepth > 0 then
data.origin = ray.Position + (movement.Unit * 0.1) -- Move origin slightly past the impact point
else
bullet:Destroy()
bullets[bullet] = nil
end
else
data.origin += movement
bullet.CFrame = CFrame.new(data.origin, data.origin + data.velocity)
if tick() > data.lifetime then
bullet:Destroy()
bullets[bullet] = nil
end
end
end
end)