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

Hi! Love that you have created this and provided all this extra info on how to use it.

I’m having trouble with running FastCaster:Fire() once but receiving two FastCaster.RayHit:Connect(OnRayHit) back.

Edit: Fixed it!
The problem was that I was having the server script in each gun, listen for the SendHitEvent. This event was in ReplicatedStorage, so every server script in every tool would damage the hit player once one of the players sent the SendHitEvent. I fixed it by moving the event inside the tool. First I tried moving the server script out of the tool, but it required that I keep the state of all the clients in a table or something. And I didn’t want the extra complexity.

Original post

This means that my DamagePlayer() function is fired twice on the server, and I don’t want that.
The issue occurs when I run a test server with two players. For one player it fires once. For three players I would imagine it fires 3 times.

I don’t think FastCaster:Fire() should be ran more than once, because it is only ran in the LocalScript of the player who fires at that moment.

LocalScript
local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local EventStorage = game:GetService("ReplicatedStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireEvent = EventStorage:WaitForChild("FireEvent")
local ClientProjectileEvent = EventStorage:WaitForChild("ClientProjectileEvent")
local Configs = Tool:WaitForChild("Configs")
local constants = require(Configs:WaitForChild("Settings"))

local Mouse = nil
local ExpectingInput = false
local Camera = workspace.CurrentCamera
local FirePointObject = Handle:WaitForChild("GunFirePoint")

local FastCast = require(ReplicatedStorage:WaitForChild("FastCastRedux"))
local OriginalShotCaster = FastCast.new()
local FireSound = Handle:WaitForChild("Fire")
local Debris = game:GetService("Debris")
local LaserBullet = require(Tool:WaitForChild("Bullet"))
local SendHitEvent = EventStorage:WaitForChild("SendHitEvent")


function UpdateMouseIcon()
	if Mouse and not Tool.Parent:IsA("Backpack") then
		Mouse.Icon = "rbxasset://textures/GunCursor.png"
	end
end

function OnEquipped(PlayerMouse)
	Mouse = PlayerMouse
	ExpectingInput = true
	UpdateMouseIcon()
end

function OnUnequipped()
	ExpectingInput = false
	UpdateMouseIcon()
end

Tool.Equipped:Connect(OnEquipped)
Tool.Unequipped:Connect(OnUnequipped)

UserInputService.InputBegan:Connect(function (input, gameHandledEvent)
	if gameHandledEvent or not ExpectingInput then
		--The ExpectingInput value is used to prevent the gun from firing when it shouldn't on the clientside.
		--This will still be checked on the server.
		return
	end
	
	if input.UserInputType == Enum.UserInputType.MouseButton1 and Mouse ~= nil then
		local FireDirection = (Mouse.Hit.p - FirePointObject.WorldPosition).Unit
		FireEvent:FireServer(FireDirection)
		
		local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1)	-- Add a timeout to this.
		local myMovementSpeed = humanoidRootPart.Velocity							-- To do: It may be better to get this value on the clientside since the server will see this value differently due to ping and such.
		local modifiedBulletSpeed = (FireDirection * constants.BULLET_SPEED) + myMovementSpeed	-- We multiply our direction unit by the bullet speed. This creates a Vector3 version of the bullet's velocity at the given speed. We then add MyMovementSpeed to add our body's motion to the velocity.
		
		FireProjectile(FirePointObject.WorldPosition, FireDirection * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY, "LaserPistol")
	end
end)

function PlayFireSound(position)
	local NewSound = FireSound:Clone()
	NewSound.Parent = Handle
	NewSound:Play()
	Debris:AddItem(NewSound, NewSound.TimeLength)
end
	
function FireProjectile(Origin, DirectionWithMagnitude, Speed, cosmeticBulletObject, ignoreDescendantsInstance, ignoreWater, bulletAcceleration, Type) 
	
	OriginalShotCaster.IgnoreDescendantsInstance = ignoreDescendantsInstance
	PlayFireSound(ignoreDescendantsInstance:WaitForChild("DebugGun"):WaitForChild("Handle"))
	if Type == "LaserPistol" then
		local NewBullet = LaserBullet:Clone()
		NewBullet.CFrame = CFrame.new(Origin, Origin + DirectionWithMagnitude.Unit)
		NewBullet.Parent = workspace.CurrentCamera
		print("Firing cast in FireProjectile")
		OriginalShotCaster:Fire(Origin, DirectionWithMagnitude, Speed, NewBullet, ignoreDescendantsInstance, ignoreWater, bulletAcceleration)
	end		
end


