using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using Unity.Netcode; using UnityEditor.Events; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.UIElements; using UnityEngine.Windows; /// This script is for networking the player with reconciliation [RequireComponent(typeof(ClientNetworkTransform))] public class ReconciliationPlayerControllerMiddleman : NetworkBehaviour, IMoveData { // Current private InputPayload currentInputState; private int currentTick => NetworkManager.Singleton.NetworkTickSystem.LocalTime.Tick; private int bufferIndex => currentTick % BUFFER_SIZE; private PlayerInput inputSource; // Shared private const int BUFFER_SIZE = 1024; // Local private StatePayload[] stateBuffer; private InputPayload[] inputBuffer; private StatePayload latestServerState; private StatePayload lastProcessedState; private Queue inputQueue = new Queue(); private ClientNetworkTransform clientNetworkTransform; [Header("Debug")] [SerializeField] private bool ForceEnableComponent = false; public event Action OnNewMoveData; void OnEnable() { if (NetworkManager.Singleton == null) { Debug.Log("[Movement] No network manager found. Ignoring reconciliation."); if (!ForceEnableComponent) { this.enabled = false; return; } } if (!TryGetComponent(out inputSource)) { Debug.LogError("[Movement] No player input found. Ignoring reconciliation."); this.enabled = false; return; } stateBuffer = new StatePayload[BUFFER_SIZE]; inputBuffer = new InputPayload[BUFFER_SIZE]; currentInputState = new InputPayload(); inputSource.OnNewMoveData += (moveData) => currentInputState.moveData = moveData; clientNetworkTransform = GetComponent(); SwitchAuthorityMode(AuthorityMode.Client); } private void Update() { if (IsServer) ServerTick(); if (IsOwner) ClientTick(); } private void ServerTick() { foreach (InputPayload payload in inputQueue) { stateBuffer[bufferIndex] = ProcessMovement(payload); } OnServerStateRecieved_ServerRpc(stateBuffer[bufferIndex]); } private void ClientTick() { if (!latestServerState.Equals(default(StatePayload)) && (lastProcessedState.Equals(default(StatePayload)) || !latestServerState.Equals(lastProcessedState))) { Reconciliation(); } // Add payload to inputBuffer InputPayload payload = new InputPayload(); payload.tick = currentTick; payload.moveData = currentInputState.moveData; inputBuffer[bufferIndex] = payload; stateBuffer[bufferIndex] = ProcessMovement(payload); if (!(IsHost || IsServer)) OnClientInput_ClientRpc(payload); } [ClientRpc] private void OnClientInput_ClientRpc(InputPayload payload) { inputQueue.Enqueue(payload); } [ServerRpc] private void OnServerStateRecieved_ServerRpc(StatePayload serverState) { if (!IsOwner) return; latestServerState = serverState; } private void Reconciliation() { lastProcessedState = latestServerState; int serverStateBufferIndex = latestServerState.tick % BUFFER_SIZE; float positionError = Vector3.Distance(latestServerState.position, stateBuffer[serverStateBufferIndex].position); if (positionError > 0.001f) { transform.position = latestServerState.position; stateBuffer[serverStateBufferIndex] = latestServerState; int tickToProcess = latestServerState.tick + 1; for (; tickToProcess < currentTick; tickToProcess++) { // Reprocess movement StatePayload reprocessedState = ProcessMovement(inputBuffer[tickToProcess]); // Update buffer with reprocessed state int bufferIndex = tickToProcess % BUFFER_SIZE; stateBuffer[bufferIndex] = reprocessedState; } Debug.Log($"[{latestServerState.tick}] Reconciliated"); } } private void SwitchAuthorityMode(AuthorityMode mode) { clientNetworkTransform.authorityMode = mode; bool shouldSync = mode == AuthorityMode.Client; clientNetworkTransform.SyncPositionX = shouldSync; clientNetworkTransform.SyncPositionY = shouldSync; clientNetworkTransform.SyncPositionZ = shouldSync; } private StatePayload ProcessMovement(InputPayload payload) { OnNewMoveData?.Invoke(payload.moveData); return new StatePayload() { tick = payload.tick, position = transform.position }; } } public struct InputPayload : INetworkSerializable { public int tick; public MoveData moveData; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref tick); serializer.SerializeValue(ref moveData); } } public struct StatePayload : INetworkSerializable { public int tick; public Vector3 position; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref tick); serializer.SerializeValue(ref position); } }