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

Can you put it inside of a github repo so I can add it to my game

Is it possible to stop the cast from terminating itself when it hits something? i.e. I need FastCast to keep calling OnRayHit and OnRayUpdated after the first OnRayHit is called.

I need this because I am making a sustained beam weapon, and i’m using FastCast to give the beam travel time.


Edit:

Also, related to this: I am unable to access the ActiveCast and move it inside the OnRayHit function.
When I do: cast.SetPosition() or cast.SetVelocity

I get: TestService: Exception thrown in your RayHit event handler: Workspace.GameObjects.Gem_API.Weapons.BeamPoint.Weapon.FastCastRedux.ActiveCast:684: Cannot statically invoke method 'SetPosition' - It is an instance method. Call it on an instance of this class created via ActiveCast.new(...)

In the documentation, it says that the cast parameter in the OnRayHit is an ActiveCast instance.

Not sure if this is supposed to be happening, but whenever I shoot it at an area with an accessory, it makes the particle come off the character, but they take 0 damage, but when I shoot just their body part, it does damage. Whenever it hits an accessory, it fails to inflict damage. If this isn’t normal, can you please fix it? Thanks! This is a very useful module btw.

didn’t mean to reply to a specific user

This has to do with the lazy hit detection implementation.

Look at the code for OnRayHit - as you can see, it searches the part’s parent for a humanoid. In the case of accessories, the part’s parent is the accessory object container (the part being the Handle part for the accessory). You simply need to check if hit.Parent:IsA("Accoutrement") then ... and account for needing to go up two objects in the hierarchy instead of one.

n.b. I use Accoutrement over Accessory because it ensures backwards compatibility with the legacy Hat object type. This doesn’t matter for games with no customization, as all previous hats have been ported. Just mentioning this so you know why I used that class name.

1 Like
--[[ Okay, but how would I go about making it ignore the hats? I'm not very good at scripting, and I only know some basic things. Further assistance with this issue would be awesome.
]]

Nevermind!

I fixed the issue with the “Server” script, for the gun.
I somehow figured out this issue without even looking it up with is pretty cool, all I did was add an elseif statement to the onRayHit area just like this:

local hitPart = raycastResult.Instance
	local hitPoint = raycastResult.Position
	local normal = raycastResult.Normal
	if hitPart ~= nil and hitPart.Parent ~= nil then -- Test if we hit something
		local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")
		if humanoid then
			humanoid:TakeDamage(40) -- Damage.
		elseif hitPart.Parent:IsA("Accoutrement") then -- this is what I added to the script
			hitPart.Parent.Parent.Humanoid:TakeDamage(40)
		end
		MakeParticleFX(hitPoint, normal)
	end

For anyone having issues with the hit detection with tons of accessories, copy this and paste it in the “Server” script that came with FastCast.

-- REMEMBER: THERE'S RESOURCES TO HELP YOU AT https://etithespirit.github.io/FastCastAPIDocs

-- Constants
local DEBUG = false								-- Whether or not to use debugging features of FastCast, such as cast visualization.
local BULLET_SPEED = 500							-- Studs/second - the speed of the bullet
local BULLET_MAXDIST = 1000							-- The furthest distance the bullet can travel 
local BULLET_GRAVITY = Vector3.new(0, 0, 0)		-- The amount of gravity applied to the bullet in world space (so yes, you can have sideways gravity)
local MIN_BULLET_SPREAD_ANGLE = 2					-- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The least accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. Generally you want to keep this at 0 so there's at least some chance of a 100% accurate shot.
local MAX_BULLET_SPREAD_ANGLE = 4					-- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The most accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. This cannot be less than the value above. A value of 90 will allow the gun to shoot sideways at most, and a value of 180 will allow the gun to shoot backwards at most. Exceeding 180 will not add any more angular varience.
local FIRE_DELAY = 1.5					-- The amount of time that must pass after firing the gun before we can fire again.
local BULLETS_PER_SHOT = 4							-- The amount of bullets to fire every shot. Make this greater than 1 for a shotgun effect.
local PIERCE_DEMO = false						-- True if the pierce demo should be used. See the CanRayPierce function for more info.
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
-- Local Variables

local Tool = script.Parent
local Handle = Tool.Handle
local MouseEvent = Tool.MouseEvent
local FirePointObject = Handle.GunFirePoint
local FastCast = require(Tool.FastCastRedux)
local FireSound = Handle.Fire
local ImpactParticle = Handle.ImpactParticle
local Debris = game:GetService("Debris")
local table = require(Tool.FastCastRedux.Table)
local PartCacheModule = require(Tool.PartCache)
local CanFire = true								-- Used for a cooldown.

