Making a combat game with ranged weapons? FastCast may be the module for you!

How can I sync up the server and clients bullets so that they are in the same position? I want to make a bow and arrow and having the arrow hit a player on one screen but completely miss on another is bad. I tried using my own physics equations but its been pretty wacky and I noticed on line 146 of the fastcast module there seems to be built in support for delta time, is there a way I can use this?

You can’t Sync server and client bullets 100% due to latency.
The best way you could sync it is fire the arrow client sided and replicate the arrow for all other clients client sided aswell.
And do the Damage on the server with Sanity checks

I have a question. I had this very weird bug now where the weapon just randomly stops working after death. Some weapons work, some don’t. There is no error in the output or console and I don’t know why.
Any1 had a similar problem?

You can sync them to nearly 100% accuracy.

When you shoot on client, the server can offset the bullet based on your ping. I did this by manipulating some of the variables when fast cast starts. The issue with this is, close range shots will go through walls, but to fix that I added a raycast and its functioning fine now, although my syntax is pretty crappy.

Theres a bug with this module that when you add gravity, the bullets have a slight chance to go through the ground.

1 Like

For the sake of simplicity I’m only using one caster on the server and one on the client for all projectiles, server is for damage and replicating to other clients, and client is for rendering your own projectiles instantly (before remote firing the server) and rendering projectiles sent by the server

ProjectileReplication_Client Module
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local Quire = require(ReplicatedStorage:WaitForChild("Shared").Quire)
local NetworkUtil = Quire("NetworkUtil")
local Caster = Quire("FastCast").new()
local Repr = Quire("Repr")

local projectileReplicationEvent = NetworkUtil:GetRemoteEvent("ProjectileReplicationEvent")
local projectileFolder = ReplicatedStorage:WaitForChild("Projectiles")
local workspaceProjectiles = workspace:WaitForChild("Projectiles")

-- black list setup and updating
local blacklist = {workspaceProjectiles}

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		table.insert(blacklist, character)
		character.AncestryChanged:Connect(function(_, parent)
			if parent then return end
			table.remove(blacklist, table.find(blacklist, character))
		end)
	end
	if player.Character then onCharacterAdded(player.Character) end	
	player.CharacterAdded:Connect(onCharacterAdded)
end

for _, player in ipairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)

-- Projectiles
-- fireball
local fireBallProjectile = projectileFolder.FireballPart
local fireBallTweenTime = .2
local fireBallFadeTweenInfo = TweenInfo.new(fireBallTweenTime)
local fireBallTweenProperties = {Transparency = 1}


local ProjectileReplication_Client = {}

function ProjectileReplication_Client.fireProjectile(projectileData, origin, direction, velocity, _, _, ignoreWater, bulletAcceleration, canPierceFunction)
	if projectileData.projectileType == "Fireball" then
		local projectileCosmeticPart = fireBallProjectile:Clone()
		projectileCosmeticPart.Parent = workspaceProjectiles
		
		projectileData.fireBallFadeTween = TweenService:Create(projectileCosmeticPart, fireBallFadeTweenInfo, fireBallTweenProperties)
		
		Caster:FireWithBlacklist(
			projectileData,
			origin, 
			direction, 
			velocity, 
			blacklist,
			projectileCosmeticPart,
			ignoreWater,
			bulletAcceleration,
			canPierceFunction
		)
		
	end
end

Caster.RayHit:Connect(function(projectileData, hitPart, hitPoint, normal, material, projectile)
	if projectileData.projectileType == "Fireball" then
		projectileData.fireBallFadeTween:Play()
		wait(fireBallTweenTime)
	end
	projectile:Destroy()
end)

Caster.LengthChanged:Connect(function(projectileData, castOrigin, segmentOrigin, segmentDirection, length, projectile)
	local baseCF = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
	local projectHalfSize = projectile.Size.Z/2
	projectile.CFrame = baseCF * CFrame.new(0, 0, -(length - projectHalfSize))	
end)

projectileReplicationEvent.OnClientEvent:Connect(ProjectileReplication_Client.fireProjectile)

return ProjectileReplication_Client
ProjectileReplication_Server Module
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Quire = require(ReplicatedStorage:WaitForChild("Shared").Quire)
local NetworkUtil = Quire("NetworkUtil")
local Caster = Quire("FastCast").new()


local projectileReplicationEvent = NetworkUtil:GetRemoteEvent("ProjectileReplicationEvent")
local workspaceProjectiles = workspace:WaitForChild("Projectiles")

