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(); } }