Client-Server Replication Glitches

I have an ability that summons a wall infront of the user and also throws out attacks when you click the mouse button. It works completely fine when in single player but it gets very glitchy as well as other abilities in the game when I test it with two players. Some of the issues include the wrong client executing the ability, one or both clients not executing the ability, the ability not working as intented or stopping at an unspecified time during the execution. Im fairly positive this has to do with the replicator script that I will link below, I got the script from a friend in the vfx community a long time ago and I mostly know how it works which is why I can see why its causing the issues im mentioning.

local script:
image

--Services  
local Replicated = game:GetService('ReplicatedStorage')
local UIS = game:GetService('UserInputService')
local TweenService = game:GetService("TweenService")
local Players = game:GetService('Players')
local Storage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

--Character/Player
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid = Character:WaitForChild('Humanoid')
local Animator = Humanoid:WaitForChild('Animator')
local RootPart = Character:WaitForChild('HumanoidRootPart')
local Mouse = Player:GetMouse()
local Torso = Character["Torso"]
--Camera
local Camera = workspace.CurrentCamera

--Remotes
local PinkHexaAutoAttackRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaAutoAttackRemote
local PinkHexaShiftRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaShiftRemote
local PinkHexaSkillRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaSkillRemote
local PinkHexaUltimateRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaUltimateRemote

--Assets
local Mobility = require(Replicated.Modules.Shared.Mobility)
local HexaWallActive = false
local Cooldown = false
local ShieldCooldown = false
local AutoAttackNumber = 1
local AbiltyCooldownTimes = {0.3,0.3,0.5,1}
local LastAutoTime = 0
local SequenceResetTimer = 1.5
local ActiveHexaE = Replicated.Assets.Actives.ActiveHexaE
local ActiveHexaShift = Replicated.Assets.Actives.ActiveHexaShift

--AutoAttacks
UIS.InputBegan:Connect(function(input,gameProcessedEvent)

	--Checking If Input Should Pass
	if gameProcessedEvent or Cooldown or HexaWallActive then return end
	if Humanoid:GetState() == Enum.HumanoidStateType.Freefall or Humanoid:GetState() == Enum.HumanoidStateType.Jumping or Humanoid:GetState() == Enum.HumanoidStateType.Landed then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 then

		--Stopping Movement
		Mobility.Stop(Humanoid)

		--Reset AutoAttackNumber 
		if tick() - LastAutoTime > SequenceResetTimer then
			AutoAttackNumber = 1	
		end

		--Cooldowns
		LastAutoTime = tick()
		Cooldown = true

		--Ability Fires To Server
		if AutoAttackNumber == 1 then
			PinkHexaAutoAttackRemote:FireServer(AutoAttackNumber)
			task.wait(AbiltyCooldownTimes[AutoAttackNumber])
		elseif AutoAttackNumber == 2 then
			PinkHexaAutoAttackRemote:FireServer(AutoAttackNumber)
			task.wait(AbiltyCooldownTimes[AutoAttackNumber])
		elseif AutoAttackNumber == 3 then
			PinkHexaAutoAttackRemote:FireServer(AutoAttackNumber)
			task.wait(AbiltyCooldownTimes[AutoAttackNumber])
		elseif AutoAttackNumber == 4 then
			PinkHexaAutoAttackRemote:FireServer(AutoAttackNumber)
			task.wait(AbiltyCooldownTimes[AutoAttackNumber])
		end		

		--Auto Attack Sequencing
		if AutoAttackNumber == 4 then
			AutoAttackNumber = 1
		else
			AutoAttackNumber = AutoAttackNumber + 1	
		end	

		Cooldown = false	

		--Returning Mobility
		Mobility.Start(Humanoid)

	end
end)


--Shift
UIS.InputBegan:Connect(function(input,gameProcessedEvent)
	if  gameProcessedEvent or ShieldCooldown then return end
	if input.KeyCode == Enum.KeyCode.LeftShift then
		HexaWallActive = true
		PinkHexaShiftRemote:FireServer("Began")
	end	
end)

UIS.InputEnded:Connect(function(input,gameProcessedEvent)
	if gameProcessedEvent then return end
	if input.KeyCode == Enum.KeyCode.LeftShift then	
		HexaWallActive = false
		PinkHexaShiftRemote:FireServer("Ended")
		
	end
end)

