using Steamworks; using UnityEngine; namespace MegaKoop.Game.Networking { [DisallowMultipleComponent] public class SteamCharacterNetworkBridge : MonoBehaviour { [Header("References")] [SerializeField] private ThirdPersonCharacterController characterController; [SerializeField] private NetworkIdentity identity; [SerializeField] private Transform rootTransform; [SerializeField] private NetworkCharacterInputProxy networkInputProxy; [Header("Settings")] [SerializeField] private float transformBroadcastInterval = 0.05f; [SerializeField] private float remoteLerpSpeed = 12f; [SerializeField] private ulong ownerSteamId; [SerializeField] private bool autoAssignOwnerToLocalPlayer = true; private SteamCoopNetworkManager networkManager; private float broadcastTimer; private bool isRegistered; private bool isLocalPlayer; private bool isAuthority; private Vector3 remoteTargetPosition; private Quaternion remoteTargetRotation; private Vector3 remoteTargetVelocity; private bool haveRemoteState; private bool localOverrideSet; public void AssignOwner(ulong steamId, bool localPlayer) { ownerSteamId = steamId; isLocalPlayer = localPlayer; localOverrideSet = true; UpdateAuthority(); ConfigureController(); } public bool IsLocalPlayer => isLocalPlayer; public bool IsAuthority => isAuthority; public ulong OwnerSteamId => ownerSteamId; private void Awake() { if (characterController == null) { characterController = GetComponent(); } if (identity == null) { identity = GetComponent(); } if (rootTransform == null) { rootTransform = transform; } if (networkInputProxy == null) { networkInputProxy = GetComponent(); if (networkInputProxy == null) { networkInputProxy = gameObject.AddComponent(); networkInputProxy.hideFlags = HideFlags.HideInInspector; } } remoteTargetPosition = rootTransform.position; remoteTargetRotation = rootTransform.rotation; } private void Start() { networkManager = SteamCoopNetworkManager.Instance; UpdateAuthority(); ConfigureController(); } private void OnEnable() { networkManager = SteamCoopNetworkManager.Instance; RegisterHandlers(); } private void OnDisable() { UnregisterHandlers(); } private void Update() { if (networkManager == null) { networkManager = SteamCoopNetworkManager.Instance; if (networkManager != null) { RegisterHandlers(); } } if (networkManager == null) { return; } if (isAuthority) { broadcastTimer -= Time.deltaTime; if (broadcastTimer <= 0f) { BroadcastTransform(); broadcastTimer = transformBroadcastInterval; } } else { if (haveRemoteState) { rootTransform.position = Vector3.Lerp(rootTransform.position, remoteTargetPosition, remoteLerpSpeed * Time.deltaTime); rootTransform.rotation = Quaternion.Slerp(rootTransform.rotation, remoteTargetRotation, remoteLerpSpeed * Time.deltaTime); } } } private void RegisterHandlers() { if (networkManager == null || isRegistered) { return; } networkManager.RegisterHandler(NetworkMessageType.PlayerInput, HandlePlayerInputMessage); networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); isRegistered = true; } private void UnregisterHandlers() { if (networkManager == null || !isRegistered) { return; } networkManager.UnregisterHandler(NetworkMessageType.PlayerInput, HandlePlayerInputMessage); networkManager.UnregisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); isRegistered = false; } private void UpdateAuthority() { if (networkManager == null) { networkManager = SteamCoopNetworkManager.Instance; } ulong localSteamId = SteamBootstrap.IsInitialized ? SteamUser.GetSteamID().m_SteamID : 0UL; if (!localOverrideSet && autoAssignOwnerToLocalPlayer && ownerSteamId == 0 && localSteamId != 0) { ownerSteamId = localSteamId; } if (!localOverrideSet) { isLocalPlayer = ownerSteamId != 0 && ownerSteamId == localSteamId; } isAuthority = isLocalPlayer; // Each player has authority over their own character. } private void ConfigureController() { if (characterController == null) { return; } if (isAuthority) { characterController.enabled = true; characterController.SetInputSource(isLocalPlayer ? null : networkInputProxy); var unityController = characterController.GetComponent(); if (unityController != null) { unityController.enabled = true; } } else { characterController.enabled = false; characterController.SetInputSource(null); var unityController = characterController.GetComponent(); if (unityController != null) { unityController.enabled = false; } } } private void BroadcastTransform() { if (identity == null) { return; } var unityController = GetComponent(); Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero; SteamCharacterStateCache.ReportLocalState(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity); var message = new CharacterTransformMessage(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity); byte[] payload = CharacterTransformMessage.Serialize(message); networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay); } private void HandlePlayerInputMessage(NetworkMessage message) { if (isAuthority) { return; } PlayerInputMessage inputMessage = PlayerInputMessage.Deserialize(message.Payload); if (inputMessage.NetworkId != identity.NetworkId) { return; } networkInputProxy.SetInput(inputMessage.MoveInput, inputMessage.JumpPressed); } private void HandleCharacterTransformMessage(NetworkMessage message) { if (isAuthority) { return; } CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload); if (identity != null && transformMessage.NetworkId != identity.NetworkId) { if (ownerSteamId != 0 && message.Sender != ownerSteamId) { return; } var existing = NetworkIdRegistry.GetById(transformMessage.NetworkId); if (existing != null && existing != identity) { return; } identity.SetNetworkId(transformMessage.NetworkId); if (identity.NetworkId != transformMessage.NetworkId) { return; } } remoteTargetPosition = transformMessage.Position; remoteTargetRotation = transformMessage.Rotation; remoteTargetVelocity = transformMessage.Velocity; haveRemoteState = true; } public void SendLocalInput(Vector2 moveInput, bool jump) { if (networkManager == null || identity == null || !isAuthority) { return; } if (!networkManager.IsConnected) { return; } networkManager.SendToAll(NetworkMessageType.PlayerInput, PlayerInputMessage.Serialize(new PlayerInputMessage(identity.NetworkId, moveInput, jump)), EP2PSend.k_EP2PSendUnreliableNoDelay); } } }