Fireball not teleporting correctly when working

Im currently working on a fireball, and until now it works perfectly fine. But only until I start moving, because then the Fireball is bugging behind the position it should be.

Script for firing the ball:

local SS = game:GetService("ServerStorage")
local ball = SS.FireballTool.Fireball
local folder = SS.FireballTool.Fireballs

local event = script.Parent.Event
folder.Parent = workspace

event.OnServerEvent:Connect(function(plr)
	local char = plr.Character
	local HRT = char.HumanoidRootPart
	
	local newBall = ball:Clone()
	newBall.Parent = folder
	newBall:SetNetworkOwner(plr)
	newBall.CFrame = HRT.CFrame * CFrame.new(1.25, 0, -2.5)
	newBall.Orientation = HRT.Orientation
	
	local Velocity = Instance.new("BodyVelocity")
	Velocity.maxForce = Vector3.new(math.huge, math.huge, math.huge) 
	Velocity.Velocity = newBall.CFrame.lookVector * 30
	Velocity.Parent = newBall
end)

Pic without moving (works fine):
image

Pic with moving (works bad):
image

1 Like

That is because you handle all the math on the server. There will always be ping between what the client sees and what the server sees.

If you want to see everything on time for all the clients, you can do the math in the LocalScript of the player firing, send it to the server, and then use :FireAllClients() to create the visuals using the math from the firing player’s LocalScript on all the clients.

1 Like

Yeah… no that just leads to very buggy behaviour of the ball
image

Script:

eventA.OnServerEvent:Connect(function(plr)
	local char = plr.Character
	local HRT = char.HumanoidRootPart
	
	local newBall = ball:Clone()
	newBall.Parent = folder
	--newBall:SetNetworkOwner(plr)
	eventA:FireAllClients(newBall, plr)
end)

LocalScript:

event.OnClientEvent:Connect(function(newBall, newPlr)
	local HRT = newPlr.Character.HumanoidRootPart
	newBall.CFrame = HRT.CFrame * CFrame.new(0, 0, 0) --1.25, 0, -2.5
	newBall.Orientation = HRT.Orientation

	local Velocity = Instance.new("BodyVelocity")
	Velocity.maxForce = Vector3.new(math.huge, math.huge, math.huge) 
	Velocity.Velocity = newBall.CFrame.lookVector * 30
	Velocity.Parent = newBall
end)
1 Like

The weird behavior is due to the fact you still use the server. In this scenario, we are only using the server to transport information from one client to all clients. If you replicate a :Clone() on all clients, it will behave the same as if it were on the server, since all clients get their own clone, thus making everyone see a ball.


To correct your scripts, I made some slight changes. Most importantly, how you log your calculations in the RemoteEvent, and how the client should receive said information.

The code starts in a LocalScript:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local fireball = ReplicatedStorage.Fireball

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	
	if input.KeyCode == Enum.KeyCode.E then --//Or any other way of how the fireball is triggered
		
		local fireballCFrame = humanoidRootPart.CFrame * CFrame.new(1.25, 0, -2.5)
		
		ReplicatedStorage.SendInfoToServer:FireServer(fireballCFrame)
		--//print("Sent remote to server.")
		
	end
	
end)

This first function has handled the calculating of the CFrame belonging to the player, which
means it is now the value of the player’s position, no matter whose client it reads.


You then receive said CFrame on the server and pass it through all the clients with the following script:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

ReplicatedStorage.SendInfoToServer.OnServerEvent:Connect(function(player, fireballCFrame, fireballOrientation)
	
	ReplicatedStorage.ShowFireball:FireAllClients(fireballCFrame)
	
end)

Now it will send back the CFrame of the fireball to all the clients, including of the person who fired the fireball. Response time is still in the milliseconds, so delay will not be noticeable for the player firing the ball.


Receive the new RemoteEvent in the same LocalScript as the first function. This will then proceed to use the passed down information and calculate the fireball’s CFrame and Velocity. Again, it will do this on all clients, so everyone will see the same (with some having just a few milliseconds difference). That happens in this function:

ReplicatedStorage.ShowFireball.OnClientEvent:Connect(function(fireballCFrame)
	--//print("Clients received task.")
	
	local newBall = fireball:Clone()
	newBall.Parent = workspace
	newBall.CFrame = fireballCFrame
	
	local Velocity = Instance.new("BodyVelocity")
	Velocity.maxForce = Vector3.new(math.huge, math.huge, math.huge) 
	Velocity.Velocity = newBall.CFrame.lookVector * 30
	Velocity.Parent = newBall
	
	task.delay(3, function()
		newBall:Destroy() --//Keep it tidy
	end)
	
end)

TL;DR:

LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local fireball = ReplicatedStorage.Fireball

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	
	if input.KeyCode == Enum.KeyCode.E then
		
		local fireballCFrame = humanoidRootPart.CFrame * CFrame.new(1.25, 0, -2.5)
		
		ReplicatedStorage.SendInfoToServer:FireServer(fireballCFrame)
		--//print("Sent remote to server.")
		
	end
	
end)

ReplicatedStorage.ShowFireball.OnClientEvent:Connect(function(fireballCFrame)
	--//print("Clients received task.")
	
	local newBall = fireball:Clone()
	newBall.Parent = workspace
	newBall.CFrame = fireballCFrame
	
	local Velocity = Instance.new("BodyVelocity")
	Velocity.maxForce = Vector3.new(math.huge, math.huge, math.huge) 
	Velocity.Velocity = newBall.CFrame.lookVector * 30
	Velocity.Parent = newBall
	
	task.delay(3, function()
		newBall:Destroy()
	end)
	
end)
ServerScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")

