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, ILookSource { // --- ILookSource Implementation (Dummy values for Proxy characters) --- public GameObject GameObject => gameObject; public Transform Transform => transform; public float LookDirectionDistance => 100f; public float Pitch => 0f; public Vector3 LookPosition() => transform.position + Vector3.up * 1.5f; // Rough head height public Vector3 LookDirection(bool characterLookDirection) => transform.forward; public Vector3 LookDirection(Vector3 lookPosition, bool characterLookDirection, int layerMask, bool useRecoil) => transform.forward; private void Awake() { // Temporarily disable the Locomotion component so it unregisters from KinematicObjectManager. // This prevents the frame-0 crash before we can safely attach the Look Source in Start(). var loco = GetComponent(); if (loco != null) loco.enabled = false; // MUST also disable the handler, otherwise it tries to update a disabled character (Index -1) var handler = GetComponent(); if (handler != null) handler.enabled = false; } private void Start() { // Now that all Opsive components have finished Awake() and are fully initialized, // we can safely attach the Look Source without causing NullReferenceExceptions in CharacterIK. Opsive.Shared.Events.EventHandler.ExecuteEvent(gameObject, "OnCharacterAttachLookSource", this); // Re-enable Locomotion. It will run OnEnable() and safely register back to KinematicObjectManager. var loco = GetComponent(); if (loco != null) loco.enabled = true; // If Fusion has already spawned us, restore the handler for the local player if (Object != null && Object.IsValid) { var handler = GetComponent(); if (handler != null) handler.enabled = Object.HasInputAuthority; } } 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 activeInput = GetComponent(); if (activeInput != null) activeInput.enabled = isLocal; // Safely enable the handler only if Locomotion is already enabled (to prevent index -1 crash) var loco = GetComponent(); var handler = GetComponent(); if (handler != null && loco != null && loco.enabled) { handler.enabled = isLocal; } if (isLocal) { var cameraController = UnityEngine.Object.FindFirstObjectByType(); if (cameraController != null) { cameraController.enabled = true; // This will override our dummy LookSource with the actual Camera cameraController.Character = gameObject; } } } // --- 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) { } } }