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; 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) { 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; } bool isHost = networkManager != null && networkManager.IsHost; 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 = isHost; // Host drives authoritative simulation. } private void ConfigureController() { if (characterController == null) { return; } if (isAuthority) { characterController.enabled = true; characterController.SetInputSource(isLocalPlayer ? null : networkInputProxy); } else { characterController.enabled = false; } } private void BroadcastTransform() { if (identity == null) { return; } var unityController = GetComponent(); Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero; 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 (transformMessage.NetworkId != identity.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) { return; } var message = new PlayerInputMessage(identity.NetworkId, moveInput, jump, Vector2.zero); byte[] payload = PlayerInputMessage.Serialize(message); if (!networkManager.IsConnected || networkManager.IsHost) { // If we are host, feed directly. HandlePlayerInputMessage(new NetworkMessage(NetworkMessageType.PlayerInput, payload, SteamUser.GetSteamID().m_SteamID)); } else { CSteamID lobby = networkManager.ActiveLobby; CSteamID lobbyOwner = lobby != CSteamID.Nil ? SteamMatchmaking.GetLobbyOwner(lobby) : CSteamID.Nil; if (lobbyOwner == CSteamID.Nil) { lobbyOwner = SteamUser.GetSteamID(); } networkManager.SendToPlayer(lobbyOwner, NetworkMessageType.PlayerInput, payload, EP2PSend.k_EP2PSendUnreliableNoDelay); } } } }