Firstborn/Assets/RPG Creation Kit/Scripts/AI/AI Scripts/AIBehaviourTreed.cs
Schaken-Mods 959e80cf72 assets upload
assets upload description.
2023-03-28 12:16:30 -05:00

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
}
}