Shotgun Raycasting Help

I’m making a shotgun system, though I’m not good with raycasting and haven’t touched coding in a while, problem is I don’t know how to make a “secure” and optimized system. I can’t find a good way to keep it from being exploited (with something like an autoclicker) that isn’t crude

I’ll just paste the entire scripts so everything stupid you find please help lol

–client

local SingleShot = script.Parent
local Model = SingleShot:WaitForChild("SingleShot")

local Configuration = SingleShot:WaitForChild("Configuration")
local Events = SingleShot:WaitForChild("Events")

local Player = game.Players.LocalPlayer
local Mouse = Player:GetMouse()
local Character = Player.Character or Player.CharacterAdded:Wait()
local Animator = Character:WaitForChild("Humanoid"):WaitForChild("Animator")

local GunParams = RaycastParams.new()
GunParams.IgnoreWater = true
GunParams.FilterDescendantsInstances = {Character}
GunParams.CollisionGroup = "Raycast"

local DebrisLife = 10
local Debounce = false
local Reloading = false
local Cooldown = SingleShot:WaitForChild("Cooldown")

local function loadAnimation(animator,id)
	local Anim = Instance.new("Animation")
	Anim.AnimationId = "rbxassetid://"..tostring(id)
	
	local LoadedAnim = animator:LoadAnimation(Anim)
	Anim:Destroy()
	return LoadedAnim
end

local IdleAnimation = loadAnimation(Animator, 17163654858)
local FireAnimation = loadAnimation(Animator, 17163684520)
local ReloadAnimation = loadAnimation(Animator, 17164431482)
local LoadedAnimations = {IdleAnimation, FireAnimation, ReloadAnimation}

local function onEquip()
	local EquipSound = Instance.new("Sound")
	EquipSound.Parent = Model.PrimaryPart
	EquipSound.Volume = 0.5
	EquipSound.SoundId = "rbxassetid://7405483764"
	EquipSound:Play()
	game.Debris:AddItem(EquipSound, 0.755)
	
	IdleAnimation:Play()
	SingleShot.Enabled = true
	
	task.wait(0.6)
	if Debounce and SingleShot.Enabled == true and Reloading == false then
		Reloading = true
		ReloadAnimation:Play()
	end
end

local function onUnequip()
	local UnequipSound = Instance.new("Sound")
	UnequipSound.Parent = Model.PrimaryPart
	UnequipSound.Volume = 0.5
	UnequipSound.SoundId = "rbxassetid://609342351"
	UnequipSound:Play()
	game.Debris:AddItem(UnequipSound, 0.503)
	
	for _,Animation in pairs(Animator:GetPlayingAnimationTracks()) do
		if table.find(LoadedAnimations, Animation) then
			Animation:Stop()
		end
	end
	SingleShot.Enabled = false
	Reloading = false
end

local function castRay(originPos, endPos)
	local Xoffset = math.random(-Configuration.Spread.Value*100,Configuration.Spread.Value*100)/100
	local Yoffset = math.random(-Configuration.Spread.Value*100,Configuration.Spread.Value*100)/100
	local Offset = CFrame.Angles(math.rad(Xoffset),math.rad(Yoffset),0)
	
	local Direction = (CFrame.new(originPos,endPos)*Offset).LookVector.Unit * Configuration.Range.Value
	local Raycast = workspace:Raycast(originPos, Direction, GunParams)
	
	if Raycast then
		Events.CastRay:FireServer(Raycast.Instance, Raycast.Position)
		if Raycast.Instance.Parent:FindFirstChildWhichIsA("Humanoid") then return end
		
		local BulletHole = Instance.new("Part")
		 BulletHole.Name = "Hole"
		BulletHole.Parent = Raycast.Instance
		
		BulletHole.Transparency = 1
		local holesize = math.random(4,5)/10
		
		BulletHole.Size = Vector3.new(holesize, holesize, 0.05)
		
		BulletHole.Anchored = true
		BulletHole.CanCollide = false
		BulletHole.CanQuery = false
		BulletHole.CanTouch = false
		BulletHole.CastShadow = false
		BulletHole.Massless = true
		
		local Decal = Instance.new("Decal")
		Decal.Parent = BulletHole
		Decal.Texture = "rbxassetid://4784905666"
		
		BulletHole.CFrame = CFrame.lookAt(Raycast.Position, Raycast.Position + Raycast.Normal)
		
		game.Debris:AddItem(BulletHole,DebrisLife)
	end
	
end

