using Steamworks; using UnityEngine; namespace MegaKoop.Game.Networking { /// /// Host-authoritative transform replication that mirrors state to all peers via Steam P2P messages. /// [DisallowMultipleComponent] public class SteamNetworkTransform : MonoBehaviour { [Header("References")] [SerializeField] private Transform targetTransform; [SerializeField] private NetworkIdentity identity; [SerializeField] private Rigidbody trackedRigidbody; [SerializeField] private UnityEngine.AI.NavMeshAgent trackedNavMeshAgent; [Header("Authority")] [SerializeField] private bool authorityIsHost = true; [SerializeField] private bool simulateWhenDisconnected = true; [Header("Synchronization")] [SerializeField] private float broadcastInterval = 0.05f; [SerializeField] private float remoteLerpSpeed = 12f; [SerializeField] private float teleportThreshold = 4f; private SteamCoopNetworkManager networkManager; private float broadcastTimer; private bool isRegistered; private Vector3 remoteTargetPosition; private Quaternion remoteTargetRotation; private Vector3 remoteTargetVelocity; private bool haveRemoteState; private SteamCharacterNetworkBridge characterBridge; private void Awake() { if (targetTransform == null) { targetTransform = transform; } if (identity == null) { identity = GetComponent(); } if (trackedRigidbody == null) { trackedRigidbody = GetComponent(); } if (trackedNavMeshAgent == null) { trackedNavMeshAgent = GetComponent(); } characterBridge = GetComponent(); if (characterBridge != null) { enabled = false; return; } remoteTargetPosition = targetTransform.position; remoteTargetRotation = targetTransform.rotation; remoteTargetVelocity = Vector3.zero; broadcastTimer = Random.Range(0f, Mathf.Max(0.01f, broadcastInterval)); } private void OnEnable() { characterBridge = GetComponent(); if (characterBridge != null) { enabled = false; return; } EnsureNetworkManager(); RegisterHandlers(); } private void OnDisable() { UnregisterHandlers(); } private void Update() { RefreshNetworkManager(); if (HasAuthority()) { broadcastTimer -= Time.deltaTime; if (broadcastTimer <= 0f) { BroadcastState(); broadcastTimer = Mathf.Max(0.01f, broadcastInterval); } } else if (haveRemoteState) { float lerpFactor = Mathf.Clamp01(remoteLerpSpeed * Time.deltaTime); Vector3 currentPosition = targetTransform.position; float sqrDistance = (remoteTargetPosition - currentPosition).sqrMagnitude; if (sqrDistance >= teleportThreshold * teleportThreshold) { targetTransform.SetPositionAndRotation(remoteTargetPosition, remoteTargetRotation); } else { targetTransform.position = Vector3.Lerp(currentPosition, remoteTargetPosition, lerpFactor); targetTransform.rotation = Quaternion.Slerp(targetTransform.rotation, remoteTargetRotation, lerpFactor); } } } private void RefreshNetworkManager() { if (networkManager == null) { networkManager = SteamCoopNetworkManager.Instance; if (networkManager != null && isActiveAndEnabled) { RegisterHandlers(); } } } private void EnsureNetworkManager() { RefreshNetworkManager(); } private bool HasAuthority() { if (!authorityIsHost) { return true; } if (networkManager == null) { return simulateWhenDisconnected; } if (!networkManager.IsConnected) { return simulateWhenDisconnected; } return networkManager.IsHost; } private void BroadcastState() { if (identity == null || targetTransform == null || networkManager == null) { return; } Vector3 velocity = Vector3.zero; if (trackedNavMeshAgent != null && trackedNavMeshAgent.enabled) { velocity = trackedNavMeshAgent.velocity; } else if (trackedRigidbody != null) { velocity = trackedRigidbody.linearVelocity; } SteamCharacterStateCache.ReportLocalState(identity.NetworkId, targetTransform.position, targetTransform.rotation, velocity); var message = new CharacterTransformMessage(identity.NetworkId, targetTransform.position, targetTransform.rotation, velocity); byte[] payload = CharacterTransformMessage.Serialize(message); networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay); } private void RegisterHandlers() { if (isRegistered || networkManager == null) { return; } networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleTransformMessage); isRegistered = true; } private void UnregisterHandlers() { if (!isRegistered || networkManager == null) { return; } networkManager.UnregisterHandler(NetworkMessageType.CharacterTransform, HandleTransformMessage); isRegistered = false; } private void HandleTransformMessage(NetworkMessage message) { if (identity == null || targetTransform == null) { return; } if (HasAuthority()) { return; } CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload); if (transformMessage.NetworkId != identity.NetworkId) { return; } remoteTargetPosition = transformMessage.Position; remoteTargetRotation = transformMessage.Rotation; remoteTargetVelocity = transformMessage.Velocity; haveRemoteState = true; } private void OnValidate() { broadcastInterval = Mathf.Max(0.01f, broadcastInterval); remoteLerpSpeed = Mathf.Max(0f, remoteLerpSpeed); teleportThreshold = Mathf.Max(0.01f, teleportThreshold); } } }