using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using UnityEngine.SceneManagement; /// /// Załozenia na przyszłość /// - player atakując powinien wysyłać informacje o akcji ataku z wartością obrażeń /// enemy przymując ją powinien kalkulować szanse uniku / bloku (i odskakiwać w bokj lub do tyłu) albo przyjmować obrazenia /// WSZĘDZIE ANIMACJA blokuąca inne akcje na czas odtwarzania efektu /// public enum WizardBattleStepsEnum { First, // have 100% hp, lasts until the value will be reduced to 7%% Second, // have 75%% hp, lasts until the value will be reduced to 50% Third, // have 50%% hp, lasts until the value will be reduced to 25% Last, Summoning, // after reducing hp to 75, 50, 25 per cent wizard start to summon minions Escaping // when player have 25% he starts escape - go to teleport } [RequireComponent(typeof(NPC))] [RequireComponent(typeof(AStarPathfindingAgent))] class BattleWizard : MonoBehaviour { public const string BATTLE_STATE = "BattleState"; public WizardBattleStepsEnum BattleState = WizardBattleStepsEnum.First; [Header("Health")] public float defence = 0; public int maxHealth = 100; public float currentHealth; public bool canTakeDamage = true; public float attackValue = 5; public float attackingRadius = 6; [Header("Summoned Minions")] public GameObject MinionRespowner; public int SummonedMinionsCounter = 3; public int KilledMinionsCounter = 0; public bool IsAfterSummoning = false; [Header("Step points")] public bool approaching = false; // mean - we can go closer to Player [Space] public int CurrentPoint = 0; public Transform[] Points; public Vector3 TargetPosition; [Space] [Header("Attacking Logic")] public bool hit = false; public bool firstAttack = false; public float timerDmg = 0f; public float timeToWaitBeforeNextAttack = 1.0f; // time which npc must wait before he can atack player again public float timerHit = 0f; public float timeToWaitBeforeNextHitFromPlayer = 0.55f; // should be moved to player script !!!! public bool isPanelEnabled = true; // flag about some panel status.... (probably youDied / respown) private void Start() { MinionRespowner.GetComponent().Respown = false; MinionRespowner.GetComponent().Counter = 0; currentHealth = maxHealth; if (HasProggress()) { BattleState = GetProggress(); switch(BattleState) { case WizardBattleStepsEnum.First: { // to do nothing to do here break; } case WizardBattleStepsEnum.Second: { BattleState = WizardBattleStepsEnum.First; // go one stop earlier to propertly go throught Escaping and Summoning gameObject.transform.position = Points[0].position; currentHealth = maxHealth * 0.75f; SummonedMinionsCounter = 3; CurrentPoint = 1; break; } case WizardBattleStepsEnum.Third: { BattleState = WizardBattleStepsEnum.Second; gameObject.transform.position = Points[1].position; currentHealth = maxHealth * 0.50f; SummonedMinionsCounter = 6; CurrentPoint = 2; break; } case WizardBattleStepsEnum.Last: { BattleState = WizardBattleStepsEnum.Third; gameObject.transform.position = Points[2].position; currentHealth = maxHealth * 0.25f; SummonedMinionsCounter = 12; CurrentPoint = 3; break; } } } else { gameObject.GetComponent().State = NPCStateEnum.None; BattleState = WizardBattleStepsEnum.First; CurrentPoint = 0; SummonedMinionsCounter = 3; // deffault value at start } defecnceCalculate(); speedCalculate(); // control battle proggress ManageBehaviourScenario(); } private void Update() { if (gameObject.GetComponent().State == NPCStateEnum.Walking) { var dir = TargetPosition - transform.position; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; dir.Normalize(); gameObject.GetComponent().SetBool("isRunning", new Vector2( dir.x, dir.y) != Vector2.zero); } // taking demage by time logic TakingDamageManagment(); // take action based on npc state HandleState(); // config battle depending on state if (IsAfterSummoning) { //wait for killing minions by player if (MinionRespowner.GetComponent().killedMinions >= SummonedMinionsCounter * 3/5f) { SummonedMinionsCounter *= 2; MinionRespowner.GetComponent().killedMinions = 0; IsAfterSummoning = false; if ((WizardBattleStepsEnum)GetProggress() == WizardBattleStepsEnum.First) BattleState = WizardBattleStepsEnum.Second; else if ((WizardBattleStepsEnum)GetProggress() == WizardBattleStepsEnum.Second) BattleState = WizardBattleStepsEnum.Third; else if ((WizardBattleStepsEnum)GetProggress() == WizardBattleStepsEnum.Third) BattleState = WizardBattleStepsEnum.Last; approaching = true; // flag whivh tell to go in player direction canTakeDamage = true; gameObject.GetComponent().State = NPCStateEnum.Walking; SaveProggress(BattleState); } } } public void OnTriggerEnter2D(Collider2D collision) { // after reaching teleport position remove component if (collision.gameObject.tag == "SceneTransition") { collision.gameObject.GetComponent().isEnabled = true; SaveProggress(WizardBattleStepsEnum.First); Destroy(gameObject); } // Hit logic if (collision.gameObject.tag == "AttackHitbox" || collision.gameObject.tag == "PickaxeHitbox") { hit = true; } } public void OnTriggerExit2D(Collider2D collision) { // Hit logic if (collision.gameObject.tag == "AttackHitbox" || collision.gameObject.tag == "PickaxeHitbox") { timerDmg = 0f; hit = false; } } // script controlling battle scenario by listening on change, depending on: // - Wizard life // - Wizard state // - battle state public void ManageBehaviourScenario() { // at first if (BattleState == WizardBattleStepsEnum.First && gameObject.GetComponent().State == NPCStateEnum.None) { Debug.Log("First"); approaching = true; gameObject.GetComponent().State = NPCStateEnum.Walking; // HandleState make rest - makes sure the wizard walks up to the player and cxhange state to attacking } else // detect health status after each taked damage from player (invoked in TakeDamage) if ((BattleState == WizardBattleStepsEnum.First || BattleState == WizardBattleStepsEnum.Second || BattleState == WizardBattleStepsEnum.Third) && (gameObject.GetComponent().State & NPCStateEnum.Attacking) > 0) { if (IsAfterSummoning == true) return; } else // 1 Change state to pending & block damage taking & go to safe position if (BattleState == WizardBattleStepsEnum.Escaping) { gameObject.GetComponent().speed = 5f; canTakeDamage = false; approaching = false; if (currentHealth <= maxHealth * 0.10f) // summon before { Debug.Log("Wizard HP critical"); TargetPosition = Points[Points.Count() - 1].position; SummonManagment(); } else { TargetPosition = Points[CurrentPoint].position; CurrentPoint++; } gameObject.GetComponent().State = NPCStateEnum.Walking; // go to newxt base point } else if (BattleState == WizardBattleStepsEnum.Summoning) { if(!IsAfterSummoning) { speedCalculate(); SummonManagment(); } } } public void HandleState() { switch (gameObject.GetComponent().State) { case NPCStateEnum.Walking: // to player, to next point, to exit xd { WalkingManagment(); break; } case NPCStateEnum.Attacking: { gameObject.GetComponent().SetBool("isRunning", false); AttackManagment(); break; } case NPCStateEnum.Pending: { gameObject.GetComponent().SetBool("isRunning", false); BattleState = WizardBattleStepsEnum.Summoning; ManageBehaviourScenario(); break; } default: { break; } } } public void WalkingManagment() { StopAllCoroutines(); if(approaching) { // when we can go closer to player - we go closer and when we are close enought we start attacking if (!IsInAttackRadious()) { gameObject.GetComponent().FindPath(); StartCoroutine(gameObject.GetComponent().FollowPath()); } else { timeToWaitBeforeNextAttack = 0; // to allowa to first hit withot waiting // in this script we set attacking mode gameObject.GetComponent().State = NPCStateEnum.Attacking; gameObject.GetComponent().path.Clear(); // if we are able to talgk we dont want go go further player } } else { // we summon minions and go to next save point, else we escape // IMPOSRANT make summoning and walking independent and unbinded in code // first focus on targetting next step from list //Debug.Log(Vector2.Distance(transform.position, TargetPosition)); if (Vector2.Distance(transform.position, TargetPosition) > 0.95f) // count value - path finding stop moving them before reachin position well.. { gameObject.GetComponent().point = TargetPosition; gameObject.GetComponent().FindPoint(); StartCoroutine(gameObject.GetComponent().FollowPath()); } else { // ------ anim.SetBool("isRunning", false); gameObject.GetComponent().SetBool("isRunning", false); // set next point for future gameObject.GetComponent().State = NPCStateEnum.Pending; // decide what next } } } public void SummonManagment() { // 2. Spown minions (depending on iteration increase spowned amount BlockRespowners(); MinionRespowner.GetComponent().Counter = SummonedMinionsCounter; MinionRespowner.GetComponent().Respown = true; IsAfterSummoning = true; Debug.Log("After summoning"); } #region damage managment /// /// Take damage only once in a time and only when IT IS ALLOWED /// public void TakingDamageManagment() { // COPIED FROM Following Enemy scripts // Taking hit logic timerHit += Time.deltaTime; if (hit == true && canTakeDamage) { if (timerHit >= timeToWaitBeforeNextHitFromPlayer) { TakeDamage(PlayerPrefs.GetFloat("attackValue")); hit = false; timerHit = 0f; TakeKnockback(); } } } private void TakeDamage(float damage) { var healthBeforeDamage = currentHealth; damage = damage - defence; damage = damage < 0 ? 0 : damage; Debug.Log("Gamage from player: " + damage + "(defence: " + defence + ")"); currentHealth -= damage; /* if (gameObject.GetComponent().health <= 0) { gameObject.SetActive(false); gameObject.GetComponent().isKilled = 1; GameObject.FindGameObjectWithTag("Player").GetComponent().GetExp(30); // pass info about killing assigned enemy to mission manager listener // pass enemy name from script NOT object name (thats allow to have many different objects variantsa with this same aggregate key (private name - not preffab name) ) ConditionManager.Instance.UpdateKillCondition(gameObject.GetComponent().MinionName); }*/ if ( (healthBeforeDamage > maxHealth * 0.75f && currentHealth <= maxHealth * 0.75f) || (healthBeforeDamage > maxHealth * 0.50f && currentHealth <= maxHealth * 0.50f) || (healthBeforeDamage > maxHealth * 0.25f && currentHealth <= maxHealth * 0.25f) || (healthBeforeDamage > maxHealth * 0.10f && currentHealth <= maxHealth * 0.10f) ){ Debug.Log("escaping"); BattleState = WizardBattleStepsEnum.Escaping; Debug.Log(BattleState); // re-calculate after each damage defecnceCalculate(); speedCalculate(); ManageBehaviourScenario(); } } private void TakeKnockback() { Rigidbody2D enemy = gameObject.GetComponent(); Rigidbody2D player = GameObject.FindGameObjectWithTag("Player").GetComponent(); if (enemy != null) { enemy.isKinematic = false; Vector2 difference = enemy.transform.position - player.transform.position; difference = difference.normalized * 5; // thrust enemy.AddForce(difference, ForceMode2D.Impulse); //StartCoroutine(KnockCo(enemy)); } } #endregion #region attack managment public void AttackManagment() { // if during Attacking mode player GO OUT of the attacking radious if (!IsInAttackRadious()) { gameObject.GetComponent().State = NPCStateEnum.Walking; return; } // Attack logic if (timerDmg >= timeToWaitBeforeNextAttack) { timerDmg = 0f; GameObject.FindGameObjectWithTag("Player").GetComponent().TakeDamage( attackValue, isPanelEnabled ); } speedCalculate(); // to restore property timeToWaitBeforeNextAttack walue timerDmg += Time.deltaTime; } public bool IsInAttackRadious() { if (Vector2.Distance(GameObject.FindGameObjectWithTag("Player").transform.position, transform.position) >= attackingRadius) return false; return true; } #endregion // jesli gracz ma "x" pkt siły // Ze wzgledu na wyniki EXCEL'a zakładajac że // - gracz ma 4/5 pkt sily (skile + bonus naszyjnika) // obrona nie moze przekroczyc 3 - 3.2f public void defecnceCalculate() { if (currentHealth > maxHealth * 0.75f) { defence = 1.5f; }else if(currentHealth <= maxHealth * 0.75f && currentHealth > maxHealth * 0.50f) { defence = 2.5f; } else if (currentHealth <= maxHealth * 0.50f && currentHealth > maxHealth * 0.25f) { defence = 3.15f; } else if (currentHealth <= maxHealth * 0.25f && currentHealth > maxHealth * 0.10f) { defence = 4f; // or higher ??? } } public void speedCalculate() { if (currentHealth > maxHealth * 0.75f) { gameObject.GetComponent().speed = 1f; timeToWaitBeforeNextAttack = 1f; } else if (currentHealth <= maxHealth * 0.75f && currentHealth > maxHealth * 0.50f) { gameObject.GetComponent().speed = 1.15f; timeToWaitBeforeNextAttack = 0.8f; } else if (currentHealth <= maxHealth * 0.50f && currentHealth > maxHealth * 0.25f) { gameObject.GetComponent().speed = 1.30f; timeToWaitBeforeNextAttack = 0.7f; } else if (currentHealth <= maxHealth * 0.25f && currentHealth > maxHealth * 0.10f) { gameObject.GetComponent().speed = 1.45f; timeToWaitBeforeNextAttack = 0.6f; } } #region Respown minions managments private void BlockRespowners() { ResetRespowners(); if (SummonedMinionsCounter == 3) // use only 1 { MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(1).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(2).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(3).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(4).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(5).GetComponent().Blocked = true; } if (SummonedMinionsCounter == 6) // use 2 & 3 { MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(0).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(3).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(4).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(5).GetComponent().Blocked = true; } if (SummonedMinionsCounter == 12) // use 2 & 4 & 5 { MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(0).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(1).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(5).GetComponent().Blocked = true; } if (SummonedMinionsCounter > 12) // use 4 & 5 & 6 { SummonedMinionsCounter = 15; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(0).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(1).GetComponent().Blocked = true; MinionRespowner.GetComponent().respownPoints.ElementAtOrDefault(2).GetComponent().Blocked = true; } } private void ResetRespowners() { foreach(var respownPointObject in MinionRespowner.GetComponent().respownPoints) { respownPointObject.GetComponent().Blocked = false; } } #endregion #region proggress API public void SaveProggress(WizardBattleStepsEnum status) { Debug.Log("Save: " + status); PlayerPrefs.SetInt(SceneManager.GetActiveScene().name + "." + BATTLE_STATE, (int)status); } public WizardBattleStepsEnum GetProggress() { return (WizardBattleStepsEnum)PlayerPrefs.GetInt(SceneManager.GetActiveScene().name + "." + BATTLE_STATE); } public bool HasProggress() { return PlayerPrefs.HasKey(SceneManager.GetActiveScene().name + "." + BATTLE_STATE); } #endregion }