-- black list setup and updating
local blacklist = {workspaceProjectiles}

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		table.insert(blacklist, character)
		character.AncestryChanged:Connect(function(_, parent)
			if parent then return end
			table.remove(blacklist, table.find(blacklist, character))
		end)
	end
	if player.Character then onCharacterAdded(player.Character) end	
	player.CharacterAdded:Connect(onCharacterAdded)
end

for _, player in ipairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)


local ProjectileReplication_Server = {}

function ProjectileReplication_Server.fireProjectile(projectileData, origin, direction, velocity, _, projectileCosmeticPart, ignoreWater, bulletAcceleration, canPierceFunction)
	Caster:FireWithBlacklist(
		projectileData,
		origin, 
		direction, 
		velocity, 
		blacklist,
		projectileCosmeticPart,
		ignoreWater,
		bulletAcceleration,
		canPierceFunction
	)
	for _, player in ipairs(Players:GetChildren()) do
		if projectileData.player ~= player then
			projectileReplicationEvent:FireClient(
				player, 
				projectileData,
				origin, 
				direction, 
				velocity, 
				projectileCosmeticPart,
				nil,
				ignoreWater,
				bulletAcceleration,
				canPierceFunction
			)
		end
	end	
end

Caster.RayHit:Connect(function(projectileData, hitPart, hitPoint, normal, material, projectile)
	local humanoid = hitPart and hitPart.Parent:FindFirstChild("Humanoid")
	if humanoid then
		humanoid:TakeDamage(projectileData.damage)
	end
end)

return ProjectileReplication_Server
Fireball_Client Tool Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Quire = _G.Quire
local ProjectileReplication_Client = Quire("ProjectileReplication_Client")
local ProjectileReplication = Quire("ProjectileReplication")

local tool = script.Parent
local handle = tool:WaitForChild("Handle")
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local character
local rightHand

local remotesFolder = tool.Remotes
local shootEvent = remotesFolder.ShootEvent

local statsFolder = tool.Stats
local stateFolder = tool.State
local SPEED = statsFolder.Speed.Value
local RANGE = statsFolder.Range.Value
local DAMAGE = statsFolder.Damage.Value
local PROJECTILE_TYPE = statsFolder.ProjectileType.Value
local FIREBALL_OFFSET = statsFolder.FireballOffset.Value
local GRAVITY = statsFolder.Gravity.Value
local COOLDOWN_TIME = statsFolder.CooldownTime.Value
local COOLDOWN_LOOP_WAIT = COOLDOWN_TIME * 1.2 -- leeway for network delay

local stateFolder = tool.State
local lastActivationTick = tick()


local looping = false

local function shoot()
	local now = tick()
	if now - lastActivationTick < COOLDOWN_TIME then return end
	lastActivationTick = now
	
	local origin = handle.Position + FIREBALL_OFFSET
	local mouseDirection = (mouse.Hit.Position - origin).Unit
	local direction = mouseDirection * RANGE
	local velocity = mouseDirection * SPEED
	local projectileCosmeticPart = nil
	local ignoreWater = true
	local bulletAcceleration = GRAVITY
	local canPierceFunction = function(hitPart, hitPoint, normal, material)
		return false	
	end
		
	local projectileData = {
		projectileType = PROJECTILE_TYPE,
		player = player,
		character = character,
	}
	
	ProjectileReplication_Client.fireProjectile(
		projectileData,
		origin, 
		direction, 
		velocity, 
		nil,
		projectileCosmeticPart,
		ignoreWater,
		bulletAcceleration,
		canPierceFunction
	)
	shootEvent:FireServer(mouseDirection)
end

local function shootLoop()
	if looping then return end
	looping = true
	
	while looping do
		shoot()
		wait(COOLDOWN_LOOP_WAIT)
	end
end

tool.Equipped:Connect(function()
	character = tool.Parent
	rightHand = character:WaitForChild("RightHand")
	mouse.TargetFilter = character
end)

tool.Unequipped:Connect(function()
	looping = false
end)

tool.Activated:Connect(shootLoop)
tool.Deactivated:Connect(function()
	looping = false
end)


Fireball_Server Tool Script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local Quire = _G.Quire
local NetworkUtil = Quire("NetworkUtil")
local ProjectileReplication_Server = Quire("ProjectileReplication_Server")
local projectileReplicationEvent = NetworkUtil:GetRemoteEvent("ProjectileReplicationEvent")
local tool = script.Parent
local handle = tool:WaitForChild("Handle")
local player = Players:FindFirstChild(tool.Parent.Name) or tool:FindFirstAncestorOfClass("Player")
local character = player.Character
local rightHand = character.RightHand
local remotesFolder = tool.Remotes
local shootEvent = remotesFolder.ShootEvent