UIS.InputBegan:Connect(function(input,gameProcessedEvent)

	--Checking If Input Should Pass
	if gameProcessedEvent then return end
	if Humanoid:GetState() == Enum.HumanoidStateType.Freefall or Humanoid:GetState() == Enum.HumanoidStateType.Jumping or Humanoid:GetState() == Enum.HumanoidStateType.Landed then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		PinkHexaShiftRemote:FireServer("Clicked")
	end
end)

--Skill

UIS.InputBegan:Connect(function(input,gameProcessedEvent,Close)

	--Checking If Input Should Pass
	if gameProcessedEvent then return end
	if input.KeyCode == Enum.KeyCode.E then
	
		PinkHexaSkillRemote:FireServer()
	
		
	end
end)

--Ultimate

UIS.InputBegan:Connect(function(input,gameProcessedEvent,Close)
	if gameProcessedEvent then return end
	if input.KeyCode == Enum.KeyCode.Q then

		PinkHexaUltimateRemote:FireServer()

	end
end)

Replicator Script:

--Services
local Replicated = game:GetService("ReplicatedStorage")

--Assets
local FXRemote = Replicated.Assets.Remotes.FX	

FXRemote.OnClientEvent:Connect(function(ModuleFolder, Module, ...)
	
--	local ModuleF = require(Replicated.Modules.Abilities:FindFirstChild(Module)) or warn("Module doesnt not exist in folder Templates!")
	local ModuleF = require(Replicated.Modules.Abilities[ModuleFolder]:FindFirstChild(Module)) or warn("Module doesnt not exist in folder Templates!")
	ModuleF(...)
	
end)

ServerScript:

---Services
local Replicated = game:GetService('ReplicatedStorage')
local UIS = game:GetService('UserInputService')
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

--Modules
local MockP = require(Replicated.Modules.Shared.MockPart)
local Raycast = require(Replicated.Modules.Shared.Raycast)
local Bezier = require(Replicated.Modules.Shared.Bezier)

--Assets
local PinkHexaAutoAttackRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaAutoAttackRemote
local PinkHexaShiftRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaShiftRemote
local PinkHexaSkillRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaSkillRemote
local PinkHexaUltimateRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaUltimateRemote
local FXRemote = Replicated.Assets.Remotes.FX
	
--Body

PinkHexaAutoAttackRemote.OnServerEvent:Connect(function(Player,AutoAttackNumber)
	
	FXRemote:FireAllClients("PinkHexaAbilities","PinkHexaAutoAttacks",Player,AutoAttackNumber)
	
end)

PinkHexaShiftRemote.OnServerEvent:Connect(function(Player,Input)
	
	FXRemote:FireAllClients("PinkHexaAbilities","PinkHexaShift",Player,Input)
	
end)

PinkHexaSkillRemote.OnServerEvent:Connect(function(Player,Close)
	
	FXRemote:FireAllClients("PinkHexaAbilities","PinkHexaSkill",Player,Close)

	
end)

PinkHexaUltimateRemote.OnServerEvent:Connect(function(Player)

	FXRemote:FireAllClients("PinkHexaAbilities","PinkHexaUltimate",Player)

end)

Module Scripts:
image

-- Services
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Replicated = game:GetService("ReplicatedStorage")

--Assets
local MockP = require(Replicated.Modules.Shared.MockPart)
local Raycast = require(Replicated.Modules.Shared.Raycast)
local Bezier = require(Replicated.Modules.Shared.Bezier)
local Mobility = require(Replicated.Modules.Shared.Mobility)
local WallCooldown = false
local LongCooldown = false
local ShortCooldownTime = 0.7
local LongCooldownTime = 6
local BeginTime
local Hexas = {}
local PinkHexaShiftRemote = Replicated.Assets.Remotes.PinkHexaRemotes.PinkHexaShiftRemote
local ActiveHexaShift = Replicated.Assets.Actives.ActiveHexaShift

--Functions

local function HexaSetSize(Hexagon,BodySizeXYZ,OutlineSizeXYZ,Time,EasingStyle,EasingDirection)			
	
	TweenService:Create(Hexagon.Body,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{Size = BodySizeXYZ}):Play()
	TweenService:Create(Hexagon.Outline,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{Size = OutlineSizeXYZ}):Play()
end