local RNG = Random.new()							-- Set up a randomizer.
local TAU = math.pi * 2							-- Set up mathematical constant Tau (pi * 2)
FastCast.DebugLogging = DEBUG
FastCast.VisualizeCasts = DEBUG

---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
-- Cast Objects

-- Cosmetic bullet container
local CosmeticBulletsFolder = workspace:FindFirstChild("CosmeticBulletsFolder") or Instance.new("Folder", workspace)
CosmeticBulletsFolder.Name = "CosmeticBulletsFolder"

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

-- Make a base cosmetic bullet object. This will be cloned every time we fire off a ray.
local CosmeticBullet = Instance.new("Part")
CosmeticBullet.Material = Enum.Material.Neon
CosmeticBullet.Color = Color3.fromRGB(255, 176, 0)
CosmeticBullet.CanCollide = false
CosmeticBullet.Anchored = true
CosmeticBullet.Size = Vector3.new(0.2, 0.2, 2.4)

-- New raycast parameters.
local CastParams = RaycastParams.new()
CastParams.IgnoreWater = true
CastParams.FilterType = Enum.RaycastFilterType.Blacklist
CastParams.FilterDescendantsInstances = {}

-- NEW V13.1.0 - PartCache tie-in. If you use the PartCache module to create cosmetic bullets, you can now directly tie that in.
-- Ensure you're using the latest version of PartCache.
local CosmeticPartProvider = PartCacheModule.new(CosmeticBullet, 100, CosmeticBulletsFolder)

-- NEW v12.0.0: Casters now use a data packet which can be made like what follows.
-- Check the API for more information: https://etithespirit.github.io/FastCastAPIDocs/fastcast-objects/fcbehavior
local CastBehavior = FastCast.newBehavior()
CastBehavior.RaycastParams = CastParams
CastBehavior.MaxDistance = BULLET_MAXDIST
CastBehavior.HighFidelityBehavior = FastCast.HighFidelityBehavior.Default

-- CastBehavior.CosmeticBulletTemplate = CosmeticBullet -- Uncomment if you just want a simple template part and aren't using PartCache
CastBehavior.CosmeticBulletProvider = CosmeticPartProvider -- Comment out if you aren't using PartCache.

CastBehavior.CosmeticBulletContainer = CosmeticBulletsFolder
CastBehavior.Acceleration = BULLET_GRAVITY
CastBehavior.AutoIgnoreContainer = false -- We already do this! We don't need the default value of true (see the bottom of this script)

-- 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 

---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
-- Helper Functions

-- A function to play fire sounds.
function PlayFireSound()
	local NewSound = FireSound:Clone()
	NewSound.Parent = Handle
	NewSound:Play()
	Debris:AddItem(NewSound, NewSound.TimeLength)
end

-- Create the spark effect for the bullet impact
function MakeParticleFX(position, normal)
	-- This is a trick I do with attachments all the time.
	-- Parent attachments to the Terrain - It counts as a part, and setting position/rotation/etc. of it will be in world space.
	-- UPD 11 JUNE 2019 - Attachments now have a "WorldPosition" value, but despite this, I still see it fit to parent attachments to terrain since its position never changes.
	local attachment = Instance.new("Attachment")
	attachment.CFrame = CFrame.new(position, position + normal)
	attachment.Parent = workspace.Terrain
	local particle = ImpactParticle:Clone()
	particle.Parent = attachment
	Debris:AddItem(attachment, particle.Lifetime.Max) -- Automatically delete the particle effect after its maximum lifetime.
	
	-- A potentially better option in favor of this would be to use the Emit method (Particle:Emit(numParticles)) though I prefer this since it adds some natural spacing between the particles.
	particle.Enabled = true
	wait(0.05)
	particle.Enabled = false
end

---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
-- Main Logic

local function Reflect(surfaceNormal, bulletNormal)
	return bulletNormal - (2 * bulletNormal:Dot(surfaceNormal) * surfaceNormal)
end

