using UnityEngine; using Fusion; using Opsive.UltimateCharacterController.Networking; using Opsive.UltimateCharacterController.Networking.Character; using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Camera; // Corrected namespace using Opsive.UltimateCharacterController.Input; // Corrected namespace namespace Hallucinate.Network { // Ensure Opsive components load before this [DefaultExecutionOrder(100)] public class FusionClientMovementBridge : NetworkBehaviour, INetworkInfo, INetworkCharacter { public override void Spawned() { // We use InputAuthority to identify the local player because this is Host mode. bool isLocal = Object.HasInputAuthority; // 1. Isolate Input: Only the local player should read keyboard/mouse inputs. var locoHandler = GetComponent(); if (locoHandler != null) locoHandler.enabled = isLocal; var activeInput = GetComponent(); // Corrected class reference if (activeInput != null) activeInput.enabled = isLocal; // 2. Isolate Camera: Only attach the camera if this is the local player. if (isLocal) { var cameraController = UnityEngine.Object.FindFirstObjectByType(); if (cameraController != null) { cameraController.Character = gameObject; } // CRITICAL: Disable NetworkTransform on the local client so Fusion doesn't pull the character back! var nt = GetComponent(); if (nt != null) nt.enabled = false; } } // --- INetworkInfo Implementation --- // We use InputAuthority to identify the local player in Host/Client mode public bool IsLocalPlayer() => Object.HasInputAuthority; public bool IsServer() => Runner.IsServer; public bool IsServerAuthoritative() => false; // --- Client-Auth Movement Sync via RPC --- // Since the Server holds State Authority, the client must send its transform to the server. // The server then broadcasts it to everyone else via NetworkTransform. public override void FixedUpdateNetwork() { if (Object.HasInputAuthority) { RPC_UpdateTransform(transform.position, transform.rotation); } } public override void Render() { // If this is a remote proxy, Fusion's NetworkTransform will update the Unity transform. // But Opsive maintains its own internal position and will snap it back unless we tell it to update! if (!Object.HasInputAuthority) { var loco = GetComponent(); if (loco != null) { // Feed the NetworkTransform's current position to Opsive so it knows we moved, // which also allows the Animator to play the correct movement animations! loco.SetPositionAndRotation(transform.position, transform.rotation, false); } } } [Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)] private void RPC_UpdateTransform(Vector3 pos, Quaternion rot) { // The Server receives this and applies it. NetworkTransform then syncs it to all other clients. transform.position = pos; transform.rotation = rot; } public void SetPosition(Vector3 position, bool snapAnimator) { if (Object.HasInputAuthority) transform.position = position; } public void SetRotation(Quaternion rotation, bool snapAnimator) { if (Object.HasInputAuthority) transform.rotation = rotation; } public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool snapAnimator) { if (Object.HasInputAuthority) transform.SetPositionAndRotation(position, rotation); } public void ResetRotationPosition() { } public void SetActive(bool active, bool uiEvent) { } // Optional interface stubs for weapons/items (leave empty for now to focus on movement) public void LoadDefaultLoadout() { } public void EquipUnequipItem(uint itemID, int slotID, bool equip) { } public void ItemIdentifierPickup(uint id, int amount, int slot, bool immediate, bool force) { } public void RemoveAllItems() { } // New missing interface stubs for Shooter/Melee compiled code public void Fire(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, float strength) { } public void StartItemReload(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction) { } public void ReloadItem(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, bool fullClip) { } public void ItemReloadComplete(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, bool success, bool immediateReload) { } public void MeleeHitCollider(Opsive.UltimateCharacterController.Items.Actions.ItemAction itemAction, int hitboxIndex, RaycastHit raycastHit, GameObject hitGameObject, UltimateCharacterLocomotion hitCharacterLocomotion) { } public void ThrowItem(Opsive.UltimateCharacterController.Items.Actions.ItemAction action) { } public void EnableThrowableObjectMeshRenderers(Opsive.UltimateCharacterController.Items.Actions.ItemAction action) { } public void StartStopBeginEndMagicActions(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, bool begin, bool start) { } public void MagicCast(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, int index, uint castID, Vector3 dir, Vector3 target) { } public void MagicImpact(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, uint castID, GameObject source, GameObject target, Vector3 pos, Vector3 norm) { } public void StopMagicCast(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, int index, uint castID) { } public void ToggleFlashlight(Opsive.UltimateCharacterController.Items.Actions.ItemAction action, bool active) { } public void PushRigidbody(Rigidbody rb, Vector3 force, Vector3 point) { } } }