local function HexaChangeCframe(Hexagon,X,Y,Z,Time,EasingStyle,EasingDirection)			
	local HexagonBodyCframe = Hexagon.Body.CFrame * CFrame.new(X,Y,Z)
	local HexagonOutlineCframe = Hexagon.Body.CFrame * CFrame.new(X,Y,Z)
	TweenService:Create(Hexagon.Body,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{CFrame = HexagonBodyCframe}):Play()
	TweenService:Create(Hexagon.Outline,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{CFrame = HexagonOutlineCframe}):Play()
end

local function HexaChangeTransparency(Hexagon,Visiblity,Time,EasingStyle,EasingDirection)
	if Visiblity == "Invisible" then
		TweenService:Create(Hexagon.Body,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{Transparency = 1}):Play()
		TweenService:Create(Hexagon.Outline,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{Transparency = 1}):Play()
	end
	
	if Visiblity == "Visible" then
		TweenService:Create(Hexagon.Body,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{Transparency = 0.7}):Play()
		TweenService:Create(Hexagon.Outline,TweenInfo.new(Time,EasingStyle,EasingDirection,0),{Transparency = 0}):Play()
	end
end
--

--Body
return 
	function(...)
		
		local Parameters = {...}
		local Player = Parameters[1]
		local Character = Player.Character 
		local RootPart = Character:WaitForChild('HumanoidRootPart')
		local Wall
		local Input = Parameters[2]
		local Connection
		
		
--Folders
		if game.Workspace:FindFirstChild("PinkHexaAffectsShift"..Player.Name) == nil then
		local Effects = Instance.new("Folder")
		Effects.Parent = workspace
		Effects.Name = "PinkHexaAffectsShift"..Player.Name
		end

		if game.Workspace:FindFirstChild("PinkHexaAffects"..Player.Name) == nil then
		local Effects = Instance.new("Folder")
		Effects.Parent = workspace
		Effects.Name = "PinkHexaAffects"..Player.Name
		end

	
	
--Begin
		if Input == "Began" then
			if WallCooldown or LongCooldown then return end
		
			ActiveHexaShift.Value = true
			
			if game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall") == nil then
				
				local HexaWall = Replicated.Assets.Meshes.PinkHexaMeshes.HexaShiftWall:Clone()
				HexaWall.Name = "HexaShiftWall"
				HexaWall.PrimaryPart = HexaWall.HS0.Body
				HexaWall.Parent = game.Workspace:FindFirstChild("PinkHexaAffectsShift"..Player.Name)	
			    BeginTime = tick()
			end

			local HexaWall = game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall")
		
			coroutine.wrap(function()	
				for count = 0,6,1 do
					--if ActiveHexaShift.Value == true then
						for i,v in pairs(HexaWall:GetDescendants()) do
							if v.Name == "HS"..count then
								
								v.Body.Transparency = 0.68
								v.Outline.Transparency = 0

							end	
						end	
						task.wait(0.03)
					--end
				end
			end)()		
	
			coroutine.wrap(function()	
				for count = 0,6,1 do
					--if ActiveHexaShift.Value == true then
						for i,v in pairs(HexaWall:GetDescendants()) do
							if v.Name == "HS"..count then
									
							HexaSetSize(v,Vector3.new(3.194, 0.435, 3.689),Vector3.new(3.269, 0.445, 3.775),0.05,Enum.EasingStyle.Quad,Enum.EasingDirection.Out)
						
							end	
						end	
						task.wait(0.03)
					--end
				end
			end)()		
		end
		
--End
		if Input == "Ended" then
	
			if WallCooldown == true or game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall") == nil or ActiveHexaShift.Value == false  then return end
			
			ActiveHexaShift.Value = false
			
			WallCooldown = true 
			
			
		
			local HexaWall = game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall")
			
		coroutine.wrap(function()
			for count = 0,6,1 do
				for i,v in pairs(HexaWall:GetDescendants()) do
					if v.Name == "HS"..count then
				
					TweenService:Create(v.Body,TweenInfo.new(0.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),{Transparency = 1}):Play()
					TweenService:Create(v.Outline,TweenInfo.new(0.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),{Transparency = 1}):Play()	

					end	
				end	
				task.wait(0.045)
			end
			
			for count = 0,6,1 do
				for i,v in pairs(HexaWall:GetDescendants()) do
					if v.Name == "HS"..count then
						v.Body.Size = Vector3.zero
						v.Outline.Size = Vector3.zero
					end	
				end	
				task.wait(0.045)
			end	
		end)()
		
	--[[	coroutine.wrap(function()	
			
			for count = 0,6,1 do
				for i,v in pairs(HexaWall:GetDescendants()) do
					if v.Name == "HS"..count then
						v.Body.Size = Vector3.zero
						v.Outline.Size = Vector3.zero
					end	
				end	
				task.wait(0.045)
			end
			
		end)()]]
			
		print("ShortCooldownTimeOn")
			task.wait(ShortCooldownTime) 
		print("ShortCooldownTimeOff")
			
				
		WallCooldown = false 
			
	end	
		
