using System.Collections; using System.Collections.Generic; using UnityEngine; namespace TBTK{ public class AI : MonoBehaviour { #if UNITY_EDITOR public static bool inspector=false; #endif public enum _AIBehaviour { passive, aggressive, }// evasive } [Tooltip("Check to override individual unit behaviour setting")] public bool overrideUnitSetting=false; public _AIBehaviour aiBehaviour=_AIBehaviour.aggressive; public bool requireTrigger=true; //when true, unit starts in passive state, then switch to aggressive or evasive (doesnt apply for passive) public static bool IsPassive(Unit unit){ if(!instance.overrideUnitSetting) return unit.IsPassive(); else return instance.aiBehaviour==AI._AIBehaviour.passive; } public static bool IsAggressive(Unit unit){ if(!instance.overrideUnitSetting) return unit.IsAggressive(); else return (instance.aiBehaviour==AI._AIBehaviour.aggressive && !instance.requireTrigger) || unit.triggered; } //public static bool IsPassive(Unit unit){ //if(!instance.overrideUnitSetting) return unit.IsPassive(); //else return instance.aiBehaviour==AI._AIBehaviour.passive || !IsAggressive(unit); //} //public static bool IsAggressive(Unit unit){ //if(!instance.overrideUnitSetting) return unit.IsPassive(); //else return instance.aiBehaviour==AI._AIBehaviour.aggressive && (unit.triggered || !instance.requireTrigger); //} [Tooltip("Delay in second between each units when they take their turn")] public float delayBetweenUnit=0.25f; [Space(10)] [Tooltip("When checked, AI will always use the best option available")] public bool alwaysUseBestOption=true; [Space(10)] [Tooltip("How much the damage for a potential attack is going to be weight into making the AI decision ")] public float damageMultiplier=1; [Tooltip("How much the hit chance for a potential attack is going to be weight into making the AI decision ")] public float hitChanceMultiplier=1; [Tooltip("How much the critical chance for a potential attack is going to be weight into making the AI decision ")] public float critChanceMultiplier=1; [Tooltip("How much of giving chase on out of range target is going to be weight into making the AI decision\nOnly applies there are no target within range")] public float pursueMultiplier=1; [Tooltip("How much cover is going to be weight into making the AI decision\nOnly applies when cover system is enabled ")] public float coverMultiplier=1; public static float dmgMul(){ return instance.damageMultiplier; } public static float hitMul(){ return instance.hitChanceMultiplier; } public static float critMul(){ return instance.critChanceMultiplier; } public static float pursueMul(){ return instance.pursueMultiplier; } public static float coverMul(){ return instance.coverMultiplier; } public static AI instance; public static void Init(){ if(instance==null) instance=(AI)FindObjectOfType(typeof(AI)); } void Awake(){ instance=this; actionInProgress=false; } [Space(10)] private static bool actionInProgress=false; public static bool ActionInProgress(){ return actionInProgress; } public static void MoveUnit(Unit unit){ instance.StartCoroutine(_MoveUnit(unit)); } public static IEnumerator _MoveUnit(Unit unit){ actionInProgress=true; TBTK.OnGameMessage("- AI's Turn -"); TBTK.OnSelectUnit(unit); yield return instance.StartCoroutine(AIRoutineUnit(unit)); if(unit==null || unit.hp<=0) yield return new WaitForSeconds(0.25f); if(instance.delayBetweenUnit>0) yield return new WaitForSeconds(instance.delayBetweenUnit); actionInProgress=false; GameControl.EndTurn(); } public static void MoveFaction(Faction faction){ instance.StartCoroutine(_MoveFaction(faction)); } public static IEnumerator _MoveFaction(Faction faction){ actionInProgress=true; TBTK.OnGameMessage("- AI's Turn -"); List unitList=new List( faction.unitList ); if(TurnControl.EnableUnitLimit() || Rand.value()<0.4f){ List newList=new List(); while(unitList.Count>0){ int rand=Rand.Range(0, unitList.Count); newList.Add(unitList[rand]); unitList.RemoveAt(rand); } unitList=newList; } if(unitList.Count>TurnControl.GetUnitLimit()){ while(unitList.Count>TurnControl.GetUnitLimit()) unitList.RemoveAt(unitList.Count-1); } for(int i=0; i0 && unit.IsVisible()) yield return new WaitForSeconds(instance.delayBetweenUnit); } actionInProgress=false; GameControl.EndTurn(); } public static IEnumerator AIRoutineUnit(Unit unit){ if(unit.IsStunned()) yield break; int safetyCounter=0; while(unit.CanMove() || unit.CanAttack()){ AIAction action=AnalyseAction(unit); if(action==null){ //Debug.Log("No valid action for unit - "+unit.gameObject); yield break; } if(action.tgtNode==unit.node && action.tgtUnit==null){ safetyCounter+=1; if(safetyCounter>3) yield break; else continue; } if(action.tgtNode!=unit.node){ yield return instance.StartCoroutine(unit.MoveRoutine(action.tgtNode)); } if(unit.hp<=0) yield break; if(unit!=null && action.tgtUnit!=null){ yield return instance.StartCoroutine(unit.AttackRoutine(action.tgtUnit.node)); } yield return null; } yield return null; } public static AIAction AnalyseAction(Unit unit){ //Debug.Log("AnalyseAction "+unit.gameObject); if(IsPassive(unit) && Rand.value()<0.7f) return new AIAction(unit.node); List actionList=new List(); //List hostileList=new List(); bool nearestHostileScanned=false; Node nearestHostileNode=null; float nearestHostileDist=Mathf.Infinity; //Node furthestHostileNode=null; float furthestHostileDist=0; float maxNearestDistToHostile=0; float minNearestDistToHostile=Mathf.Infinity; bool hasTargetWithinRange=false; float unitDamage=-99; List walkableList=unit.CanMove() ? GridManager.SetupWalkableList(unit) : null; if(walkableList==null) walkableList=new List(); walkableList.Insert(0, unit.node); List allhostileList=null; if(GameControl.EnableCoverSystem()) allhostileList=UnitManager.GetAllHostileUnits(unit.GetFacID()); for(int i=0; i attackNodeList=GridManager.GetAttackableList(unit, walkableList[i]); Vector2 cover=CheckCover(walkableList[i], unit, allhostileList); float coverScore=100 * (cover[0]!=0 ? cover[0] : cover[1]) * 0.5f ; //bcz full cover value is 2 if(attackNodeList.Count>0){ if(unitDamage<=-99) unitDamage=(unit.GetDmgHPMin() + unit.GetDmgHPMax()) * 0.5f; for(int n=0; n0){ int nearestIdx=0; //float nearest=Mathf.Infinity; //int furthestIdx=0; //float furthest=0; for(int n=0; nfurthest){ furthestHostileDist=dist; furthestIdx=n; } } nearestHostileNode=allhostileList[nearestIdx].node; //furthestHostileNode=hostileList[furthestIdx].node; } } //if(nearestHostileNode==null){ //List hostileList=UnitManager.GetAllHostileUnits(unit.GetFacID()); //~ int nearestIdx=0; float nearest=Mathf.Infinity; //~ int furthestIdx=0; float furthest=0; //~ for(int n=0; nfurthest){ furthest=dist; furthestIdx=n; } //~ } //nearestHostileNode=hostileList[nearestIdx].node; //nearestHostileDist=nearest; //} if(nearestHostileNode!=null){ action.scoreAlt=GridManager.GetDistance(walkableList[i], nearestHostileNode); //float nearestDistToHostile=GridManager.GetDistance(walkableList[i], nearestHostileNode); //action.scoreAlt=Mathf.Max(0, nearestHostileDist-nearestDistToHostile);// * instance.pursueMultiplier; //action.scoreAlt*=Rand.Range(0.75f, 1.25f); //Debug.Log(nearestHostileDist+" "+nearestDistToHostile+" "+action.scoreAlt+" "+action.score); //Debug.Log(action.scoreAlt+" "+maxNearestDistToHostile); if(action.scoreAltmaxNearestDistToHostile) maxNearestDistToHostile=action.scoreAlt; //if(action.scoreAlt>maxNearestDistToHostile) maxNearestDistToHostile=nearestDistToHostile; } //~ if(nearest==furthest) action.score+=1; //~ else action.score+=Mathf.Abs(nearest-furthest)/(furthest-nearest) * instance.pursueMultiplier; //float distToNearestHostile=GridManager.GetDistance(walkableList[i], nearestHostileNode); // //action.score+=Mathf.Max(0, nearestHostileDist-distToNearestHostile)*instance.pursueMultiplier; } actionList.Add(action); } } if(IsAggressive(unit)){ for(int i=0; i path=AStar.SearchWalkableNode(unit.node, nearestHostileNode, unit.canMovePastUnit, unit.canMovePastObs, true); List path=AStar.SearchWalkableNode(unit.node, nearestHostileNode, AStar.BypassUnitCode(unit), unit.canMovePastObs, true); if(path.Count>0){ int idx=Mathf.Clamp(unit.GetMoveRange()-1, 0, path.Count-1); AIAction action=new AIAction(path[idx], 0); return action; } } } if(actionList.Count==0) return null; //sorting the list according to the score List newList=new List(); while(actionList.Count>0){ int highestIdx=-1; float highest=-Mathf.Infinity; for(int i=0; ihighest){ highest=actionList[i].score; highestIdx=i; } } if(highestIdx<0) break; if(instance.alwaysUseBestOption) return actionList[highestIdx]; newList.Add(actionList[highestIdx]); actionList.RemoveAt(highestIdx); } actionList=newList; if(actionList[0].score>0){ for(int i=1; i0) continue; actionList.RemoveAt(i); i-=1; } } if(actionList.Count==0) return null; int rand=Rand.GetOption(new List{ 1.5f, 0.25f, 0.125f, 0.075f }); rand=Mathf.Min(rand, actionList.Count-1); return actionList[rand]; } /* public static AIAction __AnalyseAction(Unit unit){ List actionList=new List(); List walkableList=GridManager.SetupWalkableList(unit); walkableList.Insert(0, unit.node); for(int i=0; i attackNodeList=GridManager.GetAttackableList(unit, walkableList[i]); // float coverScore=100 * CheckCover(walkableList[i], unit) * instance.coverMultiplier; if(attackNodeList.Count>0){ for(int n=0; n0 && (!unit.triggered || !GameControl.EnableCoverSystem())) return actionList[Rand.Range(0, actionList.Count)]; } } else if(unit.IsAggressive()){ float furthestDist=0; List hostileList=UnitManager.GetAllHostileUnits(unit.facID); for(int i=0; ifurthestDist) furthestDist=dist; if(disthighest){ highest=actionList[i].score; highestIdx=i; } } //Debug.Log("AI action: "+actionList.Count+" "+highestIdx); if(actionList.Count>0){ if(highestIdx>=0) return actionList[highestIdx]; else return actionList[0]; } return null; } */ public static Vector2 CheckCover(Node node, Unit unit, List hostileList){ if(!GameControl.EnableCoverSystem()) return Vector2.zero; //consider xcom style side stepping for square grid int lowestCover=2; float totalCover=0; //List hostileList=UnitManager.GetAllHostileUnits(unit.GetFacID()); for(int i=0; ihostileList[i].GetAttackRange()) continue; //~ //Vector3 dir=hostileList[i].node.GetPos()-node.GetPos(); //~ //int cover=node.GetCover(Utility.Vector2ToAngle(new Vector2(dir.x, dir.z))); //~ int cover=node.GetCover(GridManager.GetAngle(node, hostileList[i].node, false)); //~ if(cover hostileList=UnitManager.GetAllHostileUnits(unit.facID); //~ for(int n=0; n