Encounter / Assets / Scripts / EntityHandler / Enemy.cs
Enemy.cs
Raw
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;
}