No, not that i’ve been reported to.
I was in the middle of remaking PrisonLife’s gun system and got to the audio part.
This is when I realized how horrible the audio was for FastCast-Redux?
Maybe I’m the only one having this problem.
But it seems like everything is very fuzzy and in first-person the audio is very bugged.
Use SoundService.PlayLocalSound
instead. I don’t think the issue is related to FastCast but an unknown engine bug.
Does using fastcast cause lag to npcs and overall simulation?
How i can use it in roblox FPS framework?
Thanks a ton for the explanation. I was having trouble making my weapons framework feel more responsive using a similar system - the key difference being that the firing client’s bullets were purely aesthetic.
I’ll implement your solution once I’ve got some free time to work on my framework, should make a huge difference in how the game feels.
I managed to solve this myself, so in case anyone else is having the same or similar issues, I came to realize that since the cast is a table itself, I didn’t actually need to have tables that use the casts as keys for the pierces, since I could just insert the pierce number and the enemies pierced into the cast itself, which solved the issue. So basically, the cast that the pierce function returned was in fact the same cast as the one that was fired, but changed somehow so that the tables wouldn’t refer to it as the same cast anymore. I’m still not entirely sure how this all happens since it doesn’t happen every single time, but since it’s the same cast, the pierce count and pierced enemies can still be found within the table.
I got an error saying this
Players.VSCPlays.Backpack.Tool.FastCastRedux.ActiveCast:198: attempt to index nil with 'Raycast'
and it’s only this line:
local resultOfCast = targetWorldRoot:Raycast(lastPoint, rayDir, cast.RayInfo.Parameters)
which is a part of this function:
local function SimulateCast(cast: ActiveCast, delta: number, expectingShortCall: boolean)
assert(cast.StateInfo.UpdateConnection ~= nil, ERR_OBJECT_DISPOSED)
PrintDebug("Casting for frame.")
local latestTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories]
local origin = latestTrajectory.Origin
local totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
local initialVelocity = latestTrajectory.InitialVelocity
local acceleration = latestTrajectory.Acceleration
local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration)
local lastVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration)
local lastDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
cast.StateInfo.TotalRuntime += delta
-- Recalculate this.
totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
local currentTarget = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration)
local segmentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration)
local totalDisplacement = currentTarget - lastPoint -- This is the displacement from where the ray was on the last from to where the ray is now.
local rayDir = totalDisplacement.Unit * segmentVelocity.Magnitude * delta
local targetWorldRoot = cast.RayInfo.WorldRoot
local resultOfCast = targetWorldRoot:Raycast(lastPoint, rayDir, cast.RayInfo.Parameters)
local point = currentTarget
local part: Instance? = nil
local material = Enum.Material.Air
local normal = Vector3.new()
if (resultOfCast ~= nil) then
point = resultOfCast.Position
part = resultOfCast.Instance
material = resultOfCast.Material
normal = resultOfCast.Normal
end
local rayDisplacement = (point - lastPoint).Magnitude
-- For clarity -- totalDisplacement is how far the ray would have traveled if it hit nothing,
-- and rayDisplacement is how far the ray really traveled (which will be identical to totalDisplacement if it did indeed hit nothing)
SendLengthChanged(cast, lastPoint, rayDir.Unit, rayDisplacement, segmentVelocity, cast.RayInfo.CosmeticBulletObject)
cast.StateInfo.DistanceCovered += rayDisplacement
local rayVisualization: ConeHandleAdornment? = nil
if (delta > 0) then
rayVisualization = DbgVisualizeSegment(CFrame.new(lastPoint, lastPoint + rayDir), rayDisplacement)
end
-- HIT DETECTED. Handle all that garbage, and also handle behaviors 1 and 2 (default behavior, go high res when hit) if applicable.
-- CAST BEHAVIOR 2 IS HANDLED IN THE CODE THAT CALLS THIS FUNCTION.
if part and part ~= cast.RayInfo.CosmeticBulletObject then
local start = tick()
PrintDebug("Hit something, testing now.")
-- SANITY CHECK: Don't allow the user to yield or run otherwise extensive code that takes longer than one frame/heartbeat to execute.
if (cast.RayInfo.CanPierceCallback ~= nil) then
if expectingShortCall == false then
if (cast.StateInfo.IsActivelySimulatingPierce) then
cast:Terminate()
error("ERROR: The latest call to CanPierceCallback took too long to complete! This cast is going to suffer desyncs which WILL cause unexpected behavior and errors. Please fix your performance problems, or remove statements that yield (e.g. wait() calls)")
-- Use error. This should absolutely abort the cast.
end
end
-- expectingShortCall is used to determine if we are doing a forced resolution increase, in which case this will be called several times in a single frame, which throws this error.
cast.StateInfo.IsActivelySimulatingPierce = true
end
------------------------------
if cast.RayInfo.CanPierceCallback == nil or (cast.RayInfo.CanPierceCallback ~= nil and cast.RayInfo.CanPierceCallback(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) == false) then
PrintDebug("Piercing function is nil or it returned FALSE to not pierce this hit.")
cast.StateInfo.IsActivelySimulatingPierce = false
if (cast.StateInfo.HighFidelityBehavior == 2 and latestTrajectory.Acceleration ~= Vector3.new() and cast.StateInfo.HighFidelitySegmentSize ~= 0) then
cast.StateInfo.CancelHighResCast = false -- Reset this here.
if cast.StateInfo.IsActivelyResimulating then
cast:Terminate()
error("Cascading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize.")
end
cast.StateInfo.IsActivelyResimulating = true
-- This is a physics based cast and it needs to be recalculated.
PrintDebug("Hit was registered, but recalculation is on for physics based casts. Recalculating to verify a real hit...")
-- Split this ray segment into smaller segments of a given size.
-- In 99% of cases, it won't divide evently (e.g. I have a distance of 1.25 and I want to divide into 0.1 -- that won't work)
-- To fix this, the segments need to be stretched slightly to fill the space (rather than having a single shorter segment at the end)
local numSegmentsDecimal = rayDisplacement / cast.StateInfo.HighFidelitySegmentSize -- say rayDisplacement is 5.1, segment size is 0.5 -- 10.2 segments
local numSegmentsReal = math.floor(numSegmentsDecimal) -- 10 segments + 0.2 extra segments
local realSegmentLength = rayDisplacement / numSegmentsReal -- this spits out 0.51, which isn't exact to the defined 0.5, but it's close
-- Now the real hard part is converting this to time.
local timeIncrement = delta / numSegmentsReal
for segmentIndex = 1, numSegmentsReal do
if cast.StateInfo.CancelHighResCast then
cast.StateInfo.CancelHighResCast = false
break
end
local subPosition = GetPositionAtTime(lastDelta + (timeIncrement * segmentIndex), origin, initialVelocity, acceleration)
local subVelocity = GetVelocityAtTime(lastDelta + (timeIncrement * segmentIndex), initialVelocity, acceleration)
local subRayDir = subVelocity * delta
local subResult = targetWorldRoot:Raycast(subPosition, subRayDir, cast.RayInfo.Parameters)
local subDisplacement = (subPosition - (subPosition + subVelocity)).Magnitude
if (subResult ~= nil) then
local subDisplacement = (subPosition - subResult.Position).Magnitude
local dbgSeg = DbgVisualizeSegment(CFrame.new(subPosition, subPosition + subVelocity), subDisplacement)
if (dbgSeg ~= nil) then dbgSeg.Color3 = Color3.new(0.286275, 0.329412, 0.247059) end
if cast.RayInfo.CanPierceCallback == nil or (cast.RayInfo.CanPierceCallback ~= nil and cast.RayInfo.CanPierceCallback(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) == false) then
-- Still hit even at high res
cast.StateInfo.IsActivelyResimulating = false
SendRayHit(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject)
cast:Terminate()
local vis = DbgVisualizeHit(CFrame.new(point), false)
if (vis ~= nil) then vis.Color3 = Color3.new(0.0588235, 0.87451, 1) end
return
else
-- Recalculating hit something pierceable instead.
SendRayPierced(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) -- This may result in CancelHighResCast being set to true.
local vis = DbgVisualizeHit(CFrame.new(point), true)
if (vis ~= nil) then vis.Color3 = Color3.new(1, 0.113725, 0.588235) end
if (dbgSeg ~= nil) then dbgSeg.Color3 = Color3.new(0.305882, 0.243137, 0.329412) end
end
else
local dbgSeg = DbgVisualizeSegment(CFrame.new(subPosition, subPosition + subVelocity), subDisplacement)
if (dbgSeg ~= nil) then dbgSeg.Color3 = Color3.new(0.286275, 0.329412, 0.247059) end
end
end
-- If the script makes it here, then it wasn't a real hit (higher resolution revealed that the low-res hit was faulty)
-- Just let it keep going.
cast.StateInfo.IsActivelyResimulating = false
elseif (cast.StateInfo.HighFidelityBehavior ~= 1 and cast.StateInfo.HighFidelityBehavior ~= 3) then
cast:Terminate()
error("Invalid value " .. (cast.StateInfo.HighFidelityBehavior) .. " for HighFidelityBehavior.")
else
-- This is not a physics cast, or recalculation is off.
PrintDebug("Hit was successful. Terminating.")
SendRayHit(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject)
cast:Terminate()
DbgVisualizeHit(CFrame.new(point), false)
return
end
else
PrintDebug("Piercing function returned TRUE to pierce this part.")
if rayVisualization ~= nil then
rayVisualization.Color3 = Color3.new(0.4, 0.05, 0.05) -- Turn it red to signify that the cast was scrapped.
end
DbgVisualizeHit(CFrame.new(point), true)
local params = cast.RayInfo.Parameters
local alteredParts = {}
local currentPierceTestCount = 0
local originalFilter = params.FilterDescendantsInstances
local brokeFromSolidObject = false
while true do
-- So now what I need to do is redo this entire cast, just with the new filter list
-- Catch case: Is it terrain?
if resultOfCast.Instance:IsA("Terrain") then
if material == Enum.Material.Water then
-- Special case: Pierced on water?
cast:Terminate()
error("Do not add Water as a piercable material. If you need to pierce water, set cast.RayInfo.Parameters.IgnoreWater = true instead", 0)
end
warn("WARNING: The pierce callback for this cast returned TRUE on Terrain! This can cause severely adverse effects.")
end
if params.FilterType == Enum.RaycastFilterType.Blacklist then
-- blacklist
-- DO NOT DIRECTLY TABLE.INSERT ON THE PROPERTY
local filter = params.FilterDescendantsInstances
table.insert(filter, resultOfCast.Instance)
table.insert(alteredParts, resultOfCast.Instance)
params.FilterDescendantsInstances = filter
else
-- whitelist
-- method implemeneted by custom table system
-- DO NOT DIRECTLY TABLE.REMOVEOBJECT ON THE PROPERTY
local filter = params.FilterDescendantsInstances
table.removeObject(filter, resultOfCast.Instance)
table.insert(alteredParts, resultOfCast.Instance)
params.FilterDescendantsInstances = filter
end
SendRayPierced(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject)
-- List has been updated, so let's cast again.
resultOfCast = targetWorldRoot:Raycast(lastPoint, rayDir, params)
-- No hit? No simulation. Break.
if resultOfCast == nil then
break
end
if currentPierceTestCount >= MAX_PIERCE_TEST_COUNT then
warn("WARNING: Exceeded maximum pierce test budget for a single ray segment (attempted to test the same segment " .. MAX_PIERCE_TEST_COUNT .. " times!)")
break
end
currentPierceTestCount = currentPierceTestCount + 1;
if cast.RayInfo.CanPierceCallback(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) == false then
brokeFromSolidObject = true
break
end
end
-- Restore the filter to its default state.
cast.RayInfo.Parameters.FilterDescendantsInstances = originalFilter
cast.StateInfo.IsActivelySimulatingPierce = false
if brokeFromSolidObject then
-- We actually hit something while testing.
PrintDebug("Broke because the ray hit something solid (" .. tostring(resultOfCast.Instance) .. ") while testing for a pierce. Terminating the cast.")
SendRayHit(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject)
cast:Terminate()
DbgVisualizeHit(CFrame.new(resultOfCast.Position), false)
return
end
-- And exit the function here too.
end
end
if (cast.StateInfo.DistanceCovered >= cast.RayInfo.MaxDistance) then
-- SendRayHit(cast, nil, segmentVelocity, cast.RayInfo.CosmeticBulletObject)
cast:Terminate()
DbgVisualizeHit(CFrame.new(currentTarget), false)
end
end
Yes, it all works the same way. FC is simply a bullet sim module so it works on anything based on projectiles
Hello everyone!
I’m trying to make an MMO game with a magic system. Would FastCast be compatible with creating orbs that explode on impact, etc? Thanks!
Can’t see the projectile when it’s in flight?
Hey, how do you make the bullet look in full flight? tell me something simple so I learn
Hey, how do they make the bullets visible? Tell me a little about that topic, please. I do it myself with CFrame or with the module. how does this work? I see that several people do it but I don’t know what strategy they take
Am I Terminating the Active Cast wrong? I keep getting this error & it’s not terminating the cast.
warn("cant fire")
if gunBrainSent ~= gunBrain then
warn("terminating cast")
Caster:Terminate()
print("ended cast")
end
Terminate is a function of ActiveCast, not the Caster.
If by “how do they make the bullets visible” you mean making the bullets visible to the rest of the clients after a client has fired a bullet then you’d pass the necessary arguments through a remote to all clients but the one that fired. And yes, you’d just use the module.
Has Parallel Lua been implemented yet?
Hey! Appreciate the module you’ve created, props! Although I do have a problem, which is figuring out how to terminate an active cast. I’ve currently tried to terminate the cast that the .Rayhit function has as parameter, but that just returns:
I also get another error quite a lot, which is: Attempt to index nil with delegate, and I suppose that is for the same reason. I have printed the casts and they make more than 1 connection.
Any ideas on how to solve these problems?
I might be overlooking something, but Im confused on how you compare the server hitpoint and the client hitpoint. Which both exist in different run contexts
Why is FastCast slow and laggy when you actually play it rather than in roblox studio?
or maybe it’s just me?