local statsFolder = tool.Stats
local SPEED = statsFolder.Speed.Value
local RANGE = statsFolder.Range.Value
local DAMAGE = statsFolder.Damage.Value
local PROJECTILE_TYPE = statsFolder.ProjectileType.Value
local FIREBALL_OFFSET = statsFolder.FireballOffset.Value
local GRAVITY = statsFolder.Gravity.Value
local COOLDOWN_TIME = statsFolder.CooldownTime.Value

local stateFolder = tool.State
local lastActivationTick = tick()

local function shoot(mouseDirection)
	local now = tick()
	if now - lastActivationTick < COOLDOWN_TIME then return end
	lastActivationTick = now
	
	local origin = handle.Position + FIREBALL_OFFSET
	local direction = mouseDirection * RANGE
	local velocity = mouseDirection * SPEED
	local projectileCosmeticPart = nil
	local ignoreWater = true
	local bulletAcceleration = GRAVITY
	local canPierceFunction = function(hitPart, hitPoint, normal, material)
		return false	
	end
	
	local projectileData = {
		projectileType = PROJECTILE_TYPE,
		damage = DAMAGE,
		player = player,
		character = character,
	}
	
	ProjectileReplication_Server.fireProjectile(
		projectileData,
		origin, 
		direction, 
		velocity, 
		nil,
		projectileCosmeticPart,
		ignoreWater,
		bulletAcceleration,
		canPierceFunction
	)
	
end	


shootEvent.OnServerEvent:Connect(function(plr, direction)
	if plr == player and assert(direction.Magnitude <= 1.1, "invalid direction") then
		shoot(direction)
	end
end)

It works, but is there any drawbacks when using only one caster for every projectile?, and I’m also having to pass an additional parameter (projectileData) to fast cast and passing it back through the events (RayHit, etc)

I haven’t had any performance issues yet, but I don’t have access to a large amount of testers to see if there will be

2 Likes

I have already answered this question – see Reply #148

@4slug I’ll investigate this. I did notice that once during my own tests but I couldn’t get it to happen again after that.

@Quoteory While this is an option, I addressed this when talking to ChefJustice in Reply #161 – The basic idea is that while you can do this setup, it’s only good if you are comfortable with handling that data. There’s no performance differences from using a single caster vs. casters for each and every individual player. The only thing that should determine your plan of action is your personal preference: if you find it easier to use a singleton caster and manually track who’s doing what, then by all means continue doing this, alternatively, if you find it easier to have individual objects for individual people, this is just fine as well.

The only problems come up from creating a new caster each time the gun is fired, since as mentioned in the past and in the module itself, a caster should represent an entire gun. It’s analogous to the firing mechanism of a gun, in a crude sense. Each gun has its own design.

3 Likes

I added a issue request on the github that explains it in a little more detail as well as a gif

Exploiters can see data sent from the client to the server via remotes without actually looking at any script source code and since fastcast is open source I don’t think an exploiter would have to look too far to find out how it works lol. Anyways if you’re going to be handling hit detection on the client then you should at least do some server verification

Ah yeah on that side of things it would be weak. My main point there was things like bullet manipulation – it’s virtually impossible for them to reliably (assuming they can at all) edit that information during runtime, compared to something like a part based bullet which can be trivially edited.

There is an issue, with a module. After time of massive shooting using this module. The firing slows down significantly because of overload. I dont know exactly what is causing this to happen.

Everything works reliably, but i literally do not know how to add gravity to the bullets with the new version
how do i add gravity to the bullets

1 Like

bulletAcceleration argument in the :Fire function

1 Like

Hey :slight_smile:
sorry for bumping this thread

But I just wanted to ask
Is there any example available of FastCast for First Person View (FPS)?

1 Like

Glad you asked, haha :grinning:
I’m working on a framework which I believe will fit your desire. Hope you like it Awesome_Playz123 fps test v1 - Roblox

1 Like

It looks really awesome :slight_smile:

I was also messing around with it lol
And did this

1 Like

You scared me, lol. I thought you took my game? What a relief :sweat_smile:

1 Like

No no I meant like messing around with the module lol

Btw will you be going to make your framework opensource?

1 Like

Probably not. I’m going to try and make a game out of it. My discord username is lucid#9360 just in case you need help with anything and also I made an open source fastcast test Untitled FastCast Voting Game Simulator 2020 - Roblox for you to study off :stuck_out_tongue:

1 Like

Good Luck

Thanks for the help :slight_smile:

1 Like