using System; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.UI; /// <summary> /// Handles the enemy stats, modifiers, and abilities of the Enemy objects /// </summary> [RequireComponent(typeof(EntityStats))] [RequireComponent(typeof(Target))] [RequireComponent(typeof(CheckCameraUtil))] [RequireComponent(typeof(EntityAnimation))] public class Enemy : Entity { public enum DifficultyRating { VeryEasy, Easy, Average, Hard, VeryHard, Legendary, Boss } [HideInInspector] public bool IsMinion; // Indicator if this enemy instance is a minion or not [Header("Enemy Special Components")] [SerializeField] private List<Dice.DiceR> damage_Dice; // The list of dice that will be considered the enemies damage dice for general fighting [SerializeField] private SpellSO.Element elementType; // The type of damage that the damage dice will afflict to the enemy [SerializeField] private WeaponSO.Type damageType; // The type of damage that the damage dice will afflict to the enemy [SerializeField] private List<SpellSO> spells; // List of spells that the enemy has access to and can be used [SerializeField] private List<AbilitySO> abilities; // List of abilities that the enemy has access to and will be used [SerializeField] private bool canRun; // Indicator if the enemy will consider running [SerializeField] private List<ItemSO> potential_Loot; // List of loot that can be dropped from this creature [SerializeField] private List<float> potentialLootChance; // List of chances correlated with the loot that can be dropped from this creature [SerializeField] private Vector2Int gold_Range; // Range of gold that can be acquired (INCLUSIVE) [SerializeField] private DifficultyRating diffRating; [SerializeField] private int experienceValue; [Header("Enemy UI Components")] [SerializeField] private Canvas canvas_Comp; [SerializeField] private Image health_Bar; [SerializeField] private TextMeshProUGUI enemy_Text_Name; [SerializeField] private TextMeshProUGUI enemy_HP; [SerializeField] private Transform modifier_Field; [SerializeField] private GameObject modifier_Prefab; // Reference to the modifier prefab object private BattleHandler battleHandler; private Inventory enemyInven; public DifficultyRating GetEnemyDifficulty() => diffRating; public Transform GetModifierField() => modifier_Field; public void ReactivateCanvas() => canvas_Comp.enabled = true; protected override void OnEnable() { base.OnEnable(); animator = GetComponent<EntityAnimation>(); canvas_Comp.worldCamera = Camera.main; if (Entity_Name.Equals("Delora")) enemy_Text_Name.text = $"Delora, The Mistress of Torment"; else enemy_Text_Name.text = Entity_Name; enemy_HP.text = this.Health.ToString(); enemyInven = new Inventory(this); // To prevent odd turning of the UI while falling canvas_Comp.enabled = false; } /// <summary> /// Updates the health of the enemy this is attached to by changing the text and changing the fill of the bar /// </summary> protected override void UpdateHealthVis() { base.UpdateHealthVis(); enemy_HP.text = this.Health.ToString(); health_Bar.fillAmount = (float)this.Health / (float)this.maxHealth; this.genStatsChanged = false; } protected override void UpdateStatusFields() { foreach (Transform child in modifier_Field) Destroy(child.gameObject); foreach (KeyValuePair<Modifier, int> mod in modList) { if (mod.Value is 0) continue; GameObject modImage = Instantiate(modifier_Prefab, modifier_Field); modImage.GetComponent<Image>().sprite = mod.Key.ModSprite; modImage.GetComponent<ModifierHelper>().InjectModifier(mod.Key, mod.Value); } base.UpdateStatusFields(); } public virtual async Task ActivateEnemy(List<SpellSO> preSpellList = null, int? preSpellRange = null) { // Logically thinks through their actions to determine using a spell (if available) or attacking the player int totalSpellRange = 0; const int RangeIncrement = 10; const int ChoiceIndexMax = 100; int choiceValue = UnityEngine.Random.Range(1, ChoiceIndexMax + 1); if (this.spells.Count > 0 && !GatherModList().SmartModCheck("Silenced")) { if (preSpellRange == null) { for (int k = 0; k < this.spells.Count; k++) totalSpellRange += RangeIncrement; } else if (preSpellRange != null) totalSpellRange = (int)preSpellRange; List<SpellSO> spellList = new List<SpellSO>(); if (preSpellList == null) spellList = spells; else spellList = preSpellList; if (choiceValue <= totalSpellRange) { int spellIndex = 0; while (choiceValue > RangeIncrement) { spellIndex++; choiceValue -= RangeIncrement; } Spell spell = Globals.TurnSpellFromObject(spellList[spellIndex]); bool recurseCall = false; // Checks to see if a healing spell is actually needed and if not takes it out of the options of the choices for the enemy if (this.Mana < spell.SpellCost) { // Removes the spell from the possible choices for the enemy before restarting the choices spellList.Remove(spells[spellIndex]); totalSpellRange -= RangeIncrement; await ActivateEnemy(spellList, totalSpellRange); recurseCall = true; } else if (spell.SpellEffects.Contains(SpellSO.SpellEffect.Heal)) { Enemy[] current_State = battleHandler.RetrieveEnemyStates(); bool trigger = current_State .Select(enemy => Health < maxHealth) .Any(); if (!trigger) { // Removes the spell from the possible choices for the enemy before restarting the choices spellList.Remove(spells[spellIndex]); totalSpellRange -= RangeIncrement; await ActivateEnemy(spellList, totalSpellRange); recurseCall = true; } } if (!recurseCall) await battleHandler.AwaitEnemySpellUse(spell, this); } else { animator.PlayAttackAnimation(); await battleHandler.AttackLeftSide(damage_Dice, damageType, this); } } else { animator.PlayAttackAnimation(); await battleHandler.AttackLeftSide(damage_Dice, damageType, this); } } /// <summary> /// Injects the battle handler into this enemy for enemy signaling for their turns from the Battle Handler /// </summary> /// <param name="battleHandler"></param> public void InjectHandler(BattleHandler battleHandler) => this.battleHandler = battleHandler; public Dictionary<ItemSO, float> GetPotentialLoot() { Dictionary<ItemSO, float> output = new Dictionary<ItemSO, float>(); if (potential_Loot.Count != potentialLootChance.Count) throw new DataMisalignedException("Potential loot and potential loot chance for enemy do not have matching counts"); for (int i = 0; i < potential_Loot.Count; i++) output.Add(potential_Loot[i], potentialLootChance[i]); return output; } /// <summary> /// Gets the range of gold that can spawn from this enemy /// </summary> /// <returns>Amount of gold to spawn within the min and max of the vector2int</returns> public Vector2Int GetGoldRange() => gold_Range; /// <summary> /// Retrieves the damage type of the enemy for resistance and weakness considerations /// </summary> /// <returns>Enemy Base Damage Type</returns> public WeaponSO.Type GetDamageType() => damageType; public void AddSpell(SpellSO spell) => spells.Add(spell); public void RemoveSpell(SpellSO spell) => spells.Remove(spell); public void AlterGoldRange(float percentIncrease) { gold_Range.x += (int)(percentIncrease * gold_Range.x); gold_Range.y += (int)(percentIncrease * gold_Range.y); } public void AlterLootRanges(float percentIncrease) { for (int i = 0; i < potentialLootChance.Count; i++) { int newChance = (int)Mathf.Clamp(potentialLootChance[i] + (potentialLootChance[i] * percentIncrease), 0, 100); potentialLootChance[i] = newChance; } } public int GetExperienceValue() => experienceValue; public void AlterExperienceValue(float experienceIncrease) => experienceValue += (int)(experienceValue * experienceIncrease); public List<SpellSO> GetEnemySpells() => this.spells; public List<AbilitySO> GetEnemyAbilities() => this.abilities; public override Inventory GetInventory() { return enemyInven; } public List<Dice.DiceR> GetDamageDice() => damage_Dice; }