Encounter / Assets / Scripts / GUIHandlers / DiceRollerGUI.cs
DiceRollerGUI.cs
Raw
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using TMPro;
using UnityEngine;
using static Dice;

/// <summary>
/// Handles the configuration of rolling dice visually
/// </summary>
public class DiceRollerGUI : MonoBehaviour
{
    [Header("Dice Prefabs and Settings")]
    [SerializeField] private Transform diceArea;                    // Reference to the area the dice prefabs will be instantiated in
    [SerializeField] private TextMeshProUGUI valueText;             // Value text reference for where the actual value of what is being rolled is
    [SerializeField] private GameObject d4Prefab;                   // Prefabrication of the D4 image dice collection
    [SerializeField] private GameObject d6Prefab;                   // Prefabrication of the D6 image dice collection
    [SerializeField] private GameObject d8Prefab;                   // Prefabrication of the D8 image dice collection
    [SerializeField] private GameObject d10Prefab;                  // Prefabrication of the D10 image dice collection
    [SerializeField] private GameObject d12Prefab;                  // Prefabrication of the D12 image dice collection
    [SerializeField] private GameObject d20Prefab;                  // Prefabrication of the D20 image dice collection
    [SerializeField] private float rollTime;                        // Reference to how long each dice should roll for before getting the actual values

    [Header("Modifier Panel Configuration")]
    [SerializeField] private GameObject modifierPrefab;             // Reference to the modifier prefab that will be instantiated in the modifier grid
    [SerializeField] private Transform modifierInstanceArea;        // Reference to the transform the modifiers will be instantiated in

    private int[] endValues;
    private DiceGUI[] diceGUIs;
    private bool[] rollsEnded;

    private bool isHitRoll;
    private int shownValue;                                         // The value currently being shown

    // Values beneath are stored in this GUI for attack rolls
    private Entity attacker;
    private Entity target;
    private int toHitValue;
    private int totalDamageValue;
    public bool wasCrit;
    private int armorValue;
    private SortedDictionary<int, (int, Dice)> damageRolls;
    private Dictionary<string, (List<string>, List<object>)> triggers;

    private int _Counter;

    public int[] EndValues
    {
        get => endValues;
    }
    public float RollDuration
    {
        get => rollTime;
    }
    public int TotalValue
    {
        get
        {
            int total = 0;
            
            foreach (int value in endValues)
                total += value;
            
            return total;
        }
    }
    public int Counter
    {
        get => _Counter++;
    }

