ProjectCreep/Assets/TBTK/Scripts/Unit.cs

1127 lines
43 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TBTK{
public class Unit : TBMonoItem {
public static bool enableRotation=true;
public static bool inspector=true;
[HideInInspector] public bool loadedFromCache=false;
public int value=50;
[Space(5)]
public int facID; //in runtime, this also correspond to the faction index in factionList
public void SetFacID(int id){ facID=id; }
public int GetBaseFacID(){ return facID ; }
public int GetFacID(){ return tempFacID>=0 ? tempFacID : facID ; }
public bool playableUnit=false;
public AI._AIBehaviour aiBehaviour=AI._AIBehaviour.aggressive;
public bool requireTrigger=true; //when true, unit starts in passive state, then switch to aggressive or evasive (doesnt apply for passive)
//[HideInInspector]
public bool triggered=false;
public bool IsPassive(){ return aiBehaviour==AI._AIBehaviour.passive; }
public bool IsAggressive(){ return (aiBehaviour==AI._AIBehaviour.aggressive && !requireTrigger) || triggered; }
//public bool IsPassive(){ return aiBehaviour==AI._AIBehaviour.passive || !IsAggressive(); }
//public bool IsAggressive(){ return aiBehaviour==AI._AIBehaviour.aggressive && (triggered || !requireTrigger); }
//public bool IsEvasive(){ return aiBehaviour=AI._AIBehaviour.evasive && (triggered || !requireTrigger); }
[Space(8)]
public float hp=10;
public float ap=2;
//public float GetFullHP(){ return stats.hp; }
public float GetHPRatio(){ return hp/GetFullHP(); }
//public float GetFullAP(){ return stats.ap; }
public float GetAPRatio(){ return ap/GetFullAP(); }
public float GetFullHP(){ return stats.hp * GetFullHPMul() + GetFullHPMod(); }
public float GetFullHPMul(){ return activeEffectMul.stats.hp * PerkManager.GetUnitMulHP(prefabID); }
public float GetFullHPMod(){ return activeEffectMod.stats.hp + PerkManager.GetUnitModHP(prefabID); }
public float GetFullAP(){ return stats.ap * GetFullAPMul() + GetFullAPMod(); }
public float GetFullAPMul(){ return activeEffectMul.stats.ap * PerkManager.GetUnitMulAP(prefabID); }
public float GetFullAPMod(){ return activeEffectMod.stats.ap + PerkManager.GetUnitModAP(prefabID); }
public float GetHPRegen(){ return stats.hpRegen * GetHPRegenMul() + GetHPRegenMod(); }
public float GetHPRegenMul(){ return activeEffectMul.stats.hpRegen * PerkManager.GetUnitMulHPRegen(prefabID); }
public float GetHPRegenMod(){ return activeEffectMod.stats.hpRegen + PerkManager.GetUnitModHPRegen(prefabID); }
public float GetAPRegen(){ return stats.apRegen * GetAPRegenMul() + GetAPRegenMod(); }
public float GetAPRegenMul(){ return activeEffectMul.stats.apRegen * PerkManager.GetUnitMulAPRegen(prefabID); }
public float GetAPRegenMod(){ return activeEffectMod.stats.apRegen + PerkManager.GetUnitModAPRegen(prefabID); }
public int GetMoveLimit(){ return (int)(stats.moveLimit * GetMoveLimitMul()) + (int)GetMoveLimitMod(); }
public float GetMoveLimitMul(){ return activeEffectMul.stats.moveLimit * PerkManager.GetUnitMulMoveLim(prefabID); }
public float GetMoveLimitMod(){ return activeEffectMod.stats.moveLimit + PerkManager.GetUnitModMoveLim(prefabID); }
public int GetMoveRemain(){ return GetMoveLimit()-moveThisTurn; }
public int moveThisTurn=0;
public int GetAttackLimit(){ return (int)(stats.attackLimit * GetAttackLimitMul()) + (int)GetAttackLimitMod(); }
public float GetAttackLimitMul(){ return activeEffectMul.stats.moveLimit * PerkManager.GetUnitMulAttackLim(prefabID); }
public float GetAttackLimitMod(){ return activeEffectMod.stats.moveLimit + PerkManager.GetUnitModAttackLim(prefabID); }
public int GetAttackRemain(){ return GetAttackLimit()-attackThisTurn; }
public int attackThisTurn=0;
public int GetCounterLimit(){ return (int)(stats.counterLimit * GetCounterLimitMul()) + (int)GetCounterLimitMod(); }
public float GetCounterLimitMul(){ return activeEffectMul.stats.counterLimit * PerkManager.GetUnitMulCounterLim(prefabID); }
public float GetCounterLimitMod(){ return activeEffectMod.stats.counterLimit + PerkManager.GetUnitModCounterLim(prefabID); }
public int counterThisTurn=0;
public int GetAbilityLimit(){ return (int)(stats.abilityLimit * GetAbilityLimitMul() + GetAbilityLimitMod()); }
public float GetAbilityLimitMul(){ return activeEffectMul.stats.abilityLimit * PerkManager.GetUnitMulAbilityLim(prefabID); }
public float GetAbilityLimitMod(){ return activeEffectMod.stats.abilityLimit + PerkManager.GetUnitModAbilityLim(prefabID); }
public int GetAbilityRemain(){ return GetAbilityLimit()-abilityThisTurn; }
public int abilityThisTurn=0;
//public const int apPerMove=1;
//public const int apPerNode=0;
//public const int apPerAttack=1;
public bool HasTakenAction(){ return (moveThisTurn + attackThisTurn + abilityThisTurn + counterThisTurn) > 0 ; }
[Space(8)]
public AStar._BypassUnit canMovePastUnit;
//public bool canMovePastUnit;
public bool canMovePastObs;
[Space(8)]
public Transform targetPoint;
public float radius=0.25f;
public Vector3 GetTargetPoint(){ return targetPoint!=null ? targetPoint.position : GetPos(); }
public float GetRadius(){ return radius; }
public ShootObject soRange;
public ShootObject soMelee;
public float shootPointSpacing=0.1f;
public List<Transform> shootPointList=new List<Transform>();
public Transform turretPivot;
public Transform barrelPivot;
public bool snapAiming;
public bool aimInXAxis;
public bool rotateWhileAiming;
private float aimSpeed=7;
private bool instantRotate=false;
[Space(5)] public float moveSpeed=5;
public delegate bool ActionCamCheck (bool actionType);
public delegate IEnumerator ActionCamStart(Vector3 srcPos, Vector3 tgtPos);
public delegate IEnumerator ActionCamEnd();
public static ActionCamCheck actionCamCheck;
public static ActionCamStart actionCamStart;
public static ActionCamEnd actionCamEnd;
void Awake(){
thisT=transform;
thisObj=gameObject;
if(shootPointList.Count==0) shootPointList.Add(thisT);
InitAbility();
UpdateActiveEffect();
if(turretPivot!=null) defaultTurretRot=turretPivot.localRotation;
if(barrelPivot!=null) defaultBarrelRot=barrelPivot.localRotation;
triggered=false;
InitAnimation();
}
void Start(){
if(GameControl.EnableFogOfWar() && !playableUnit){
Utility.SetLayerRecursively(thisT, TBTK.GetLayerInvisible());
}
}
public void NewTurn(bool restoreFullHP=false){ //restoreFullHP is used when the game start
if(restoreFullHP) hp=GetFullHP();
if(GameControl.RestoreAPOnTurn()) ap=GetFullAP();
else ap=Mathf.Min(ap+GetAPRegen(), GetFullAP());
hp=Mathf.Min(hp+GetHPRegen(), GetFullHP());
moveThisTurn=0;
attackThisTurn=0;
counterThisTurn=0;
abilityThisTurn=0;
}
public bool CanAttack(){
if(attackThisTurn>=GetAttackLimit()) return false;
if(GameControl.UseAPToAttack() && GameControl.GetAPPerAttack()>ap) return false;
return true;
}
public bool CanMove(){
if(moveThisTurn>=GetMoveLimit()) return false;
if(GameControl.UseAPToMove() && GameControl.GetAPPerMove()>ap) return false;
if(GameControl.UseAPToMove() && GameControl.GetAPPerNode()>ap) return false;
return true;
}
public bool CanCounter(Unit tgtUnit){
if(IsStunned()) return false;
if(counterThisTurn>=GetCounterLimit()) return false;
if(GameControl.UseAPToAttack() && GameControl.GetAPPerAttack()>ap) return false;
int targetRange=GridManager.GetDistance(tgtUnit.node, node);
if(targetRange>GetAttackRange()) return false;
float minAttackRange=GetAttackRangeMin();
if(minAttackRange>0 && targetRange<minAttackRange) return false;
if(requireLOSToAttack && !GridManager.CheckLOS(node, tgtUnit.node, GetSight())) return false;
return true;
}
[Space(8)]
public bool hasMeleeAttack=true;
public bool requireLOSToAttack=true;
public int armorType=0;
public int damageType=0;
public Stats stats;
public int damageTypeMelee=0;
public Stats statsMelee;
public List<int> attackEffectIDList=new List<int>();
public List<int> GetRuntimeAttackEffectIDList(){
return PerkManager.ModifyUnitAttackEffectList(prefabID, attackEffectIDList);
//return attackEffectIDList;
}
public float GetAttack(){ return stats.attack * GetAttackMul() + GetAttackMod(); }
public float GetAttackMul(){ return activeEffectMul.stats.attack * PerkManager.GetUnitMulAttack(prefabID); }
public float GetAttackMod(){ return activeEffectMod.stats.attack + PerkManager.GetUnitModAttack(prefabID); }
public float GetDefense(){ return stats.defense * GetDefenseMul() + GetDefenseMod(); }
public float GetDefenseMul(){ return activeEffectMul.stats.defense * PerkManager.GetUnitMulDefense(prefabID); }
public float GetDefenseMod(){ return activeEffectMod.stats.defense + PerkManager.GetUnitModDefense(prefabID); }
public float GetHit(){ return stats.hit * GetHitMul() + GetHitMod(); }
public float GetHitMul(){ return activeEffectMul.stats.hit * PerkManager.GetUnitMulHit(prefabID); }
public float GetHitMod(){ return activeEffectMod.stats.hit + PerkManager.GetUnitModHit(prefabID); }
public float GetDodge(){ return stats.dodge * GetMulDodge() + GetModDodge(); }
public float GetMulDodge(){ return activeEffectMul.stats.dodge * PerkManager.GetUnitMulDodge(prefabID); }
public float GetModDodge(){ return activeEffectMod.stats.dodge + PerkManager.GetUnitModDodge(prefabID); }
public float GetDmgHPMin(){ return stats.dmgHPMin * GetMulDmgHPMin() + GetModDmgHPMin(); }
public float GetMulDmgHPMin(){ return activeEffectMul.stats.dmgHPMin * PerkManager.GetUnitMulDmgHPMin(prefabID); }
public float GetModDmgHPMin(){ return activeEffectMod.stats.dmgHPMin + PerkManager.GetUnitModDmgHPMin(prefabID); }
public float GetDmgHPMax(){ return stats.dmgHPMax * GetMulDmgHPMax() + GetModDmgHPMax(); }
public float GetMulDmgHPMax(){ return activeEffectMul.stats.dmgHPMax * PerkManager.GetUnitMulDmgHPMax(prefabID); }
public float GetModDmgHPMax(){ return activeEffectMod.stats.dmgHPMax + PerkManager.GetUnitModDmgHPMax(prefabID); }
public float GetDmgAPMin(){ return stats.dmgAPMin * GetMulDmgAPMin() + GetModDmgAPMin(); }
public float GetMulDmgAPMin(){ return activeEffectMul.stats.dmgAPMin * PerkManager.GetUnitMulDmgAPMin(prefabID); }
public float GetModDmgAPMin(){ return activeEffectMod.stats.dmgAPMin + PerkManager.GetUnitModDmgAPMin(prefabID); }
public float GetDmgAPMax(){ return stats.dmgAPMax * GetMulDmgAPMax() + GetModDmgAPMax(); }
public float GetMulDmgAPMax(){ return activeEffectMul.stats.dmgAPMax * PerkManager.GetUnitMulDmgAPMax(prefabID); }
public float GetModDmgAPMax(){ return activeEffectMod.stats.dmgAPMax + PerkManager.GetUnitModDmgAPMax(prefabID); }
public float GetCritChance(){ return stats.critChance * GetMulCritChance() + GetModCritChance(); }
public float GetMulCritChance(){ return activeEffectMul.stats.critChance * PerkManager.GetUnitMulCritC(prefabID); }
public float GetModCritChance(){ return activeEffectMod.stats.critChance + PerkManager.GetUnitModCritC(prefabID); }
public float GetCritReduc(){ return stats.critReduc * GetMulCritReduc() + GetModCritReduc(); }
public float GetMulCritReduc(){ return activeEffectMul.stats.critReduc * PerkManager.GetUnitMulCritR(prefabID); }
public float GetModCritReduc(){ return activeEffectMod.stats.critReduc + PerkManager.GetUnitModCritR(prefabID); }
public float GetCritMultiplier(){ return stats.critMultiplier * GetMulCritMul() + GetModCritMul(); }
public float GetMulCritMul(){ return activeEffectMul.stats.critMultiplier * PerkManager.GetUnitMulCritM(prefabID); }
public float GetModCritMul(){ return activeEffectMod.stats.critMultiplier + PerkManager.GetUnitModCritM(prefabID); }
public float GetCDmgMul(){ return stats.cDmgMultip * GetMulCDmgMul() + GetModCDmgMul(); }
public float GetMulCDmgMul(){ return activeEffectMul.stats.cDmgMultip * PerkManager.GetUnitMulCDmgMul(prefabID); }
public float GetModCDmgMul(){ return activeEffectMod.stats.cDmgMultip + PerkManager.GetUnitModCDmgMul(prefabID); }
public float GetCHitPenalty(){ return stats.cHitPenalty * GetMulCHitPen() + GetModCHitPen(); }
public float GetMulCHitPen(){ return activeEffectMul.stats.cHitPenalty * PerkManager.GetUnitMulCHitPen(prefabID); }
public float GetModCHitPen(){ return activeEffectMod.stats.cHitPenalty + PerkManager.GetUnitModCHitPen(prefabID); }
public float GetCCritPenalty(){ return stats.cCritPenalty * GetMulCCritPen() + GetModCCritPen(); }
public float GetMulCCritPen(){ return activeEffectMul.stats.cCritPenalty * PerkManager.GetUnitMulCCritPen(prefabID); }
public float GetModCCritPen(){ return activeEffectMod.stats.cCritPenalty + PerkManager.GetUnitModCCritPen(prefabID); }
public float GetODmgMul(){ return stats.oDmgMultip * GetMulODmgMul() + GetModODmgMul(); }
public float GetMulODmgMul(){ return activeEffectMul.stats.oDmgMultip * PerkManager.GetUnitMulODmgMul(prefabID); }
public float GetModODmgMul(){ return activeEffectMod.stats.oDmgMultip + PerkManager.GetUnitModODmgMul(prefabID); }
public float GetOHitPenalty(){ return stats.oHitPenalty * GetMulOHitPen() + GetModOHitPen(); }
public float GetMulOHitPen(){ return activeEffectMul.stats.oHitPenalty * PerkManager.GetUnitMulOHitPen(prefabID); }
public float GetModOHitPen(){ return activeEffectMod.stats.cHitPenalty + PerkManager.GetUnitModOHitPen(prefabID); }
public float GetOCritPenalty(){ return stats.oCritPenalty * GetMulOCritPen() +GetModOCritPen(); }
public float GetMulOCritPen(){ return activeEffectMul.stats.oCritPenalty * PerkManager.GetUnitMulOCritPen(prefabID); }
public float GetModOCritPen(){ return activeEffectMod.stats.oCritPenalty + PerkManager.GetUnitModOCritPen(prefabID); }
public int GetAttackRange(){ return (int)Mathf.Round(stats.attackRange * GetMulARange() + GetModARange()); }
public float GetMulARange(){ return activeEffectMul.stats.attackRange * PerkManager.GetUnitMulARange(prefabID); }
public float GetModARange(){ return activeEffectMod.stats.attackRange + PerkManager.GetUnitModARange(prefabID); }
public int GetAttackRangeMin(){ return (int)Mathf.Round(stats.attackRangeMin * GetMulARangeMin() + GetModARangeMin()); }
public float GetMulARangeMin(){ return activeEffectMul.stats.attackRangeMin * PerkManager.GetUnitMulARangeMin(prefabID); }
public float GetModARangeMin(){ return activeEffectMod.stats.attackRangeMin + PerkManager.GetUnitModARangeMin(prefabID); }
public int GetMoveRange(){ return (int)Mathf.Round(stats.moveRange * GetMulMRange() + GetModMRange()); }
public float GetMulMRange(){ return activeEffectMul.stats.moveRange * PerkManager.GetUnitMulMRange(prefabID); }
public float GetModMRange(){ return activeEffectMod.stats.moveRange + PerkManager.GetUnitModMRange(prefabID); }
public float GetTurnPriority(){ return stats.turnPriority * GetMulTPriority() + GetModTPriority(); }
public float GetMulTPriority(){ return activeEffectMul.stats.turnPriority * PerkManager.GetUnitMulTPrioity(prefabID); }
public float GetModTPriority(){ return activeEffectMod.stats.turnPriority + PerkManager.GetUnitModTPrioity(prefabID); }
public int GetSight(){ return (int)Mathf.Round(stats.sight * GetMulSight() + GetModSight()); }
public float GetMulSight(){ return activeEffectMul.stats.sight * PerkManager.GetUnitMulSight(prefabID); }
public float GetModSight(){ return activeEffectMod.stats.sight + PerkManager.GetUnitModSight(prefabID); }
public bool IsStunned(){ return activeEffectMod.stun; }
public bool AbilityDisabled(){ return activeEffectMod.disableAbility; }
//public bool HasOverwatch(){ return activeEffectMod.overwatch; }
public float GetAttackM(){ return statsMelee.attack * GetAttackMul() + GetAttackMod(); }
public float GetHitM(){ return statsMelee.hit * GetHitMul() + GetHitMod(); }
public float GetDmgHPMinM(){ return statsMelee.dmgHPMin * GetMulDmgHPMin() + GetModDmgHPMin(); }
public float GetDmgHPMaxM(){ return statsMelee.dmgHPMax * GetMulDmgHPMax() + GetModDmgHPMax(); }
public float GetDmgAPMinM(){ return statsMelee.dmgAPMin * GetMulDmgAPMin() + GetModDmgAPMin(); }
public float GetDmgAPMaxM(){ return statsMelee.dmgAPMax * GetMulDmgAPMax() + GetModDmgAPMax(); }
public float GetCritChanceM(){ return statsMelee.critChance * GetMulCritChance() + GetModCritChance(); }
public float GetCritMultiplierM(){ return statsMelee.critMultiplier * GetMulCritMul() +GetModCritMul(); }
public int GetAttackRangeMelee(){ return (int)(statsMelee.attackRange); }
//public int GetAttackRangeMelee(){ return (int)(statsMelee.attackRange *activeEffectMul.stats.attackRange + activeEffectMod.stats.attackRange); }
public bool overwatching;
public bool HasOverwatch(){ return activeEffectMod.overwatch || overwatching; }
[Space(8)] //faction switch
public int tempFacID=-1;
public int tempFacDur=-1;
public bool tempFacControl=false;
public void SwitchFaction(int newFacID, int dur, bool controllable){
tempFacID=newFacID;
tempFacDur=dur;
tempFacControl=controllable;
UnitManager.AddFacSwitchUnit(this);
}
[Space(8)]
public List<int> immuneEffectList=new List<int>();
public Effect activeEffectMod;
public Effect activeEffectMul;
public List<Effect> effectList=new List<Effect>();
public Effect GetEffect(int idx){ return effectList[idx]; }
public void ApplyEffect(List<int> list){
if(list.Count==0) return;
for(int i=0; i<list.Count; i++){
Effect eff=EffectDB.GetPrefab(list[i]).Clone(true);
List<int> immuneList=PerkManager.ModifyUnitImmuneEffectList(prefabID, immuneEffectList);
if(immuneList.Contains(list[i])){
TBTK.TextOverlay("Immuned to "+eff.name, GetPos());
continue;
}
eff.hitVisualEffect.Spawn(GetPos(), Quaternion.identity);
if(eff.activeVisualEffect!=null){
eff.activeVisualEffect=ObjectPoolManager.Spawn(eff.activeVisualEffect, GetPos(), Quaternion.identity);
eff.activeVisualEffect.parent=thisT;
}
eff.durationRemain=eff.duration;
effectList.Add(eff);
ApplyEffectImpact(effectList[effectList.Count-1]);
}
UpdateActiveEffect();
}
public void UpdateActiveEffect(){
activeEffectMod=new Effect(); activeEffectMod.stats.ResetAsModifier();
activeEffectMul=new Effect(); activeEffectMul.stats.ResetAsMultiplier();
for(int i=0; i<effectList.Count; i++){
//if(effectList[i]==null){ effectList.RemoveAt(i); i-=1; continue; }
activeEffectMod.stun|=effectList[i].stun;
activeEffectMod.disableAbility|=effectList[i].disableAbility;
activeEffectMod.overwatch|=effectList[i].overwatch;
if(effectList[i].IsMultiplier()){
activeEffectMul.stats.ApplyMultiplier(effectList[i].stats);
}
else{
activeEffectMod.stats.ApplyModifier(effectList[i].stats);
}
}
}
public void RemoveOverwatch(){
for(int i=0; i<effectList.Count; i++){
if(effectList[i].overwatch){ effectList.RemoveAt(i); break; }
}
UpdateActiveEffect();
}
public void IterateCD(){
bool requireUpdate=false;
for(int i=0; i<effectList.Count; i++){
effectList[i].durationRemain-=1;
if(effectList[i].durationRemain<=0){
if(effectList[i].activeVisualEffect!=null){
ObjectPoolManager.Unspawn(effectList[i].activeVisualEffect);
}
requireUpdate=true;
effectList.RemoveAt(i); i-=1;
continue;
}
ApplyEffectImpact(effectList[i]);
}
if(requireUpdate) UpdateActiveEffect();
if(hp<=0) return; //UnitManager will call the detroy function
for(int i=0; i<abilityList.Count; i++) abilityList[i].IterateCD();
if(tempFacID>=0){
tempFacDur-=1;
if(tempFacDur<=0){
UnitManager.RemoveFacSwitchUnit(this);
tempFacID=-1;
}
}
}
public void ApplyEffectImpact(Effect eff){
if(eff.HasNoImpact()) return;
if(eff.HasPositiveImpact()){
hp=Mathf.Min(hp+eff.GetRandHPModifier(), GetFullHP());
ap=Mathf.Min(ap+eff.GetRandAPModifier(), GetFullAP());
}
if(eff.HasNegativeImpact()){
hp-=eff.GetRandHPModifier() * DamageTable.GetMultiplier(eff.damageType, armorType);
ap-=eff.GetRandAPModifier();
}
}
public void ResetEffect(bool forceUpdate=false){
if(!forceUpdate && effectList.Count==0) return;
effectList.Clear();
UpdateActiveEffect();
}
#region ability
[Space(8)]
public List<int> abilityIDList=new List<int>();
public List<Ability> abilityList=new List<Ability>(); //runtime attribute
public Ability GetAbility(int idx){ return abilityList[idx]; }
//private int selectedAbIdx=-1;
private bool abilityInitiated=false;
public void InitAbility(){
if(abilityInitiated) return;
abilityInitiated=true;
//abilityIDList=new List<int>{ 0, 1 };
abilityList.Clear();
List<int> extraAbIDList=PerkManager.GetUnitAbilityID(prefabID);
abilityIDList.AddRange(extraAbIDList);
for(int i=0; i<abilityIDList.Count; i++) AddAbility(abilityIDList[i]);
}
public void AddAbility(int abPrefabID){
abilityList.Add(AbilityUDB.GetPrefab(abPrefabID).Clone());
abilityList[abilityList.Count-1].Init(this, abilityList.Count-1);
}
public Ability._AbilityStatus SelectAbility(int idx){
Ability._AbilityStatus abilityStatus=abilityList[idx].IsAvailable();
if(abilityStatus!=Ability._AbilityStatus.Ready) return abilityStatus;
//~ int usable=abilityList[idx].IsAvailable();
//~ if(usable!=0) return usable;
if(!abilityList[idx].requireTarget){
//cast ability on self
UseAbility(idx, node);
}
else{
//GridManager.SetupAbilityTargetList(this, abilityList[idx]);
//selectedAbIdx=idx;
AbilityManager.AbilityTargetModeUnit(this, abilityList[idx]);
}
return 0;
}
public void UseAbility(int idx, Node target){
GameControl.UnitUseAbility(this, abilityList[idx], target);
//StartCoroutine(_UseAbility(abilityList[idx], target));
}
public IEnumerator UseAbilityRoutine(Ability ability, Node tgtNode){
bool actionCam=(actionCamCheck!=null && actionCamStart!=null && actionCamCheck(false));
if(actionCam) yield return StartCoroutine(actionCamStart(GetTargetPoint(), tgtNode.GetPos()));
ability.Activate();
if(ability.requireTarget){
if(ability.IsLine()) tgtNode=tgtNode.abLineParent;
//yield return StartCoroutine(AbilityRoutine(target, ability));
while(Rotate(tgtNode.GetPos())>2) yield return null;
if(ability.useAttackSequence){
bool useMelee=CheckUseMeleeAttack(tgtNode);
float attackDelay=AnimPlayAttack(useMelee); AudioPlayAttack(useMelee);
if(attackDelay>0) yield return new WaitForSeconds(attackDelay);
GameObject soObj=ability.shootObject!=null ? ability.shootObject.gameObject : GetShootObject(tgtNode);
//Vector3 offset=new Vector3(0, (ability.IsLine() ? shootPointList[0].position.y-node.GetPos().y : 0), 0);
Vector3 offset=new Vector3(0, shootPointList[0].position.y-node.GetPos().y, 0);
yield return StartCoroutine(FireShootObject(soObj, tgtNode, ability.aimAtUnit & ability.type!=Ability._AbilityType.Line, offset));
}
else{
float animationDelay=AnimPlayAbility(ability.index);
if(animationDelay>0) yield return new WaitForSeconds(animationDelay);
}
}
else{
float animationDelay=AnimPlayAbility(ability.index);
if(animationDelay>0) yield return new WaitForSeconds(animationDelay);
}
yield return CRoutine.Get().StartCoroutine(ability.HitTarget(tgtNode));
//AbilityHit(ability, target);
if(actionCam && actionCamEnd!=null) yield return StartCoroutine(actionCamEnd());
}
//~ public void AbilityHit(Ability ability, Node target){
//~ ability.HitTarget(target);
//~ }
#endregion
void CheckMoveSpeed(){
if(moveSpeed<=0){
Debug.LogError("Setting Error, unit move speed has been set to 0", gameObject);
moveSpeed=1;
}
}
private float Rotate(Vector3 tgtPos){ //for move
if(!enableRotation) return 0;
Quaternion wantedRot=Quaternion.LookRotation(tgtPos-thisT.position);
wantedRot=Quaternion.Euler(0, wantedRot.eulerAngles.y, 0);
if(instantRotate){ thisT.rotation=wantedRot; return 0; }
CheckMoveSpeed();
//turretObject.rotation=Quaternion.Slerp(turretObject.rotation, wantedRot, Time.deltaTime*moveSpeed*2);
thisT.rotation=Quaternion.Slerp(thisT.rotation, wantedRot, Time.deltaTime*moveSpeed*3f);
return Quaternion.Angle(thisT.rotation, wantedRot);
}
public bool Aiming(Vector3 tgtPoint){
if(turretPivot==null) return true;
//float elevation=shootObject.GetElevationAngle(shootPoint[0].position, tgtPoint);
if(!aimInXAxis || barrelPivot!=null) tgtPoint.y=turretPivot.position.y;
Quaternion wantedRot=Quaternion.LookRotation(tgtPoint-turretPivot.position);
//if(elevation!=0 && aimInXAxis && barrelPivot==null) wantedRot*=Quaternion.Euler(elevation, 0, 0);
if(snapAiming) turretPivot.rotation=wantedRot;
else turretPivot.rotation=Quaternion.Lerp(turretPivot.rotation, wantedRot, aimSpeed*Time.deltaTime);
if(!aimInXAxis || barrelPivot==null) return Quaternion.Angle(turretPivot.rotation, wantedRot)<2;
Quaternion wantedRotX=Quaternion.LookRotation(tgtPoint-barrelPivot.position);
//if(elevation!=0) wantedRotX*=Quaternion.Euler(elevation, 0, 0);
if(snapAiming) barrelPivot.rotation=wantedRotX;
else barrelPivot.rotation=Quaternion.Lerp(barrelPivot.rotation, wantedRotX, aimSpeed*Time.deltaTime*2);
return Quaternion.Angle(turretPivot.rotation, wantedRot)<2 & Quaternion.Angle(barrelPivot.rotation, wantedRotX)<2;
}
IEnumerator AimRoutine(Node tgtNode, float duration=3){
while(duration>0){
if(rotateWhileAiming) Rotate(tgtNode.GetPos());
if(Aiming(tgtNode.unit!=null ? tgtNode.unit.GetTargetPoint() : tgtNode.GetPos())) break;
duration-=Time.deltaTime;
yield return null;
}
}
private Quaternion defaultTurretRot;
private Quaternion defaultBarrelRot;
IEnumerator ResetAim(){
while(turretPivot!=null){
turretPivot.localRotation=Quaternion.Lerp(turretPivot.localRotation, defaultTurretRot, aimSpeed*Time.deltaTime);
bool reset=false;
if(barrelPivot!=null){
barrelPivot.localRotation=Quaternion.Lerp(barrelPivot.localRotation, defaultBarrelRot, aimSpeed*Time.deltaTime*2);
reset=(Quaternion.Angle(turretPivot.localRotation, defaultTurretRot)<1 & Quaternion.Angle(barrelPivot.localRotation, defaultBarrelRot)<1);
}
else reset=(Quaternion.Angle(turretPivot.localRotation, defaultTurretRot)<1);
if(reset) break;
yield return null;
}
}
public IEnumerator MoveRoutine(Node targetNode, float speedMul=1){
List<Node> path=AStar.SearchWalkableNode(node, targetNode, AStar.BypassUnitCode(this), canMovePastObs, true);
//List<Node> path=AStar.SearchWalkableNode(node, targetNode, canMovePastUnit, canMovePastObs, true);
//while(path.Count>GetMoveRange()) path.RemoveAt(path.Count-1);
CheckMoveSpeed();
moveThisTurn+=1;
ap-=GameControl.GetAPPerMove()+path.Count*GameControl.GetAPPerNode();
waitingForMoveRoutine=true;
//Debug.Log("MoveRoutine "+path.Count+" "+node.GetPos()+" "+targetNode.GetPos());
Unit cachedUnit=null; //for when unit can goes pass an occupied node
AnimPlayMove(true); AudioPlayMove();
while(path.Count>0){
if(!IsVisible() && !path[0].IsVisible()){
thisT.position=path[0].GetPos();
}
else{
while(Rotate(path[0].GetPos())>5) yield return null;
while(true){
float dist=Vector3.Distance(thisT.position, path[0].GetPos());
if(dist<0.05f) break;
if(enableRotation){
Quaternion wantedRot=Quaternion.LookRotation(path[0].GetPos()-thisT.position);
wantedRot=Quaternion.Euler(0, wantedRot.eulerAngles.y, 0);
thisT.rotation=Quaternion.Slerp(thisT.rotation, wantedRot, Time.deltaTime*moveSpeed*speedMul);
}
Vector3 dir=(path[0].GetPos()-thisT.position).normalized;
thisT.Translate(dir*Mathf.Min(moveSpeed*Time.deltaTime*speedMul, dist), Space.World);
yield return null;
}
}
node.unit=cachedUnit; cachedUnit=null;
node=path[0];
if(path[0].unit!=null) cachedUnit=path[0].unit;
path[0].unit=this;
GridManager.SetupFogOfWar();
UnitManager.CheckAITrigger(this);
yield return StartCoroutine(CheckOverwatch());
if(hp<=0) break; //if destroyed by overwatch
path.RemoveAt(0);
}
AnimPlayMove(false); AudioStopMove();
if(node.collectible!=null) yield return StartCoroutine(node.collectible.Trigger(this));
//ApplyAttack(50, 0);
//UnitManager.SelectUnit(this);
if(IsVisible()) yield return new WaitForSeconds(0.2f);
waitingForMoveRoutine=false;
}
public IEnumerator CheckOverwatch(){
List<Unit> hostileList=UnitManager.GetAllHostileUnits(facID);
for(int i=0; i<hostileList.Count; i++){
if(!hostileList[i].HasOverwatch()) continue;
if(GameControl.UseAPToAttack() && GameControl.GetAPPerAttack()>hostileList[i].ap) continue;
int targetRange=GridManager.GetDistance(node, hostileList[i].node);
if(targetRange>hostileList[i].GetAttackRange()) continue;
float minAttackRange=hostileList[i].GetAttackRangeMin();
if(minAttackRange>0 && targetRange<minAttackRange) continue;
if(requireLOSToAttack && !GridManager.CheckLOS(node, hostileList[i].node, GetSight())) continue;
yield return StartCoroutine(hostileList[i].Overwatch(this));
if(hp<=0) yield break;
}
}
public IEnumerator Overwatch(Unit unit){
yield return StartCoroutine(AttackRoutine(unit.node, false, true));
}
public bool CheckUseMeleeAttack(Node tgtNode){
if(!hasMeleeAttack) return false;
return GridManager.GetDistance(node, tgtNode)<=GetAttackRangeMelee();
}
public GameObject GetShootObject(Node tgtNode){
bool useMelee=CheckUseMeleeAttack(tgtNode);
if(useMelee && soMelee!=null) return soMelee.gameObject;
if(!useMelee && soRange!=null) return soRange.gameObject;
//Debug.LogWarning("No ShootObject - you need to assign shoot-object to unit prefab for attack to work");
return GetDummySO();
//return CheckUseMeleeAttack(tgtNode) ? soMelee.gameObject : soRange.gameObject ;
}
public IEnumerator AttackRoutine(Node targetNode, bool isCounter=false, bool isOverwatch=false){
if(!isOverwatch){
if(!isCounter){
attackThisTurn+=1;
ap-=GameControl.GetAPPerAttack();
}
else{
counterThisTurn+=1;
}
}
else{
RemoveOverwatch();
}
if(!isCounter && !isOverwatch && GameControl.EndMoveAfterAttack()) EndAllAction();
bool actionCam=(actionCamCheck!=null && actionCamStart!=null && actionCamCheck(true));
if(actionCam) yield return StartCoroutine(actionCamStart(GetTargetPoint(), targetNode.GetPos()));
//yield return StartCoroutine(CameraControl.ActionCamFadeIn(GetTargetPoint(), targetNode.GetPos()));
yield return StartCoroutine(AimRoutine(targetNode));
bool useMelee=CheckUseMeleeAttack(targetNode);
float attackDelay=AnimPlayAttack(useMelee); AudioPlayAttack(useMelee);
if(attackDelay>0) yield return new WaitForSeconds(attackDelay);
yield return StartCoroutine(FireShootObject(GetShootObject(targetNode), targetNode, true));
if(targetNode.unit!=null){
Attack attack=new Attack(this, targetNode.unit, null, isCounter, isOverwatch);
//attack.hit=true; attack.crit=true;
if(attack.hit){
if(!useMelee) effectAttackHit.Spawn(targetNode.unit.GetTargetPoint(), Quaternion.identity);
else effectAttackHitMelee.Spawn(targetNode.unit.GetTargetPoint(), Quaternion.identity);
}
bool targetDestroyed=targetNode.unit.ApplyAttack(attack);
//bool targetDestroyed=targetNode.unit.ApplyAttack(this, isCounter, isOverwatch);
if(GameControl.EnableCounterAttack() && !targetDestroyed && !isCounter && !isOverwatch && targetNode.unit.CanCounter(this)){
waitingForCounter=true;
yield return StartCoroutine(targetNode.unit.AttackRoutine(node, true, false));
}
}
if(actionCam && actionCamEnd!=null) yield return StartCoroutine(actionCamEnd());
//yield return StartCoroutine(CameraControl.ActionCamFadeOut());
if(waitingForAttackAnimation){ while(waitingForAttackAnimation) yield return null; }
else yield return new WaitForSeconds(0.2f);
if(turretPivot!=thisT) StartCoroutine(ResetAim());
waitingForCounter=false;
}
public IEnumerator FireShootObject(GameObject soPrefab, Node tgtNode, bool aimAtUnit, Vector3 offset=default(Vector3)){
waitingForHit=true;
for(int i=0; i<shootPointList.Count; i++){
GameObject sObj=(GameObject)Instantiate(soPrefab, shootPointList[i].position, shootPointList[i].rotation);
ShootObject soInstance=sObj.GetComponent<ShootObject>();
if(aimAtUnit && tgtNode.unit!=null){
if(i==shootPointList.Count-1) soInstance.InitShoot(tgtNode.unit, HitCallback, shootPointList[i]);
else soInstance.InitShoot(tgtNode.unit, null, shootPointList[i]);
}
else{
if(i==shootPointList.Count-1) soInstance.InitShoot(tgtNode, HitCallback, shootPointList[i], offset);
else soInstance.InitShoot(tgtNode, null, shootPointList[i], offset);
}
if(i<shootPointList.Count-1) yield return new WaitForSeconds(shootPointSpacing);
}
while(waitingForHit) yield return null;
}
private bool waitingForMoveRoutine=false;
private bool waitingForCounter=false;
private bool waitingForHit=false;
public void HitCallback(){ waitingForHit=false; }
//public bool ApplyAttack(Unit srcUnit, bool isCounter, bool isOverwatch){
public bool ApplyAttack(Attack attack){
if(!playableUnit && !triggered) triggered=true;
//Attack attack=new Attack(srcUnit, this, null, isCounter, isOverwatch);
Overlay(attack);
bool destroyed=_ApplyAttack(attack.damageHP, attack.damageAP);
if(!destroyed) ApplyEffect(attack.srcUnit.GetRuntimeAttackEffectIDList());
return destroyed;
}
public bool ApplyAttack(Ability srcAbility){
bool destroyed=false;
bool applyEffect=Rand.value()<srcAbility.GetEffHitChance();
if(srcAbility.clearAllEffect) ResetEffect();
if(srcAbility.HasNegativeImpact()){
if(srcAbility.factorInTargetStats){
Attack attack=new Attack(srcAbility, this); Overlay(attack);
destroyed=_ApplyAttack(attack.damageHP, attack.damageAP);
}
else{
float dmg=srcAbility.GetRandHPModifier() * DamageTable.GetMultiplier(srcAbility.damageType, armorType);
TBTK.TextOverlay(Mathf.Round(dmg).ToString("f0"), GetPos());
_ApplyAttack(dmg);
ap-=srcAbility.GetRandAPModifier();
}
}
else if(srcAbility.HasPositiveImpact()){
hp=Mathf.Min(hp+srcAbility.GetRandHPModifier(), GetFullHP());
ap=Mathf.Min(ap+srcAbility.GetRandAPModifier(), GetFullAP());
}
if(!destroyed && applyEffect){
ApplyEffect(srcAbility.GetRuntimeEffectIDList());
if(srcAbility.switchFaction && srcAbility.facID!=facID){
SwitchFaction(srcAbility.facID, srcAbility.GetDuration(), srcAbility.switchFacControllable);
}
}
return destroyed;
}
public bool _ApplyAttack(float damageHP, float damageAP=0){
if(damageAP>0) ap=Mathf.Max(0, ap-damageAP);
if(damageHP<=0) return false;
AnimPlayHit(); AudioPlayHit();
hp-=damageHP;
if(hp<=0){
hp=0;
StartCoroutine(DestroyRoutine());
}
return hp<=0;
}
public void Overlay(Attack attack){ StartCoroutine(_Overlay(attack)); }
public IEnumerator _Overlay(Attack attack){
if(attack.hit){
if(attack.damageAP>0){
if(attack.crit) TBTK.TextOverlay("<i>Critical -"+Mathf.Round(attack.damageAP).ToString("f0")+"AP</i>", GetPos()+new Vector3(0, .5f, 0));
else TBTK.TextOverlay("<i>-"+Mathf.Round(attack.damageAP).ToString("f0")+"AP</i>", GetPos()+new Vector3(0, .5f, 0));
yield return new WaitForSeconds(0.15f);
}
if(attack.damageHP>0){
if(attack.crit) TBTK.TextOverlay("Critical -"+Mathf.Round(attack.damageHP).ToString("f0")+"HP", GetPos());
else TBTK.TextOverlay("-"+Mathf.Round(attack.damageHP).ToString("f0")+"HP", GetPos());
}
}
else TBTK.TextOverlay("Missed", GetPos());
yield return null;
}
[Space(8)] public bool dead=false;
public IEnumerator DestroyRoutine(){
node.unit=null;
UnitManager.UnitDestroyed(this);
//yield return null; //wait a frame for all on-going coroutine to end
while(waitingForCounter) yield return null;
while(waitingForMoveRoutine) yield return null;
effectOnDestroyed.Spawn(GetTargetPoint());
float delay=Mathf.Max(AudioPlayDestroy(), AnimPlayDestroyed());
if(delay>0) yield return new WaitForSeconds(delay);
yield return null;
Destroy(thisObj);
}
public bool IsAllActionCompleted(){
if(hp<=0) return true;
if(CanMove()) return false;
if(CanAttack()) return false;
for(int i=0; i<abilityList.Count; i++){
if(abilityList[i].IsAvailable()==0) return false;
}
return true;
}
public void EndAllAction(){
ap=0;
moveThisTurn=(int)stats.moveLimit;
//attackThisTurn=(int)stats.attackLimit; //dont end attack incase the unit can do two attack in a single turn
abilityThisTurn=(int)stats.abilityLimit;
}
public bool IsVisible(){ return thisObj.layer!=TBTK.GetLayerInvisible(); }
private static GameObject dummySO;
public static GameObject GetDummySO(){
if(dummySO==null){
dummySO=new GameObject("Dummy ShootObject");
ShootObject so=dummySO.AddComponent<ShootObject>();
so.type=ShootObject._Type.Effect;
so.effectDuration=0.25f;
}
return dummySO;
}
[Header("Visual Effects")]
public VisualObject effectAttackHit=new VisualObject();
public VisualObject effectAttackHitMelee=new VisualObject();
public VisualObject effectOnDestroyed=new VisualObject();
#region animation
[Header("Animation")]
public Transform animatorT;
protected Animator animator;
[Space(5)]
public AnimationClip clipIdle;
public AnimationClip clipMove;
public AnimationClip clipHit;
public AnimationClip clipDestroyed;
public AnimationClip clipAttackRange;
public AnimationClip clipAttackMelee;
public float animAttackDelayRange=0;
public float animAttackDelayMelee=0;
public List<AnimationClip> clipAbilityList=new List<AnimationClip>();
public List<float> animAbilityDelayList=new List<float>();
private void InitAnimation(){
if(animatorT!=null) animator=animatorT.GetComponent<Animator>();
if(animator==null) return;
AnimatorOverrideController aniOverrideController = new AnimatorOverrideController();
aniOverrideController.runtimeAnimatorController = animator.runtimeAnimatorController;
animator.runtimeAnimatorController = aniOverrideController;
if(clipIdle!=null) aniOverrideController["Idle"] = clipIdle;
if(clipMove!=null) aniOverrideController["Move"] = clipMove;
if(clipHit!=null) aniOverrideController["Hit"] = clipHit;
if(clipAttackRange!=null) aniOverrideController["AttackRange"] = clipAttackRange;
if(clipAttackMelee!=null) aniOverrideController["AttackMelee"] = clipAttackMelee;
if(clipDestroyed!=null) aniOverrideController["Destroyed"] = clipDestroyed;
for(int i=0; i<6; i++){
if(clipAbilityList[i]==null) continue;
aniOverrideController["Ability"+(i+1)] = clipAbilityList[i];
}
}
private float AnimPlayAbility(int idx){
Debug.Log("AnimPlayAbility "+idx);
if(animator==null || idx<0 || clipAbilityList.Count<=idx || clipAbilityList[idx]==null) return 0;
animator.SetTrigger("Ability"+(idx+1));
return animAbilityDelayList.Count>idx ? animAbilityDelayList[idx] : 0;
}
private void AnimPlayMove(bool moving){
if(animator!=null) animator.SetBool("Moving", moving);
}
private void AnimPlayHit(){
if(animator!=null && clipHit!=null) animator.SetTrigger("Hit");
}
private float AnimPlayDestroyed(){
if(animator==null || clipDestroyed==null) return 0;
if(clipDestroyed!=null) animator.SetBool("Destroyed", true);
return clipDestroyed!=null ? clipDestroyed.length : 0 ;
}
private float AnimPlayAttack(bool isMelee){
if(isMelee){
if(animator==null || clipAttackMelee==null) return 0;
if(clipAttackMelee!=null){
animator.SetTrigger("AttackMelee");
StartCoroutine(WaitingForAttackAnimation(clipAttackMelee.length));
}
return animAttackDelayMelee;
}
else{
if(animator==null || clipAttackRange==null) return 0;
if(clipAttackRange!=null){
animator.SetTrigger("AttackRange");
StartCoroutine(WaitingForAttackAnimation(clipAttackRange.length));
}
return animAttackDelayRange;
}
}
private bool waitingForAttackAnimation=false;
IEnumerator WaitingForAttackAnimation(float duration){
waitingForAttackAnimation=true;
yield return new WaitForSeconds(duration);
waitingForAttackAnimation=false;
}
//private int attackCounter=0;
#endregion
#region animation
[Header("Audio")]
public bool loopMoveSound=false;
private AudioSource audioSrc;
public AudioClip selectSound;
public AudioClip moveSound;
public AudioClip attackRangeSound;
public AudioClip attackMeleeSound;
public AudioClip hitSound;
public AudioClip destroySound;
private void InitAudio(){
if(!loopMoveSound || audioSrc!=null) return;
audioSrc=gameObject.AddComponent<AudioSource>();
audioSrc.playOnAwake=false; audioSrc.loop=true; audioSrc.volume=1; //src.spatialBlend=.75f;
audioSrc.clip=moveSound;
}
private void AudioPlayMove(){
if(moveSound==null) return;
if(loopMoveSound){
InitAudio();
audioSrc.Play();
return;
}
else AudioManager.PlaySound(moveSound, GetPos());
}
private void AudioStopMove(){
if(audioSrc!=null && moveSound!=null) audioSrc.Stop();
}
public void AudioPlaySelect(){
if(selectSound!=null) AudioManager.PlaySound(selectSound, GetPos());
}
public void AudioPlayAttack(bool isMelee){
if(!isMelee && attackRangeSound!=null) AudioManager.PlaySound(attackRangeSound, GetPos());
else if(attackMeleeSound!=null) AudioManager.PlaySound(attackMeleeSound, GetPos());
}
public void AudioPlayHit(){
if(hitSound!=null) AudioManager.PlaySound(hitSound, GetPos());
}
public float AudioPlayDestroy(){
if(destroySound==null) return 0;
AudioManager.PlaySound(destroySound, GetPos());
return destroySound.length;
}
#endregion
}
}