function OnRayHit(hitPart, hitPoint, normal, material, cosmeticBulletObject)
	print("OnRayHit for OriginalShotCaster")
	SendHitEvent:FireServer(hitPart, hitPoint, normal, material, cosmeticBulletObject)
end

--OriginalShotCaster.LengthChanged:Connect(DrawBulletModule.OnRayUpdated)
--OriginalShotCaster.RayHit:Connect(DrawBulletModule.OnRayHit) --For displaying the hit marker
OriginalShotCaster.RayHit:Connect(OnRayHit) --For taking damage
Server
-- Latest update 24 November 2019 - Upgrade to redux.

local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local EventStorage = game:GetService("ReplicatedStorage")

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireEvent = EventStorage:WaitForChild("FireEvent")
local FirePointObject = Handle:WaitForChild("GunFirePoint")
local FastCast = require(ReplicatedStorage:WaitForChild("FastCastRedux"))

local ImpactParticle = Handle:WaitForChild("ImpactParticle")
local SendCastEvt = EventStorage:WaitForChild("SendCast")
local Debris = game:GetService("Debris")
local Configs = Tool:WaitForChild("Configs")
local SendHitEvent = EventStorage:WaitForChild("SendHitEvent")

local constants = require(Configs:WaitForChild("Settings"))
					

local CanFire = true								-- Used for a cooldown.

-- Now we set the caster values.
--local Caster = FastCast.new() --Create a new caster object.

-- Bonus points: If you're going to be slinging a ton of bullets in a short period of time, you may see it fit to use PartCache.
-- https://devforum.roblox.com/t/partcache-for-all-your-quick-part-creation-needs/246641 