-- The pierce function can also be used for things like bouncing.
-- In reality, it's more of a function that the module uses to ask "Do I end the cast now, or do I keep going?"
-- Because of this, you can use it for logic such as ray reflection or other redirection methods.
-- A great example might be to pierce or bounce based on something like velocity or angle.
-- You can see this implementation further down in the OnRayPierced function.
function CanRayPierce(cast, rayResult, segmentVelocity)
	
	-- Let's keep track of how many times we've hit something.
	local hits = cast.UserData.Hits
	if (hits == nil) then
		-- If the hit data isn't registered, set it to 1 (because this is our first hit)
		cast.UserData.Hits = 1
	else
		-- If the hit data is registered, add 1.
		cast.UserData.Hits += 1
	end
	
	-- And if the hit count is over 3, don't allow piercing and instead stop the ray.
	if (cast.UserData.Hits > 3) then
		return false
	end
	
	-- Now if we make it here, we want our ray to continue.
	-- This is extra important! If a bullet bounces off of something, maybe we want it to do damage too!
	-- So let's implement that.
	local hitPart = rayResult.Instance
	if hitPart ~= nil and hitPart.Parent ~= nil then
		local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid")
		if humanoid then
			humanoid:TakeDamage(10) -- Damage.
		end
	end
	
	-- And then lastly, return true to tell FC to continue simulating.
	return true
	
	--[[
	-- This function shows off the piercing feature literally. Pass this function as the last argument (after bulletAcceleration) and it will run this every time the ray runs into an object.
	
	-- Do note that if you want this to work properly, you will need to edit the OnRayPierced event handler below so that it doesn't bounce.
	
	if material == Enum.Material.Plastic or material == Enum.Material.Ice or material == Enum.Material.Glass or material == Enum.Material.SmoothPlastic then
		-- Hit glass, plastic, or ice...
		if hitPart.Transparency >= 0.5 then
			-- And it's >= half transparent...
			return true -- Yes! We can pierce.
		end
	end
	return false
	--]]