ReplicatedStorage.SendInfoToServer.OnServerEvent:Connect(function(player, fireballCFrame, fireballOrientation)
	
	ReplicatedStorage.ShowFireball:FireAllClients(fireballCFrame)
	
end)
3 Likes

Yeap that works, thank you! Only have to find now a way to make it detect.

1 Like

Do you know a method how can I detect if the fireball hits something? Raycasts dont work cause the ball is not moving fast enough. Touch events firing on every client will lead to overlapping events

1 Like

I went back into the file I created to check how this was possible. I immediately thought of the :Once() function, which acts the same as connection, but instead of the connection being permanent until disconnected, :Once() will disconnect itself as soon as the function is completed, defeating the risk of a data leak.

Since I wanted to familiarize myself with this type of function to practice myself, you can use the scripts I made. I even added a RemoteEvent extra to communicate more with the server for functions you want to happen after the fireball touches some one. You can change it if preferred.


Explorer

image
Make sure the LocalScript is located in the StarterCharacterScripts so the script works after the player dies.

LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local fireball = ReplicatedStorage.Fireball

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	
	if input.KeyCode == Enum.KeyCode.E then
		
		local fireballCFrame = humanoidRootPart.CFrame * CFrame.new(1.25, 0, -2.5)
		
		ReplicatedStorage.SendInfoToServer:FireServer(fireballCFrame)
		--//print("Sent remote to server.")
		
		if not game.Workspace:WaitForChild(player.Name .. "Fireball") then
			warn("Could not find " .. player.Name .. "'s fireball.")
		else
			local playerFireball = game.Workspace[player.Name .. "Fireball"]
			playerFireball.Name = "Processed"
			
			task.wait(0.1) --//So the fireball does not register touching the player who is firing it
			
			playerFireball.Touched:Once(function(hit)
				print("Touched.")
				local modelHit = hit:FindFirstAncestorWhichIsA("Model")
				
				if not modelHit.Humanoid then
					warn("Humanoid not found in", modelHit)
				else
					local otherHumanoid = modelHit.Humanoid
					
					print("Found humanoid:", otherHumanoid)
					ReplicatedStorage.TakeDamage:FireServer(otherHumanoid)
					
				end
				
			end)
			
		end
		
	end
	
end)

ReplicatedStorage.ShowFireball.OnClientEvent:Connect(function(playerInstance, fireballCFrame)
	--//print("Clients received task.")
	
	local newBall = fireball:Clone()
	newBall.Name = playerInstance.Name .. "Fireball"
	newBall.Parent = workspace
	newBall.CFrame = fireballCFrame
	
	local Velocity = Instance.new("BodyVelocity")
	Velocity.maxForce = Vector3.new(math.huge, math.huge, math.huge) 
	Velocity.Velocity = newBall.CFrame.lookVector * 30
	Velocity.Parent = newBall
	
	task.delay(3, function()
		newBall:Destroy()
	end)
	
end)
ServerScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")

ReplicatedStorage.SendInfoToServer.OnServerEvent:Connect(function(player, fireballCFrame, fireballOrientation)
	
	ReplicatedStorage.ShowFireball:FireAllClients(player, fireballCFrame)
	
end)

ReplicatedStorage.TakeDamage.OnServerEvent:Connect(function(player, humanoid)
	
	humanoid:TakeDamage(20)
	
end)

I added debugging prints to help show what is going on with the script, they are optional to remove.

1 Like

I love your script, I actually did not think about doing the .Touched event in this thread. However, your :Once() functions does not work. It only works for the first fireball that is shot and then never again. I also tried using :Connect() and then Disconnecting the event but that does also not solve the issue. Thats a bit weird, because shouldnt the event be refreshed if a new trigger thread has opened?
The part of my local script (Im using tool.Activated as a trigger):

cooldown = false

script.Parent.Activated:Connect(function()
	if cooldown == false then
		Anim:Play()
		cooldown = true
		local connection
		connection =  Anim:GetMarkerReachedSignal("Hit"):Connect(function()
			local HRT = char.HumanoidRootPart
			local fireballCFrame = HRT.CFrame * CFrame.new(1.25, 0, -2.5)
			event:FireServer(fireballCFrame)
			connection:Disconnect()
		end)
		
		Anim.Ended:Connect(function()
			cooldown = false
		end)
		
		local plrFireball = folder:WaitForChild(plr.Name)
		

		plrFireball.Touched:Once(function(hit)
			if hit.Parent:FindFirstChildWhichIsA("Humanoid") and hit.Parent.Name ~= plr.Name then
				hitEvent:FireServer(hit)
			end
		end)
	end
end)

It may look unimportant, but in this scenario, this line is most crucial for the :Once() function to connect;

Let me explain why.


In your code, you execute the following:

It leads me to the assumption that you summon the fireball, name it to the player, and then connect the :Once() function to the fireball with that player’s name. The issue is that if the player fires multiple fireballs, creating more player named instances, the :WaitForChild(<player's name>) will alway get the same or already long gone fireballs. Hence why I made sure to change the name of the fireball to “Processed” as soon as the connection was made, so that it will not try to connect the :Once() to the unintended fireballs.

I suggest adding the same line as follows:

...
		local plrFireball = folder:WaitForChild(plr.Name)
		plrFireball.Name = "Processed" --//This will still work, since the plrFireball is not being searched by name but now works by instance

		plrFireball.Touched:Once(function(hit)
			if hit.Parent:FindFirstChildWhichIsA("Humanoid") and hit.Parent.Name ~= plr.Name then
				hitEvent:FireServer(hit)
			end
		end)
...
1 Like

Yeap, that works perfectly, thank you very much. This is the end result:

External Media
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.