function Fire(direction)
	-- Called when we want to fire the gun.
	if Tool.Parent:IsA("Backpack") then return end -- Can't fire if it's not equipped.
	-- Note: Above isn't in the event as it will prevent the CanFire value from being set as needed.
	
	-- UPD. 11 JUNE 2019 - Add support for random angles.
	local directionalCF = CFrame.new(Vector3.new(), direction)
	-- Now, we can use CFrame orientation to our advantage.
	-- Overwrite the existing Direction value.
	local direction = (directionalCF * CFrame.fromOrientation(0, 0, constants.RNG:NextNumber(0, constants.TAU)) * CFrame.fromOrientation(math.rad(constants.RNG:NextNumber(constants.MIN_BULLET_SPREAD_ANGLE, constants.MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector
	
	-- UPDATE V6: Proper bullet velocity!
	-- IF YOU DON'T WANT YOUR BULLETS MOVING WITH YOUR CHARACTER, REMOVE THE THREE LINES OF CODE BELOW THIS COMMENT.
	-- Requested by https://www.roblox.com/users/898618/profile/
	-- We need to make sure the bullet inherits the velocity of the gun as it fires, just like in real life.
	local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1)	-- Add a timeout to this.
	local myMovementSpeed = humanoidRootPart.Velocity							-- To do: It may be better to get this value on the clientside since the server will see this value differently due to ping and such.
	local modifiedBulletSpeed = (direction * constants.BULLET_SPEED) + myMovementSpeed	-- We multiply our direction unit by the bullet speed. This creates a Vector3 version of the bullet's velocity at the given speed. We then add MyMovementSpeed to add our body's motion to the velocity.
	
	-- NOTE: It may be a good idea to make a Folder in your workspace named "CosmeticBullets" (or something of that nature) and use FireWithBlacklist on the descendants of this folder!
	-- Quickly firing bullets in rapid succession can cause the caster to hit other casts' bullets from the same gun (The caster only ignores the bullet of that specific shot, not other bullets).
	-- Do note that if you do this, you will need to remove the Equipped connection that sets IgnoreDescendantsInstance, as this property is not used with FireWithBlacklist
	
	-- Fire the caster
	--Caster:Fire(FirePointObject.WorldPosition, direction * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY)
	-- Uncomment this later: SendCastEvt:FireAllClients(FirePointObject.WorldPosition, direction * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY, "LaserPistol")	
end


function DamagePlayer(player, hitPart, hitPoint, normal, material, cosmeticBulletObject)
	-- This function will be connected to the Caster's "RayHit" event.
	-- (removed from server) cosmeticBulletObject:Destroy() -- Destroy the cosmetic bullet.
	if hitPart and hitPart.Parent then -- Test if we hit something
		local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid") -- Is there a humanoid?
		if humanoid then
			print("Player "..player.Name.." ordered Take damage")
			humanoid:TakeDamage(Configs:WaitForChild("ShotDamage").Value) -- Damage. Configs:WaitForChild("ShotDamage").Value script.Parent.Configs:WaitForChild("ShotDamage").Value
		end
	end
end

	
-- Before I make the connections, I will check for proper values. In production scripts that you are writing that you know you will write properly, you should not do this.
-- This is included exclusively as a result of this being an example script, and users may tweak the values incorrectly.
assert(constants.MAX_BULLET_SPREAD_ANGLE >= constants.MIN_BULLET_SPREAD_ANGLE, "Error: MAX_BULLET_SPREAD_ANGLE cannot be less than MIN_BULLET_SPREAD_ANGLE!")
if (constants.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.")
	constants.MAX_BULLET_SPREAD_ANGLE = 180
end

FireEvent.OnServerEvent:Connect(function (clientThatFired, mouseDirection)
	if not CanFire then
		return
	end
	CanFire = false
	Fire(mouseDirection)
	wait(constants.FIRE_DELAY)
	CanFire = true
end)

SendHitEvent.OnServerEvent:Connect(DamagePlayer)
--Caster.RayHit:Connect(OnRayHit)

In my script, FastCaster is called OriginalShotCaster

6 Likes

Sorry for the week-late response! I’ve been busy.

Right now, no, the module does not support changing the direction of the projectile in runtime. It’s a feature I could look into, but generally speaking rockets are slow enough to (more) safely use stock physics, especially if you set the network owner of the rocket to the client that fires it.

I may add this later down the line but right now it’s fairly low priority, I’m afraid.

1 Like

How would I have one server script handle all my fast cast guns? Creating more caster objects?

Yeah. As the module states, a caster should represent a type of gun.

I recommend creating your own module that serves as a sort of “caster factory” for a specific type of gun. What you’d need to do is go to this factory and make some function that returns a pre-made caster with the stats necessary for that specific type of gun.

1 Like

Does this module support slowing down projectiles as they move farther and farther?

1 Like

It could, but the method would be very tricky. You would need to specify the bullet’s extraForce parameter to be opposite to the direction the bullet is flying (so that it gets a force pulling backwards on it). This runs the risk of the bullet actually starting to fly backwards.

Natively, there is no way to change the direction or speed of the bullet to a specific value during a bullet’s lifetime. This may be added later down the line.

1 Like

Are you saying that this can simulate projectile ammo in contrast to just hitscan weapons? Or am I just misinterpreting it?

1 Like

https://www.roblox.com/library/4741764036/FastCast-Ak47
I made a Ak47 using the Template you gave me.

1 Like

@EtiTheSpirit,

Do you recommend this to be used on the Server or Client?

How to they compare and what are the differences other than Client side would be smoother and better for UX but less secure;

How can we address in the best way possible to suit this module?

Is it designed to be used on the Client or Sever


Also your gun doesn’t work on Touch Devices

I believe its used to be for the server but you can easily change it to client.
That’s what I did, I am shooting and making the bullet visually on the client but I am not doing anything with the RayHit function (other than FX effects). I don’t even send a remotevent to server to deal damage, I think it may be to insecure.

What I do is everytime a bullet is fired from the client I create the same bullet on the server and simply make the server handle all RayHit stuff and dealing damage stuff.

this works really fine performanse wise and clients get a very smooth experience. There is also no lag in incoming damage and overall it’s very good.

Don’t forget to do some sanity checks on the server everytime you pass the event to the server, that the client fired.
I am passing the tool the client fired and the direction of course.
(for my specific game I am storing all Weapons Stats in a modulescript and I am reading from the serverscript the Stats from the weapon. Lets say, Client A has a pistol and wants to shoot. He sends a event to the server passing his tool name (pistol) and his direction ( vector3 ). On the server I check if the player actually has the tool equipped and if so I apply all the right stats to the bullet created on the server (Bullet speed, Damage, Gravity, Rounds_per_seconds, Max Distance etc.).
This way the client cannot change stats around and I believe I’ve got a pretty secure system.

I fire a remote via the RayHit function on the client, I only do this because I had performance issues while doing RayHit on the server (e.g. if it would hit something the activity spikes > 10% for some reason), I do sanity checks to make sure an exploiter cant just fire it.

So you create 2 bullets total or one on each client and server?

Is it accurate UX wise?

It’s terrible when the player sees that they hit a target but it doesn’t register.

1 Like

It’s super accurate.
I create one bullet on each client and 1 on the server
edit: And the server version is really just the ray, no FX or sounds or anything.

I mean if you do sanity checks that completly make it save for exploiters to use then handling the damaging remote event on the client should be fine I guess, but alot of work.

1 Like

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