local function onActivate()
	if Debounce == false and Reloading == false then
		Debounce = true
		
		Events.Fire:FireServer()
		FireAnimation:Play()
		
		for i = 1,Configuration.Pellets.Value do
			castRay(Model.GripPoint.Position, Mouse.Hit.Position)
		end
		
		task.wait(0.5)
		if SingleShot.Enabled == true and Reloading == false then
			Reloading = true
			ReloadAnimation:Play()
		end
		
	end
end

SingleShot.Equipped:Connect(onEquip)
SingleShot.Unequipped:Connect(onUnequip)
SingleShot.Activated:Connect(onActivate)
Cooldown.Changed:Connect(function()
	if Cooldown.Value == false then
		Debounce = false
	end
end)

--Animation Sequence Events

ReloadAnimation:GetMarkerReachedSignal("Eject"):Connect(function()
	local EjectSound = Instance.new("Sound")
	EjectSound.Parent = Model.PrimaryPart
	EjectSound.Volume = 0.5
	EjectSound.SoundId = "rbxassetid://9117307583"
	EjectSound:Play()
	game.Debris:AddItem(EjectSound, 0.824)
	
	if Model.Shell.Transparency ~= 1 then
		local EjectedShell = Model.Shell:Clone()
		EjectedShell.Parent = workspace

		EjectedShell.CanCollide = true
		EjectedShell.Weld:Destroy()
		EjectedShell.Velocity = EjectedShell.CFrame.UpVector * -25
		game.Debris:AddItem(EjectedShell,DebrisLife/2)
	end
	
	Model.Shell.Transparency = 1
end)

ReloadAnimation:GetMarkerReachedSignal("Insert"):Connect(function()
	local InsertSound = Instance.new("Sound")
	InsertSound.Parent = Model.PrimaryPart
	InsertSound.Volume = 0.5
	InsertSound.SoundId = "rbxassetid://142429556"
	InsertSound:Play()
	game.Debris:AddItem(InsertSound, 0.339)
	
	Model.Shell.Transparency = 0
end)

ReloadAnimation.Stopped:Connect(function()
	if SingleShot.Enabled == true then
		Reloading = false
		Events.Reload:FireServer()
	end
end)

–Server

local Model = SingleShot:WaitForChild("SingleShot")

local Configuration = SingleShot:WaitForChild("Configuration")
local Cooldown = SingleShot:WaitForChild("Cooldown")
local Events = SingleShot:WaitForChild("Events")

Events.CastRay.OnServerEvent:Connect(function(Player, Hit, Pos)
	local Humanoid = Hit.Parent:FindFirstChild("Humanoid")
	local Victim = game:GetService("Players"):GetPlayerFromCharacter(Hit.Parent)

	if Humanoid then

		if Victim == nil or Victim.Team ~= Player.Team then
			Humanoid:TakeDamage(Configuration.Damage.Value)

			if Hit.Name == "Head" then
				Humanoid:TakeDamage(Configuration.Damage.Value)
			end
		end

	end
	
	--Digging noise
	local ta = Instance.new("Attachment")
	ta.Name = "soundpos"
	ta.Position = Pos
	ta.Parent = workspace.Terrain

	local Sound = Instance.new("Sound")
	Sound.RollOffMinDistance = 15
	Sound.Name = "Hit"
	Sound.SoundId = 'rbxassetid://'..1489924400
	Sound.Parent = ta
	Sound:Play()
	game.Debris:AddItem(ta,0.5)
end)

Events.Fire.OnServerEvent:Connect(function()
	Cooldown.Value = true
	
	Model.GripPoint.Fire:Play()
end)

Events.Reload.OnServerEvent:Connect(function()
	Cooldown.Value = false
end)

SingleShot.Equipped:Wait()

local Motor6D = Instance.new("Motor6D")
Motor6D.Parent = SingleShot
Motor6D.Name = "BodyAttach"
Motor6D.Part0 = SingleShot.Parent:WaitForChild("Torso")
Motor6D.Part1 = Model.GripPoint
1 Like

Just a tip, use blockcasting instead of raycasting for shotguns. I have 2 main reasons for these.

  • Blockcasting gives a shorter range (max direction being 1200 studs or something). Raycasting can be set to any amount of direction.
  • Blockcasting uses blocks to detect hit. You can make a bigger block to replicate shot spread in a shotgun.
3 Likes

Also, I’d detect hit in the server rather than in the client.

2 Likes