--MousePressed		
		if Input == "Clicked" and ActiveHexaShift.Value then
			if LongCooldown == true then return end
			
			local HexaWall = game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall")
	
			if table.maxn(Hexas) <= 0 then 
				for i,v in pairs(HexaWall:GetChildren()) do	
					if v.Name ~= "HS0" then
						table.insert(Hexas,v)
					end
				end
			end	
  		

			if table.maxn(Hexas) <= 0 then
				
				ActiveHexaShift.Value = false
				if Connection then
				Connection:Disconnect()
				end
				
				local Hexa = HexaWall.HS0
				
				
				LongCooldown = true print("LongCooldownTrueTablemaxn")
			
				Hexa.Body.Anchored = true
				Hexa.Parent = workspace
				HexaChangeCframe(Hexa,0,30,0,0.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out)
				
				coroutine.wrap(function()
					task.wait(0.2)
					HexaChangeTransparency(Hexa,"Invisible",0.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out)
					task.wait(0.2)
					HexaWall:Destroy()
				end)()
				
				task.wait(LongCooldownTime)
				
				LongCooldown = false print("LongCooldownFalseTablemaxn")
				
			else	
				local TableNumber = table.maxn(Hexas)
				local Randomizer = math.random(1,TableNumber)
				local Hexa = Hexas[Randomizer]
			
		
				table.remove(Hexas,Randomizer)
				Hexa.Body.Anchored = true
				Hexa.Parent = workspace
				HexaChangeCframe(Hexa,0,30,0,0.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out)
				coroutine.wrap(function()
					task.wait(0.2)
				HexaChangeTransparency(Hexa,"Invisible",0.2,Enum.EasingStyle.Quad,Enum.EasingDirection.Out)
					task.wait(0.2)
					Hexa:Destroy()
				end)()	
			end
		end
		
		
		
--Mover		
		Connection = RunService.RenderStepped:Connect(function()
			if game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall") == nil or LongCooldown then Connection:Disconnect() return end
			
			if (tick() - BeginTime) >= 10 then
			
			Connection:Disconnect()
			
			
			LongCooldown = true print("LongCooldownTrueConnection")
			
			
			table.clear(Hexas)
			PinkHexaShiftRemote:FireServer("Ended")
			coroutine.wrap(function()
				task.wait(0.6)
				game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall"):Destroy()
			end)()
			task.wait(LongCooldownTime)
			
			
			
			
			LongCooldown = false   print("LongCooldownFalseConnection")
			
			return end
			
				if ActiveHexaShift.Value == true then
				---
				Wall = game.Workspace["PinkHexaAffectsShift"..Player.Name]:FindFirstChild("HexaShiftWall")
				Wall.PrimaryPart.CFrame = RootPart.CFrame * CFrame.new(0,1.7,-10) *CFrame.Angles(math.rad(270),math.rad(0),math.rad(0))
				Wall.PrimaryPart.Anchored = true
			
			end
		end)		
	end  
--Timeout

I included pictures of the location of some of the scripts just to give you an idea of how the script is structed, like I said I have feeling this has to do with replicator script not being a traditional onclientevent function and as a result one module script is being shared by one client. The specific wall ability im mention in the beginning is called the PinkHexaShift if you look through the script. Im not one hundred percent sure but if you know what the problem could be please let me know thanks!

1 Like

After testing the script it seems the issue is indeed that the two clients keep using the same module script which is what is causing the errors, not sure out to circumvent this issue though.

1 Like

See, first of all: don’t use 4 remotes for single abillity! use one and send name and shared data like position, Hit or smth to them from client, then of course check some important stuff, also you pretty much want to create visuals on client, and then you can yield as many times as you want, cause only one client is effected

