namespace Fusion.Addons.KCC { using System; using System.Collections.Generic; using UnityEngine; // This file contains implementation related to interactions - modifiers, collisions and processors. public partial class KCC { // PUBLIC METHODS /// /// Check if the KCC has a collision with interaction provider of type T in KCCData.Collisions. /// public bool HasCollision() where T : class { return Data.Collisions.HasProvider() == true; } /// /// Check if the KCC has a collision with interaction provider in KCCData.Collisions. /// /// IKCCInteractionProvider instance. public bool HasCollision(T provider) where T : Component, IKCCInteractionProvider { if (provider == null) return false; return Data.Collisions.HasProvider(provider) == true; } /// /// Explicitly remove a collision (interaction provider) from KCCData.Collisions. Removed processor won't execute any pending stage method. /// Changes done in render will vanish with next fixed update. /// /// IKCCInteractionProvider instance. /// Ignore result from IKCCInteractionProvider.CanStopInteraction() and force remove the collision. Be careful with this option. public bool RemoveCollision(T provider, bool forceRemove = false) where T : Component, IKCCInteractionProvider { if (provider == null) return false; KCCData data = Data; KCCCollision collision = data.Collisions.Find(provider); if (collision == null) return false; return RemoveCollision(data, collision, forceRemove); } /// /// Check if the KCC has registered custom modifier (interaction provider) of type T in KCCData.Modifiers. /// public bool HasModifier() where T : class { return Data.Modifiers.HasProvider() == true; } /// /// Check if the KCC has registered custom modifier (interaction provider) in KCCData.Modifiers. /// /// IKCCInteractionProvider instance. public bool HasModifier(T provider) where T : Component, IKCCInteractionProvider { if (provider == null) return false; return Data.Modifiers.HasProvider(provider) == true; } /// /// Returns any registered custom modifier (interaction provider) of type T from KCCData.Modifiers. /// public T GetModifier() where T : class { return Data.Modifiers.GetProvider(); } /// /// Returns all registered custom modifiers (interaction providers) of type T from KCCData.Modifiers. /// /// List filled with interaction providers of type T. public void GetModifiers(List providers) where T : class { Data.Modifiers.GetProviders(providers, true); } /// /// Returns all registered custom modifiers (interaction providers) of type T from KCCData.Modifiers. /// public List GetModifiers() where T : class { List providers = new List(); GetModifiers(providers); return providers; } /// /// Register custom modifier (interaction provider) to KCCData.Modifiers. /// Changes done in render will vanish with next fixed update. /// /// IKCCInteractionProvider instance. /// Ignore result from IKCCInteractionProvider.CanStartInteraction() and force add the modifier. Be careful with this option. public bool AddModifier(T provider, bool forceAdd = false) where T : Component, IKCCInteractionProvider { if (provider == null) return false; if (CheckSpawned() == false) return false; KCCData data = Data; if (data.Modifiers.HasProvider(provider) == true) return false; NetworkObject networkObject = provider.GetComponentNoAlloc(); if (networkObject == null) { LogError($"Interaction provider {provider.name} doesn't have {nameof(NetworkObject)} component! Ignoring.", provider.gameObject); return false; } IKCCInteractionProvider checkProvider = provider.gameObject.GetComponentNoAlloc(); if (object.ReferenceEquals(checkProvider, provider) == false) { LogError($"Object {provider.name} has multiple {nameof(IKCCInteractionProvider)} components, this is not allowed for custom modifiers! Ignoring.", provider.gameObject); return false; } if (forceAdd == false && provider.CanStartInteraction(this, data) == false) return false; KCCModifier modifier = data.Modifiers.Add(Runner, networkObject, provider); if (modifier.Processor != null) { OnProcessorAdded(data, modifier.Processor); } return true; } /// /// Try to register custom modifier (interaction provider) to KCCData.Modifiers. /// Changes done in render will vanish with next fixed update. /// /// IKCCInteractionProvider instance. /// Ignore result from IKCCInteractionProvider.CanStartInteraction() and force add the modifier. Be careful with this option. public bool TryAddModifier(IKCCInteractionProvider provider, bool forceAdd = false) { if (provider == null) return false; if (CheckSpawned() == false) return false; Component providerComponent = provider as Component; if (object.ReferenceEquals(providerComponent, null) == true) return false; KCCData data = Data; if (data.Modifiers.HasProvider(provider) == true) return false; NetworkObject networkObject = providerComponent.GetComponentNoAlloc(); if (networkObject == null) { LogError($"Interaction provider {providerComponent.name} doesn't have {nameof(NetworkObject)} component! Ignoring.", providerComponent.gameObject); return false; } IKCCInteractionProvider checkProvider = providerComponent.gameObject.GetComponentNoAlloc(); if (object.ReferenceEquals(checkProvider, provider) == false) { LogError($"Object {providerComponent.name} has multiple {nameof(IKCCInteractionProvider)} components, this is not allowed for custom modifiers! Ignoring.", providerComponent.gameObject); return false; } if (forceAdd == false && provider.CanStartInteraction(this, data) == false) return false; KCCModifier modifier = data.Modifiers.Add(Runner, networkObject, provider); if (modifier.Processor != null) { OnProcessorAdded(data, modifier.Processor); } return true; } /// /// Try to register custom modifier (interaction provider) to KCCData.Modifiers if the network object has any. /// Changes done in render will vanish with next fixed update. /// /// NetworkObject of the modifier. /// Ignore result from IKCCInteractionProvider.CanStartInteraction() and force add the modifier. Be careful with this option. public bool TryAddModifier(NetworkObject networkObject, bool forceAdd = false) { if (networkObject == null) return false; if (CheckSpawned() == false) return false; IKCCInteractionProvider provider = networkObject.GetComponentNoAlloc(); if (provider == null) return false; KCCData data = Data; if (data.Modifiers.HasProvider(provider) == true) return false; if (forceAdd == false && provider.CanStartInteraction(this, data) == false) return false; KCCModifier modifier = data.Modifiers.Add(Runner, networkObject, provider); if (modifier.Processor != null) { OnProcessorAdded(data, modifier.Processor); } return true; } /// /// Unregister custom modifier (interaction provider) from KCCData.Modifiers. Removed processor won't execute any pending stage method. /// Changes done in render will vanish with next fixed update. /// /// IKCCInteractionProvider instance. /// Ignore result from IKCCInteractionProvider.CanStopInteraction() and force remove the modifier. Be careful with this option. public bool RemoveModifier(T provider, bool forceRemove = false) where T : Component, IKCCInteractionProvider { if (provider == null) return false; KCCData data = Data; KCCModifier modifier = data.Modifiers.Find(provider); if (modifier == null) return false; return RemoveModifier(data, modifier, forceRemove); } /// /// Check if the KCC has registered any interaction provider of type T. /// public bool HasInteraction() where T : class { KCCData data = Data; if (data.Modifiers.HasProvider() == true) return true; if (data.Collisions.HasProvider() == true) return true; return false; } /// /// Check if the KCC has registered interaction provider. /// /// IKCCInteractionProvider instance. public bool HasInteraction(T provider) where T : Component, IKCCInteractionProvider { if (provider == null) return false; KCCData data = Data; if (data.Modifiers.HasProvider(provider) == true) return true; if (data.Collisions.HasProvider(provider) == true) return true; return false; } /// /// Returns any registered interaction provider of type T. /// public T GetInteraction() where T : class { T provider; KCCData data = Data; provider = data.Modifiers.GetProvider(); if (object.ReferenceEquals(provider, null) == false) return provider; provider = data.Collisions.GetProvider(); if (object.ReferenceEquals(provider, null) == false) return provider; return null; } /// /// Returns all registered interaction providers of type T. /// /// List filled with interaction providers of type T. public void GetInteractions(List providers) where T : class { providers.Clear(); KCCData data = Data; data.Modifiers.GetProviders(providers, false); data.Collisions.GetProviders(providers, false); } /// /// Returns all registered interaction providers of type T. /// public List GetInteractions() where T : class { List providers = new List(); GetInteractions(providers); return providers; } /// /// Unregister custom modifier (interaction provider) from KCCData.Modifiers or remove a collision from KCCData.Collisions. Removed processor won't execute any pending stage method. /// Changes done in render will vanish with next fixed update. /// /// Interaction provider instance. /// Ignore result from IKCCInteractionProvider.CanStopInteraction() and force remove the interaction. Be careful with this option. public bool RemoveInteraction(T provider, bool forceRemove = false) where T : Component, IKCCInteractionProvider { if (provider == null) return false; bool removed = false; removed |= RemoveModifier(provider, forceRemove); removed |= RemoveCollision(provider, forceRemove); return removed; } /// /// Check if the KCC interacts with any processor of type T. /// This method looks in modifiers, collisions, local and external processors. /// public bool HasProcessor() where T : class { KCCData data = Data; if (data.Modifiers.HasProcessor() == true) return true; if (data.Collisions.HasProcessor() == true) return true; List localProcessors = _localProcessors; for (int i = 0, count = localProcessors.Count; i < count; ++i) { if (localProcessors[i] is T) return true; } if (GetExternalProcessors != null) { try { IList externalProcessors = GetExternalProcessors(); if (externalProcessors != null && externalProcessors.Count > 0) { for (int i = 0, count = externalProcessors.Count; i < count; ++i) { if (externalProcessors[i] is T) return true; } } } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } return false; } /// /// Check if the KCC interacts with a given processor. /// This method looks in modifiers, collisions, local and external processors. /// /// IKCCProcessor instance. public bool HasProcessor(T processor) where T : Component, IKCCProcessor { if (processor == null) return false; KCCData data = Data; if (data.Modifiers.HasProcessor(processor) == true) return true; if (data.Collisions.HasProcessor(processor) == true) return true; List localProcessors = _localProcessors; for (int i = 0, count = localProcessors.Count; i < count; ++i) { if (object.ReferenceEquals(localProcessors[i], processor) == true) return true; } if (GetExternalProcessors != null) { try { IList externalProcessors = GetExternalProcessors(); if (externalProcessors != null && externalProcessors.Count > 0) { for (int i = 0, count = externalProcessors.Count; i < count; ++i) { if (object.ReferenceEquals(externalProcessors[i], processor) == true) return true; } } } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } return false; } /// /// Returns any interacting processor of type T. /// This method looks in modifiers, collisions, local and external processors. /// public T GetProcessor() where T : class { T processor; KCCData data = Data; processor = data.Modifiers.GetProcessor(); if (object.ReferenceEquals(processor, null) == false) return processor; processor = data.Collisions.GetProcessor(); if (object.ReferenceEquals(processor, null) == false) return processor; List localProcessors = _localProcessors; for (int i = 0, count = localProcessors.Count; i < count; ++i) { if (localProcessors[i] is T localProcessor) return localProcessor; } if (GetExternalProcessors != null) { try { IList externalProcessors = GetExternalProcessors(); if (externalProcessors != null && externalProcessors.Count > 0) { for (int i = 0, count = externalProcessors.Count; i < count; ++i) { if (externalProcessors[i] is T externalProcessor) return externalProcessor; } } } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } return null; } /// /// Returns all interacting processors of type T. /// This method looks in modifiers, collisions, local and external processors. /// /// List filled with processors of type T. /// Processors are sorted by processor priority. For stage sorting use KCCUtility.SortStages(). public void GetProcessors(List processors, bool sortByProcessorPriority = false) where T : class { processors.Clear(); KCCData data = Data; data.Modifiers.GetProcessors(processors, false); data.Collisions.GetProcessors(processors, false); List localProcessors = _localProcessors; for (int i = 0, count = localProcessors.Count; i < count; ++i) { if (localProcessors[i] is T localProcessor) { processors.Add(localProcessor); } } if (GetExternalProcessors != null) { try { IList externalProcessors = GetExternalProcessors(); if (externalProcessors != null && externalProcessors.Count > 0) { for (int i = 0, count = externalProcessors.Count; i < count; ++i) { IKCCProcessor processor = externalProcessors[i]; if (processor is T externalProcessor) { processors.Add(externalProcessor); } } } } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } if (sortByProcessorPriority == true) { KCCUtility.SortProcessors(this, processors); } } /// /// Returns all interacting processors of type T. /// This method looks in modifiers, collisions, local and external processors. /// /// Processors are sorted by processor priority. For stage sorting use KCCUtility.SortStages(). public List GetProcessors(bool sortByProcessorPriority = false) where T : class { List processors = new List(); GetProcessors(processors, sortByProcessorPriority); return processors; } /// /// Register local processor to LocalProcessors list. Local processors are NOT networked, be careful! /// Note: KCCSettings.Processors are added as local processors upon initialization. /// /// IKCCProcessor instance. public bool AddLocalProcessor(IKCCProcessor processor) { if (processor == null) throw new ArgumentNullException(nameof(processor)); if (CheckSpawned() == false) return false; if (_localProcessors.Contains(processor) == true) return false; _localProcessors.Add(processor); try { processor.OnEnter(this, Data); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } return true; } /// /// Unregister local processor from LocalProcessors list. Local processors are NOT networked, be careful! /// /// IKCCProcessor instance. public bool RemoveLocalProcessor(IKCCProcessor processor) { if (processor == null) return false; if (_localProcessors.Remove(processor) == false) return false; try { processor.OnExit(this, Data); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } return true; } /// /// Add/remove networked collider to/from custom ignore list. The object must have NetworkObject component to correctly synchronize over network. /// Changes done in render will vanish with next fixed update. /// /// True if there is a change in the ignore list. public bool SetIgnoreCollider(Collider ignoreCollider, bool ignore) { if (ignoreCollider == null) return false; if (CheckSpawned() == false) return false; KCCData data = Data; if (ignore == true) { if (data.Ignores.HasCollider(ignoreCollider) == true) return false; NetworkObject networkObject = ignoreCollider.GetComponentNoAlloc(); if (networkObject == null) { LogError($"Collider {ignoreCollider.name} doesn't have {nameof(NetworkObject)} component! Ignoring.", ignoreCollider.gameObject); return false; } Collider checkCollider = ignoreCollider.gameObject.GetComponentNoAlloc(); if (object.ReferenceEquals(checkCollider, ignoreCollider) == false) { LogError($"Object {ignoreCollider.name} has multiple {nameof(Collider)} components, this is not allowed for ignored colliders! Ignoring.", ignoreCollider.gameObject); return false; } data.Ignores.Add(Runner, networkObject, ignoreCollider, false); } else { if (data.Ignores.Remove(ignoreCollider) == false) return false; } return true; } // PRIVATE METHODS private void UpdateCollisions(KCCData data, KCCOverlapInfo trackOverlapInfo) { int addCollisionsCount = 0; int removeCollisionsCount = 0; List collisions = data.Collisions.All; for (int i = 0, count = collisions.Count; i < count; ++i) { KCCCollision collision = collisions[i]; _removeColliders[removeCollisionsCount] = collision.Collider; _removeCollisions[removeCollisionsCount] = collision; ++removeCollisionsCount; } KCCOverlapHit[] trackHits = trackOverlapInfo.AllHits; for (int i = 0, count = trackOverlapInfo.AllHitCount; i < count; ++i) { Collider trackCollider = trackHits[i].Collider; bool trackColliderFound = false; for (int j = 0; j < removeCollisionsCount; ++j) { if (object.ReferenceEquals(_removeColliders[j], trackCollider) == true) { trackColliderFound = true; --removeCollisionsCount; _removeColliders[j] = _removeColliders[removeCollisionsCount]; _removeCollisions[j] = _removeCollisions[removeCollisionsCount]; break; } } if (trackColliderFound == false) { _addColliders[addCollisionsCount] = trackCollider; ++addCollisionsCount; } } for (int i = 0; i < removeCollisionsCount; ++i) { RemoveCollision(data, _removeCollisions[i], false); } for (int i = 0; i < addCollisionsCount; ++i) { AddCollision(data, _addColliders[i]); } } private bool AddCollision(KCCData data, Collider collisionCollider) { if (ReferenceEquals(collisionCollider, _lastNonNetworkedCollider) == true) return false; GameObject collisionObject = collisionCollider.gameObject; NetworkObject networkObject = collisionObject.GetComponentNoAlloc(); if (networkObject == null) { _lastNonNetworkedCollider = collisionCollider; return false; } IKCCInteractionProvider interactionProvider = collisionObject.GetComponentNoAlloc(); if (interactionProvider != null && interactionProvider.CanStartInteraction(this, data) == false) return false; KCCCollision collision = data.Collisions.Add(Runner, networkObject, interactionProvider, collisionCollider); if (collision.Processor != null) { OnProcessorAdded(data, collision.Processor); } if (OnCollisionEnter != null) { try { OnCollisionEnter(this, collision); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } return true; } private bool RemoveCollision(KCCData data, KCCCollision collision, bool forceRemove) { if (forceRemove == false) { IKCCInteractionProvider interactionProvider = collision.Provider; if (interactionProvider != null) { try { if (interactionProvider.CanStopInteraction(this, data) == false) return false; } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } } if (OnCollisionExit != null) { try { OnCollisionExit(this, collision); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } if (collision.Processor != null) { OnProcessorRemoved(data, collision.Processor); } data.Collisions.Remove(collision); return true; } private void ForceRemoveAllCollisions(KCCData data) { List collisions = data.Collisions.All; while (collisions.Count > 0) { RemoveCollision(data, collisions[collisions.Count - 1], true); } } private bool RemoveModifier(KCCData data, KCCModifier modifier, bool forceRemove) { if (forceRemove == false) { IKCCInteractionProvider interactionProvider = modifier.Provider; if (interactionProvider != null) { try { if (interactionProvider.CanStopInteraction(this, data) == false) return false; } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } } IKCCProcessor processor = modifier.Processor; if (data.Modifiers.Remove(modifier) == true) { if (processor != null) { OnProcessorRemoved(data, processor); } } return true; } private void ForceRemoveAllModifiers(KCCData data) { List modifiers = data.Modifiers.All; while (modifiers.Count > 0) { RemoveModifier(data, modifiers[modifiers.Count - 1], true); } } private void OnProcessorAdded(KCCData data, IKCCProcessor processor) { try { processor.OnEnter(this, data); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } private void OnProcessorRemoved(KCCData data, IKCCProcessor processor) { for (int j = 0, stageCount = _activeStages.Count; j < stageCount; ++j) { KCCStageInfo activeStageInfo = _activeStages[j]; IKCCProcessor[] activeStageProcessors = activeStageInfo.Processors; for (int i = 0, count = activeStageInfo.ProcessorCount; i < count; ++i) { if (ReferenceEquals(activeStageProcessors[i], processor) == true) { activeStageProcessors[i] = null; break; } } } if (_cachedProcessorCount > 0) { IKCCProcessor[] cachedProcessors = _cachedProcessors; for (int i = 0, count = _cachedProcessorCount; i < count; ++i) { if (ReferenceEquals(cachedProcessors[i], processor) == true) { cachedProcessors[i] = null; break; } } } if (_stageProcessorCount > 0) { IKCCProcessor[] stageProcessors = _stageProcessors; for (int i = 0, count = _stageProcessorCount; i < count; ++i) { if (ReferenceEquals(stageProcessors[i], processor) == true) { stageProcessors[i] = null; break; } } } try { processor.OnExit(this, data); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } private void CacheProcessors(KCCData data) { _cachedProcessorCount = 0; List modifiers = data.Modifiers.All; for (int i = 0, count = modifiers.Count; i < count; ++i) { KCCUtility.AddUniqueProcessor(this, modifiers[i].Processor, _cachedProcessors, ref _cachedProcessorCount); } List collisions = data.Collisions.All; for (int i = 0, count = collisions.Count; i < count; ++i) { KCCUtility.AddUniqueProcessor(this, collisions[i].Processor, _cachedProcessors, ref _cachedProcessorCount); } List localProcessors = _localProcessors; for (int i = 0, count = localProcessors.Count; i < count; ++i) { KCCUtility.AddUniqueProcessor(this, localProcessors[i], _cachedProcessors, ref _cachedProcessorCount); } if (GetExternalProcessors != null) { try { IList externalProcessors = GetExternalProcessors(); if (externalProcessors != null && externalProcessors.Count > 0) { for (int i = 0, count = externalProcessors.Count; i < count; ++i) { KCCUtility.AddUniqueProcessor(this, externalProcessors[i], _cachedProcessors, ref _cachedProcessorCount); } } } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } KCCUtility.SortProcessors(this, _cachedProcessors, _cachedProcessorCount); _stageProcessorCount = _cachedProcessorCount; if (_stageProcessorCount > 0) { Array.Copy(_cachedProcessors, _stageProcessors, _stageProcessorCount); } } } }