using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.SceneManagement; using static SpellSO; using static SpellManagement; using Unity.VisualScripting; using UnityEditor.Experimental.GraphView; /// <summary> /// Primary logic and handler for battles /// </summary> public class BattleHandler : MonoBehaviour { public static BattleHandler Instance; // Will be apart of the singleton object with the Game Manager private EnemyUI uiHandler; // Reference to the EnemyUI that is currently active within the scene private PositionHandler pHandler; // Reference to the position handler within the scene private SpellManagement spellManager; // Reference to the spell manager that is within the same object as the battle handler private ConsumableManagement consumableManager; // Reference to the consumable manager that is within the same object as the battle handler private GameObject playerObject; // Container for the player game object private Player playerComponent; private GameObject[] allyObjects; // Container for the ally game objects private GameObject[] enemyObjects; // Container for the enemy game objects private Enemy[] enemyComps; // Containers for the enemy components within the game objects above private Ally[] allyComps; // Containers for the ally components within the game objects above private Ally[] allyMinionComps; private GameObject[] allyMinionObjects; private Enemy[] enemyMinionComps; private GameObject[] enemyMinionObjects; private OrderOfCombat combatOrder; // Reference to the Combat Order creator private List<Entity> order; // Order of entities for battling private Entity currentEntity; private List<Entity> deadEntities; // Collection of all of the dead entities private Entity previousEntity; // Reference to the previous entity in the turn order private int activeEntity; // Current entity reference that is battling private bool stillBattling; // Condition indicator for if the battle is still occurring private int TurnCounter; // Counter for how many turns have passed for the battle private Task entityDestruction; private bool? bountyEnabled; private int currentAttack; private const short DoubleDice = 2; // Double value for when double dice is called for when critting private const short CriticalHit = 20; // The base crit value that indicates when a critical hit has been struck /// <summary> /// Retrieval for the EnemyUI Handler /// </summary> /// <returns></returns> public EnemyUI GetUIHandler() => uiHandler; /// <summary> /// Retrieves the value of the turn counter /// </summary> /// <returns>Turn Counter Value</returns> public int GetTurnCounter() => TurnCounter; /// <summary> /// Sets the EnemyUI once the EnemyUI is enabled within the game scene /// </summary> /// <param name="handler">The currently active EnemyUI</param> public void SetHandler(EnemyUI handler) => uiHandler = handler; /// <summary> /// Call to set the battling status of the current encounter /// </summary> /// <param name="trigger">Set trigger</param> public void SetBattlingStatus(bool trigger) => stillBattling = trigger; public bool GetBattlingStatus() => stillBattling; /// <summary> /// Retrieves the enemy components from the handler /// </summary> /// <returns>The current enemy components that are not destroyed</returns> public Enemy[] RetrieveEnemyStates() => enemyComps; /// <summary> /// Gather components /// </summary> private void Awake() { Instance = this; spellManager = GetComponent<SpellManagement>(); consumableManager = GetComponent<ConsumableManagement>(); pHandler = PositionHandler.Instance; entityDestruction = Task.CompletedTask; } /// <summary> /// Sets the positions of all entities in the scene in the position handler and acquires the game objects /// for the enemies, allies, and player in the process /// </summary> /// <param name="player">The player to instantiate and set</param> /// <param name="enemies">The enemies to instantiate and set</param> /// <param name="allies">The allies to be instantiated and set</param> /// <returns>Array of the enemy and ally object components that were instantiated</returns> public Enemy[] CallForEntityPositioning(GameObject[] enemies, bool isBoss) { playerObject = GameManager.Instance.GetPlayerObject(); playerComponent = playerObject.GetComponent<Player>(); playerComponent.ReactivateVisually(); (enemyObjects, enemyComps) = pHandler.PositionEnemies(enemies, this, isBoss); allyObjects = GameManager.Instance.GetAllyObjects(); if (allyObjects != null) { foreach (GameObject allyObject in allyObjects) allyObject.GetComponent<Ally>().ReactivateVisually(); allyComps = allyObjects.Select(ally => ally.GetComponent<Ally>()).ToArray(); PhyllSurpriseCheck(); } allyComps ??= new Ally[0]; return enemyComps; void PhyllSurpriseCheck() { if (allyComps.Any(ally => ally.GetName().Equals("Phyll"))) { Ally phyllComp = allyComps.Where(ally => ally.GetName().Equals("Phyll")).FirstOrDefault(); if (phyllComp.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[57])) { Ability abRef = phyllComp.GetInventory().Abilities.Where(ability => ability.AbilityID is 57).FirstOrDefault(); phyllComp.HideEntity(); abRef.active = true; } } } } /// <summary> /// Calculates the rolling to hit for the attacker /// </summary> /// <param name="entityPosition">The position of the component arrays</param> /// <param name="attacker">The attacking entity</param> /// <param name="isHostileAttack">Trigger for if the enemy component array needs to be searched or the ally component array</param> /// <returns></returns> public async void RollToHit(int entityPosition, Entity attacker, bool isHostileAttack, int attackPerformed) { currentAttack = attackPerformed; SortedDictionary<int, (int, Dice)> ToHitRolls = new SortedDictionary<int, (int, Dice)>(); SortedDictionary<int, (int, Dice)> DamageRolls = new SortedDictionary<int, (int, Dice)>(); // Determines if the enemy target is a minion or a true enemy Entity target; if (entityPosition is -1) target = playerComponent; else if (isHostileAttack) { try { target = allyComps[entityPosition]; } catch { target = allyMinionComps[entityPosition - allyComps.Length]; } } else { try { target = enemyComps[entityPosition]; } catch { target = enemyMinionComps[entityPosition - enemyObjects.Length]; } } ExtractAllAvailableEntities(out Ally[] allyAbilityChecks, out Enemy[] enemyAbilityChecks); (bool isDisadvantaged, bool isAdvantaged, bool autoHit) = AbilityBrain.PreToHitRollCheck(attacker, target, allyAbilityChecks, enemyAbilityChecks); if (!autoHit) { int critRange = CriticalHit - attacker.GatherStats().GetStat(EntityStats.Stat.ToCritMod); int toHitValue = DiceRoller.RollDice(new DTwenty()); ToHitRolls.Add(0, (toHitValue, new DTwenty())); if (isDisadvantaged && !isAdvantaged) { int newToHitRoll = DiceRoller.RollDice(new DTwenty()); ToHitRolls.Add(1, (newToHitRoll, new DTwenty())); if (newToHitRoll < toHitValue) toHitValue = newToHitRoll; } else if (isAdvantaged && !isDisadvantaged) { int newToHitRoll = DiceRoller.RollDice(new DTwenty()); ToHitRolls.Add(1, (newToHitRoll, new DTwenty())); if (newToHitRoll > toHitValue) toHitValue = newToHitRoll; } bool hasCrit = false; if (toHitValue >= critRange || target.GatherModList().SmartModCheck("Sleeping") || target.GatherModList().SmartModCheck("Paralyzed")) hasCrit = true; else toHitValue += attacker.GatherStats().GetStat(EntityStats.Stat.Dexterity); // Abilities activate at the beginning of the turn Dictionary<string, (List<string>, List<object>)> triggers = AbilityBrain.PreTurnCompletionCheck(order[activeEntity], target, playerObject.activeSelf ? playerComponent : null, allyAbilityChecks, enemyAbilityChecks, ToHitRolls, DamageRolls, ref hasCrit); if (attacker.GatherStats().GetStat(EntityStats.Stat.ToHit) is not 0) { int toHitMod = attacker.GatherStats().GetStat(EntityStats.Stat.ToHit); if (triggers.ContainsKey("ToHitAdd")) { triggers["ToHitAdd"].Item1.Add("To Hit Modifier"); triggers["ToHitAdd"].Item2.Add(toHitMod); } else triggers.Add("ToHitAdd", (new List<string> { "To Hit Modifier" }, new List<object> { toHitMod })); } // Plays player attack animation if (attacker is Player player) { toHitValue += player.ParseToHitClassModifiers(target, triggers, ToHitRolls); player.InitiateAttack(); } // If the attacking entity is exhausted, decrease the to hit value by a roll of a four dice if (attacker.GatherModList().SmartModCheck("Exhausted")) { int rolledValue = UtilityHandler.RollValue(Dice.DiceR.Four); toHitValue -= rolledValue; int currentDice = ToHitRolls.Last().Key; ToHitRolls.Add(++currentDice, (-rolledValue, new DFour())); } int armorValue = target.GatherStats().GetStat(EntityStats.Stat.Armor, target.RetrieveArmor()); ArmorTriggerSearch(triggers, ref armorValue, ref toHitValue); uiHandler.ResetUI(); if (toHitValue >= armorValue || hasCrit) { if (attacker.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[42])) { if (DamageRolls.Count > 0) { int currentDice = DamageRolls.Last().Key; DamageRolls.Add(++currentDice, (UtilityHandler.RollValue(Dice.DiceR.Six), new DSix())); } else DamageRolls.Add(0, (UtilityHandler.RollValue(Dice.DiceR.Six), new DSix())); } GeneralUI.Instance.RollToHit(true, hasCrit, ToHitRolls, attacker, target, toHitValue, armorValue, DamageRolls, triggers, attackPerformed == attacker.AttackCount - 1); } else { Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); // Roulette Ability Check if (attacker.GetInventory().Abilities.Contains(abilityList[130])) { Ability abRef = attacker.GetInventory().Abilities.Where(ability => ability.AbilityID is 130).FirstOrDefault(); abRef.active = true; } // Unstoppable Force if (attacker.GetInventory().Abilities.Contains(abilityList[42])) DamageEntity(attacker, attacker, UtilityHandler.RollValue(Dice.DiceR.Four), false, false); GeneralUI.Instance.RollToHit(false, hasCrit, ToHitRolls, attacker, target, toHitValue, armorValue, DamageRolls, triggers, attackPerformed == attacker.AttackCount - 1); // Temper Tantrum if (attacker.GetInventory().Abilities.Contains(abilityList[144])) { int rollValue = UtilityHandler.RollValue(Dice.DiceR.Ten); if (rollValue is 1) { int turnValue = UtilityHandler.RollValue(Dice.DiceR.Four); attacker.AddModToList(abilityList[144].Mod, 0, turnValue); } } } } else { Inventory attackerInven = attacker.GetInventory(); List<Ability> attackerAbilities = new List<Ability>(attackerInven.Abilities); attackerAbilities.AddRange(attackerInven.limitedAbilities); if (attackerAbilities.Contains(AbilityBrain.GetAbilityList()[78])) { uiHandler.RetrieveActionText().UpdateActionTextUnique(17, -1, target, attacker); uiHandler.ResetUI(); await uiHandler.RetrieveActionText().GetTextShownStatus(); if (target is Enemy enemy) DamageEnemy(enemy, 999, false, attackPerformed == attacker.AttackCount - 1); else if (target is Ally ally) DamageAlly(ally, 999, false); else DamagePlayer(999, false); } } } /// <summary> /// Attacks the designated entity /// </summary> /// <param name="attacker">The attacking entity</param> /// <param name="target">The target entity of the attacker</param> /// <param name="toHitValue">The value of the to hit rolls</param> /// <param name="armorValue">The value of the target's total armor</param> /// <param name="damageRolls">The collection of dice that need to be visually rolled</param> /// <param name="triggers">The collection of triggers for the damage trigger searches</param> public async void CalculateDamage(bool wasCrit, bool? wasHit, Entity attacker, Entity target, int toHitValue, int armorValue, SortedDictionary<int, (int, Dice)> damageRolls, Dictionary<string, (List<string>, List<object>)> triggers, bool isFinalAttack) { // If the to hit was a miss than simply return from calculating the damage if (wasHit is null) { EnemyUI.Instance.SwitchMemberTurnText(true); return; } if ((bool)wasHit is false || attacker.ForcedMiss) // Missing Conditional { attacker.ForcedMiss = false; uiHandler.RetrieveActionText() .UpdateActionTextBasic(attacker.GetName(), target.GetName(), ActionTextBox.ActionType.Misses); uiHandler.ResetUI(); await uiHandler.RetrieveActionText().GetTextShownStatus(); EnemyUI.Instance.SwitchMemberTurnText(true); DisablePlayerTurn(); return; } int damage = 0; Dictionary<int, (List<Element>, List<WeaponSO.Type>)> usedTypeModifiers = new Dictionary<int, (List<Element>, List<WeaponSO.Type>)>(); Weapon attackerWeapon = attacker.GetInventory().GetPrimaryActiveWeapon() ?? null; // If the value rolled was not a critical the to hit modifier is added, otherwise the critical hit will always hit // which doubles the dice used for damage WeaponSO.Type damageType = attackerWeapon != null ? attackerWeapon.WeaponType : ((Ally)attacker).attackType; if (wasCrit) { // Rumbling Onslaught companion side if (attacker.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[118])) { int aliveCount = enemyComps.Where(enemy => enemy.HasDied is false).Count() + enemyMinionComps.Where(enemy => enemy.HasDied is false).Count(); if (aliveCount > 1) { Entity secondaryTarget; do { int posValue = UnityEngine.Random.Range(-1, enemyComps.Length); secondaryTarget = enemyComps[posValue]; } while (secondaryTarget.GetUniqueID() == target.GetUniqueID() || secondaryTarget.HasDied); DamageEnemy(secondaryTarget as Enemy, damage / 2, wasCrit, false); } } // Dirty Tactics if (target.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[103])) attacker.ForcedMiss = true; int baseDamage = 0; for (int i = 0; i < DoubleDice; ++i) { Dice.DiceR[] damageDice = attackerWeapon is not null ? attackerWeapon.WeaponDamage.ToArray() : (attacker as Ally).damageDice.ToArray(); int totalDamage = DecipherDiceRolls(damageRolls, damageDice); baseDamage += totalDamage; damage += totalDamage; } // Modify damage based on other factors damage = DamageTriggerSearch(target, attacker, triggers, damage, true); damage += UtilityHandler.DetermineModifierAndCalculate(damageType, attacker); damage = UtilityHandler.WeaponTypeChecks(damage, attacker, target, usedTypeModifiers); damage = UtilityHandler.ElementTypeChecks(damage, attacker, target, usedTypeModifiers, damageRolls); damage = attacker.GatherStats().GetStat(EntityStats.Stat.Damage, damage); damage = Mathf.Clamp(damage, 0, int.MaxValue); if (attacker.GetInventory().GetSecondaryActiveWeapon() != null) { Weapon secondaryWeapon = attacker.GetInventory().GetSecondaryActiveWeapon(); // Secondary weapon potential if (!secondaryWeapon.WeaponIsTwoHanded) { // 60% of the damage is applied of the weapons stats int baseDmg = 0; for (int i = 0; i < DoubleDice; ++i) { int rolledDmg = DecipherDiceRolls(damageRolls, secondaryWeapon.WeaponDamage.ToArray()); baseDamage += rolledDmg; baseDmg += rolledDmg; } int addedDamage; if (triggers.ContainsKey("FullOffHandSignal")) addedDamage = baseDmg; else addedDamage = (int)Math.Ceiling(baseDmg * .6f); addedDamage = DamageTriggerSearch(target, attacker, triggers, addedDamage, true); addedDamage += UtilityHandler.DetermineModifierAndCalculate(damageType, attacker) / 2; addedDamage = UtilityHandler.WeaponTypeChecks(addedDamage, attacker, target, usedTypeModifiers, true); addedDamage = UtilityHandler.ElementTypeChecks(addedDamage, attacker, target, usedTypeModifiers, damageRolls, true); addedDamage = Mathf.Clamp(addedDamage, 0, int.MaxValue); damage += addedDamage; } } // Check for unique Life Contract trigger if (attacker.GatherModList().SmartModCheck("Life Contract") && damage > 0) attacker.GatherModList().IndicateModification("Life Contract", damage); if (attacker is Player player) damage += player.ParseClassDamageModifiers(target, triggers, damageRolls); // Check for Tracked Target Modifier damage = SharpTrackerModCheck(attacker, target, triggers, damage); uiHandler.ResetUI(); if (damageRolls is not null) await GeneralUI.Instance.RollForDamage(damageRolls, baseDamage, damage, usedTypeModifiers); ActuateDamageText(attacker, target, damage, damageType, wasCrit, isFinalAttack); } // If the value rolled is greater than or equal to the armor value of the enemy with its armor mod than they will be damaged // otherwise the current attacking entity will have missed if (toHitValue >= armorValue && wasCrit is false) { int baseDamage; damage += DecipherDiceRolls(damageRolls, attackerWeapon != null ? attackerWeapon.WeaponDamage.ToArray() : ((Ally)attacker).damageDice.ToArray()); baseDamage = damage; // Modify damage based on other factors damage = DamageTriggerSearch(target, attacker, triggers, damage, false); damage += UtilityHandler.DetermineModifierAndCalculate(damageType, attacker); damage = UtilityHandler.WeaponTypeChecks(damage, attacker, target, usedTypeModifiers); damage = UtilityHandler.ElementTypeChecks(damage, attacker, target, usedTypeModifiers, damageRolls); damage = attacker.GatherStats().GetStat(EntityStats.Stat.Damage, damage); damage = Mathf.Clamp(damage, 0, int.MaxValue); if (attacker.GetInventory().GetSecondaryActiveWeapon() is not null) { Weapon secondaryWeapon = attacker.GetInventory().GetSecondaryActiveWeapon(); if (!secondaryWeapon.WeaponIsTwoHanded) { // 60% of the damage is applied of the weapons stats for a secondary weapon int baseDmg = DecipherDiceRolls(damageRolls, secondaryWeapon.WeaponDamage.ToArray()); int addedDamage; if (triggers.ContainsKey("FullOffHandSignal")) addedDamage = baseDmg; else addedDamage = (int)Math.Ceiling(baseDmg * .6f); addedDamage = DamageTriggerSearch(target, attacker, triggers, addedDamage, false); addedDamage += UtilityHandler.DetermineModifierAndCalculate(damageType, attacker); addedDamage = UtilityHandler.WeaponTypeChecks(addedDamage, attacker, target, usedTypeModifiers, true); addedDamage = UtilityHandler.ElementTypeChecks(addedDamage, attacker, target, usedTypeModifiers, damageRolls, true); damage = Mathf.Clamp(addedDamage, 0, int.MaxValue); damage += addedDamage; } } // Check for unique Life Contract trigger if (attacker.GatherModList().SmartModCheck("Life Contract") && damage > 0) attacker.GatherModList().IndicateModification("Life Contract", damage); if (attacker is Player player) damage += player.ParseClassDamageModifiers(target, triggers, damageRolls); // Check for Tracked Target Modifier damage = SharpTrackerModCheck(attacker, target, triggers, damage); uiHandler.ResetUI(); if (damageRolls is not null) await GeneralUI.Instance.RollForDamage(damageRolls, baseDamage, damage, usedTypeModifiers); ActuateDamageText(attacker, target, damage, damageType, wasCrit, isFinalAttack); } } private static int SharpTrackerModCheck(Entity attacker, Entity target, Dictionary<string, (List<string>, List<object>)> triggers, int damage) { if (target.GatherModList().SmartModCheck("Tracked Target")) { Modifier targetMod = target.GatherModList().GatherModifier("Tracked Target"); if (targetMod.RetentionObject is Player playerRetentionData && playerRetentionData.GetUniqueID() == attacker.GetUniqueID()) { int addedDamage = (int)(damage * .25f); damage += addedDamage; if (triggers.ContainsKey("AddDamageValue")) { triggers["AddDamageValue"].Item1.Add("Sharp Tracker"); triggers["AddDamageValue"].Item2.Add(addedDamage); triggers["AddDamageType"].Item2.Add(Element.Normal); } else { triggers.Add("AddDamageValue", (new List<string> { "Sharp Tracker" }, new List<object> { addedDamage })); triggers.Add("AddDamageType", (new List<string>(), new List<object> { Element.Normal })); } } else if (targetMod.RetentionObject is Ally allyRetentionData && allyRetentionData.GetUniqueID() == attacker.GetUniqueID()) { int addedDamage = (int)(damage * .25f); damage += addedDamage; if (triggers.ContainsKey("AddDamageValue")) { triggers["AddDamageValue"].Item1.Add("Sharp Tracker"); triggers["AddDamageValue"].Item2.Add(addedDamage); triggers["AddDamageType"].Item2.Add(Element.Normal); } else { triggers.Add("AddDamageValue", (new List<string> { "Sharp Tracker" }, new List<object> { addedDamage })); triggers.Add("AddDamageType", (new List<string>(), new List<object> { Element.Normal })); } } } return damage; } /// <summary> /// Gathers the dice to be rolled and rolls each of them while setting the rolls actual values connection to each dice /// </summary> /// <param name="DamageRolls">Collection of visual dice to be rolled correlated with their rolled values in this function</param> /// <param name="attackerDice">The collection of dice to be rolled</param> /// <returns></returns> private int DecipherDiceRolls(SortedDictionary<int, (int, Dice)> DamageRolls, Dice.DiceR[] attackerDice) { int totalDamage = 0; for (int j = 0; j < attackerDice.Length; ++j) { int damageRolled; Dice diceType = new DHundred(); switch (attackerDice[j]) { case Dice.DiceR.Four: diceType = new DFour(); break; case Dice.DiceR.Six: diceType = new DSix(); break; case Dice.DiceR.Eight: diceType = new DEight(); break; case Dice.DiceR.Ten: diceType = new DTen(); break; case Dice.DiceR.Twelve: diceType = new DTwelve(); break; case Dice.DiceR.Twenty: diceType = new DTwenty(); break; } damageRolled = DiceRoller.RollDice(diceType); if (DamageRolls is not null) { if (DamageRolls.Count > 0 && diceType is not DHundred) { int currentDice = DamageRolls.Last().Key; DamageRolls.Add(++currentDice, (damageRolled, diceType)); } else if (diceType is not DHundred) DamageRolls.Add(0, (damageRolled, diceType)); } totalDamage += damageRolled; } return totalDamage; } /// <summary> /// Damages the targetted enemy component with the damage amount and calls the action text with the weapon type /// </summary> /// <param name="enemyPosition"></param> /// <param name="attackingEntity"></param> /// <param name="target"></param> /// <param name="damage"></param> /// <param name="weaponType"></param> private async void ActuateDamageText(Entity attackingEntity, Entity target, int damage, WeaponSO.Type weaponType, bool wasCrit, bool isFinalAttack) { damage = Mathf.Clamp(damage, 0, int.MaxValue); // Misses if the damage is 0 if (damage is 0) { uiHandler.RetrieveActionText() .UpdateActionTextBasic(attackingEntity.GetName(), target.GetName(), ActionTextBox.ActionType.Misses); } // If ranged or magic than the ranged type is indicated as of now else if (weaponType is WeaponSO.Type.Ranged || weaponType is WeaponSO.Type.Magic) { uiHandler.RetrieveActionText() .UpdateActionTextBasic(attackingEntity.GetName(), target.GetName(), ActionTextBox.ActionType.Ranged, damage); } // Otherwise, the weapon type is thought to be of a melee variety else { uiHandler.RetrieveActionText() .UpdateActionTextBasic(attackingEntity.GetName(), target.GetName(), ActionTextBox.ActionType.Melee, damage); } bool afterImageCheck = attackingEntity.GatherModList().SmartModCheck("Afterimage"); DamageEntity(attackingEntity, target, damage, wasCrit, isFinalAttack && !afterImageCheck); // Afterimage modifier effect if (afterImageCheck && !target.HasDied) { await uiHandler.RetrieveActionText().GetTextShownStatus(); int afterDamage = (int)Math.Floor(damage * .4); uiHandler.RetrieveActionText().UpdateActionTextUnique(36, afterDamage, target, attackingEntity); DamageEntity(attackingEntity, target, afterDamage, false, isFinalAttack); } if (isFinalAttack) EnemyUI.Instance.SwitchMemberTurnText(true); } /// <summary> /// Damages the designated entity after determining its type /// </summary> /// <param name="attacker">The attacking entity</param> /// <param name="target">The target entity</param> /// <param name="damage">The amount of damage to do</param> private async void DamageEntity(Entity attacker, Entity target, int damage, bool wasCrit, bool isFinalAttack) { bool modsActivated = false; if (target.GatherModList().SmartModCheck("Counter Stance")) { if (attacker is Player) DamagePlayer(damage / 2, false); else if (attacker is Ally) DamageAlly(attacker as Ally, damage / 2, false); else DamageEnemy(attacker as Enemy, damage / 2, false, false); } if (target is Enemy enemy) { try { if (damage > 0) { AddActivatedMods(attacker.CallMods(enemy), enemy); modsActivated = true; } DamageEnemy(enemy, damage, wasCrit, isFinalAttack); } catch (IndexOutOfRangeException) { if (enemyComps.Length is 0) return; enemy = enemyComps[^1]; if (damage > 0 && !modsActivated) AddActivatedMods(attacker.CallMods(enemy), enemy); DamageEnemy(enemy, damage, wasCrit, isFinalAttack); } } else if (target is Ally ally) { try { if (damage > 0) { AddActivatedMods(attacker.CallMods(ally), ally); modsActivated = true; } DamageAlly(ally, damage, wasCrit); } catch (IndexOutOfRangeException) { if (allyComps.Length is 0) return; ally = allyComps[^1]; if (damage > 0 && !modsActivated) AddActivatedMods(attacker.CallMods(ally), ally); DamageAlly(ally, damage, wasCrit); } } else if (target is Player player) { if (damage > 0) AddActivatedMods(attacker.CallMods(player), player); DamagePlayer(damage, wasCrit); } await uiHandler.RetrieveActionText().GetTextShownStatus(); if ((attacker is Player || attacker is Ally) && isFinalAttack) DisablePlayerTurn(); else { if (enemyComps.All(enemy => enemy.HasDied)) { DisablePlayerTurn(); return; } uiHandler.Fight(attacker, ++currentAttack); } } public async Task AttackLeftSide(List<Dice.DiceR> damageDice, WeaponSO.Type actionType, Enemy enemyRef, bool confused = false) { Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); for (int i = 0; i < enemyRef.AttackCount; ++i) { // Gather target Entity target; target = GatherTargetForEnemy(enemyRef, confused); await ActivatePreparationSpells(enemyRef, PreparationCalls.NextAttackingEntity); // This is simply an easier way to wipe the enemy out if they were killed, // as their health won't drop further and still signal an enemy perished if (enemyRef.HasDied) { DamageEnemy(enemyRef, 1, false, false); return; } if (target == null) { uiHandler.RetrieveActionText().UpdateActionTextUnique(21, -1, target, enemyRef); await uiHandler.RetrieveActionText().GetTextShownStatus(); return; } List<object> damageAndCritIndic = CalculateEnemyDamage(target, damageDice, enemyRef); if (enemyRef.ForcedMiss) { damageAndCritIndic[0] = 0; enemyRef.ForcedMiss = false; } else if ((bool)damageAndCritIndic[1]) { // Dirty Tactics if (target.GetInventory().Abilities.Contains(abilityList[103])) enemyRef.ForcedMiss = true; } if ((int)damageAndCritIndic[0] is not 0) { AddActivatedMods(enemyRef.CallMods(target), target); // Check for unique Life Contract trigger if (enemyRef.GatherModList().SmartModCheck("Life Contract") && (int)damageAndCritIndic[0] > 0) enemyRef.GatherModList().IndicateModification("Life Contract", (int)damageAndCritIndic[0]); } if (target.GatherModList().SmartModCheck("Counter Stance")) DamageEnemy(enemyRef, (int)damageAndCritIndic[0] / 2, false, false); if (target is Player) { bool wasRedirected = false; foreach (Ally ally in allyComps) { if (ally.GetInventory().Abilities.Contains(abilityList[115]) && (int)damageAndCritIndic[0] < ally.RetrieveHealth()) { int currentTargetHealth = target.GetHealthPercentage(); int chance = 1; if (currentTargetHealth < 75) chance = 2; else if (currentTargetHealth < 50) chance = 3; else chance = 4; int rolledChance = UnityEngine.Random.Range(0, 11); if (chance > rolledChance) { target = ally; DamageAlly(target as Ally, (int)damageAndCritIndic[0], (bool)damageAndCritIndic[1]); wasRedirected = true; break; } } } if (wasRedirected is false) DamagePlayer((int)damageAndCritIndic[0], (bool)damageAndCritIndic[1]); } else { bool wasRedirected = false; if (playerComponent.GetInventory().Abilities.Contains(abilityList[115]) && (int)damageAndCritIndic[0] < playerComponent.RetrieveHealth()) { int currentTargetHealth = target.GetHealthPercentage(); int chance = 1; if (currentTargetHealth < 75) chance = 2; else if (currentTargetHealth < 50) chance = 3; else chance = 4; int rolledChance = UnityEngine.Random.Range(0, 11); if (chance > rolledChance) { target = playerComponent; DamagePlayer((int)damageAndCritIndic[0], (bool)damageAndCritIndic[1]); wasRedirected = true; } } if (wasRedirected is false) { foreach (Ally ally in allyComps) { if (ally.GetInventory().Abilities.Contains(abilityList[115]) && (int)damageAndCritIndic[0] < ally.RetrieveHealth() && ally.GetUniqueID() != target.GetUniqueID()) { int currentTargetHealth = target.GetHealthPercentage(); int chance = 1; if (currentTargetHealth < 75) chance = 2; else if (currentTargetHealth < 50) chance = 3; else chance = 4; int rolledChance = UnityEngine.Random.Range(0, 11); if (chance > rolledChance) { target = ally; DamageAlly(target as Ally, (int)damageAndCritIndic[0], (bool)damageAndCritIndic[1]); wasRedirected = true; break; } } } } if (wasRedirected is false) DamageAlly(target as Ally, (int)damageAndCritIndic[0], (bool)damageAndCritIndic[1]); } // Afterimage modifier effect if (enemyRef.GatherModList().SmartModCheck("Afterimage") && !target.HasDied) { await uiHandler.RetrieveActionText().GetTextShownStatus(); int afterDamage = (int)Math.Floor((int)damageAndCritIndic[0] * .4); uiHandler.RetrieveActionText().UpdateActionTextUnique(36, afterDamage, target, enemyRef); if (target is Player) DamagePlayer(afterDamage, false); else DamageAlly(target as Ally, afterDamage, false); } // If the target perished, then the entity will attempt to laugh if (target.HasDied) enemyRef.GetAnimationHandler().PlayKilledAllySound(); if (playerComponent.HasDied && allyComps.All(ally => ally.HasDied)) { DisableBattle(); return; } EnemyTextCall(actionType, enemyRef, (int)damageAndCritIndic[0], target.GetName()); await uiHandler.RetrieveActionText().GetTextShownStatus(); } } private async Task ActivatePreparationSpells(Entity attacker, PreparationCalls callForm) { object retrievedObject = null; bool isCorrectPrepCall = attacker.IsPreparing.Item2.Item1 == callForm; if (!isCorrectPrepCall) return; switch (callForm) { case PreparationCalls.NextAttackingEntity: if (attacker is Enemy enemy) { bool activePreparation = allyComps.Any(ally => ally.IsPreparing.Item1) || playerComponent.IsPreparing.Item1; if (!activePreparation) return; if (playerComponent.IsPreparing.Item2.Item1 is PreparationCalls.NextAttackingEntity) { retrievedObject = playerComponent.IsPreparing.Item2.Item2; if (retrievedObject is List<Dice.DiceR> dices) { int totalDamage = UtilityHandler.RollValue(dices.ToArray()); attacker.DecreaseHealth(totalDamage); attacker.StatusChanged(); uiHandler.RetrieveActionText().UpdateActionTextUnique(29, totalDamage, attacker, playerComponent); await uiHandler.RetrieveActionText().GetTextShownStatus(); } playerComponent.IsPreparing = (false, (PreparationCalls.FinishRound, null)); } foreach (Ally allyComp in allyComps) { if (allyComp.IsPreparing.Item2.Item1 is PreparationCalls.NextAttackingEntity) { retrievedObject = allyComp.IsPreparing.Item2.Item2; if (retrievedObject is List<Dice.DiceR> dices) { int totalDamage = UtilityHandler.RollValue(dices.ToArray()); attacker.DecreaseHealth(totalDamage); attacker.StatusChanged(); uiHandler.RetrieveActionText().UpdateActionTextUnique(29, totalDamage, attacker, allyComp); await uiHandler.RetrieveActionText().GetTextShownStatus(); } allyComp.IsPreparing = (false, (PreparationCalls.FinishRound, null)); } } } else { bool activePreparation = enemyComps.Any(ally => ally.IsPreparing.Item1); if (!activePreparation) return; foreach (Enemy enemyComp in enemyComps) { if (enemyComp.IsPreparing.Item2.Item1 is PreparationCalls.NextAttackingEntity) { retrievedObject = enemyComp.IsPreparing.Item2.Item2; if (retrievedObject is List<Dice.DiceR> dices) { int totalDamage = UtilityHandler.RollValue(dices.ToArray()); attacker.DecreaseHealth(totalDamage); attacker.StatusChanged(); uiHandler.RetrieveActionText().UpdateActionTextUnique(29, totalDamage, attacker, enemyComp); await uiHandler.RetrieveActionText().GetTextShownStatus(); } enemyComp.IsPreparing = (false, (PreparationCalls.FinishRound, null)); } } } attacker.IsPreparing = (false, (PreparationCalls.FinishRound, null)); break; case PreparationCalls.NextTurn: retrievedObject = attacker.IsPreparing.Item2.Item2; List<object> objectList = (List<object>)retrievedObject; List<Dice.DiceR> damageDice = objectList[0] as List<Dice.DiceR>; Entity target = objectList[1] as Entity; string spellCalled = objectList[2] as string; switch (spellCalled) { case "Precise Shot": int totalDamage = UtilityHandler.RollValue(damageDice.ToArray()); if (attacker.IsHidden) totalDamage *= 2; if (target.HasDied) { do { target = enemyComps[UnityEngine.Random.Range(0, enemyComps.Length)]; } while (target.HasDied); } target.DecreaseHealth(totalDamage); target.StatusChanged(); uiHandler.RetrieveActionText().UpdateActionTextUnique(31, totalDamage, target, attacker); await uiHandler.RetrieveActionText().GetTextShownStatus(); if (target.HasDied) DamageEnemy(target as Enemy, 1, false); break; } attacker.IsPreparing = (false, (PreparationCalls.FinishRound, null)); break; case PreparationCalls.FinishRound: attacker.IsPreparing = (false, (PreparationCalls.FinishRound, null)); break; case PreparationCalls.NextEntity: attacker.IsPreparing = (false, (PreparationCalls.FinishRound, null)); break; } } private List<object> CalculateEnemyDamage(Entity target, List<Dice.DiceR> damageDice, Enemy enemyRef) { Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); ExtractAllAvailableEntities(out Ally[] allyAbilityChecks, out Enemy[] enemyAbilityChecks); (bool isDisadvantaged, bool isAdvantaged, bool autoHit) = AbilityBrain.PreToHitRollCheck(enemyRef, target, allyAbilityChecks, enemyAbilityChecks); bool hasCrit = false; int damage = 0; if (!autoHit) { int critValue = CriticalHit - enemyRef.GatherStats().GetStat(EntityStats.Stat.ToCritMod); int toHitValue = DiceRoller.RollDice(new DTwenty()); if (isDisadvantaged && !isAdvantaged) { int newToHitValue = DiceRoller.RollDice(new DTwenty()); if (newToHitValue < toHitValue) toHitValue = newToHitValue; } else if (isAdvantaged && !isDisadvantaged) { int newToHitValue = DiceRoller.RollDice(new DTwenty()); if (newToHitValue > toHitValue) toHitValue = newToHitValue; } hasCrit = toHitValue >= critValue; int armorValue = target.GatherStats().GetStat(EntityStats.Stat.Armor, target.RetrieveArmor()); Dictionary<string, (List<string>, List<object>)> triggers = AbilityBrain.PreTurnCompletionCheck(enemyRef, target, playerObject.activeSelf ? playerComponent : null, allyAbilityChecks, enemyAbilityChecks, toHitValue >= armorValue, null, ref hasCrit); if (enemyRef.GatherModList().SmartModCheck("Exhausted")) toHitValue -= UtilityHandler.RollValue(Dice.DiceR.Four); if (!hasCrit) toHitValue = Mathf.Clamp(toHitValue + enemyRef.GatherStats().GetStat(EntityStats.Stat.ToHit) + enemyRef.GatherStats().GetStat(EntityStats.Stat.Dexterity), 0, int.MaxValue); ArmorTriggerSearch(triggers, ref armorValue, ref toHitValue); // Unbreakable Chain Relic Flag if (target.GetInventory().GetActiveRelic() != null) { if (target.GetInventory().GetActiveRelic().ItemId is 0) { int totalArmorValue = 4; int targetSize = target.GetSizeIndic(); int healthPercentage = target.GetHealthPercentage(); while (healthPercentage >= 25) { healthPercentage -= 25; totalArmorValue--; } armorValue += totalArmorValue; bool isWithinThreshold = (healthPercentage <= 5 && targetSize is 0) || (healthPercentage <= 15 && targetSize is 1) || (healthPercentage <= 25 && targetSize is 2); Modifier strangledMod = AbilityBrain.GetModifierList()["Strangled"]; if (isWithinThreshold && !target.GatherModList().SmartModCheck("Strangled")) target.AddModToList(strangledMod, 0, 3); else if (!isWithinThreshold && target.GatherModList().SmartModCheck("Strangled")) target.GatherModList().RemoveModifier(strangledMod); target.AfflictedStatus(); } } // Rumbling Onslaught Ability if (enemyRef.GetInventory().Abilities.Contains(abilityList[118])) { int aliveCount = (playerComponent.HasDied ? 0 : 1) + allyComps.Where(ally => ally.HasDied is false).Count() + allyMinionComps.Length; if (aliveCount > 1) { Entity secondaryTarget; do { int posValue = UnityEngine.Random.Range(-1, allyComps.Length); secondaryTarget = posValue is -1 ? playerComponent : allyComps[posValue]; } while (secondaryTarget.GetUniqueID() == target.GetUniqueID() || secondaryTarget.HasDied); if (secondaryTarget is Player) DamagePlayer(damage / 2, hasCrit); else if (secondaryTarget is Ally ally) DamageAlly(ally, damage / 2, hasCrit); } } if (hasCrit) { for (int k = 0; k < DoubleDice; k++) { int damageRolled = UtilityHandler.RollValue(damageDice.ToArray()); damage += damageRolled; } // Modify damage value as needed damage = DamageTriggerSearch(target, enemyRef, triggers, damage, true); damage += enemyRef.GatherStats().GetStat(EntityStats.Stat.Strength); damage = UtilityHandler.WeaponTypeChecks(damage, enemyRef, target); damage = UtilityHandler.ElementTypeChecks(damage, enemyRef, target, null, null); damage = enemyRef.GatherStats().GetStat(EntityStats.Stat.Damage, damage); damage = Mathf.Clamp(damage, 0, int.MaxValue); if (enemyRef.GetInventory().Abilities.Contains(abilityList[42])) damage += UtilityHandler.RollValue(Dice.DiceR.Six); } else if (toHitValue >= armorValue) { damage += UtilityHandler.RollValue(damageDice.ToArray()); // Modify damage value as needed damage = DamageTriggerSearch(target, enemyRef, triggers, damage, false); damage += enemyRef.GatherStats().GetStat(EntityStats.Stat.Strength); damage = UtilityHandler.WeaponTypeChecks(damage, enemyRef, target); damage = UtilityHandler.ElementTypeChecks(damage, enemyRef, target, null, null); damage = enemyRef.GatherStats().GetStat(EntityStats.Stat.Damage, damage); damage = Mathf.Clamp(damage, 0, int.MaxValue); if (enemyRef.GetInventory().Abilities.Contains(abilityList[42])) damage += UtilityHandler.RollValue(Dice.DiceR.Six); } else { // Unstoppable Force if (enemyRef.GetInventory().Abilities.Contains(abilityList[42])) DamageEnemy(enemyRef, UtilityHandler.RollValue(Dice.DiceR.Four), false, false); // Temper Tantrum if (enemyRef.GetInventory().Abilities.Contains(abilityList[144])) { int rollValue = UtilityHandler.RollValue(Dice.DiceR.Ten); if (rollValue is 1) { int turnValue = UtilityHandler.RollValue(Dice.DiceR.Four); enemyRef.AddModToList(abilityList[144].Mod, 0, turnValue); } } } if (target.GatherModList().SmartModCheck("Tracked Target")) { Modifier trackerMod = target.GatherModList().GatherModifier("Tracked Target"); if (trackerMod.RetentionObject is Enemy enemyRetentionData && enemyRetentionData.GetUniqueID() == enemyRef.GetUniqueID()) damage += (int)(damage * .25f); } } else { Inventory attackerInven = enemyRef.GetInventory(); List<Ability> attackerAbilities = new List<Ability>(attackerInven.Abilities); attackerAbilities.AddRange(attackerInven.limitedAbilities); if (attackerAbilities.Contains(AbilityBrain.GetAbilityList()[78])) { uiHandler.RetrieveActionText().UpdateActionTextUnique(17); uiHandler.ResetUI(); uiHandler.RetrieveActionText().GetTextShownStatus().GetAwaiter(); if (target is Enemy enemy) DamageEnemy(enemy, 999, false, false); else if (target is Ally ally) DamageAlly(ally, 999, false); else DamagePlayer(999, false); } } return new List<object> { damage, hasCrit }; } private void EnemyTextCall(WeaponSO.Type actionType, Enemy enemyRef, int damageValue, string targetName) { if (damageValue is 0) { uiHandler.RetrieveActionText() .UpdateActionTextBasic(enemyRef.GetName(), targetName, ActionTextBox.ActionType.Misses); } else if (actionType is WeaponSO.Type.Magic || actionType is WeaponSO.Type.Ranged) { uiHandler.RetrieveActionText() .UpdateActionTextBasic(enemyRef.GetName(), targetName, ActionTextBox.ActionType.Ranged, damageValue); } else { uiHandler.RetrieveActionText() .UpdateActionTextBasic(enemyRef.GetName(), targetName, ActionTextBox.ActionType.Melee, damageValue); } } /// <summary> /// Adds all of the mods that were rolled and indicated to be activated to the target's mod list /// </summary> /// <param name="modList">The list of mods that were rolled for, true is activated, false is disabled</param> /// <param name="target">The entity to add to the mod list</param> private void AddActivatedMods(Dictionary<Modifier, bool> modList, Entity target) { if (modList == null || modList.Count is 0 || target == null) return; // Selects all mods that hid a value of true and adds them to the target foreach (KeyValuePair<Modifier, bool> pair in modList) { if (pair.Value) target.AddModToList(pair.Key); } } /// <summary> /// Damages the enemy before checking if they have perished /// </summary> /// <param name="enemy">The index of the enemy in their array</param> /// <param name="damage">The amount of damage done</param> public async void DamageEnemy(Enemy enemy, int damage, bool wasCrit , bool endOfTurn = true) { // Activates certain functions depending on which flags are flipped in Globals and the inventory references for (int i = 1; i <= 2; ++i) damage = FindTableFlagFunction(enemy, i, damage, wasCrit); // Decrease health of enemy bool statusTrigger = enemy.DecreaseHealth(damage); enemy.StatusChanged(); // Destroys the enemy game object on screen if the decrease health call yielded true to reaching zero if (statusTrigger) { // UI is reset to prevent player interactions after killing an enemy uiHandler.ResetUI(); while (enemy.GetAnimationHandler().AcquireAnimationState()) await Task.Yield(); if (!enemy.IsMinion) { if (bountyEnabled is true) { Ability abRef = playerComponent.GetInventory().Abilities.Find(ability => ability.AbilityID is 70); TakenBounty bountyLogic = ((TakenBounty)abRef.ReferenceObject); Enemy bounty = bountyLogic.Bounty; if (bounty.GetName().Equals(enemy.GetName())) { bountyLogic.IndicateBountyHasFallen(); bountyEnabled = false; } } int indexPoint = enemyComps.ToList().IndexOf(enemy); await DeactivateEntity(indexPoint, true); await uiHandler.DeactivateEntityValue(indexPoint, true); } else DestroyMinionInstance(enemy); } if (endOfTurn) { uiHandler.ResetUI(); DisablePlayerTurn(); } } /// <summary> /// Damages an ally and destroys it if the HP drops to 0 /// </summary> /// <param name="ally">The ally being targetted</param> /// <param name="damage">The amount of damage being afflicted to the ally</param> public async void DamageAlly(Ally ally, int damage, bool wasCrit) { // Activates certain functions depending on which flags are flipped in Globals and the inventory references // TODO ---> Optimize, no magic 3 for (int i = 1; i <= 3; ++i) damage = FindTableFlagFunction(ally, i, damage, wasCrit); // Pulls true if the health drops below or equal to 0 bool statusTrigger = ally.DecreaseHealth(damage); ally.StatusChanged(); // If the health is below or equal to 0 then the entity is destroyed if (statusTrigger) { // TODO ---> Implement ally animation systems when more animations are given //while (ally.GetAnimationHandler().AcquireAnimationState()) // await Task.Yield(); // Check if the ally is a minion first if (!ally.IsMinion) { ally.GetGUI().UpdateStats(ally); int indexPoint = allyComps.ToList().IndexOf(ally); await DeactivateEntity(indexPoint, false); await uiHandler.DeactivateEntityValue(indexPoint, false); } else { GeneralUI.Instance.EliminateMinionGUI(ally); DestroyMinionInstance(ally); } } } /// <summary> /// Damages the player and updates the health condition on screen /// </summary> /// <param name="damage">Damage the player takes</param> /// <param name="pRef">The player being affected</param> public async void DamagePlayer(int damage, bool wasCrit) { Player pRef = playerComponent; // Activates certain functions depending on which flags are flipped in Globals and the inventory references for (int i = 1; i <= 2; ++i) damage = FindTableFlagFunction(pRef, i, damage, wasCrit); bool trigger = pRef.DecreaseHealth(damage); pRef.StatusChanged(); if (trigger) { uiHandler.ResetUI(); GeneralUI.Instance.SetInformation(false); while (pRef.GetAnimationHandler().AcquireAnimationState()) await Task.Yield(); await DeactivateEntity(-1, false); await uiHandler.DeactivateEntityValue(-1, false); } } private int FindTableFlagFunction(Entity entityComponent, int idValue, int damage, bool wasCrit) { try { if (entityComponent.GetInventory().Flaglist[idValue] && Globals.Flags[idValue]) { switch (idValue) { case 1: damage -= 3; // Improved Iron Helmet Damage Reduction Effect break; case 2: // Immutable Star Ring replenish mana effect entityComponent.IncreaseMana(2); entityComponent.StatusChanged(); break; case 3: // Ring of Rage if (wasCrit) entityComponent.AddModToList(AbilityBrain.GetModifierList()["Rage"]); break; case 4: break; } } } catch { } return damage; } /// <summary> /// Disables the player's turn /// </summary> public async void DisablePlayerTurn(bool checkProcess = false) { await uiHandler.RetrieveActionText().GetTextShownStatus(); await uiHandler.DisableLeftBattle(TurnCounter == 0, checkProcess); } /// <summary> /// Destroys the entity via gameobject and component in their respective collections based on /// if they are an ally or an enemy in the battle /// </summary> /// <param name="entityPosition">The index of the entity in their respective collection</param> /// <param name="isEnemy">True for if the enemy collections need to be changed or False if ally collections need to be changed</param> private async Task DeactivateEntity(int entityPosition, bool isEnemy) { // This acts as a queue guard for destroying entities if (entityDestruction == null) await Task.Yield(); entityDestruction = null; // This is a signal for when the player has been taken out if (entityPosition is -1) { // If the entity being killed is the player than simply remove from the order and signal completion before returning deadEntities.Add(playerComponent); playerComponent.DeactivateVisually(); entityDestruction = Task.CompletedTask; return; } // Removes and disables the necessary garbage game object marked by the position else { if (isEnemy) { deadEntities.Add(enemyObjects[entityPosition].GetComponent<Enemy>()); enemyObjects[entityPosition].SetActive(false); } else { deadEntities.Add(allyObjects[entityPosition].GetComponent<Ally>()); allyObjects[entityPosition].GetComponent<Ally>().DeactivateVisually(); } } int loopLength = isEnemy ? enemyComps.Length : allyComps.Length; // Sets the array values with the old array values and removes the unneccessary value in the CombatOrder for (int i = 0; i < loopLength; i++) { if (i == entityPosition && isEnemy) { enemyComps[i].enabled = false; break; } } entityDestruction = Task.CompletedTask; } /// <summary> /// Destroys all enemies and their respective health indicator child /// </summary> public void DeactivateAllEnemies() { // Deactivates all enemy attributes for (int i = 0; i < enemyObjects.Length; i++) { enemyObjects[i].SetActive(false); enemyComps[i].enabled = false; } } /// <summary> /// Destroys and clears the list of ally game objects within the handler and scene /// </summary> public void DeactivateAllAllies() { // 'Destroy' the allies by deactivating their models for (int i = 0; i < allyComps.Length; ++i) allyComps[i].DeactivateVisually(); } /// <summary> /// Creates the order that this battle will carry out in /// </summary> /// <exception cref="InvalidOperationException">Exception is thrown if the enemy values rolled does not match the number of enemies in the scene</exception> public async void InitializeOrder() { // Resets the combat order class container if (combatOrder != null) combatOrder = null; combatOrder = new OrderOfCombat(); deadEntities = new(); TurnCounter = 0; await AbilityBrain.PreBattleChecks(playerComponent, allyComps, enemyComps); Dictionary<int, Ability> abList = AbilityBrain.GetAbilityList(); // Bounty Check if (playerComponent.GetInventory().Abilities.Contains(abList[70]) && bountyEnabled is null) { Ability abRef = playerComponent.GetInventory().Abilities.Find(ability => ability.AbilityID is 70); if (abRef.ReferenceObject is not null) { Enemy bounty = ((TakenBounty)abRef.ReferenceObject).Bounty; foreach (Enemy enemy in enemyComps) { if (enemy.GetName().Equals(bounty.GetName())) { bountyEnabled = true; break; } } if (bountyEnabled is not true) bountyEnabled = false; } } // Check for Root Network bool groupAdvantage = allyComps.Any(ally => ally.GetInventory().Abilities.Contains(abList[62])); // Calls player roll initiative Player playerComp = playerComponent; int playerV = playerComp.RollOrderValue(); if (groupAdvantage || playerComponent.GetInventory().Abilities.Contains(abList[129])) // Quickdraw check { int newPlayerV = playerComp.RollOrderValue(); if (newPlayerV > playerV) playerV = newPlayerV; } combatOrder.CallOrderInitiative(playerComp, playerV); // Gathers all of the enemy rolls, makes sure that that all of the enemy rolls are unique int[] enemyVs = new int[enemyComps.Length]; for (int i = 0; i < enemyComps.Length; i++) { int c = enemyComps[i].RollOrderValue(); // Unique roll check, if not unique than the enemy is rerolled if (c == playerV || enemyVs.Contains(c)) i--; else enemyVs[i] = c; } if (enemyVs.Length != enemyComps.Length) throw new InvalidOperationException($"Non equal rolls detected for enemy_Vs : {enemyVs} and enemy_Comps : " + $"{enemyComps.Length}, check loop to make sure all enemies are rolling for their turn!"); // Combat Order is called to input all of the enemy roll values int pos = 0; foreach (int v in enemyVs) combatOrder.CallOrderInitiative(enemyComps[pos++], v); if (allyComps != null) { // Gathers all of the ally rolls, also makes sure that all of the ally rolls are unique int[] allyVs = new int[allyComps.Length]; for (int i = 0; i < allyComps.Length; i++) { int c = allyComps[i].RollOrderValue(); if (groupAdvantage) { int newC = allyComps[i].RollOrderValue(); if (newC > c) c = newC; } // Unique roll check for the ally if (c == playerV || enemyVs.Contains(c) || allyVs.Contains(c)) i--; else allyVs[i] = c; } if (allyVs.Length != allyComps.Length) throw new InvalidOperationException($"Non equal rolls detected for ally_Vs : {allyVs} and ally_Comps : " + $"{allyComps.Length}, check loop to make sure all allies are rolling for their turn!"); // Combat Order is called to input all of the ally roll values pos = 0; foreach (int v in allyVs) combatOrder.CallOrderInitiative(allyComps[pos++], v); } // Then then combat order is sorted based off of the roll values for each of the entities order = new List<Entity>(combatOrder.SortOrder()); activeEntity = 0; uiHandler.currentEntityTurnText.text = $"{order[activeEntity].GetName()}"; uiHandler.currentEntityTurnText.enabled = true; Debug.Log(combatOrder.ToString()); } /// <summary> /// Sets the active entity within the turn order /// </summary> public async Task SetActiveEntity() { Debug.LogError($"{order[activeEntity].GetName()} is going now!"); // Set the current entity that is currently performing their turn currentEntity = order[activeEntity]; uiHandler.currentEntityTurnText.text = $"{order[activeEntity].GetName()}"; // If entity has health regen they will regenerate before any adverse weather effects them Dictionary<string, object> triggers = AbilityBrain.StartTurnCheck(currentEntity, playerComponent, allyComps, enemyComps); ProcHealthRegen(triggers); ProcManaRegen(); // Weather check protocols bool entityStatus = await PerformWeatherFunctions(currentEntity); if (!entityStatus) { // This prevents being stuck in battle when all enemies or player entities are destroyed if (enemyObjects.All(enemy => !enemy.activeSelf) || (playerComponent.HasDied && allyComps.All(ally => ally.HasDied))) { DisableBattle(); return; } // If entity died to blizzard damage then simply go to the next turn previousEntity = currentEntity; GoToNextTurn(); return; } // Calls for any DOT damage to be done on the entity (bool skippingTurn, bool confused)= currentEntity.CallTurnModifierEffects(); // If the entity is under certain modifiers they will attack an ally of theirs if (confused && !skippingTurn) { if (currentEntity is Player || currentEntity is Ally) { Entity target = null; do { int posValue = UnityEngine.Random.Range(-1, allyComps.Length); target = posValue is -1 ? playerComponent : allyComps[posValue]; } while (target.HasDied || target.GetUniqueID() == currentEntity.GetUniqueID()); CalculateDamage(false, true, currentEntity, target, 999, 0, null, null, true); } else if (currentEntity is Enemy enemy) AttackLeftSide(enemy.GetDamageDice(), enemy.GetDamageType(), enemy, true).GetAwaiter(); } else if (!skippingTurn) { if (currentEntity is Player || currentEntity is Ally) { if (uiHandler.GetUITask() is null && previousEntity != currentEntity) await uiHandler.DisableLeftBattle(TurnCounter == 0); for (int i = 0; i < currentEntity.ActionCount; ++i) { if (enemyComps.All(enemy => enemy.HasDied)) break; // If the entity is already preparing a spell, activate the spell then move on if (currentEntity.IsPreparing.Item1) await ActivatePreparationSpells(currentEntity, PreparationCalls.NextTurn); // The player takes their turn, to which the thread halts until a choice is made else await uiHandler.EnableLeftBattle(currentEntity, triggers); } } else if (currentEntity is Enemy enemy) { // The enemy takes its turn await uiHandler.DisableLeftBattle(TurnCounter == 0); for (int i = 0; i < currentEntity.ActionCount; ++i) { if (currentEntity.IsPreparing.Item1) await ActivatePreparationSpells(currentEntity, PreparationCalls.NextTurn); // The player takes their turn, to which the thread halts until a choice is made else await enemy.ActivateEnemy(); } } } // Calculates post turn checks from each of the entities abilities, // including the dead entity list await AbilityBrain.PostTurnCheck(order, false); if (deadEntities != null) await AbilityBrain.PostTurnCheck(deadEntities, true); // Check system that allies were present in the battle or not bool alliesPresentIndicator = allyComps is not null; bool allyStateIndicator = alliesPresentIndicator; if (alliesPresentIndicator) { for (int i = 0; i < allyComps.Length; i++) { if (!allyComps[i].HasDied) break; else if (i == allyComps.Length - 1) allyStateIndicator = false; } } // If the battle is still occurring then the next entity is set active, // otherwise the battle is disabled and the post screen is created if (!stillBattling || (playerObject.activeSelf is false && (!alliesPresentIndicator || !allyStateIndicator))) { DisableBattle(); return; } // If the entity whose turn is about to end has the condition Amped they can go once again // Once the entities second turn is performed than the trigger is switched to its opposite once it // returns false if (order[activeEntity].GatherModList().SmartModCheck("Amped")) { previousEntity = currentEntity; return; } // Decrements the current entities mods if they fully completed their turn currentEntity.DecrementTurnsOnMods(); // Increments the turn order until an active entity is found then starts the next entities turn GoToNextTurn(); previousEntity = currentEntity; } /// <summary> /// Health regenerates slightly if the current entity has any health regeneration /// </summary> private void ProcHealthRegen(Dictionary<string, object> triggers) { // Prevents health regeneration while dead if (currentEntity.HasDied) return; int entityHPRegen = currentEntity.GatherStats().GetStat(EntityStats.Stat.HPRegen, currentEntity.RetrieveMaxHealth()); if (triggers.ContainsKey("Adrenal Surge")) entityHPRegen += (int)triggers["Adrenal Surge"]; if (currentEntity.GetInventory().GetActiveRelic() != null) { Relic relic = currentEntity.GetInventory().GetActiveRelic(); if (relic.ItemId is 2) currentEntity.IncreaseHealth(UtilityHandler.RollValue(Dice.DiceR.Six)); } if (entityHPRegen >= 0) currentEntity.IncreaseHealth((int)Math.Ceiling(currentEntity.RetrieveMaxHealth() * (entityHPRegen / 100f))); currentEntity.StatusChanged(); } private void ProcManaRegen() { bool bypassRegen = false; if (currentEntity.GetInventory().GetActiveRelic() != null) { Relic relic = currentEntity.GetInventory().GetActiveRelic(); if (relic.ItemId is 2 && currentEntity.RetrieveMana() > 0 && !currentEntity.GatherModList().SmartModCheck("Unquenched")) { currentEntity.DecreaseMana(UtilityHandler.RollValue(Dice.DiceR.Four)); if (currentEntity.RetrieveMana() is 0) currentEntity.AddModToList(AbilityBrain.GetModifierList()["Unquenched"]); bypassRegen = true; } } if (currentEntity.GatherStats().GetStat(EntityStats.Stat.MPRegen) is not 0 && !bypassRegen) { int refillAmount = currentEntity.GatherStats().GetStat(EntityStats.Stat.MPRegen); currentEntity.IncreaseMana(refillAmount); } currentEntity.StatusChanged(); } private async Task<bool> PerformWeatherFunctions(Entity currentEntity) { if (currentEntity is Player || currentEntity is Ally) { if (currentEntity.HasDied) return false; } // Modifier check for Impalement and weather conditions if (currentEntity.GatherModList().SmartModCheck("Impaled")) await ImpalementFunctionality(ClimateManager.Instance.GetWeather()); if (currentEntity.HasDied) return false; // If the current weather is a Blizzard, then perform damage on the current turn entity if (ClimateManager.Instance.GetWeather() is ClimateManager.Weather.Blizzard) { int damage = UtilityHandler.RollValue(Dice.DiceR.Four); damage = UtilityHandler.ElementWeaknessAndResistChecks(damage, Element.Ice, currentEntity); if (currentEntity is Enemy enemy && damage is not 0) DamageEnemy(enemy, damage, false, false); else if (currentEntity is Ally ally && damage is not 0) DamageAlly(ally, damage, false); else if (damage is not 0) DamagePlayer(damage, false); if (damage is not 0) uiHandler.RetrieveActionText().UpdateActionTextBasic("Blizzard", currentEntity.GetName(), ActionTextBox.ActionType.Ranged, damage); else uiHandler.RetrieveActionText().UpdateActionTextBasic("Blizzard", currentEntity.GetName(), ActionTextBox.ActionType.Ranged); await uiHandler.RetrieveActionText().GetTextShownStatus(); } if (currentEntity.HasDied) return false; return true; async Task ImpalementFunctionality(ClimateManager.Weather currentWeather) { int damageRoll; switch (currentWeather) { case ClimateManager.Weather.Snowy: // 1D4 of Ice Damage damageRoll = UtilityHandler.RollValue(Dice.DiceR.Four); damageRoll = UtilityHandler.ElementWeaknessAndResistChecks(damageRoll, Element.Ice, currentEntity); break; case ClimateManager.Weather.Blizzard: // 2D4 of Ice Damage damageRoll = UtilityHandler.RollValue(Dice.DiceR.Four, Dice.DiceR.Four); damageRoll = UtilityHandler.ElementWeaknessAndResistChecks(damageRoll, Element.Ice, currentEntity); break; case ClimateManager.Weather.Rainy: // 1D4 of Energy Damage damageRoll = UtilityHandler.RollValue(Dice.DiceR.Four); damageRoll = UtilityHandler.ElementWeaknessAndResistChecks(damageRoll, Element.Energy, currentEntity); break; case ClimateManager.Weather.Monsoon: // 2D12 of Energy Damage (Basically a lightning strike) damageRoll = UtilityHandler.RollValue(Dice.DiceR.Twelve, Dice.DiceR.Twelve); damageRoll = UtilityHandler.ElementWeaknessAndResistChecks(damageRoll, Element.Energy, currentEntity); break; default: // 1D4 of Normal Damage damageRoll = UtilityHandler.RollValue(Dice.DiceR.Four); damageRoll = UtilityHandler.ElementWeaknessAndResistChecks(damageRoll, Element.Normal, currentEntity); break; } if (currentEntity is Enemy enemy && damageRoll is not 0) DamageEnemy(enemy, damageRoll, false, false); else if (currentEntity is Ally ally && damageRoll is not 0) DamageAlly(ally, damageRoll, false); else if (damageRoll is not 0) DamagePlayer(damageRoll, false); if (damageRoll is not 0) { switch (currentWeather) { case ClimateManager.Weather.Rainy: uiHandler.RetrieveActionText().UpdateActionTextUnique(12, damageRoll, currentEntity); break; case ClimateManager.Weather.Monsoon: uiHandler.RetrieveActionText().UpdateActionTextUnique(13, damageRoll, currentEntity); break; case ClimateManager.Weather.Snowy: uiHandler.RetrieveActionText().UpdateActionTextUnique(10, damageRoll, currentEntity); break; case ClimateManager.Weather.Blizzard: uiHandler.RetrieveActionText().UpdateActionTextUnique(11, damageRoll, currentEntity); break; default: uiHandler.RetrieveActionText().UpdateActionTextUnique(9, damageRoll, currentEntity); break; } } else uiHandler.RetrieveActionText().UpdateActionTextUnique(14, damageRoll, currentEntity); await uiHandler.RetrieveActionText().GetTextShownStatus(); } } /// <summary> /// Determines the next entity to go next in the turn order /// </summary> private void GoToNextTurn() { // Takes reference of the current entity before beginning to switch between entities Entity previousTurnEntity = order[activeEntity]; bool disableBattle = false; // Loops until an active entity is found while (true) { // Switches to next entity in line activeEntity = activeEntity + 1 > order.Count - 1 ? 0 : activeEntity + 1; // If the entities game object is active continue the battle with the current entity selected if (order[activeEntity].gameObject.activeSelf) break; // If the loop goes full circle and finds the previous entity that went, send trigger to disable battle else if (order[activeEntity] == previousTurnEntity) { disableBattle = true; break; } // Otherwise keep incrementing the turns to find an active entity to do their turn else continue; } // If a full entity loop is performed in the while loop, disable the battle if (disableBattle) DisableBattle(); // Otherwise increment the turn since an entity was found else ++TurnCounter; } public Entity GatherTargetForEnemy(Enemy enemyRef, bool confused = false) { if (confused && enemyComps.Length > 1) { Enemy target; do { target = enemyComps[UnityEngine.Random.Range(0, enemyComps.Length)]; } while (target.HasDied || target.GetUniqueID() == enemyRef.GetUniqueID()); return target; } else if (confused && enemyComps.Length is 1) return null; if (allyComps.Length is 0 && UniqueTargetCheck(enemyRef, playerComponent)) { if (playerComponent.IsHidden) return null; else return playerComponent; } else { Entity target; target = CheckForAttentionMods(); return GatherRandomTarget(target); } // Gathers a random target from the order list that is not fainted or an enemy Entity GatherRandomTarget(Entity target) { int targetGatherAttempts = 0; if (target == null) { while (true) { // Failure to gather a target if (targetGatherAttempts is 5) break; targetGatherAttempts++; int randomTargetValue = UnityEngine.Random.Range(-1, order.Count); if (randomTargetValue is -1) { if (playerComponent.HasDied) continue; else return playerComponent; } else if (order[randomTargetValue].HasDied || order[randomTargetValue] is Enemy || order[randomTargetValue].IsHidden) continue; else { target = order[randomTargetValue]; break; } } } return target; } } private bool UniqueTargetCheck(Entity attackerComponent, Entity entityComponent) { List<Ability> targetAbilities = new List<Ability>(entityComponent.GetInventory().Abilities); targetAbilities.AddRange(entityComponent.GetInventory().limitedAbilities); Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); // Sin Incarnation Check if (targetAbilities.Contains(abilityList[121]) && attackerComponent.RetrieveEnemyType().Equals("Demonic")) return false; // Death and Decay Check if (targetAbilities.Contains(abilityList[114]) && attackerComponent.RetrieveEnemyType().Equals("Undead")) return false; return true; } /// <summary> /// Extracts all of the minion components if available and combines them with their respective true component arrays /// </summary> /// <param name="allyAbilityChecks">Output of all available ally systems</param> /// <param name="enemyAbilityChecks">Output of all available enemy systems</param> private void ExtractAllAvailableEntities(out Ally[] allyAbilityChecks, out Enemy[] enemyAbilityChecks) { Ally[] availableAllies = allyComps.Where(ally => ally.HasDied is false).ToArray(); Enemy[] availableEnemies = enemyComps.Where(enemy => enemy.HasDied is false).ToArray(); try { Ally[] availableMinionAllies = allyMinionComps; if (availableMinionAllies.Length is 0) throw new InvalidOperationException(); // Attempt to combine minion component and true ally component allyAbilityChecks = new Ally[availableAllies.Length + availableMinionAllies.Length]; Array.Copy(availableAllies, 0, allyAbilityChecks, 0, availableAllies.Length); Array.Copy(availableMinionAllies, 0, allyAbilityChecks, availableAllies.Length, availableMinionAllies.Length); } catch { // If exception is thrown simply copy just the base ally components allyAbilityChecks = new Ally[availableAllies.Length]; Array.Copy(availableAllies, 0, allyAbilityChecks, 0, availableAllies.Length); } try { Enemy[] availableMinionEnemies = enemyMinionComps; if (availableMinionEnemies.Length is 0) throw new InvalidOperationException(); // Attempt to combine minion component and true enemy component enemyAbilityChecks = new Enemy[availableEnemies.Length + availableMinionEnemies.Length]; Array.Copy(availableEnemies, 0, enemyAbilityChecks, 0, availableEnemies.Length); Array.Copy(availableMinionEnemies, 0, enemyAbilityChecks, availableEnemies.Length, availableMinionEnemies.Length); } catch { // If exception is thrown simply copy just the base enemy components enemyAbilityChecks = new Enemy[availableEnemies.Length]; Array.Copy(availableEnemies, 0, enemyAbilityChecks, 0, availableEnemies.Length); } } private Entity CheckForAttentionMods() { // Check player if (playerObject.activeSelf is true) { bool playerHas = playerComponent.GatherModList().SmartModCheck("Attention Getter"); if (playerHas) { int focusedRollValue = UnityEngine.Random.Range(1, 5); if (focusedRollValue >= 2) return playerComponent; } } // Check true allies if (allyComps.Length is not 0) { for (int i = 0; i < allyComps.Length; ++i) { if (allyComps[i].GatherModList().SmartModCheck("Attention Getter")) { int focusedRollValue = UnityEngine.Random.Range(1, 5); if (focusedRollValue >= 2) return allyComps[i]; } } } // Check ally minions if (allyMinionComps is not null) { for (int i = 0; i < allyMinionComps.Length; ++i) { if (allyMinionComps[i].GatherModList().SmartModCheck("Attention Getter")) { int focusedRollValue = UnityEngine.Random.Range(1, 5); if (focusedRollValue >= 2) return allyMinionComps[i + allyComps.Length]; } } } return null; } /// <summary> /// Simpler form of the AttackWithSpell method that damages the target with the value designated /// before changing the mana and status of the user of the spell and showing the necessary text on screen /// </summary> /// <param name="damageValue">Value for the Spell</param> /// <param name="spell">The spell being cast</param> /// <param name="spellUser">The entity using the spell</param> /// <param name="spellTarget">The target of the spell</param> /// <param name="endSpell">Indicator if the spell is an advanced spell and has not ended</param> /// <returns></returns> public async Task AttackWithSpell(int damageValue, Spell spell, Entity spellUser, Entity spellTarget, bool endSpell = true) { // Check for unique Life Contract trigger if (spellUser.GatherModList().SmartModCheck("Life Contract") && damageValue > 0) spellUser.GatherModList().IndicateModification("Life Contract", damageValue); // Unique to Sorcerer - Obsessive Nature if (spellUser is Enemy && !playerComponent.HasDied && playerComponent.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[128])) playerComponent.IncreaseExperience(playerComponent.GetInventory().Spells.Contains(spell) ? 50 : 25); // Shows the necessary action text for the series of logic that took place if (endSpell) { switch (spell.SpellEffects[0]) { case SpellEffect.Damage: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), spellTarget.GetName(), ActionTextBox.ActionType.DamagingSpell, spell, damageValue); if (damageValue <= 0) { await StopPlayerTurn(spellUser); return; } break; case SpellEffect.Heal: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), spellTarget.GetName(), ActionTextBox.ActionType.HealingSpell, spell, damageValue * -1); if (damageValue <= 0) { await StopPlayerTurn(spellUser); return; } break; case SpellEffect.Refill: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), spellTarget.GetName(), ActionTextBox.ActionType.RefillingSpell, spell, damageValue * -1); if (damageValue <= 0) { await StopPlayerTurn(spellUser); return; } break; case SpellEffect.Buff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), spellTarget.GetName(), ActionTextBox.ActionType.BuffingSpell, spell); await StopPlayerTurn(spellUser); return; case SpellEffect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), spellTarget.GetName(), ActionTextBox.ActionType.DebuffingSpell, spell); await StopPlayerTurn(spellUser); return; } } // Damages the necessary entity if (damageValue is not 0 && spellTarget is Player) DamagePlayer(damageValue, false); else if (damageValue is not 0 && spellTarget is Enemy enemyTarget) DamageEnemy(enemyTarget, damageValue, false, endSpell); else if (damageValue is not 0 && spellTarget is Ally allyTarget) DamageAlly(allyTarget, damageValue, false); await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; async Task StopPlayerTurn(Entity spellUser) { uiHandler.ResetUI(); if (spellUser is Ally || spellUser is Player) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } } /// <summary> /// Advanced form of the AttackWithSpell method meant for targetting whole sides /// </summary> /// <param name="v">Spell value</param> /// <param name="spell">Spell being cast</param> /// <param name="spellUser">The entity casting the spell</param> /// <param name="left">Indicator if the left side is being targetted</param> /// <param name="right">Indicator if the right side is being targetted</param> /// <returns>The completed task of the state of the spell attack</returns> /// <exception cref="DataMisalignedException">Exception is thrown when both left and right are false which indicates malignant data</exception> public async Task AttackWithSpell(int[] damageValues, Spell spell, Entity spellUser, bool left, bool right) { // Check for unique Life Contract trigger if (spellUser.GatherModList().SmartModCheck("Life Contract") && damageValues.Any(x => x > 0)) spellUser.GatherModList().IndicateModification("Life Contract", damageValues.Where(x => x > 0).ToArray()); // Theory Crafting Copy Ability needs to be placed inline if (spellUser is Enemy) { Inventory playerInven = playerComponent.GetInventory(); if (playerInven.Abilities.Contains(AbilityBrain.GetAbilityList()[90]) && !playerInven.Spells.Contains(spell)) { int rolledValue = DiceRoller.RollDice(new DHundred()); if (rolledValue <= 3) playerInven.Add(spell); } } // Cycles through to the necessary action text desired for the circumstance if (left && right) { switch (spell.SpellEffects[0]) { case SpellEffect.Damage: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "everyone", ActionTextBox.ActionType.DamagingSpell, spell, UtilityHandler.Average(damageValues)); break; case SpellEffect.Heal: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "everyone", ActionTextBox.ActionType.HealingSpell, spell, UtilityHandler.Average(damageValues)); break; case SpellEffect.Buff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "everyone", ActionTextBox.ActionType.BuffingSpell, spell); uiHandler.ResetUI(); if (spellUser is Player || spellUser is Ally) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; return; case SpellEffect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "everyone", ActionTextBox.ActionType.DebuffingSpell, spell); uiHandler.ResetUI(); if (spellUser is Player || spellUser is Ally) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; return; } } else if (left) { switch (spell.SpellEffects[0]) { case SpellEffect.Damage: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Companions", ActionTextBox.ActionType.DamagingSpell, spell, UtilityHandler.Average(damageValues)); break; case SpellEffect.Heal: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Companions", ActionTextBox.ActionType.HealingSpell, spell, UtilityHandler.Average(damageValues)); break; case SpellEffect.Buff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Companions", ActionTextBox.ActionType.BuffingSpell, spell); uiHandler.ResetUI(); if (spellUser is Player || spellUser is Ally) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; return; case SpellEffect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Companions", ActionTextBox.ActionType.DebuffingSpell, spell); uiHandler.ResetUI(); if (spellUser is Player || spellUser is Ally) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; return; } } else if (right) { switch (spell.SpellEffects[0]) { case SpellEffect.Damage: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Enemies", ActionTextBox.ActionType.DamagingSpell, spell, UtilityHandler.Average(damageValues)); break; case SpellEffect.Heal: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Enemies", ActionTextBox.ActionType.HealingSpell, spell, UtilityHandler.Average(damageValues)); break; case SpellEffect.Buff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Enemies", ActionTextBox.ActionType.BuffingSpell, spell); uiHandler.ResetUI(); if (spellUser is Player || spellUser is Ally) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; return; case SpellEffect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextSpell(spellUser.GetName(), "all Enemies", ActionTextBox.ActionType.DebuffingSpell, spell); uiHandler.ResetUI(); if (spellUser is Player || spellUser is Ally) DisablePlayerTurn(); else await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; return; } } // Damages the designated side first if (left && spell.SpellEffects[0] is not SpellEffect.Heal) { if (!playerComponent.HasDied) { if (damageValues[0] is not 0 && UniqueSpellDamageChecks(spell, playerComponent)) DamagePlayer(damageValues[0], false); } int counter = 0; Ally[] aliveAllies = allyComps.Where(ally => ally.HasDied is false).ToArray(); if (allyMinionComps != null) { if (allyMinionComps.Length > 0) aliveAllies.Concat(allyMinionComps.Where(allyMinion => allyMinion.HasDied is false)); } foreach (Ally ally in aliveAllies) { if (damageValues[counter + 1] is not 0 && UniqueSpellDamageChecks(spell, ally)) { try { DamageAlly(ally, damageValues[counter + 1], false); } catch { Debug.Log("error caught"); } } ++counter; } } if (right && spell.SpellEffects[0] is not SpellEffect.Heal) { int counter = 0; List<Enemy> aliveEnemies = enemyComps.Where(enemy => enemy.HasDied is false).ToList(); if (enemyMinionComps != null) { if (enemyMinionComps.Length > 0) aliveEnemies.AddRange(enemyMinionComps); } foreach (Enemy enemy in aliveEnemies) { if (damageValues[counter] is not 0 && UniqueSpellDamageChecks(spell, enemy)) { try { DamageEnemy(enemy, damageValues[counter], false); } catch { Debug.Log("error caught"); } } ++counter; } } await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } public async Task FailedSpellCall(Spell spellFailure) { uiHandler.RetrieveActionText().SignalFailedSpellText(spellFailure); uiHandler.ResetUI(); if (currentEntity is Player || currentEntity is Ally) DisablePlayerTurn(); await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } private bool UniqueSpellDamageChecks(Spell spellToCheck, Entity entity) { bool output = true; switch (spellToCheck.SpellID) { case 23: // Order: Rupture only affects people inflicted with 'Sick' if (!entity.GatherModList().SmartModCheck("Sick")) output = false; break; default: break; } return output; } /// <summary> /// Damages the target with the value of the consumable while also calling for the action text /// </summary> /// <param name="damageValue">The value of the item rolls</param> /// <param name="consumable">The consumable being used</param> /// <param name="itemUser">The entity user of the consumable</param> /// <param name="itemTarget">The entity target of the consumable</param> /// <returns>The completed state when the damage is carried through</returns> public async Task AttackWithItem(int damageValue, Consumable consumable, Entity itemUser, Entity itemTarget) { if (damageValue is not 0 && itemTarget is Player) DamagePlayer(damageValue, false); else if (damageValue is not 0 && itemTarget is Enemy) DamageEnemy(itemTarget as Enemy, damageValue, false); else if (damageValue is not 0 && itemTarget is Ally) DamageAlly(itemTarget as Ally, damageValue, false); uiHandler.RetrieveActionText().UpdateActionTextItem(itemUser.GetName(), itemTarget, consumable, ActionTextBox.ActionType.DamagingItemOnSingle, damageValue); await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } #nullable enable /// <summary> /// Damages the necessary targets based on parameters passed, i.e. left damages all allies and players, right damages all enemies /// </summary> /// <param name="damageValue">The value of the item rolls</param> /// <param name="consumable">The consumable being used</param> /// <param name="itemUser">The entity user of the consumable</param> /// <param name="itemTarget">The entity target of the consumable</param> /// <param name="left">Indicator if all of the companions side is affected</param> /// <param name="right">Indicator if all of the enemies side is affected</param> /// <returns>The completed state when the damage is carried through</returns> /// <exception cref="DataMisalignedException">Exception is thrown when left and right are false which indicates malignant data</exception> public async Task AttackWithItem(int damageValue, Consumable consumable, Entity itemUser, Entity itemTarget, bool left, bool right) { if (left && right) { if (damageValue is not 0) { DamagePlayer(damageValue, false); foreach (Ally a in allyComps) DamageAlly(a, damageValue, false); foreach (Enemy e in enemyComps) DamageEnemy(e, damageValue, false); } uiHandler.RetrieveActionText().UpdateActionTextItem(itemUser.GetName(), itemTarget, consumable, ActionTextBox.ActionType.DamagingItemOnAll, damageValue); } // Damages the designated side first else if (left) { if (damageValue is not 0) { DamagePlayer(damageValue, false); foreach (Ally a in allyComps) DamageAlly(a, damageValue, false); } uiHandler.RetrieveActionText().UpdateActionTextItem(itemUser.GetName(), itemTarget, consumable, ActionTextBox.ActionType.DamagingItemOnType, damageValue); } else if (right) { if (damageValue is not 0) foreach (Enemy e in enemyComps) DamageEnemy(e, damageValue, false); uiHandler.RetrieveActionText().UpdateActionTextItem(itemUser.GetName(), itemTarget, consumable, ActionTextBox.ActionType.DamagingItemOnType, damageValue); } else throw new DataMisalignedException($"The consumable has flawed information for attacking - Consumable ({consumable.ItemName}) - Left Ind {left} - Right Ind {right}"); await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } #nullable disable private void ArmorTriggerSearch(Dictionary<string, (List<string>, List<object>)> triggers, ref int armorValue, ref int toHitRoll) { if (triggers.ContainsKey("AddArmor")) armorValue = UtilityHandler.AddArmorDecipher(triggers, armorValue); if (triggers.ContainsKey("MinusArmor")) armorValue = UtilityHandler.MinusArmorDecipher(triggers, armorValue); if (triggers.ContainsKey("ToHitAdd")) toHitRoll = UtilityHandler.ToHitAddDecipher(triggers, toHitRoll); // The value component should be negative to begin with if (triggers.ContainsKey("ToHitMinus")) toHitRoll = UtilityHandler.ToHitMinusDecipher(triggers, toHitRoll); } private int DamageTriggerSearch(Entity target, Entity attacker, Dictionary<string, (List<string>, List<object>)> triggers, int value, bool isCrit) { if (triggers is null) return value; // Doubles the damage if (triggers.ContainsKey("DoubleDamage")) { foreach (var item in triggers["DoubleDamage"].Item2) value *= 2; } // Halves the damage if (triggers.ContainsKey("HalfCrits") && isCrit) { foreach (var item in triggers["HalfCrits"].Item2) value /= 2; } // Add more damage if (triggers.ContainsKey("AddDamageValue")) { int counterIndex = 0; foreach (var item in triggers["AddDamageValue"].Item2) { if (item is float fVal) { int damage = (int)Math.Floor(fVal * value); DetermineValue(damage, counterIndex); } else if (item is int iVal) { int damage = iVal; DetermineValue(damage, counterIndex); } else if (item is Dice dVal) { int damage = DiceRoller.RollDice(dVal); DetermineValue(damage, counterIndex); } else if (item is Dice[] dVals) { int damage = DiceRoller.RollDice(dVals); DetermineValue(damage, counterIndex); } else if (item is Dice.DiceR d) { int damage = UtilityHandler.RollValue(d); DetermineValue(damage, counterIndex); } else if (item is Dice.DiceR[] ds) { int damage = UtilityHandler.RollValue(ds); DetermineValue(damage, counterIndex); } else throw new TypeLoadException(); ++counterIndex; } } // Take away damage if (triggers.ContainsKey("MinusDamageValue")) { foreach (var item in triggers["MinusDamageValue"].Item2) { // Additive, item values should be negative to begin with, if strange values check in AbilityBrain if (item is float fVal) value += (int)Math.Ceiling(value * fVal); else if (item is int iVal) value += iVal; else if (item is Dice dval) value += DiceRoller.RollDice(dval); else if (item is Dice[] dVals) value += DiceRoller.RollDice(dVals); else if (item is Dice.DiceR d) value += UtilityHandler.RollValue(d); else if (item is Dice.DiceR[] ds) value += UtilityHandler.RollValue(ds); else if (item is List<object> listOfvalues) value += (int)listOfvalues[0]; else throw new TypeLoadException(); } } // Percentage of damage back to the attacker if (triggers.ContainsKey("DamageBack")) { int damageBack = 0; foreach (var item in triggers["DamageBack"].Item2) { if (item is float fVal) damageBack += (int)Math.Floor(value * fVal); else if (item is int iVal) damageBack += iVal; else if (item is Dice dVal) damageBack += DiceRoller.RollDice(dVal); else if (item is Dice[] dVals) damageBack += DiceRoller.RollDice(dVals); else if (item is Dice.DiceR d) damageBack += UtilityHandler.RollValue(d); else if (item is Dice.DiceR[] ds) damageBack += UtilityHandler.RollValue(ds); else throw new TypeLoadException(); } if (attacker is Enemy enemy) DamageEnemy(enemy, damageBack, false, false); else if (attacker is Ally ally) DamageAlly(ally, damageBack, false); else DamagePlayer(damageBack, false); } // Scavenge Functionality if (triggers.ContainsKey("Scavenge") && !attacker.HasDied) { int healthIncrease = 0; foreach (var item in triggers["Scavenge"].Item2) { if (item is float fVal) healthIncrease += (int)Math.Floor(value * fVal); else if (item is int iVal) healthIncrease += iVal; else if (item is Dice dVal) healthIncrease += DiceRoller.RollDice(dVal); else if (item is Dice[] dVals) healthIncrease += DiceRoller.RollDice(dVals); else if (item is Dice.DiceR d) healthIncrease += UtilityHandler.RollValue(d); else if (item is Dice.DiceR[] ds) healthIncrease += UtilityHandler.RollValue(ds); else throw new TypeLoadException(); } attacker.IncreaseHealth(healthIncrease); attacker.StatusChanged(); } return value; // Check function to determine the type of damage being inflicted void DetermineValue(int damage, int counterIndex) { try { value += UtilityHandler.ElementWeaknessAndResistChecks(damage, (Element)triggers["AddDamageType"].Item2[counterIndex], target); } catch { value += UtilityHandler.WeaponTypeChecks(damage, attacker, target); } } } /// <summary> /// Disables the enemy battling portion of the Encounter /// </summary> /// <param name="ran">Trigger if the player ran away from the enemies</param> public async void DisableBattle(bool ran = false) { Globals.IsBossEncounter = false; uiHandler.currentEntityTurnText.enabled = false; // Destroys all minions ClearTemporaryMinions(); uiHandler.ClearTempMinions(); GeneralUI.Instance.EliminateTempGUIs(); // Checks if all the allies have fainted bool allFainted = true; for (int i = 0; i < allyObjects.Length; i++) { if (!allyObjects[i].GetComponent<Ally>().HasDied) { allFainted = false; break; } } // If all of the companions pass out/die than the game over screen will pop up if (playerComponent.HasDied && allFainted) { SceneManager.LoadScene(2); return; } else { if (playerComponent.HasDied) playerComponent.Revive(true); foreach (Ally ally in allyComps) { if (ally.HasDied) ally.Revive(true); } } await AbilityBrain.PostBattleChecks(playerComponent, allyComps); bountyEnabled = null; uiHandler.GiveExperience(playerComponent, allyComps); // Deactivates all the companion game objects playerComponent.DeactivateVisually(); DeactivateAllAllies(); // Creates post battle buttons determined by if the player ran uiHandler.CreatePostBattleButtons(ran); } /// <summary> /// Injects information into the spell manager to begin the usage of a spell /// </summary> /// <param name="spell">The spell being used</param> /// <param name="entity">The entity using the spell</param> public async Task InjectIntoSpellManager(Spell spell, Entity entity, bool noChoose = false) { // Guards spell activation if the entity attempting to cast a spell is silenced if (entity.GatherModList().SmartModCheck("Silenced")) return; if (!noChoose) { ExtractAllAvailableEntities(out Ally[] allyComponents, out Enemy[] enemyComponents); if (playerObject.activeSelf == true) { await spellManager.BeginSpellProcess(entity, spell, playerComponent, enemyComponents, allyComponents, this); if (entity is Enemy eRef) { bool anyKills = allyComponents.Any(ally => ally.HasDied) || playerObject.activeSelf is false; if (anyKills) eRef.GetAnimationHandler().PlayKilledAllySound(); } } else { await spellManager.BeginSpellProcess(entity, spell, null, enemyComponents, allyComponents, this); if (entity is Enemy eRef) { bool anyKills = allyComponents.Any(ally => ally.HasDied); if (anyKills) eRef.GetAnimationHandler().PlayKilledAllySound(); } } } // This call is primarily meant for the Unstable Magical Core Ability for Wild Magic instances if (noChoose) { if (spell.SpellEffects.Contains(SpellEffect.Damage) || spell.SpellEffects.Contains(SpellEffect.Debuff)) spellManager.ChooseTarget(enemyComps[UnityEngine.Random.Range(0, enemyComps.Length)]); else { List<Entity> entitiesToChoose = new List<Entity>(); if (playerObject.activeSelf) entitiesToChoose.Add(playerComponent); foreach (Ally ally in allyComps) entitiesToChoose.Add(ally); spellManager.ChooseTarget(entitiesToChoose[UnityEngine.Random.Range(0, entitiesToChoose.Count)]); } } } /// <summary> /// Injects a spell and enemy into the spell manager for the battle handler when a spell is preparing to be used by an enemy /// </summary> /// <param name="spell">The spell being casted</param> /// <param name="entity">The enemy casting the spell</param> /// <returns>The completed state when an enemy uses a spell</returns> public async Task AwaitEnemySpellUse(Spell spell, Entity entity) => await InjectIntoSpellManager(spell, entity); /// <summary> /// Unique Call for Action Text for spells determined by the user and target /// </summary> /// <param name="user">Name of the object that used the spell</param> /// <param name="target">Name of the target of the spell</param> /// <param name="spell">The spell being used</param> /// <param name="value">Heal amount, damage amount, 0 otherwise</param> public async void CallSpellActionText(string user, string target, Spell spell, int value = 0, bool failed = false) { if (failed) uiHandler.RetrieveActionText().UpdateActionTextSpell(user, target, ActionTextBox.ActionType.SpellFailed, spell); else if (spell.SpellEffects.Contains(SpellEffect.Heal)) uiHandler.RetrieveActionText().UpdateActionTextSpell(user, target, ActionTextBox.ActionType.HealingSpell, spell, value); else if (spell.SpellEffects.Contains(SpellEffect.Debuff)) uiHandler.RetrieveActionText().UpdateActionTextSpell(user, target, ActionTextBox.ActionType.DebuffingSpell, spell); else if (spell.SpellEffects.Contains(SpellEffect.Buff)) uiHandler.RetrieveActionText().UpdateActionTextSpell(user, target, ActionTextBox.ActionType.BuffingSpell, spell); else uiHandler.RetrieveActionText().UpdateActionTextSpell(user, target, ActionTextBox.ActionType.DamagingSpell, spell, value); await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } /// <summary> /// Unique Call for Action Text for spells determined by which side is fully affected /// </summary> /// <param name="user">The entity casting the spell</param> /// <param name="left">Indicator if the companions side is being targetted</param> /// <param name="right">Indicator if the enemy side is being targetted</param> /// <param name="spell">The spell being casted</param> /// <param name="value">The value for the spell</param> public async void CallSpellActionText(Entity user, bool left, bool right, SpellEffect spellEffect, Spell spell, int value = 0) { if (left) { switch (spellEffect) { case SpellSO.SpellEffect.Damage: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Companions", ActionTextBox.ActionType.DamagingSpell, spell, value); break; case SpellSO.SpellEffect.Heal: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Companions", ActionTextBox.ActionType.HealingSpell, spell, value); break; case SpellSO.SpellEffect.Buff: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Companions", ActionTextBox.ActionType.BuffingSpell, spell); break; case SpellSO.SpellEffect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Companions", ActionTextBox.ActionType.DebuffingSpell, spell); break; } } else if (right) { switch (spellEffect) { case SpellSO.SpellEffect.Damage: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Enemies", ActionTextBox.ActionType.DamagingSpell, spell, value); break; case SpellSO.SpellEffect.Heal: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Enemies", ActionTextBox.ActionType.HealingSpell, spell, value); break; case SpellSO.SpellEffect.Buff: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Enemies", ActionTextBox.ActionType.BuffingSpell, spell); break; case SpellSO.SpellEffect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextSpell(user.GetName(), "all Enemies", ActionTextBox.ActionType.DebuffingSpell, spell); break; } } await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } #nullable enable /// <summary> /// Unique call for action text when using an item /// </summary> /// <param name="user">The name of the user of the item</param> /// <param name="target">The target of the item when applicable</param> /// <param name="consumable">The consumable that is being used</param> /// <param name="grouping">Indicator of what is being targetted</param> /// <param name="value">The value of the damage or healing being done</param> public async void CallItemActionText(string user, Entity? target, Consumable consumable, Target.Targetted grouping = Target.Targetted.Single, int value = 0) { switch (grouping) { case Target.Targetted.Single: switch (consumable.Effects[0]) { case ConsumableSO.Effect.Heal: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.HealingItemOnSingle, value); break; case ConsumableSO.Effect.Refill: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.RefillingItemOnSingle, value); break; case ConsumableSO.Effect.Buff: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.BuffingItemOnSingle); break; case ConsumableSO.Effect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.DebuffingItemOnSingle); break; case ConsumableSO.Effect.Damage: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.DamagingItemOnSingle, value); break; } break; case Target.Targetted.AllOfType: switch (consumable.Effects[0]) { case ConsumableSO.Effect.Heal: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.HealingItemOnType, value); break; case ConsumableSO.Effect.Refill: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.RefillingItemOnType, value); break; case ConsumableSO.Effect.Buff: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.BuffingItemOnType); break; case ConsumableSO.Effect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.DebuffingItemOnType); break; case ConsumableSO.Effect.Damage: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.DamagingItemOnType, value); break; } break; case Target.Targetted.All: switch (consumable.Effects[0]) { case ConsumableSO.Effect.Heal: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.HealingItemOnAll, value); break; case ConsumableSO.Effect.Refill: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.RefillingItemOnAll, value); break; case ConsumableSO.Effect.Buff: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.BuffingItemOnAll); break; case ConsumableSO.Effect.Debuff: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.DebuffingItemOnAll); break; case ConsumableSO.Effect.Damage: uiHandler.RetrieveActionText().UpdateActionTextItem(user, target, consumable, ActionTextBox.ActionType.DamagingItemOnAll, value); break; } break; } await uiHandler.RetrieveActionText().GetTextShownStatus(); await Task.CompletedTask; } #nullable disable /// <summary> /// Disables the interactivity of the spells while choosing a target for a spell that was already chosen /// </summary> public void AwaitSpellChoice(Entity entity) => uiHandler.DisableSpellButtonInteractivity(entity); /// <summary> /// Disables the interactivity of the item buttons while choosing a target when necessary /// </summary> /// <param name="entity">The entity that is choosing</param> public void AwaitConsumableChoice(Entity entity) => uiHandler.DisableItemButtonInteractivity(entity); /// <summary> /// Cancels the spell attempting to be used /// </summary> public void CancelSpellUse() => spellManager.Cancel(); /// <summary> /// Cancels the item usage and cancels the tasks in the consumable manager /// </summary> public void CancelItemUse() => consumableManager.Cancel(); /// <summary> /// Activates the consumable selected and prepares to utilize it /// </summary> /// <param name="activeInven">The active inventory that the consumable is being used from</param> /// <param name="consumable">The consumable that is being used</param> /// <param name="user">The user of the consumable</param> public async void ActivateConsumable(Inventory activeInven, Consumable consumable, Entity user) { ExtractAllAvailableEntities(out Ally[] allyComponents, out Enemy[] enemyComponents); if (playerObject.activeSelf == true) consumableManager.BeginConsumableProcess(user, consumable, playerComponent, enemyComponents, allyComponents, this); else consumableManager.BeginConsumableProcess(user, consumable, null, enemyComponents, allyComponents, this); // Waits for the consumable manager to complete the task state, or for the task state to be canceled while (consumableManager.GetUsedConsumableState() is false && !consumableManager.GetConsumableTaskState()) await Task.Yield(); // If the consumable was used than it is removed from the active inventory bool check = consumableManager.GetUsedConsumableState(); if (check) activeInven.Remove(consumable, 1); } /// <summary> /// Revives the entity indicated by switching the game object back on and reimplementing their turn order /// </summary> /// <param name="entity">The entity to be revived</param> public void Revive(Entity entity) { if (entity is Player player) { playerObject.SetActive(true); deadEntities.Remove(player); } else if (entity is Ally) { for (int i = 0; i < deadEntities.Count; i++) { if (deadEntities[i] is Ally ally && ally == entity as Ally) { deadEntities[i].gameObject.SetActive(true); deadEntities.RemoveAt(i); } } } else { for (int i = 0; i < deadEntities.Count; i++) { if (deadEntities[i].TryGetComponent(out Enemy enemy)) { enemy.gameObject.SetActive(true); deadEntities.RemoveAt(i); } } } } /// <summary> /// Creates a temporary minion for one of the sides /// </summary> /// <param name="minion">The minion game object to summon</param> /// <param name="isLeft">Indicator if the minion being summoned is an Ally, true is Ally, false is Enemy</param> /// <returns></returns> public bool CreateMinion(GameObject minion, bool isLeft) { // When no free spots are available just return false and indicate it was a failed spell cast if ((isLeft && allyComps.Length is 3) || (!isLeft && enemyComps.Length is 4)) return false; // If the minion comps don't exist then simply continue on, since the prior check was false try { int trueAllyLength = allyComps.Length; if (isLeft && trueAllyLength + allyMinionComps.Length is 3) return false; } catch { } try { int trueEnemyLength = enemyComps.Length; if (!isLeft && trueEnemyLength + enemyMinionComps.Length is 4) return false; } catch { } // Create Temp Minion GameObject createdMinion = pHandler.InjectMinion(minion, isLeft); int turnValue; if (isLeft) { if (allyMinionComps is null || allyMinionObjects is null) { allyMinionComps = new Ally[1]; allyMinionObjects = new GameObject[1]; } else { Array.Resize(ref allyMinionComps, allyMinionComps.Length + 1); Array.Resize(ref allyMinionObjects, allyMinionObjects.Length + 1); } Ally allyComp = createdMinion.GetComponent<Ally>(); allyComp.IsMinion = true; uiHandler.InjectTempMinion(allyComp); allyMinionObjects[^1] = createdMinion; allyMinionComps[^1] = allyComp; turnValue = allyComp.RollOrderValue(); combatOrder.CallOrderInitiative(allyComp, turnValue); } else { if (enemyMinionComps is null || enemyMinionObjects is null) { enemyMinionComps = new Enemy[1]; enemyMinionObjects = new GameObject[1]; } else { Array.Resize(ref enemyMinionComps, enemyMinionComps.Length + 1); Array.Resize(ref enemyMinionObjects, enemyMinionObjects.Length + 1); } Enemy enemyComp = createdMinion.GetComponent<Enemy>(); enemyComp.InjectHandler(this); enemyComp.IsMinion = true; uiHandler.InjectTempMinion(enemyComp); enemyMinionObjects[^1] = createdMinion; enemyMinionComps[^1] = enemyComp; turnValue = enemyComp.RollOrderValue(); combatOrder.CallOrderInitiative(enemyComp, turnValue); } // Re-sort the combat order order = combatOrder.SortOrder(); // Make sure that the current entity is still selected for the set active entity method foreach (Entity entity in order) { if (entity.GetUniqueID() == currentEntity.GetUniqueID()) { activeEntity = order.IndexOf(entity); break; } } // If it is a friendly minion create a temporary GUI if (isLeft) GeneralUI.Instance.CreateTempGUI(createdMinion.GetComponent<Ally>()); return true; } /// <summary> /// All of the minions summoned this battle are destroyed and wiped /// </summary> private void ClearTemporaryMinions() { // Try loop is used in case the Enemy Minion Objects wasn't initialized try { for (int i = 0; i < enemyMinionObjects.Length; ++i) Destroy(enemyMinionObjects[i]); } catch { } // See above try { for (int i = 0; i < allyMinionObjects.Length; ++i) Destroy(allyMinionObjects[i]); } catch { } enemyMinionObjects = null; enemyMinionComps = null; allyMinionObjects = null; allyMinionComps = null; } /// <summary> /// Destroys the minion object and minion component in the arrays /// </summary> /// <param name="minionComp">The minion component to eliminate</param> /// <exception cref="DataMisalignedException">Exception thrown when the Lengths of the ally or enemy minion objects and components don't match</exception> private void DestroyMinionInstance(Entity minionComp) { bool isAlly = minionComp is Ally; // Both ally minion arrays or enemy minion arrays should match up in length // Ally destruction protocol if (isAlly) { if (allyMinionObjects.Length != allyMinionComps.Length) throw new DataMisalignedException("The Length of the Ally Minion Objects and the Ally Minion Components do NOT match! Error will occur if destructing instance."); int objectLength = allyMinionObjects.Length; for (int i = 0; i < objectLength; ++i) { if (allyMinionComps[i].GetUniqueID() == minionComp.GetUniqueID()) { // Remove from the order order.Remove(minionComp); // Collect the garbage value GameObject garbageValue = allyMinionObjects[i]; Ally allyComponent = allyMinionComps[i]; // Cycle through all minions pass the point that is being nullified // and move their values down the index for (int k = i; k < objectLength - 1; ++k) { allyMinionComps[k] = allyMinionComps[k + 1]; allyMinionComps[k + 1] = null; allyMinionObjects[k] = allyMinionObjects[k + 1]; allyMinionObjects[k + 1] = null; } // Shrink the arrays and destroy the garbage value Array.Resize(ref allyMinionObjects, objectLength - 1); Array.Resize(ref allyMinionComps, objectLength - 1); if (allyMinionComps.Length is 0) { allyMinionComps = null; allyMinionObjects = null; } uiHandler.DestroyTempMinion(allyComponent); Destroy(garbageValue); } } } // Enemy destruction protocol else { if (enemyMinionObjects.Length != enemyMinionComps.Length) throw new DataMisalignedException("The Length of the Enemy Minion Objects and the Enemy Minion Components do NOT match! Error will occur if destructing instance."); int objectLength = enemyMinionObjects.Length; for (int i = 0; i < objectLength; ++i) { if (enemyMinionComps[i].GetUniqueID() == minionComp.GetUniqueID()) { // Remove from the order order.Remove(minionComp); // Collect the garbage value GameObject garbageValue = enemyMinionObjects[i]; Enemy enemyComponent = enemyMinionComps[i]; // Cycle through all minions pass the point that is being nullified // and move their values down the index for (int k = i; k < objectLength - 1; ++k) { enemyMinionComps[k] = enemyMinionComps[k + 1]; enemyMinionComps[k + 1] = null; enemyMinionObjects[k] = enemyMinionObjects[k + 1]; enemyMinionObjects[k + 1] = null; } // Shrink the arrays and destroy the garbage value Array.Resize(ref enemyMinionObjects, objectLength - 1); Array.Resize(ref enemyMinionComps, objectLength - 1); if (enemyMinionComps.Length is 0) { enemyMinionComps = null; enemyMinionObjects = null; } uiHandler.DestroyTempMinion(enemyComponent); Destroy(garbageValue); } } } // Re-establish the correct entity in the order foreach (Entity entity in order) { if (entity.GetUniqueID() == currentEntity.GetUniqueID()) { activeEntity = order.IndexOf(entity); break; } } } public Task DealDamageToLeftRandom(int damageValue) { List<Entity> leftEntities = new List<Entity>(); if (!playerComponent.HasDied) leftEntities.Add(playerComponent); foreach (Ally ally in allyComps) { if (!ally.HasDied) leftEntities.Add(ally); } Entity chosenEntity = leftEntities[UnityEngine.Random.Range(0, leftEntities.Count)]; uiHandler.RetrieveActionText().UpdateActionTextUnique(16, damageValue, chosenEntity); if (chosenEntity is Player) DamagePlayer(damageValue, false); else DamageAlly(chosenEntity as Ally, damageValue, false); return Task.CompletedTask; } public Task DealDamageToRightRandom(int damageValue) { List<Enemy> rightEntities = new List<Enemy>(enemyComps.Where(enemy => enemy.HasDied is false).ToList()); Enemy target = rightEntities[UnityEngine.Random.Range(0, rightEntities.Count)]; uiHandler.RetrieveActionText().UpdateActionTextUnique(16, damageValue, target); DamageEnemy(target, damageValue, false, false); return Task.CompletedTask; } public async Task<(bool, Entity)> IndicateRevivalChoices() { (bool choice, Entity spellTarget) = await uiHandler.ProvideDeadChoices(deadEntities); return (choice, spellTarget); } public Enemy ChooseRandomDeadEnemy() { foreach (Entity entity in deadEntities) { if (entity is Enemy enemy) return enemy; } return null; } }