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 shootPointList=new List(); 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 attackEffectIDList=new List(); public List 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 immuneEffectList=new List(); public Effect activeEffectMod; public Effect activeEffectMul; public List effectList=new List(); public Effect GetEffect(int idx){ return effectList[idx]; } public void ApplyEffect(List list){ if(list.Count==0) return; for(int i=0; i 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=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 abilityIDList=new List(); public List abilityList=new List(); //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{ 0, 1 }; abilityList.Clear(); List extraAbIDList=PerkManager.GetUnitAbilityID(prefabID); abilityIDList.AddRange(extraAbIDList); for(int i=0; i2) 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 path=AStar.SearchWalkableNode(node, targetNode, AStar.BypassUnitCode(this), canMovePastObs, true); //List 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 hostileList=UnitManager.GetAllHostileUnits(facID); for(int i=0; ihostileList[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 && targetRange0) 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(); 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(i0) 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("Critical -"+Mathf.Round(attack.damageAP).ToString("f0")+"AP", GetPos()+new Vector3(0, .5f, 0)); else TBTK.TextOverlay("-"+Mathf.Round(attack.damageAP).ToString("f0")+"AP", 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(); 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 clipAbilityList=new List(); public List animAbilityDelayList=new List(); private void InitAnimation(){ if(animatorT!=null) animator=animatorT.GetComponent(); 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(); 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 } }