Hi . It is nice seeing you use client side hit detection for better hit responses. Leme just write down steps I use to prevent exploit.

  1. make sure you send the raycast’s origin, direction, hit position and hit instance to the server . When you do this , you allow the server to have more information of the shots
  2. do all the ammo checks or fire rate checks
  3. check that the client origin is lets say less 5 studs away from the server’s origin to prevent spoofing
  4. check of the hit position is close to the hit direction vector (this step requires a bit of math . I currently don’t have this code but you can do some research ig) This prevents origin, hit position and hit instance spoofing
  5. make sure the hit position is close to hit instance
  6. raycast from origin in the given client direction to prevent wall hacks

hope this helps :cowboy_hat_face:

6 Likes

ok thank u so much, should I do all these checks on the server or client?

How similar is it to raycasting? I have never heard of blockcasting

1 Like

They were recently added so I understand.

2 Likes

I woudlnt recommend Blockcasts for a shotgun because:

  1. If the block cast hits anything even by a corner the rest of “pellets” cant continue flying forward they will just get cancelled.
  2. Its harder to make good looking visuals for a shotgun from a blockcast than from a bunch of raycasts.
  3. If you make a single system that uses Raycasts you can reuse it for other weapons keeping similar patterns in the codebase instead of having an odd one out.

I did this sometime ago for a third person shooter game, heres how i solved this issue.

Shoot like 8 or however many raycasts you need on the client FROM THE CAMERA, Then to prevent clipping the camera through objects to shoot, fire a single raycast from the muzzle of the gun to where the raycast is supposed to hit, this lets you detecr obstacles in the way of the gun. If the obstacle check passes and the raycasts hit a player, fire a remote event to the server signifying a potential hit. On the server compare the server copy of raycast origins and directions to the one the player sent, if they match within the error margin allow the hit and deal damage. You can play all the visuals on the client without servers verification.
If you want to be extra secure you could fire some raycasts on the server too but that might be just wasteful.

3 Likes

Thank you, I was thinking about those setbacks with blockcasting. When you say matching the server copy of the raycast origin and direction, do you mean firing a raycast to copy the client? if not how would I go about what you were saying?

you should do all the checks on the server . The client should just send the hit data like origin, direction,hit position, hit instance to the server . The server will validate the hit base on the hit data

1 Like

ok many many thanks, one more question, should I put a cooldown on the client or server? That is my main struggle, the client is less secure and I don’t know how I would add a cooldown to the server.

I haven’t had the issues doing a shotgun with this, but fair.

Its still worth making the checks on client first to not have to send unnecessary data to the server. Remote events cause allot of traffic on the network, Computing power is plentiful but network traffic IS NOT. Roblox servers are awful when it comes to networking.

1 Like

Yeah it may be more of an edge case, but still is valid. I had these issues before. So in saying from experience, it really depends on how you want the guns to feel.

personally , i would do a hybrid . I add cooldown on client to lower the network traffic incase the client spam the mouse . I add cooldown on the server to prevent firerate exploits . However , on the server, maybe the cooldown would be (cooldown)-tolerance . The tolerance value is to account for lag spikes . I usually use a 0.1 tolerance for change of ping of around 200ms

1 Like

ah thank you so much for the cooldown part, I was thinking of doing something like this but didn’t know how well it would work

1 Like

Hey sorry to bother lol I forgot to ask, #4 what do you mean check if the hit position is close to te direction vector? also doing the sanity checks, if the player is moving too fast it triggers the sanity check

answering the latter qn, if lets say the speed of the player is 50 without hacks . In this case , you need to give a larger threshold to the player’s origin like lets say 25 . Or maybe u can even do a dynamic system like (Walkspeed/2) . I would recommend a non dynamic system unless speed in your game varies

answering to the first qn , this is an image of what i meant


green dot is the origin
red line is the direction(assuming it is infinitely long)
the two purple dots are different hit position.
the yellow line is the distance from the closest point the dot is from the red line .

you can see that ine of the yellow dot is way closer to the red line then the other . Thus , one will pass the validation cuz the distance (yellow line) is within the threshold . Hope this makes sense.

Oh yea also before doing this check, i recommend just checking if hit position is even in range from the origin to save a little bit of computing power

1 Like

I think I understand how the direction thingy works, but I don’t know how I would code it at all unless its just (direction - endpos).magnitude. Again forgot to say, I couldn’t find a way to fix the origin threshold thing, when the player is falling it goes too fast and I couldn’t think of a good way to solve that so I just scrapped it for now

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.