Fast cast the opposite of fast?

My game’s ping has taken a huge hit with the recent addition of a fast cast spell. I am creating the spell’s orb and hit effects on the client, while the server creates the beams and moves the projectile using fast cast. Is this normal, or am I doing something wrong?

local COOLDOWN = 2
local DISTANCE = 4
local DAMAGE = 5
local ORB_LIFE = 3
local NUMBER_OF_SHOTS = 5
local MANA_CONSUMPTION = 25
local CAST_TIME = 1
local BULLET_SPEED = 250
local MAX_BULLET_DISTANCE = 100
local BULLET_GRAVITY = Vector3.new(0,0,0)
local MIN_BULLET_SPREAD_ANGLE = .5
local MAX_BULLET_SPREAD_ANGLE = 1
local FIRE_DELAY = .1
local RUNNING = true

local hitChars = {}
local debounce = false

local damageMod = require(game.ServerStorage.Modules.DamageModule)
local fastCast = require(game.ServerStorage.Modules.FastCastRedux)

local remote = script.Parent.Remotes.cast
local getMouseRemote = script.Parent.Remotes.getMouse
local visualRemote = game.ReplicatedStorage.Remotes.Fx.icePillars
local char

local Caster = fastCast.new()
local ignoreList = {workspace.NPCs, workspace.Areas, workspace.Trinkets, workspace.FX, workspace.Cameras, workspace.BagItems, workspace.DebugHB}
local TAU = math.pi * 2
local RNG = Random.new()

local beam = Instance.new("Part")
beam.Material = Enum.Material.Neon
beam.BrickColor = BrickColor.new("White")
beam.Anchored = true
beam.CanCollide = false
beam.Size = Vector3.new(.25,.25,4)

local function fire(direction, origin)
	local directionalCF = CFrame.new(Vector3.new(), direction)
	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
	local modifiedBulletSpeed = direction * BULLET_SPEED
	local bullet = beam:Clone()
	bullet.CFrame = CFrame.new(origin)
	bullet.Parent = workspace.Projectiles
	local sound = Instance.new("Sound", bullet)
	sound.SoundId = "rbxassetid://1624609598"
	sound.MaxDistance = 100
	sound:Play()
	Caster:FireWithBlacklist(origin, direction * MAX_BULLET_DISTANCE, modifiedBulletSpeed, ignoreList, bullet, false, BULLET_GRAVITY)
end

function OnRayUpdated(castOrigin, segmentOrigin, segmentDirection, length, 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.
	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

local function OnRayHit(hitPart, hitPoint, normal, material, cosmeticBulletObject)
	cosmeticBulletObject:Destroy()
	if hitPart and hitPart.Parent then
		local hChar = hitPart.Parent
		local hHum = hChar:FindFirstChild("Humanoid")
		if hHum then
			damageMod.Projectile(char, hChar, hitPoint, DAMAGE, true, .5, false, 0, 0)
		end
		
		game.ReplicatedStorage.Remotes.Fx.splash:FireAllClients(hitPoint, normal)
	end
end

remote.OnServerEvent:Connect(function(plr)
	
	char = plr.Character
	local root = char.HumanoidRootPart
	local hum = char.Humanoid
	local Mana = hum.Mana
	local States = hum.States
	
	local ignoreList = {char, workspace.NPCs, workspace.Areas, workspace.DebugHB, workspace.FX, workspace.Trinkets, workspace.Projectiles, workspace.Cameras, workspace.BagItems, workspace.Spawns}
	
	local KNOCKED = States.Knocked
	local RAGDOLL = States.Ragdoll
	local ATTACKING = States.Attacking
	local SKILLUSE = States.SkillUse
	
	if KNOCKED.Value == true or RAGDOLL.Value == true or ATTACKING.Value == true or SKILLUSE.Value == true then return end
	
	if debounce == false and Mana.Value - MANA_CONSUMPTION >= 0 then
		
		debounce = true
		Mana.Value = Mana.Value - MANA_CONSUMPTION
		local castAnim = hum:LoadAnimation(script.CastAnim)
		castAnim:Play()
		SKILLUSE.Value = true
		
		wait(CAST_TIME)
		
		if KNOCKED.Value == true or RAGDOLL.Value == true then
			RUNNING = false
			SKILLUSE.Value = false
		end
		
		if RUNNING then
			-- IF CAST IS SUCCESSFUL THEN
			local originPos = root.Position + Vector3.new(0, 8, 0)
			game.ReplicatedStorage.Remotes.Fx.lightball:FireAllClients(originPos, ORB_LIFE)
			wait(1)
			for i = 1, NUMBER_OF_SHOTS, 1 do
				local mouseDirection = getMouseRemote:InvokeClient(plr, originPos)
				fire(mouseDirection, originPos)
				wait(FIRE_DELAY)
			end			
			SKILLUSE.Value = false
		end
		wait(COOLDOWN)
		
		hitChars = {}
		debounce = false
	end

end)

Caster.LengthChanged:Connect(OnRayUpdated)
Caster.RayHit:Connect(OnRayHit)
1 Like

I suggest letting the client do the effects (beams, projectile, etc) while the server does the damage. This will improve the performance in the server and the client.

It seems like it’s your code problem. It’s not a coincidence that you wait 2 seconds before actually casting the ray, as sown here:

wait(CAST_TIME) -- One second
		
if KNOCKED.Value == true or RAGDOLL.Value == true then
	RUNNING = false
	SKILLUSE.Value = false
end
		
if RUNNING then
	-- IF CAST IS SUCCESSFUL THEN
	local originPos = root.Position + Vector3.new(0, 8, 0)
	game.ReplicatedStorage.Remotes.Fx.lightball:FireAllClients(originPos, ORB_LIFE)
	wait(1) -- One second
    for i = 1, NUMBER_OF_SHOTS, 1 do
		local mouseDirection = getMouseRemote:InvokeClient(plr, originPos) -- Cast here
		fire(mouseDirection, originPos)
		wait(FIRE_DELAY)
	end			

And it’s not a coincidence that it happens exactly 2 seconds after the click.
I would recommend removing the waits.

Those waits are supposed to be there. If a person was to get comboed during that one second, then the spell won’t cast. The issue I’m facing is my ping sky rocketing.

FastCast is explicitly meant to be used client-side. It is much harder to do anti-exploit checks based on client hit detection, but is absolutely necessary if you intend to use the FastCast module.

1 Like

Alternate the wait() into RunService.Stepped:Wait() and use tick() as a starting variable. After clicking, the variable becomes tick() which then later runs into a fast loop including the RunService.Stepped:Wait() to check if tick() - start >= 1.

This is more flexible to use than using wait().


Also if the module is used server-side, it is not that recommended due to input being delayed. Your best alternative is to do sanity checks on server if you intend the timing to be as fast as possible.

2 Likes

How should I go about using fast cast on the client while retaining my visual effects? The only thing I can come up with is firing a remote to all clients to update a client sided beam’s cframe, but that’d probably be a bad idea?

You will essentially fire an event to the server telling it that client A is shooting a projectile with origin, direction, and any other appropriate data. Client A will then use FastCast:FireWithBlackList to simulate the projectile. Server receives the remote with the appropriate information and sends the same information to all other clients. All other clients then are able to use FastCast:FireWithBlacklist to replicate the projectile.

2 Likes