Kicking a ball has client-server-client latency

Hello!

In my game, Mini Cup, there are around 16 players in game. They are divided into 4 teams of 4 and play soccer against each other in turns. And in like any soccer (football) game, they try to kick the ball into the oppositions net.

However it is hard to play with much precision because the latency makes it so that the opposing players look far away from the ball when they kick it, when in reality, on their end of the game, on their screen, they are already touching the ball. But to you, you are actually closer to the ball and they are still miles away.

On the other hand, when you kick the ball, it takes a while before the server actually received the input and performs the kick.

If you play one match in this game you will see the issue yourself:

So now my issue is how do I fix this?
I’ve been trying for months but have come to no solution.
(I’ve changed velocity types, added a cooldown, set network owner to nil and everything I’ve heard of)

This is the current server script which is parented to the ball and is replicated from ServerStorage to the Workspace as needed.

local Players = game:GetService("Players") -- Get the Players service
local Debris = game:GetService("Debris") -- Get the Debris service

local Ball = script.Parent -- Get the ball object
local KickSound = Ball:WaitForChild("Kick") -- Get the kick sound
local bounce = false -- Set a flag to prevent multiple touches from being registered
local gameModule = require(game.ServerStorage.Modules.GameModule)

Ball.Touched:Connect(function(t) -- Connect to the Touched event on the ball
	if t.Name == "PlayerHitBox" then
	local character = t.Parent -- Get the character that touched the ball
	if not character then return end -- If there is no character, return

	local player = Players:GetPlayerFromCharacter(character) -- Get the player associated with the character
	local humanoid = character:FindFirstChildOfClass("Humanoid") -- Get the humanoid of the character
	local rootPart = character:FindFirstChild("HumanoidRootPart") -- Get the root part of the character
	if not player or not humanoid or humanoid.Health <= 0 or not rootPart then return end -- If any of these are missing or the character is not alive, return

	local ballVelocity = Ball.AssemblyLinearVelocity -- Check if the ball already has a velocity

	if bounce == false and not player:GetAttribute("kicked") then -- If the flag is set and no other touches have been registered
		player:SetAttribute("kicked", true)
		print("Bounce is true")
		bounce = true -- Set the bounce flag to prevent further touches from being registered

		local direction = rootPart.CFrame.LookVector -- Get the direction the character is facing
		if direction.Magnitude < 0.001 then return end -- If the direction is very small, return

		Ball:SetNetworkOwner()
		
	
		local kickForce = humanoid.WalkSpeed -- Get the character's walk speed
		
		Ball.AssemblyLinearVelocity = (direction.Unit * kickForce * 1.95) + Vector3.new(0, kickForce * 1.15, 0) -- Set the velocity of the ball based on the direction and walk speed of the character
		
		KickSound:Play() -- Play the kick sound

		-- Set the attributes for the last and second-to-last player to touch the ball
		if Ball:GetAttribute('LastTouch') ~= nil then
			if Ball:GetAttribute('LastTouch') ~= player.Name then
				local previousTouch = Ball:GetAttribute('LastTouch')
				Ball:SetAttribute('LastTouch', player.Name)
				Ball:SetAttribute('SecondTouch', previousTouch)
			else
				-- Do nothing if the current player is the same as the last player to touch the ball
			end
		else
			Ball:SetAttribute('LastTouch', player.Name)
		end
		print("Sending For KickTimer")
		gameModule.kickTimer(player)
		print("Kicktimer Returned")
		task.wait(0.6) -- Wait for 1/2 second
		print("Debounce is false")
		bounce = false -- Reset the bounce flag to allow for future touches to be registered

		-- Increment the touch count for the player's team in the "Standings" folder
		local teamName = player:GetAttribute('TeamName')
		if game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName) ~= nil and game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName):GetAttribute("Touches") ~= nil then
			game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName):SetAttribute('Touches', game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName):GetAttribute('Touches') + 1)
		end
		end
	end
end)

At this point I think it requires an advance solution which I do not have the knowledge to implement. Any ideas or answers would help greatly!

I am also willing to hire an expert who can solve (or at least greatly remedy) the issue as a one time commission.

Everytime the ball touches a player they recieve network owner. You could try to maybe make it set the network owner every time the ball is touched, however I can be wrong and this can make it look a little laggy, never hurts to try though

That will make it more jittery as one client is not synced with the others.