do calculations on server and you would be fun, if those calculations are for player-only like moves then you can do it on client too, cheaters will break them anyways

At the end, to fix your problem, your code is long and messy, you use events inside modules! don’t do that! also you use a lot of corountines and i don’t see player determination here , also never check input on server it’s simply not the way

You store too much stuff in those modules, you simply should rewrite it and correct mistakes above, remember to use tabs in every scope, sometimes you don’t use them

This is example of how module should have been done:


Server ^

Client^

Only example, but still

Thank you! I understand what you mean but to make things clearer for myself can you elaborate on why you shouldn’t use use events inside modules, why using a lot of corountines is bad, what player determination is and how I can use tabs in every scopre? Sorry if this a loaded question, you dont have to answer all of them.

1 Like

it’s mostly because code organization, modules are specially designed to handle many tasks that are simillar, the best example would be car module, it will hold some functions like:

  • Build car
  • Explode car
  • Boost car

ect.

Also corountine creates another thread, this mean that every time you create new corountine, you run another process and have another thread, when used wisely they will be pretty much usefull, but when used too much they’ll cause performance drop

Player determination is simply creating code that can run independently without any new threads for each player, with help of modules and don’t using yielding stuff you can achieve that every player can use one script and be happy with that

tabs? they are TAB keys and make spaces between each code level, for example services are in the beginning of script and they have no space from left, then code inside function is one TAB left because it’s inside another scope, it’s esthetic thing and it’s very very very usefull!

Even beginners should use it to make their code cleaner and save time by searching one line

I believe you now understand :} i wish you luck with your code

Thank you for explanation! I understand Player Determination but im having trouble applying it to the script I have so I can fix it. I know my problem is the players are using one module script which cant be used to make two functioning abilities at once. So do Im just wondering what do I change in order for every client to able to both display their own ability while displaying another players ability at the same time.

1 Like

use client side effects, make effect on client and calculate it on server

My script already does that though, the client fires the remote event to the server and the server fires the the remote event to all the clients with the module script, is this not what your talking about?

1 Like

yea it is, then what is the problem?

The problem is when I go into test mode with two players or test it with a friend the module script keeps getting confused on who is calling the function which causes the ability to spawn infront of the wrong player, spawn in the wrong location, not spawn at all, mess up cooldown timers, etc.

1 Like

ok, i have few solutions:

  • first send position of start through remote, soo it can play correct thing
  • two, instead of using waits for cooldown use Debounces and ticks() like this:
if Debounce.SomeDebounce.CooldownTime <= tick() then 
-- code
end

Also if there are task.waits and ect. that blocks some effects, you might use Parallel Luau | Documentation - Roblox Creator Hub as it helps when trying to play many different objects on their own

I don’t believe either of these will work as the problem lies in the awkward usage of the module script. If I send the position of the start through the remote, it wont fix the rest of the script not working if another player fires the module script which causes the script to malfunction. This applies to the debounce solution as well, it won’t matter because the script will get overriden by another player calling the script.

1 Like

listen, the best thing you can do now is to rewrite entire script from beginning, fixing it now would be harder, but back to the problem, do this:

  • Some event on client, remote sends info about player and what ability he used

  • On server you receive this info, to the MAIN server script of your ability system, then it reads ability and checks if it’s valid, also it checks inside Debounce module that holds every player’s debounce in tick() for ability, if it’s less or equal current tick then it’s safe to pass, also overwrite it when using ability with current tick() and set player’s ability to nil on player leaving

  • When we checked our debounce without yielding, and also we determined which ability player want to use we call function in specific ability’s module, you can create table of required modules and then access it by this function, send info about player required for ability to be calculated

  • apart from that you send remote back to all clients, with info required to run visuals on client, then on client do almost the same, find module that contains same info that was returned by remote

  • on server: do calculations, don’t use task.wait if not neccesary, you should check if waiting don’t yield other modules at the same time, you can test this with your friend

  • on client: also do check if it’s working as intended, do visuals, you can use tween service there and some optimization tricks or alternatives to it soo you can achieve great VFX

1 Like

I believe i helped, if it will still yield then try alternative ways such as events or maybe try OOP to create new abillity objects

1 Like

anyways good luck with your ability system :+1:

1 Like

I fixed the issue by using dictionaries and inserting the player associated with the cooldown or variable which helped the script differentiate which player was who

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