638 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			638 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|   | using System.Collections; | ||
|  | using System.Collections.Generic; | ||
|  | using UnityEngine; | ||
|  | using RPGCreationKit; | ||
|  | using RPGCreationKit.BehaviourTree; | ||
|  | using System.Linq; | ||
|  | 
 | ||
|  | namespace RPGCreationKit.AI | ||
|  | { | ||
|  |     /// <summary> | ||
|  |     /// Manages the Behaviour Trees and the interactions with the AI agent from a tree. | ||
|  |     /// </summary> | ||
|  |     public class AIBehaviourTreed : AICombatSystem | ||
|  |     { | ||
|  |         public TreeTickRate tickRate; | ||
|  |         public int xFrames = 5; | ||
|  |         public float xSeconds = 1f; | ||
|  | 
 | ||
|  |         public PurposeState purposeState; | ||
|  | 
 | ||
|  |         [Space(10)] | ||
|  |         [Header("Behaviour Tree Control")] | ||
|  |         public bool useBT = true; | ||
|  |         public bool pauseBT = false; | ||
|  |         public RPGCK_BT currentBehaviour; | ||
|  |         public bool isUsingPurposeBehaviour = true; | ||
|  | 
 | ||
|  |         public RPGCK_BT purposeBehaviourTree; | ||
|  |         public RPGCK_BT combatBehaviourTree; | ||
|  | 
 | ||
|  |         [Header("Settings")] | ||
|  |         public bool keepTicking = true; | ||
|  | 
 | ||
|  |         public override void Start() | ||
|  |         { | ||
|  |             base.Start(); | ||
|  | 
 | ||
|  |             if (!isAlive) | ||
|  |                 Die(); | ||
|  |             else | ||
|  |                 if (useBT) | ||
|  |                 { | ||
|  |                     purposeBehaviourTree = purposeBehaviourTree.RPGCK_BTCopy(this.gameObject, GetComponent<RckAI>()); | ||
|  |                     combatBehaviourTree = combatBehaviourTree.RPGCK_BTCopy(this.gameObject, GetComponent<RckAI>()); | ||
|  | 
 | ||
|  |                     // Always start with the NormalBehaviourTree | ||
|  |                     SwitchBehaviourTree(false); | ||
|  | 
 | ||
|  |                     StartTicking(); | ||
|  |                 } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void StartTicking() | ||
|  |         { | ||
|  |             keepTicking = true; | ||
|  | 
 | ||
|  |             StopCoroutine(TickBehaviourTree()); | ||
|  |             StartCoroutine(TickBehaviourTree()); | ||
|  |         } | ||
|  | 
 | ||
|  |         IEnumerator TickBehaviourTree() | ||
|  |         { | ||
|  |             while (keepTicking) | ||
|  |             { | ||
|  |                 while (pauseBT) | ||
|  |                     yield return null; | ||
|  | 
 | ||
|  |                 switch(tickRate) | ||
|  |                 { | ||
|  |                     case TreeTickRate.EveryFrame: | ||
|  |                         yield return new WaitForEndOfFrame(); | ||
|  |                         break; | ||
|  | 
 | ||
|  |                     case TreeTickRate.EveryXFrames: | ||
|  |                         int framesPassed = 0; | ||
|  | 
 | ||
|  |                         while(framesPassed <= xFrames) | ||
|  |                         { | ||
|  |                             yield return new WaitForEndOfFrame(); | ||
|  |                             framesPassed++; | ||
|  |                         } | ||
|  |                         break; | ||
|  | 
 | ||
|  |                     case TreeTickRate.EveryXSeconds_Realtime: | ||
|  |                         yield return new WaitForSecondsRealtime(xSeconds); | ||
|  |                         break; | ||
|  | 
 | ||
|  |                     case TreeTickRate.EveryXSeconds_GameTime: | ||
|  |                         yield return new WaitForSeconds(xSeconds); | ||
|  |                         break; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 while (pauseBT || currentBehaviour == null || currentBehaviour.nodes.Count <= 0 || currentBehaviour.nodes[0] == null) | ||
|  |                     yield return null; | ||
|  | 
 | ||
|  |                 try | ||
|  |                 { | ||
|  |                     (currentBehaviour.nodes[0] as BTNode).Execute(); | ||
|  |                 }catch { } | ||
|  |                  | ||
|  |                 /* | ||
|  |                 if (firstTick == false) | ||
|  |                 { | ||
|  | 
 | ||
|  |                     // Tick | ||
|  |                     (currentBehaviour.nodes[0] as BTNode).Execute(); | ||
|  |                     firstTick = true; | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     // Find all the nodes that are still running and execute them | ||
|  |                     ((currentBehaviour.nodes[0] as BTNode).GetOutputPort("firstNode").Connection.node as BTNode).Execute(); | ||
|  |                 } | ||
|  |                 */ | ||
|  | 
 | ||
|  |                 yield return null; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void ChangeTickRate(TreeTickRate newTickRate) | ||
|  |         { | ||
|  |             tickRate = newTickRate; | ||
|  |         } | ||
|  | 
 | ||
|  |         protected bool anyWaitForSetRunning = false; | ||
|  |         protected bool anyWaitForSwitchRunning = false; | ||
|  | 
 | ||
|  | 
 | ||
|  |         public IEnumerator QueueSwitch(bool _combat) | ||
|  |         { | ||
|  |             if (anyWaitForSwitchRunning) | ||
|  |                 yield return null; | ||
|  | 
 | ||
|  |             SwitchBehaviourTree(_combat); | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void SwitchBehaviourTree(bool _combat) | ||
|  |         { | ||
|  |             if (anyWaitForSwitchRunning) | ||
|  |             { | ||
|  |                 StartCoroutine(QueueSwitch(_combat)); | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             // Execute OnExit node if present | ||
|  |             if (currentBehaviour != null && currentBehaviour.nodes.Count > 0 && ((currentBehaviour.nodes[0] as BTNode).GetOutputPort("onExitNode").Connection != null && ((currentBehaviour.nodes[0] as BTNode).GetOutputPort("onExitNode").Connection.node != null))) | ||
|  |             { | ||
|  |                 anyWaitForSwitchRunning = true; | ||
|  |                 // Execute OnExit before changing the tree | ||
|  |                 StartCoroutine(SwitchBehaviourTree_WaitForExitNodeTask(_combat)); | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  | 
 | ||
|  |                 if (_combat) | ||
|  |                 { | ||
|  |                     currentBehaviour = combatBehaviourTree; | ||
|  |                     isUsingPurposeBehaviour = false; | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     currentBehaviour = purposeBehaviourTree; | ||
|  |                     isUsingPurposeBehaviour = true; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         IEnumerator SwitchBehaviourTree_WaitForExitNodeTask(bool _combat) | ||
|  |         { | ||
|  |             pauseBT = true; | ||
|  |             yield return new WaitForEndOfFrame(); | ||
|  | 
 | ||
|  |             var node = (((currentBehaviour.nodes[0] as BTNode).GetOutputPort("onExitNode").Connection.node) as BTNode); | ||
|  |             node.ReEvaluate(); | ||
|  |             while (node.m_NodeState == NodeState.Null || node.m_NodeState == NodeState.Running) | ||
|  |             { | ||
|  |                 node.Execute(); | ||
|  |                 yield return new WaitForEndOfFrame(); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (anyWaitForSetRunning) | ||
|  |             { | ||
|  |                 yield return new WaitForEndOfFrame(); | ||
|  |             } | ||
|  | 
 | ||
|  | 
 | ||
|  |             if (_combat) | ||
|  |             { | ||
|  |                 currentBehaviour = combatBehaviourTree; | ||
|  |                 isUsingPurposeBehaviour = false; | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 currentBehaviour = purposeBehaviourTree; | ||
|  |                 isUsingPurposeBehaviour = true; | ||
|  |             } | ||
|  | 
 | ||
|  |             anyWaitForSwitchRunning = false; | ||
|  |             pauseBT = false; | ||
|  |             yield break; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void SwitchBehaviourTreeToAI(string _rckID, bool _combat) | ||
|  |         { | ||
|  |             // Get the RckAI if possible | ||
|  |             RckAI sTo = null; | ||
|  |             CellsSystem.CellInformation.TryToGetAI(_rckID, out sTo); | ||
|  | 
 | ||
|  |             if (sTo != null) | ||
|  |             { | ||
|  |                 sTo.SwitchBehaviourTree(_combat); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public override void OnEnterInCombat() | ||
|  |         { | ||
|  |             base.OnEnterInCombat(); | ||
|  | 
 | ||
|  |             if (combatBehaviourTree.resetVariablesUponStartResume) | ||
|  |                 combatBehaviourTree.ResetVariables(); | ||
|  | 
 | ||
|  |             if (isUsingActionPoint && !leavingActionPoint) | ||
|  |                 StopUsingNPCActionPoint(); | ||
|  | 
 | ||
|  |             SwitchBehaviourTree(true); | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public override void LeaveCombat() | ||
|  |         { | ||
|  |             base.LeaveCombat(); | ||
|  | 
 | ||
|  |             if (purposeBehaviourTree.resetVariablesUponStartResume) | ||
|  |                 purposeBehaviourTree.ResetVariables(); | ||
|  | 
 | ||
|  |             // reset ishostile | ||
|  |             isHostile = false; | ||
|  |             isHostileAgainstPC = false; | ||
|  | 
 | ||
|  |             SwitchBehaviourTree(false); | ||
|  | 
 | ||
|  |             // Reset PurposeState | ||
|  |             if (purposeState.IsAssigned) | ||
|  |             { | ||
|  |                 SetTarget(purposeState.Target); | ||
|  |                 purposeState.ResumePurpose(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public override void Damage(DamageContext context) | ||
|  |         { | ||
|  |             if (context.sender != null && !context.sender.CompareTag("Player")) | ||
|  |             { | ||
|  |                 // If they're both members of the same factions chances are they didn't meant to attach each other, so ignore | ||
|  |                 for (int i = 0; i < belongsToFactions.Count; i++) | ||
|  |                 { | ||
|  |                     if ((context.sender as EntityFactionable).GetInFaction(belongsToFactions[i].ID)) | ||
|  |                         return; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             base.Damage(context); | ||
|  | 
 | ||
|  |             if (aiSounds != null && isAlive) | ||
|  |             { | ||
|  |                 if (aiSounds.takeDamageSounds.Length > 0) | ||
|  |                 { | ||
|  |                     int n = Random.Range(0, aiSounds.takeDamageSounds.Length); | ||
|  | 
 | ||
|  |                     if (aiSounds.takeDamageSounds[n] != null) | ||
|  |                     { | ||
|  |                         audioSource.clip = aiSounds.takeDamageSounds[n]; | ||
|  |                         audioSource.Play(); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (context.sender != null) | ||
|  |             { | ||
|  |                 // If they're both members of the same factions chances are they didn't meant to attach each other, so ignore | ||
|  |                 if (isInCombat) | ||
|  |                 { | ||
|  |                     for (int i = 0; i < belongsToFactions.Count; i++) | ||
|  |                     { | ||
|  |                         if ((context.sender as EntityFactionable).GetInFaction(belongsToFactions[i].ID)) | ||
|  |                             return; | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (!enemyTargets.Any(t => t.m_entity == context.sender)) | ||
|  |                 { | ||
|  |                     if (isInConversation) | ||
|  |                         StopConversation(); | ||
|  | 
 | ||
|  |                     if (!m_isInCombat) | ||
|  |                         EnterInCombatAgainst(context.sender); | ||
|  | 
 | ||
|  |                     enemyTargets.Add(new VisibleEnemy(context.sender, context.sender.GetComponent<EntityAttributes>(), new AggroInfo(50))); | ||
|  |                 } | ||
|  |                 else // this enemy was already in combat with this agent so just update the aggro | ||
|  |                 { | ||
|  |                     var entTarget = enemyTargets.Find(x => x.m_entity == context.sender); | ||
|  |                     entTarget.m_aggro.AlterAggroValue(AggroSettings.Modifier.Damage, context.amount); | ||
|  | 
 | ||
|  |                     float weaponAggroModifier = 0.0f; | ||
|  |                     if (context.weaponItem != null) | ||
|  |                         weaponAggroModifier = context.weaponItem.aggroModifier; | ||
|  |                     else if (context.spell != null) | ||
|  |                         weaponAggroModifier = context.spell.aggroModifier; | ||
|  | 
 | ||
|  |                     entTarget.m_aggro.AlterAggroValue(AggroSettings.Modifier.Weapon, weaponAggroModifier); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public override void Die() | ||
|  |         { | ||
|  |             base.Die(); | ||
|  |             attributes.CurHealth = -1; | ||
|  |             attributes.activeEffects.Clear(); | ||
|  |             keepTicking = false; | ||
|  |             useBT = false; | ||
|  |             pauseBT = true; | ||
|  |             for (int i = 0; i < inventory.Items.Count; i++) { | ||
|  |                 inventory.Items[i].metadata.IsOwnedByNPC = false; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         #region Methods and Functions specifically for Behaviour Trees | ||
|  |         public bool hasARangedWeapon = false; | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void HasRangedWeaponCheck() | ||
|  |         { | ||
|  |             WeaponItem weaponItem = null; | ||
|  | 
 | ||
|  |             for (int i = 0; i < inventory.subLists[(int)ItemTypes.WeaponItem].Count; i++) | ||
|  |             { | ||
|  |                 weaponItem = (WeaponItem)inventory.subLists[(int)ItemTypes.WeaponItem][i].item; | ||
|  | 
 | ||
|  |                 if (weaponItem.weaponType == WeaponType.Bow || weaponItem.weaponType == WeaponType.Crossbow) | ||
|  |                 { | ||
|  |                     if (HasAmmoForWeapon(weaponItem)) | ||
|  |                     { | ||
|  |                         hasARangedWeapon = true; | ||
|  |                         break; | ||
|  |                     } | ||
|  |                     else | ||
|  |                     { | ||
|  |                         Debug.Log("don't have ammo for " + weaponItem.ItemID); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public bool hasAMeleeWeapon = false; | ||
|  |         [AIInvokable] | ||
|  |         public void HasMeleeWeaponCheck() | ||
|  |         { | ||
|  |             WeaponItem weaponItem = null; | ||
|  | 
 | ||
|  |             for (int i = 0; i < inventory.subLists[(int)ItemTypes.WeaponItem].Count; i++) | ||
|  |             { | ||
|  |                 weaponItem = (WeaponItem)inventory.subLists[(int)ItemTypes.WeaponItem][i].item; | ||
|  | 
 | ||
|  |                 if (weaponItem.weaponType == WeaponType.BladeOneHand || weaponItem.weaponType == WeaponType.BluntOneHand || | ||
|  |                     weaponItem.weaponType == WeaponType.BladeTwoHands || weaponItem.weaponType == WeaponType.BluntTwoHands || | ||
|  |                     weaponItem.weaponType == WeaponType.DaggerOneHand) | ||
|  |                 { | ||
|  |                     hasAMeleeWeapon = true; | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         public float distanceFromMainTarget; | ||
|  |         [AIInvokable] | ||
|  |         public void GetDistanceFromMainTarget() | ||
|  |         { | ||
|  |             if(mainTarget == null) | ||
|  |             { | ||
|  |                 Debug.LogWarning("Called GetDistanceFromMainTarget() but no MainTarget has been set"); | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             distanceFromMainTarget = Vector3.Distance(this.transform.position, mainTarget.transform.position); | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Weapon_SwitchToMelee() | ||
|  |         { | ||
|  |             isSwitchingWeapon = true; | ||
|  | 
 | ||
|  |             WeaponItem weaponItem = null; | ||
|  | 
 | ||
|  |             for (int i = 0; i < inventory.subLists[(int)ItemTypes.WeaponItem].Count; i++) | ||
|  |             { | ||
|  |                 weaponItem = (WeaponItem)inventory.subLists[(int)ItemTypes.WeaponItem][i].item; | ||
|  | 
 | ||
|  |                 if (weaponItem.weaponType == WeaponType.BladeOneHand || weaponItem.weaponType == WeaponType.BluntOneHand || | ||
|  |                     weaponItem.weaponType == WeaponType.BladeTwoHands || weaponItem.weaponType == WeaponType.BluntTwoHands | ||
|  |                     || weaponItem.weaponType == WeaponType.DaggerOneHand) | ||
|  |                 { | ||
|  |                     ChangeWeapon(weaponItem.ItemID); | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Weapon_SwitchToRanged() | ||
|  |         { | ||
|  |             isSwitchingWeapon = true; | ||
|  | 
 | ||
|  |             WeaponItem weaponItem = null; | ||
|  | 
 | ||
|  |             for (int i = 0; i < inventory.subLists[(int)ItemTypes.WeaponItem].Count; i++) | ||
|  |             { | ||
|  |                 weaponItem = (WeaponItem)inventory.subLists[(int)ItemTypes.WeaponItem][i].item; | ||
|  | 
 | ||
|  |                 if (weaponItem.weaponType == WeaponType.Bow || weaponItem.weaponType == WeaponType.Crossbow) | ||
|  |                 { | ||
|  |                     // We got a weapon, check for ammo | ||
|  |                     if (EquipAmmoForWeapon(weaponItem)) | ||
|  |                     { | ||
|  |                         ChangeWeapon(weaponItem); | ||
|  |                         break; | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Weapon_SwitchToDefault() | ||
|  |         { | ||
|  |             ChangeWeapon(defaultWeapon.ItemID); | ||
|  |         } | ||
|  | 
 | ||
|  |         public float currentWeaponReach = RCKSettings.DEFAULT_WEAPON_REACH; | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void GetWeaponsReach() | ||
|  |         { | ||
|  |             if (equipment.currentWeapon != null) | ||
|  |                 currentWeaponReach = equipment.currentWeapon.Reach; | ||
|  |             else | ||
|  |                 currentWeaponReach = RCKSettings.DEFAULT_WEAPON_REACH; // 4 is default value | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void SpeakToTarget() | ||
|  |         { | ||
|  |             if (mainTarget.CompareTag("Player")) | ||
|  |                 RPGCreationKit.Player.RckPlayer.instance.StartDialogue(this.GetComponent<IDialoguable>()); | ||
|  |             else | ||
|  |             { | ||
|  |                 IDialoguable[] targetDialoguable = new IDialoguable[2]; | ||
|  |                 targetDialoguable[0] = mainTarget.GetComponent<IDialoguable>(); | ||
|  | 
 | ||
|  |                 if (targetDialoguable == null) | ||
|  |                     targetDialoguable[0] = mainTarget.GetComponentInParent<IDialoguable>(); | ||
|  | 
 | ||
|  |                 // Set us | ||
|  |                 targetDialoguable[1] = this; | ||
|  | 
 | ||
|  |                 if (targetDialoguable[0] != null) | ||
|  |                 { | ||
|  |                     DialogueLogic(targetDialoguable, GetCurrentDialogueGraph()); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void SetRckAIAsTarget(string _rckID) | ||
|  |         { | ||
|  |             // Get the RckAI if possible | ||
|  |             RckAI sTo = null; | ||
|  |             CellsSystem.CellInformation.TryToGetAI(_rckID, out sTo); | ||
|  | 
 | ||
|  |             if (sTo != null) | ||
|  |                 SetTarget(sTo.gameObject); | ||
|  |         } | ||
|  | 
 | ||
|  |         public override void OnDialogueEnds(GameObject target) | ||
|  |         { | ||
|  |             //// PURPOSE_CALLBACK: ClearOnTeleportToCell \\\\\ | ||
|  |             if (purposeState != null && purposeState.IsAssigned && purposeState.clearsOn == PurposeClearTypes.ClearOnFinishTalkingWith) | ||
|  |             { | ||
|  |                 if (purposeState.clearsOnData.stringData == speakingTo.GetEntityGameObject().GetComponent<Entity>().entityID) | ||
|  |                 { | ||
|  |                     // We did it | ||
|  |                     purposeState.CompletePurpose(); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             base.OnDialogueEnds(target); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Forces the AI to stop the dialogue | ||
|  |         /// </summary> | ||
|  |         public void StopConversation() | ||
|  |         { | ||
|  |             if (entitiesTalkingWith != null) | ||
|  |             { | ||
|  |                 // Copy before destroying entitiesTalkingWith | ||
|  |                 List<IDialoguable> dial = new List<IDialoguable>(); | ||
|  | 
 | ||
|  |                 for (int i = 0; i < entitiesTalkingWith.Length; i++) | ||
|  |                     dial.Add(entitiesTalkingWith[i]); | ||
|  | 
 | ||
|  |                 for (int i = 0; i < dial.Count; i++) | ||
|  |                     dial[i].ForceToStopDialogue(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         bool goingToSpeak = false; | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void SendAIToSpeakToPlayer(bool run) | ||
|  |         { | ||
|  |             if (goingToSpeak) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             goingToSpeak = true; | ||
|  | 
 | ||
|  |             StopCoroutine(SendAIToSpeakToPlayerTask()); | ||
|  | 
 | ||
|  |             if ( isReachingActionPoint || isUsingActionPoint) | ||
|  |             { | ||
|  |                 ResetActionPointAgentState(); | ||
|  |                 StopUsingNPCActionPoint(true); | ||
|  |             } | ||
|  | 
 | ||
|  |             if (run) | ||
|  |                 Movements_SwitchToRun(); | ||
|  |             else | ||
|  |                 Movements_SwitchToWalk(); | ||
|  | 
 | ||
|  |             // Set maintarget | ||
|  |             SetTarget(Player.RckPlayer.instance.gameObject); | ||
|  | 
 | ||
|  |             // Follow until he reaches it | ||
|  |             StartCoroutine(SendAIToSpeakToPlayerTask()); | ||
|  |         } | ||
|  | 
 | ||
|  |         public IEnumerator SendAIToSpeakToPlayerTask() | ||
|  |         { | ||
|  |             if (!isLoaded) | ||
|  |             { | ||
|  |                 goingToSpeak = false; | ||
|  |                 yield break; | ||
|  |             } | ||
|  | 
 | ||
|  |             while (Vector3.Distance(this.gameObject.transform.position, Player.RckPlayer.instance.transform.position) > RCKSettings.NPC_TO_NPC_CONVERSATION_DISTANCE || !CanDialogue() || | ||
|  |                 !Player.RckPlayer.instance.CanDialogue() || !WorldManager.instance.isLoading && !Player.RckPlayer.instance.IsControlledByPlayer() || isInOfflineMode || !isLoaded) | ||
|  |                 yield return new WaitForEndOfFrame(); | ||
|  | 
 | ||
|  |             Player.RckPlayer.instance.StartDialogue(this); | ||
|  | 
 | ||
|  |             goingToSpeak = false; | ||
|  |             yield break; | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void AssignPurpose(RckAI _AI, GameObject _target, PurposeClearTypes _clearsOn, PurposeStateClearsOnData _clearOnData, string _nextPurposeBehaviourTree = null) | ||
|  |         { | ||
|  |             purposeState.AssignPurpose(_AI, _target, _clearsOn, _clearOnData, _nextPurposeBehaviourTree); | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Purpose_AssignToFollowCurrentPath() | ||
|  |         { | ||
|  |             if(aiPath != null && aiPath.gameObject != null) | ||
|  |                 purposeState.AssignPurpose(GetComponent<RckAI>(), aiPath.gameObject, PurposeClearTypes.ClearOnDeath, null, null); | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Magic_SwitchToHealingSpell() | ||
|  |         { | ||
|  |             // Check if the AI already has an healing spell | ||
|  |             if (spellsKnowledge.spellInUse != null && spellsKnowledge.spellInUse.tag == SpellTag.RestoreHealth) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             for(int i = 0; i < spellsKnowledge.Spells.Count; i++) | ||
|  |             { | ||
|  |                 if(spellsKnowledge.Spells[i].tag == SpellTag.RestoreHealth) | ||
|  |                 { | ||
|  |                     spellsKnowledge.spellInUse = spellsKnowledge.Spells[i]; | ||
|  |                     return; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Magic_SwitchToDamageSpell() | ||
|  |         { | ||
|  |             // Check if the AI already has an healing spell | ||
|  |             if (spellsKnowledge.spellInUse != null && spellsKnowledge.spellInUse.tag == SpellTag.DamageProjectile || spellsKnowledge.spellInUse.tag == SpellTag.DamageTouch) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             for (int i = 0; i < spellsKnowledge.Spells.Count; i++) | ||
|  |             { | ||
|  |                 if (spellsKnowledge.Spells[i].tag == SpellTag.DamageProjectile || spellsKnowledge.Spells[i].tag == SpellTag.DamageTouch) | ||
|  |                 { | ||
|  |                     spellsKnowledge.spellInUse = spellsKnowledge.Spells[i]; | ||
|  |                     return; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Magic_SetSpell(string _spellIDToSet) | ||
|  |         { | ||
|  |             Spell _spellToSet = SpellsDatabase.GetSpell(_spellIDToSet); | ||
|  | 
 | ||
|  |             if (_spellToSet != null) | ||
|  |                 spellsKnowledge.spellInUse = _spellToSet; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         [AIInvokable] | ||
|  |         public void Magic_AddSpellToKnowledge(string _spellIDToAdd) | ||
|  |         { | ||
|  |             Spell _spellToAdd = SpellsDatabase.GetSpell(_spellIDToAdd); | ||
|  | 
 | ||
|  |             if (_spellToAdd != null) | ||
|  |                 spellsKnowledge.Spells.Add(_spellToAdd); | ||
|  |         } | ||
|  |         #endregion | ||
|  |     } | ||
|  | } |