Desync between client & server with attributes..?

I am trying to make a door system. However, sometimes, when clicking the door, it breaks and either the cooldown stops functioning, or the door starts rotating in the wrong way, or both. I believe the cause of this issue is because there is a tiny period where the attributes between the server and client are not connected, breaking it; however, that is just a guess. I truly have no idea.

(DOORMANAGER - REPLICATEDFIRST - CLIENT SCRIPT)
DoorEvent.OnClientEvent:Connect(function(door)
	local subtype = door:GetAttribute("Subtype")
	local open = door:GetAttribute("Open")
	local cooldown = door:GetAttribute("Cooldown")
	
	-- find subtype module script
	local doorType = script.DoorTypes:FindFirstChild(subtype)
	if not doorType then
		warn("No door subtype found.")
		return
	else 
		doorType = require(script.DoorTypes[subtype])
	end
	
	-- play door functionality
	if not cooldown then
		if doorType.moveType == "Hinge" then
			ActivateDoorCDEvent:FireServer(door, true)
			
			local Hinge = door.Door.Hinge

			local rotation = doorType.Rotation
			
			if not open then
				rotation = -rotation
			end

			local DoorTween = TweenService:Create(Hinge, doorType.tweenInfo, {CFrame = Hinge.CFrame * CFrame.Angles(0, math.rad(rotation), 0)})
			DoorTween:Play()
			
			-- wait for door to open and then remove cooldown
			DoorTween.Completed:Wait()
			if door:GetAttribute("FinalizingPlayer") == Client.UserId then -- makes sure only one player fires the server
				FinalizeDoorEvent:FireServer(door, "Hinge", Hinge:GetPivot())
			end
		end
	end
end)
(OPEN DOOR SCRIPT - SERVER SCRIPT SERVICE - SERVER SIDE)
-- doors
finalizeDoorEvent.OnServerEvent:Connect(function(player, door, specification, rotation)
	if not door:FindFirstChild("Base", true) then return end
	
	if specification == "Hinge" and door then
		local Hinge = door.Door.Hinge
		Hinge:PivotTo(rotation)
		
		door:SetAttribute("Open", not door:GetAttribute("Open"))
		door:SetAttribute("Cooldown", false)
	end
end)

activateDoorCDEvent.OnServerEvent:Connect(function(player, door, on)
	if not door:FindFirstChild("Base", true) then return end
	
	door:SetAttribute("Cooldown", on)
	
	if on then
		door:SetAttribute("FinalizingPlayer", player.UserId)
	end
end)

For further clarification, the door tweening is being fired to all clients. Which is why it needs to check the userId. Also, it takes a lot of effort to break them sometimes, or not at all, in the attached screenshot

Do synchronization between the client and server by adding proper state management and cooldown handling. Instead of relying solely on attributes, use server-side validation to control the door’s state and cooldown, and communicate updates to clients via RemoteEvents. (e.g. add a server-side check to prevent multiple clients from triggering the door simultaneously and ensure the cooldown is enforced server-side before allowing the door to activate)

1 Like

I’m having a little bit of trouble understanding, could you simplify what you mean? Like for example, what do you mean by “use server-side validation”?

When I say “use server-side validation,” I mean that the server should be the one in control of the door’s state (whether it’s open or closed) and its cooldown. Right now, it seems like the client is making decisions about the door (like playing the tween and checking the cooldown), and this can cause issues if the client and server are out of sync.

Server-side:

-- Server controls the door
activateDoorCDEvent.OnServerEvent:Connect(function(player, door)
    if not door:FindFirstChild("Base", true) then return end

    -- Check if the door is on cooldown
    if door:GetAttribute("Cooldown") then return end

    -- Start cooldown
    door:SetAttribute("Cooldown", true)
    door:SetAttribute("FinalizingPlayer", player.UserId)

    -- Tell the client to play the tween
    DoorEvent:FireAllClients(door, "Open") -- "Open" or "Close" based on the door’s state

    -- Wait for the cooldown to finish
    task.wait(5) -- Adjust this to your cooldown time

    -- End cooldown
    door:SetAttribute("Cooldown", false)
end)

Client-side:

-- Client just listens and plays the tween
DoorEvent.OnClientEvent:Connect(function(door, action)
    local subtype = door:GetAttribute("Subtype")
    local doorType = script.DoorTypes:FindFirstChild(subtype)
    if not doorType then return end
    doorType = require(script.DoorTypes[subtype])

    if doorType.moveType == "Hinge" then
        local Hinge = door.Door.Hinge
        local rotation = doorType.Rotation

        if action == "Open" then
            rotation = -rotation
        end

        local DoorTween = TweenService:Create(Hinge, doorType.tweenInfo, {CFrame = Hinge.CFrame * CFrame.Angles(0, math.rad(rotation), 0)})
        DoorTween:Play()
    end
end)

1 Like

Had to modify a little bit, but GOD do I feel stupid… I have no idea in the first place as to why I had it function around the client instead of the server… :man_facepalming:

Thank you!

1 Like

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