end

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, RNG:NextNumber(0, TAU)) * CFrame.fromOrientation(math.rad(RNG:NextNumber(MIN_BULLET_SPREAD_ANGLE, 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 * 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.
	
	if PIERCE_DEMO then
		CastBehavior.CanPierceFunction = CanRayPierce
	end
	
	local simBullet = Caster:Fire(FirePointObject.WorldPosition, direction, modifiedBulletSpeed, CastBehavior)
	-- Optionally use some methods on simBullet here if applicable.
	
	-- Play the sound
	PlayFireSound()
end

---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------
-- Event Handlers

function OnRayHit(cast, raycastResult, segmentVelocity, cosmeticBulletObject)
	-- This function will be connected to the Caster's "RayHit" event.
	local hitPart = raycastResult.Instance
	local hitPoint = raycastResult.Position
	local normal = raycastResult.Normal
	if hitPart ~= nil and hitPart.Parent ~= nil then -- Test if we hit something
		local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid") -- Is there a humanoid?
		if humanoid then
			humanoid:TakeDamage(40) -- Damage.
		elseif hitPart.Parent:IsA("Accoutrement") then
			hitPart.Parent.Parent.Humanoid:TakeDamage(40)
		end
		MakeParticleFX(hitPoint, normal) -- Particle FX
	end
end

function OnRayPierced(cast, raycastResult, segmentVelocity, cosmeticBulletObject)
	-- You can do some really unique stuff with pierce behavior - In reality, pierce is just the module's way of asking "Do I keep the bullet going, or do I stop it here?"
	-- You can make use of this unique behavior in a manner like this, for instance, which causes bullets to be bouncy.
	local position = raycastResult.Position
	local normal = raycastResult.Normal
	
	local newNormal = Reflect(normal, segmentVelocity.Unit)
	cast:SetVelocity(newNormal * segmentVelocity.Magnitude)
	
	-- It's super important that we set the cast's position to the ray hit position. Remember: When a pierce is successful, it increments the ray forward by one increment.
	-- If we don't do this, it'll actually start the bounce effect one segment *after* it continues through the object, which for thin walls, can cause the bullet to almost get stuck in the wall.
	cast:SetPosition(position)
	
	-- Generally speaking, if you plan to do any velocity modifications to the bullet at all, you should use the line above to reset the position to where it was when the pierce was registered.
end

function OnRayUpdated(cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject)
	-- Whenever the caster steps forward by one unit, this function is called.
	-- The bullet argument is the same object passed into the fire function.
	if cosmeticBulletObject == nil then return end
	local bulletLength = cosmeticBulletObject.Size.Z / 2 -- This is used to move the bullet to the right spot based on a CFrame offset
	local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
	cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
end

function OnRayTerminated(cast)
	local cosmeticBullet = cast.RayInfo.CosmeticBulletObject
	if cosmeticBullet ~= nil then
		-- This code here is using an if statement on CastBehavior.CosmeticBulletProvider so that the example gun works out of the box.
		-- In your implementation, you should only handle what you're doing (if you use a PartCache, ALWAYS use ReturnPart. If not, ALWAYS use Destroy.
		if CastBehavior.CosmeticBulletProvider ~= nil then
			CastBehavior.CosmeticBulletProvider:ReturnPart(cosmeticBullet)
		else
			cosmeticBullet:Destroy()
		end
	end
end

MouseEvent.OnServerEvent:Connect(function (clientThatFired, mousePoint)
	if not CanFire then
		return
	end
	CanFire = false
	local mouseDirection = (mousePoint - FirePointObject.WorldPosition).Unit
	for i = 1, BULLETS_PER_SHOT do
		Fire(mouseDirection)
	end
	if FIRE_DELAY > 0.03 then wait(FIRE_DELAY) end
	CanFire = true
end)

Caster.RayHit:Connect(OnRayHit)
Caster.RayPierced:Connect(OnRayPierced)
Caster.LengthChanged:Connect(OnRayUpdated)
Caster.CastTerminating:Connect(OnRayTerminated)

Tool.Equipped:Connect(function ()
	CastParams.FilterDescendantsInstances = {Tool.Parent, CosmeticBulletsFolder}
end)


------------------------------------------------------------------------------------------------------------------------------
-- 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(MAX_BULLET_SPREAD_ANGLE >= MIN_BULLET_SPREAD_ANGLE, "Error: MAX_BULLET_SPREAD_ANGLE cannot be less than MIN_BULLET_SPREAD_ANGLE!")
if (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.")
	MAX_BULLET_SPREAD_ANGLE = 180
end

Also thank you @EtiTheSpirit for the right step in the right direction. I don’t know if my fix is all that good, but I know that it works. :smiley:

In case anyone is going to ask, yes, v13.2.1 plans to implement PreSimulation and PostSimulation rather than the now deprecated RenderStepped and Heartbeat.

The API changes are really simple. The only new developer-facing property is Caster.SimulateBeforePhysics, which should be self explanatory.

This update will be released as soon as the new client/studio builds go live for me.

7 Likes

Hello! I have this problem where high RPM guns (1000+ RPM) would cast bullets much further than the barrel. Heres a video showing that:
https://gyazo.com/cf087897c736959fdd54fe42319dbed9

Is this a mistake on my part or is this known?

Its a display bug. The bullet’s speed is so high that LengthChanged fires once, but the ray terminates immediately, so you never get a chance to see the bullet. This has nothing to do with fire rate, just projectile velocity.

1 Like

Oh alright got it. Thanks for replying!

I’ve just realised that the camera orientation affects the direction of the ray from a player’s gun to their cursor position. Is there a setting to make it so that it’s only a straight ray from the origin?

Great module, unclear instructions.

1 Like

I’m not sure if you’re aware of this already, but there is currently a very annoying bug in fast cast redux. The bug from what I have experienced mostly happens when using tools, and what it is is that it makes your origin position lower than it actually is, making your bullets come from the ground or your feet.

This clip here shows the problem.
Also Clarifications:
I did use an attachment for the position and I used world position
I did weld everything Correctly
I am also pretty sure I scripted it correctly
The bullets are being created server side, not client side

This is my Client side script

local plyr = game.Players.LocalPlayer

local tool = script.Parent

local fastcast = require(tool:WaitForChild("FastCastRedux"))

local eventfire = tool:WaitForChild("Fire")

local m = plyr:GetMouse()

tool.Activated:Connect(function()

local mpos = m.Hit.Position

eventfire:FireServer(mpos)

end)

This is my Server Side

local tool = script.Parent

local eventfire = tool.Fire

local fastcast = require(tool:WaitForChild("FastCastRedux"))

local firepos = tool.Hole:WaitForChild("Attachment")

local bf = workspace.Terrain:FindFirstChild("BulletsFolder") or Instance.new("Folder")

bf.Name = "BulletsFolder"

bf.Parent = workspace.Terrain

local BT = Instance.new("Part")

BT.Anchored = true

BT.CanCollide = false

BT.Size = Vector3.new(.25,.25,1)

fastcast.VisualizeCasts = false

local caster = fastcast.new()

local castParams = RaycastParams.new()

castParams.FilterType = Enum.RaycastFilterType.Blacklist

castParams.IgnoreWater = true

local castBehavior = fastcast.newBehavior()

castBehavior.RaycastParams = castParams

castBehavior.Acceleration = Vector3.new(0,math.random(-2.5,2.5),0)

castBehavior.AutoIgnoreContainer = false

castBehavior.CosmeticBulletContainer = bf

castBehavior.CosmeticBulletTemplate = BT

local function onEquipped()

castParams.FilterDescendantsInstances = {tool,bf}

end

local function onLengthChanged(cast,lp,d,length,vel,bullet)

if bullet then

local BL = bullet.Size.Z/2

local offset = CFrame.new(0,0,-(length-BL))

bullet.CFrame = CFrame.lookAt(lp,lp+d):ToWorldSpace(offset)

end

end

local function onRayHit(cast,result,velocity,bullet)

local hit = result.Instance

local e = Instance.new("Explosion")

e.Parent= workspace

e.Position = bullet.Position

bullet:Destroy()

end

local function fire(plyr, pos)

local o = firepos.WorldPosition

local d = (pos-o).Unit

caster:Fire(o,d,50, castBehavior)

end

eventfire.OnServerEvent:Connect(fire)

tool.Equipped:Connect(onEquipped)

caster.LengthChanged:Connect(onLengthChanged)

caster.RayHit:Connect(onRayHit)

I remember seeing this problem before though I don’t remember where it was in community resources.

Without looking at the script I believe the issue is caused by the velocity inheritance because the view model is unanchored and so it’s still experiencing gravity though it may not be visible if the viewmodel is constantly being CFramed during renderstep which causes the bullet to be spawning down because of the high velocity going downwards.

Hopefully, I can find the other post where the guy has experienced the same problems.

Edit: Searching online I found this which confirms anchoring solved the problem but it’s not the exact post I was looking for:

Edit 2: Found the specific post in this other tutorial not resource

this is very helpful for viewmodels, but doing this on a tool is impossible.

edit: I said view models cause i didn’t know if the guy was aware, but I’ll remove the view model part now since he I know he is aware and there is a fix for it.

that is due to the speed you set the bullet to. The faster the speed, the more distance from the gun it will be. The bullet is still technically starting from the starting point, but it’s just moving so fast that our eyes can’t see it. You can compensate for this by making the bullet longer.

1 Like

Not one that’s built in. You just need to cast a ray from gun end to mouse position. That’s what I do, so I’m not sure why the direction changes.

Yeah I’m also confused then, when looking down for example with your camera and clicking instead of travelling in a straight line the ray travels downwards because the mouse is facing downwards due to the camera influencing the mouse’s 3d position.

Having the same issue here, I mean I understand that by logic you should not be terminating a cast in events. However, I am working on a ping compensation system which uses regions as hitboxes, instead of instancing actual parts for performance reasons.

However this means, that I want the cast to stop if it hit a player hitbox, which is checked each segment.

You should be able to instance a single part for every player and recycle it (i.e. one part per player slot) – CFraming parts is the fastest update possible for them. Size is a bit slower, but I don’t think it’ll have any performance loss even if you do it every frame (and afaik, if your hitbox size is constant, you only need to CFrame - very good!) Disabling CanTouch would further help that.

1 Like

Hmm, yes. I’ve thought about implementing it that way before, using partCache for hitboxes, however I ran into multiple issues in the logic.

  1. Instance x (8 - client = 7 for example) amount of hitboxes for each caster
  2. Add the shooter’s ping into userdata
  3. When caster fires cframe those hitboxes based on previously saved player cframes and shooter’s ping
    However, since projectiles are not instant this means players have quite possibly moved while the projectile is travelling.
  4. That means cframing again for every segment to make sure they are moved ahead in time
  5. Projectile hits an instance, casting is complete.

There are two issues with this method:

  1. Let’s say the maximum travel time on a projectile is two seconds, and the gun can fire every 0.5 seconds. That means we can be tracking up to 4 projectiles in a span of two seconds, which means I need 7players x 4 instances in just those two seconds for one player firing. Now combine that with other 7 players firing and we get a total of: 196 hitbox instances required at a time for the server to calculate the hitboxes, also keep in mind this is a slow firing tool. 2 bullets per seconds is nothing.
  2. Furthermore since they are instances I cannot easily contain the hitboxes to only one cast. Then I am forced to create extra logic which makes sure that casts do not collide with other casts hitboxes. CanPierce can be used for this so I don’t think it’s a huge limitation.

Still adding a 1000 instances of a hitbox so the server always has enough, for high firerate guns and even if I increase the player count. Sure this would happen only once, but how much memory is that going to take up among other performance concerns.

EDIT: For anyone wondering, I managed to go around this limitation. Since I have a function which is fired whenever the cast hit something I trigger it when a player has been hit. I also save a boolean into cast.UserData which is set to true when a player has been hit, and the server sends information to stop rendering this bullet to all clients. Since HitPlayer is true now, the script ignores any other hits the ray makes from this point on. Basically it’s still casting until reaching maximum distance or hitting something, but it has no effect.