    public void StartDiceRoller(SortedDictionary<int, (int, Dice)> diceToRoll, bool rollingToHit)
    {
        // Clear any previous dice from the roller
        foreach (Transform child in diceArea)
            Destroy(child.gameObject);

        isHitRoll               = rollingToHit;
        _Counter                = 0;
        
        DiceR[] diceParams      = new DiceR[diceToRoll.Count];
        diceGUIs                = new DiceGUI[diceToRoll.Count];
        rollsEnded              = new bool[diceToRoll.Count];
        endValues               = new int[diceToRoll.Count];
        shownValue              = 0;
        valueText.text          = string.Empty;

        int counter = 0;
        foreach ((int, Dice) diceRoll in diceToRoll.Values)
        {
            if (diceRoll.Item2 is DFour)
                diceParams[counter] = DiceR.Four;
            else if (diceRoll.Item2 is DSix)
                diceParams[counter] = DiceR.Six;
            else if (diceRoll.Item2 is DEight)
                diceParams[counter] = DiceR.Eight;
            else if (diceRoll.Item2 is DTen)
                diceParams[counter] = DiceR.Ten;
            else if (diceRoll.Item2 is DTwelve)
                diceParams[counter] = DiceR.Twelve;
            else if (diceRoll.Item2 is DTwenty)
                diceParams[counter] = DiceR.Twenty;

            endValues[counter++] = diceRoll.Item1;
        }

        InitializeGUIDice(diceParams);
    }
    private async void InitializeGUIDice(params DiceR[] dice)
    {
        for (int i = 0; i < dice.Length; ++i)
        {
           // Each of the dice are instantiated and their diceGUI components are saved within
            switch (dice[i])
            {
                case DiceR.Four:
                    GameObject dice4 = Instantiate(d4Prefab, diceArea, false);
                    diceGUIs[i] = dice4.GetComponent<DiceGUI>();
                    break;
                case DiceR.Six:
                    GameObject dice6 = Instantiate(d6Prefab, diceArea, false);
                    diceGUIs[i] = dice6.GetComponent<DiceGUI>();
                    break;
                case DiceR.Eight:
                    GameObject dice8 = Instantiate(d8Prefab, diceArea, false);
                    diceGUIs[i] = dice8.GetComponent<DiceGUI>();
                    break;
                case DiceR.Ten: 
                    GameObject dice10 = Instantiate(d10Prefab, diceArea, false);
                    diceGUIs[i] = dice10.GetComponent<DiceGUI>();
                    break;
                case DiceR.Twelve: 
                    GameObject dice12 = Instantiate(d12Prefab, diceArea, false);
                    diceGUIs[i] = dice12.GetComponent<DiceGUI>();
                    break;
                case DiceR.Twenty:
                    GameObject dice20 = Instantiate(d20Prefab, diceArea, false);
                    diceGUIs[i] = dice20.GetComponent<DiceGUI>();
                    break;
                default:
                    throw new DataMisalignedException();
            }

            // The dice prefabs set up their respective dictionary map values
            // Must be awaited to prevent multiple dice from taking from the same end value
            await diceGUIs[i].InitializeDiceDic(this);
        }
    }
    public IEnumerator DeactivateDiceGUI(DiceGUI diceGui)
    {
        rollsEnded[Array.IndexOf(diceGUIs, diceGui)] = true;
        shownValue += diceGui.HeldDiceValue;
        valueText.text = shownValue.ToString();

        while (rollsEnded.Any(roll => roll is false))
            yield return null;

        if (isHitRoll)
            valueText.text = toHitValue.ToString();
        else
            valueText.text = totalDamageValue.ToString();

        yield return new WaitForSeconds(2.5f);
        GeneralUI.Instance.DiceAllRolled(attacker, target, toHitValue, armorValue, damageRolls, triggers);
    }
    public void InjectNecessaryInfo(bool wasCrit, Entity attacker, Entity target, int toHitValue, int armorValue, SortedDictionary<int, (int, Dice)> damageRolls, Dictionary<string, (List<string>, List<object>)> triggers)
    {
        this.attacker               = attacker;
        this.target                 = target;
        this.toHitValue             = toHitValue;
        this.armorValue             = armorValue;
        this.wasCrit                = wasCrit;
        this.triggers               = new Dictionary<string, (List<string>, List<object>)>(triggers);
        this.damageRolls            = new SortedDictionary<int, (int, Dice)>(damageRolls);
    }
    public void InjectToHitModifiers()
    {
        // Destroy any remaining list modifiers
        foreach (Transform child in modifierInstanceArea)
            Destroy(child.gameObject);

        if (triggers.ContainsKey("ToHitAdd"))
        {
            int triggerLength = triggers["ToHitAdd"].Item1.Count;
            for (int i = 0; i < triggerLength; ++i)
            {
                string name = triggers["ToHitAdd"].Item1[i];
                object val = triggers["ToHitAdd"].Item2[i];

                InstantiateModifier(name, val, null);
            }
        }

        if (triggers.ContainsKey("ToHitMinus"))
        {
            int triggerLength = triggers["ToHitMinus"].Item1.Count;
            for (int i = 0; i < triggerLength; ++i)
            {
                string name = triggers["ToHitMinus"].Item1[i];
                object val = triggers["ToHitMinus"].Item2[i];

                InstantiateModifier(name, val, null);
            }
        }

        int dexMod = attacker.GatherStats().GetStat(EntityStats.Stat.Dexterity);

        if (dexMod is not 0)
            InstantiateModifier("DEX Mod", dexMod, null);
    }
    public void InjectDamageModifiers(int baseDamage, int totalDamage, Dictionary<int, (List<SpellSO.Element>, List<WeaponSO.Type>)> targetTypeEffects)
    {
        // Destroy any remaining list modifiers
        foreach (Transform child in modifierInstanceArea)
            Destroy(child.gameObject);

        totalDamageValue = totalDamage;

        // Doubles the damage
        if (triggers.ContainsKey("DoubleDamage"))
        {
            // The damage value is doubled for each name instance, due to damage being doubled repeatedly
            int damageValue = baseDamage;
            foreach (string name in triggers["DoubleDamage"].Item1)
            {
                InstantiateModifier(name, damageValue, null);
                damageValue *= 2;
            }
            baseDamage = damageValue;
        }

        if (triggers.ContainsKey("HalfCrits") && wasCrit)
        {
            int damageValue = baseDamage / 2;
            foreach (string name in triggers["HalfCrits"].Item1)
            {
                InstantiateModifier(name, damageValue, null);
                damageValue /= 2;
            }
            baseDamage = damageValue;
        }

        if (triggers.ContainsKey("AddDamageValue"))
        {
            int triggerLength = triggers["AddDamageValue"].Item1.Count;
            for (int i = 0; i < triggerLength; ++i)
            {
                string name = triggers["AddDamageValue"].Item1[i];
                object val = triggers["AddDamageValue"].Item2[i];
                object type = triggers["AddDamageType"].Item2[i];

                InstantiateModifier(name, val, type);
            }
        }

        if (triggers.ContainsKey("MinusDamageValue"))
        {
            int triggerLength = triggers["MinusDamageValue"].Item1.Count;
            for (int i = 0; i < triggerLength; ++i)
            {
                string name = triggers["MinusDamageValue"].Item1[i];
                object val = triggers["MInusDamageValue"].Item2[i];
                // No type necessary as of now

                InstantiateModifier(name, val, null);
            }
        }

        ProvideStatModModifiers();

        // Include Resistance and Weakness Modifiers
        foreach (KeyValuePair<int, (List<SpellSO.Element>, List<WeaponSO.Type>)> typeEffect in targetTypeEffects)
        {
            if (typeEffect.Key is 0)
            {
                typeEffect.Value.Item1.ForEach(element =>
                {
                    InstantiateModifier(GatherElementName(element), "Weakness", null);
                });
                typeEffect.Value.Item2.ForEach(weapon =>
                {
                    InstantiateModifier(GatherTypeName(weapon), "Weakness", null);
                });
            }
            else if (typeEffect.Key is 1)
            {
                typeEffect.Value.Item1.ForEach(element =>
                {
                    InstantiateModifier(GatherElementName(element), "Resistance", null);
                });
                typeEffect.Value.Item2.ForEach(weapon =>
                {
                    InstantiateModifier(GatherTypeName(weapon), "Resistance", null);
                });
            }
            else
                throw new DataMisalignedException();
        }

        Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList();

        // Earth Elemental 
        if (target.GetInventory().Abilities.Contains(abilityList[32]) && attacker.GetInventory().GetPrimaryActiveWeapon().WeaponElement is SpellSO.Element.Earth)
            InstantiateModifier(GatherElementName(SpellSO.Element.Earth), "Resistance", null);

        // Ice Elemental
        if (target.GetInventory().Abilities.Contains(abilityList[33]) && attacker.GetInventory().GetPrimaryActiveWeapon().WeaponElement is SpellSO.Element.Ice)
            InstantiateModifier(GatherElementName(SpellSO.Element.Ice), "Resistance", null);

        // Fire Elemental
        if (target.GetInventory().Abilities.Contains(abilityList[34]) && attacker.GetInventory().GetPrimaryActiveWeapon().WeaponElement is SpellSO.Element.Fire)
            InstantiateModifier(GatherElementName(SpellSO.Element.Fire), "Resistance", null);

        // Air Elemental
        if (target.GetInventory().Abilities.Contains(abilityList[35]) && attacker.GetInventory().GetPrimaryActiveWeapon().WeaponElement is SpellSO.Element.Wind)
            InstantiateModifier(GatherElementName(SpellSO.Element.Wind), "Resistance", null);

        static string GatherElementName(SpellSO.Element element)
        {
            return element switch
            {
                SpellSO.Element.Normal => "Normal",
                SpellSO.Element.Earth => "Earth",
                SpellSO.Element.Water => "Water",
                SpellSO.Element.Wind => "Wind",
                SpellSO.Element.Fire => "Fire",
                SpellSO.Element.Darkness => "Darkness",
                SpellSO.Element.Light => "Light",
                SpellSO.Element.Necro => "Necro",
                SpellSO.Element.Energy => "Energy",
                SpellSO.Element.Ice => "Ice",
                SpellSO.Element.Poison => "Poison",
                SpellSO.Element.Strange => "Strange",

                _ => throw new InvalidEnumArgumentException(),
            };
        }

        static string GatherTypeName(WeaponSO.Type weapon)
        {
            return weapon switch
            {
                WeaponSO.Type.Sharp     => "Sharp",
                WeaponSO.Type.Blunt     => "Blunt",
                WeaponSO.Type.Ranged    => "Ranged",
                WeaponSO.Type.Magic     => "Magic",
                WeaponSO.Type.Extended  => "Extended",
                WeaponSO.Type.Firearm   => "Firearm",

                _ => throw new InvalidEnumArgumentException(),
            };
        }
    }
    private void ProvideStatModModifiers()
    {
        WeaponSO.Type primaryWeaponType = attacker.GetInventory().GetPrimaryActiveWeapon() != null ? attacker.GetInventory().GetPrimaryActiveWeapon().WeaponType : ((Ally)attacker).attackType;
        WeaponSO.Type secondaryWeaponType = WeaponSO.Type.None;
        if (attacker.GetInventory().GetSecondaryActiveWeapon() != null)
            secondaryWeaponType = attacker.GetInventory().GetPrimaryActiveWeapon().WeaponIsTwoHanded is false ? attacker.GetInventory().GetSecondaryActiveWeapon().WeaponType : WeaponSO.Type.None;

        switch (primaryWeaponType)
        {
            case WeaponSO.Type.Sharp:
                InstantiateModifier("STR Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Strength), null);
                break;
            case WeaponSO.Type.Blunt:
                InstantiateModifier("STR Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Strength), null);
                break;
            case WeaponSO.Type.Ranged:
                InstantiateModifier("DEX Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Dexterity), null);
                break;
            case WeaponSO.Type.Magic:
                InstantiateModifier("INT Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Intelligence), null);
                break;
            case WeaponSO.Type.Extended:
                InstantiateModifier("DEX Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Dexterity), null);
                break;
            case WeaponSO.Type.Firearm:
                InstantiateModifier("WIS Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Wisdom), null);
                break;
            default:
                throw new InvalidEnumArgumentException();
        }
        switch (secondaryWeaponType)
        {
            case WeaponSO.Type.Sharp:
                InstantiateModifier("STR Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Strength) / 2, null);
                break;
            case WeaponSO.Type.Blunt:
                InstantiateModifier("STR Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Strength) / 2, null);
                break;
            case WeaponSO.Type.Ranged:
                InstantiateModifier("DEX Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Dexterity) / 2, null);
                break;
            case WeaponSO.Type.Magic:
                InstantiateModifier("INT Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Intelligence) / 2, null);
                break;
            case WeaponSO.Type.Extended:
                InstantiateModifier("DEX Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Dexterity) / 2, null);
                break;
            case WeaponSO.Type.Firearm:
                InstantiateModifier("WIS Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Wisdom) / 2, null);
                break;
            case WeaponSO.Type.None:
                // Nothing happens since no secondary weapon is detected
                break;
            default:
                throw new InvalidEnumArgumentException();
        }

        if (attacker.GatherStats().GetStat(EntityStats.Stat.Damage) is not 0)
            InstantiateModifier("Damage Mod", attacker.GatherStats().GetStat(EntityStats.Stat.Damage), null);
    }
    private void InstantiateModifier(string modifierName, object valueIndic, object type)
    {
        GameObject modifierStatement = Instantiate(modifierPrefab, modifierInstanceArea);

        DiceModifierConnector dmc = modifierStatement.GetComponent<DiceModifierConnector>();
        dmc.InjectName(modifierName);
        if (valueIndic is float fVal)
        {
            float value = fVal < 1f ? fVal * 100 : fVal;
            dmc.InjectValue($"{value}%");
        }
        else if (valueIndic is int iVal)
        {
            if (iVal < 0)
                dmc.InjectValue($"-{Math.Abs(iVal)}");
            else if (iVal > 0)
                dmc.InjectValue($"+{Math.Abs(iVal)}");
            else
                dmc.InjectValue("0");
        }
        else if (valueIndic is DiceR dicer)
            dmc.InjectDice(dicer);
        else if (valueIndic is DiceR[])
            dmc.InjectDice(valueIndic as DiceR[]);
        else if (valueIndic is Dice baseDice)
            dmc.InjectDice(baseDice);
        else if (valueIndic is Dice[] baseDices)
            dmc.InjectDice(baseDices);
        else if (valueIndic is string stringValue)
            dmc.InjectValue(stringValue);
        else if (valueIndic is List<object> incepValues)
            dmc.InjectDice((DiceR)incepValues[1]);
        else
            throw new TypeLoadException($"Exception was thrown for valueIndic ({valueIndic}) from modifier {modifierName}");

        if (type != null)
            dmc.InjectType(type);
        else
            dmc.DeactivateTypeInfo();
    }
}