ProjectCreep/Assets/TBTK/Scripts/AI.cs

505 lines
19 KiB
C#

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<Unit> unitList=new List<Unit>( faction.unitList );
if(TurnControl.EnableUnitLimit() || Rand.value()<0.4f){
List<Unit> newList=new List<Unit>();
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; i<unitList.Count; i++){
if(unitList[i]==null) continue;
Unit unit=unitList[i]; TBTK.OnSelectUnit(unit);
yield return instance.StartCoroutine(AIRoutineUnit(unit));
if(unit==null || (unit.hp<=0 && unit.IsVisible())){ yield return new WaitForSeconds(0.25f); }
if(unit!=null && instance.delayBetweenUnit>0 && 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<AIAction> actionList=new List<AIAction>();
//List<Unit> hostileList=new List<Unit>();
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<Node> walkableList=unit.CanMove() ? GridManager.SetupWalkableList(unit) : null;
if(walkableList==null) walkableList=new List<Node>();
walkableList.Insert(0, unit.node);
List<Unit> allhostileList=null;
if(GameControl.EnableCoverSystem()) allhostileList=UnitManager.GetAllHostileUnits(unit.GetFacID());
for(int i=0; i<walkableList.Count; i++){
List<Node> 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; n<attackNodeList.Count; n++){
AIAction action=new AIAction(walkableList[i], attackNodeList[n].unit);
Attack attack=new Attack(unit, attackNodeList[n].unit, walkableList[i], false, false);
action.score+=(0.5f*(attack.damageHPMin+attack.damageHPMax))/unitDamage*100*instance.damageMultiplier;
action.score+=attack.hitChance*100f*instance.hitChanceMultiplier;
action.score+=attack.critChance*100f*instance.critChanceMultiplier;
action.score+=coverScore;
//~ float score1=(0.5f*(attack.damageHPMin+attack.damageHPMax))/unitDamage*100*instance.damageMultiplier;
//~ float score2=attack.hitChance*100f*instance.hitChanceMultiplier;
//~ float score3=attack.critChance*100f*instance.critChanceMultiplier;
//~ float score4=coverScore;
//~ float ccover=Attack.GetCover(walkableList[i], attackNodeList[n]);
//~ Debug.Log((0.5f*(attack.damageHPMin+attack.damageHPMax))+" "+unitDamage+" "+attack.cover+" "+score1+" "+score2+" "+score3+" "+score4+" ");
//Debug.Log(coverScore+" "+attack.cover+" "+attack.hitChance+" "+attack.critChance+" "+action.score);
//Debug.DrawLine(walkableList[i].GetPos(), walkableList[i].GetPos()+new Vector3(0, 1, 0)*(action.score*0.01f), Color.white, 2);
actionList.Add(action);
}
hasTargetWithinRange=true;
}
else{
AIAction action=new AIAction(walkableList[i], coverScore);
if(IsPassive(unit)){
action.score+=Mathf.Min(0, 3-GridManager.GetDistance(walkableList[i], unit.node));
}
else if(IsAggressive(unit)){
//Node nearestHostileNode=null; float nearestHostileDist=0;
if(!nearestHostileScanned){
nearestHostileScanned=true;
if(allhostileList==null) allhostileList=UnitManager.GetAllHostileUnits(unit.GetFacID());
if(allhostileList.Count>0){
int nearestIdx=0; //float nearest=Mathf.Infinity;
//int furthestIdx=0; //float furthest=0;
for(int n=0; n<allhostileList.Count; n++){
float dist=GridManager.GetDistance(unit.node, allhostileList[n].node);
if(dist<nearestHostileDist){ nearestHostileDist=dist; nearestIdx=n; }
//if(dist>furthest){ furthestHostileDist=dist; furthestIdx=n; }
}
nearestHostileNode=allhostileList[nearestIdx].node;
//furthestHostileNode=hostileList[furthestIdx].node;
}
}
//if(nearestHostileNode==null){
//List<Unit> hostileList=UnitManager.GetAllHostileUnits(unit.GetFacID());
//~ int nearestIdx=0; float nearest=Mathf.Infinity;
//~ int furthestIdx=0; float furthest=0;
//~ for(int n=0; n<hostileList.Count; n++){
//~ float dist=GridManager.GetDistance(unit.node, hostileList[n].node);
//~ if(dist<nearest){ nearest=dist; nearestIdx=n; }
//~ if(dist>furthest){ 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.scoreAlt<minNearestDistToHostile) minNearestDistToHostile=action.scoreAlt;
if(action.scoreAlt>maxNearestDistToHostile) 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<actionList.Count; i++){
if(!actionList[i].CachedScore()) continue;
float range=maxNearestDistToHostile-minNearestDistToHostile;
actionList[i].scoreAlt=1-(actionList[i].scoreAlt-minNearestDistToHostile)/range;
//Debug.Log(i+" alt score - "+actionList[i].scoreAlt+" "+actionList[i].score);
//actionList[i].scoreAlt=(maxNearestDistToHostile-actionList[i].scoreAlt);///maxNearestDistToHostile * instance.pursueMultiplier;
//Debug.Log(" - "+actionList[i].scoreAlt+" "+maxNearestDistToHostile);
//~ actionList[i].score+=(100*actionList[i].scoreAlt/maxNearestDistToHostile) * instance.pursueMultiplier;
actionList[i].score+=100 * actionList[i].scoreAlt * instance.pursueMultiplier;
}
}
//no target in range but unit has attacked
//unit has either just destroyed a unit within range or use up or it's attack option
if(!hasTargetWithinRange && unit.attackThisTurn<=0){
if(!unit.CanMove()) return null;
if(IsAggressive(unit) && !GameControl.EnableCoverSystem()){
//List<Node> path=AStar.SearchWalkableNode(unit.node, nearestHostileNode, unit.canMovePastUnit, unit.canMovePastObs, true);
List<Node> 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<AIAction> newList=new List<AIAction>();
while(actionList.Count>0){
int highestIdx=-1; float highest=-Mathf.Infinity;
for(int i=0; i<actionList.Count; i++){
if(actionList[i].score>highest){ 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; i<actionList.Count; i++){
if(actionList[i].score>0) continue;
actionList.RemoveAt(i); i-=1;
}
}
if(actionList.Count==0) return null;
int rand=Rand.GetOption(new List<float>{ 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<AIAction> actionList=new List<AIAction>();
List<Node> walkableList=GridManager.SetupWalkableList(unit);
walkableList.Insert(0, unit.node);
for(int i=0; i<walkableList.Count; i++){
List<Node> attackNodeList=GridManager.GetAttackableList(unit, walkableList[i]);
//
float coverScore=100 * CheckCover(walkableList[i], unit) * instance.coverMultiplier;
if(attackNodeList.Count>0){
for(int n=0; n<attackNodeList.Count; n++){
AIAction action=new AIAction(walkableList[i], attackNodeList[n].unit);
Attack attack=new Attack(unit, attackNodeList[n].unit, false, false);
action.score+=0.5f*(attack.damageHPMin+attack.damageHPMax)*instance.damageMultiplier;
action.score+=attack.hitChance*100*instance.hitChanceMultiplier;
action.score+=attack.critChance*100*instance.critChanceMultiplier;
action.score+=coverScore;
actionList.Add(action);
}
}
else{
}
}
if(actionList.Count==0){
if(unit.IsPassive()){
if(Rand.value()<0.7f) return new AIAction(unit.node);
else{
for(int i=1; i<walkableList.Count; i++){
float coverScore=100 * CheckCover(walkableList[i], unit) * instance.coverMultiplier;
float dist=GridManager.GetDistance(unit.node, walkableList[i]);
if(dist==1) actionList.Add(new AIAction(walkableList[i], coverScore));
else if(dist==2 && Rand.value()<0.3f) actionList.Add(new AIAction(walkableList[i], coverScore));
}
//if unit is not triggered or cover system is not active,
//just move randomly, otherwise let the sorting algorithm at the end of the function to choose the node with higeste cover
if(actionList.Count>0 && (!unit.triggered || !GameControl.EnableCoverSystem()))
return actionList[Rand.Range(0, actionList.Count)];
}
}
else if(unit.IsAggressive()){
float furthestDist=0;
List<Unit> hostileList=UnitManager.GetAllHostileUnits(unit.facID);
for(int i=0; i<walkableList.Count; i++){
float nearest=Mathf.Infinity;
for(int n=0; n<hostileList.Count; n++){
float dist=GridManager.GetDistance(walkableList[i], hostileList[n].node);
if(dist>furthestDist) furthestDist=dist;
if(dist<nearest) nearest=dist;
}
actionList.Add(new AIAction(walkableList[i], nearest, 100*CheckCover(walkableList[i], unit)*instance.coverMultiplier));
}
for(int i=0; i<actionList.Count; i++){
actionList[i].score=instance.pursueMultiplier * (furthestDist-actionList[i].score)/furthestDist;
actionList[i].score+=actionList[i].scoreAlt;
}
}
}
int highestIdx=-1; float highest=-1;
for(int i=0; i<actionList.Count; i++){
if(actionList[i].score>highest){
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<Unit> hostileList){
if(!GameControl.EnableCoverSystem()) return Vector2.zero;
//consider xcom style side stepping for square grid
int lowestCover=2; float totalCover=0;
//List<Unit> hostileList=UnitManager.GetAllHostileUnits(unit.GetFacID());
for(int i=0; i<hostileList.Count; i++){
//~ if(GridManager.GetDistance(hostileList[i].node, node)>hostileList[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<lowestCover) lowestCover=cover;
int cover=Attack.GetCover(hostileList[i].node, node);
if(cover<lowestCover) lowestCover=cover;
totalCover+=cover;
}
totalCover=totalCover/(float)hostileList.Count * 0.4f;
//Color color=lowestCover==0 ? Color.red : Color.white ;
//Debug.DrawLine(node.GetPos(), node.GetPos()+new Vector3(0, 1, 0)*(totalCover), color, 2);
return new Vector2(lowestCover, totalCover);
//~ Vector3 avgPos=Vector3.zero;
//~ List<Unit> hostileList=UnitManager.GetAllHostileUnits(unit.facID);
//~ for(int n=0; n<hostileList.Count; n++) avgPos+=hostileList[n].GetPos();
//~ avgPos/=hostileList.Count;
//~ Vector3 dir=avgPos-node.GetPos();
//~ return node.GetCover(Utility.Vector2ToAngle(new Vector2(dir.x, dir.z)));
}
}
public class AIAction{
public Node tgtNode;
public Unit tgtUnit;
public float score=0;
public float scoreAlt=-999; //used to temporarily cache value
public AIAction(Node node, Unit tgtU=null){
tgtNode=node; tgtUnit=tgtU;
}
public AIAction(Node node, float ss, float ssAlt=0){
tgtNode=node; score=ss; scoreAlt=ssAlt;
}
public bool CachedScore(){ return scoreAlt!=-999; }
}
}