Encounter / Assets / Scripts / SkillTreeManager.cs
SkillTreeManager.cs
Raw
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class SkillTreeManager : MonoBehaviour
{
    public static SkillTreeManager Instance { get; private set; }

    [Header("UI Components")]
    [SerializeField] private StatusScreenHandler statusComp; 
    [SerializeField] private TextMeshProUGUI skillPointsLeft;
    [SerializeField] private GameObject choicePrefab;
    [SerializeField] private GameObject objectToInitialize;         // References the background object that holds the rest of the components for the skill tree screen
    [SerializeField] private GameObject choiceObject;
    [SerializeField] private Button nextButton;
    [SerializeField] private Button prevButton;
    [SerializeField] private Transform contentFieldTransform;
    [SerializeField] private Transform choiceFieldTransform;
    private GameObject objectToSwitchBackOn;

    private List<GameObject> choiceObjectsInstanced;

    private Entity entityReference;
    private List<GameObject> skillTreeObjects;
    private Dictionary<string, Guid> entitySkillListsInit;

    private GameObject activeSkillTreeObject;
    private int index;

    private readonly Color DisabledColor = new Color(.65f, .65f, .65f, 1f);

    private void Awake()
    {
        Instance = this;
        entitySkillListsInit = new Dictionary<string, Guid>();
    }

    public Task Initialize(List<GameObject> skillTrees, Entity entityAffected, GameObject objectToTurnBackOn)
    {
        objectToInitialize.SetActive(true);
        GetComponent<Image>().enabled       = true;
        entityReference                     = entityAffected;
        skillTreeObjects                    = new List<GameObject>(skillTrees);
        index                               = 0;
        objectToSwitchBackOn                = objectToTurnBackOn;
        IndicateSkillPointsRemaining();

        // If only one skill tree is available then deactivate the buttons 
        if (skillTrees.Count is 1)
        {
            nextButton.interactable = false;
            nextButton.GetComponent<Image>().color = DisabledColor;

            prevButton.interactable = false;
            prevButton.GetComponent<Image>().color = DisabledColor;
        }
        // If at least two are available then activate the buttons
        else
        {
            nextButton.interactable = true;
            nextButton.GetComponent<Image>().color = Color.white;

            prevButton.interactable = true;
            prevButton.GetComponent<Image>().color = Color.white;
        }

        StartDefaultSkillTree();

        return Task.CompletedTask;
    }
    /// <summary>
    /// Determines if any skill points are remaining and if so determines the layout of the text object
    /// </summary>
    private void IndicateSkillPointsRemaining()
    {
        if (entityReference.HasSkillPoints())
        {
            skillPointsLeft.gameObject.SetActive(true);

            if (entityReference.GetSkillPoints() is 1)
                skillPointsLeft.text = $"{entityReference.GetSkillPoints()} Skill Point Available";
            else
                skillPointsLeft.text = $"{entityReference.GetSkillPoints()} Skill Points Available";
        }
        else
            skillPointsLeft.gameObject.SetActive(false);
    }
    /// <summary>
    /// Starts on the first tree in the list, the default
    /// </summary>
    private void StartDefaultSkillTree()
    {
        activeSkillTreeObject = Instantiate(skillTreeObjects.FirstOrDefault(), contentFieldTransform);
        CheckForExistingData();
    }
    /// <summary>
    /// Checks for any existing data regarding the active entity skill set
    /// </summary>
    private void CheckForExistingData()
    {
        GraphicalSkillTree graphicalComp = activeSkillTreeObject.GetComponent<GraphicalSkillTree>();

        if (entitySkillListsInit.ContainsKey(activeSkillTreeObject.name))
        {
            string nameRef = graphicalComp.GetEntityNameReference();
            Guid id = entitySkillListsInit[activeSkillTreeObject.name];

            graphicalComp.InjectExistingData(Globals.SearchForEntitySkillData(nameRef, id));
        }
        else
            graphicalComp.InitializeTree();
    }
    /// <summary>
    /// Iterates forward in the list of skill trees
    /// </summary>
    public void NextSkillTree()
    {
        RetainSkillTreeData();

        // If already at last in list, go to first
        if (index == skillTreeObjects.Count - 1)
            index = 0;
        else
            ++index;

        ShowSkillTree();
    }
    /// <summary>
    /// Iterates backward in the list of skill trees
    /// </summary>
    public void PrevSkillTree()
    {
        RetainSkillTreeData();

        // If already at first in list, go to last
        if (index == 0)
            index = skillTreeObjects.Count - 1;
        else
            --index;

        ShowSkillTree();
    }
    /// <summary>
    /// Saves the currently active skill tree
    /// </summary>
    private void RetainSkillTreeData()
    {
        GraphicalSkillTree graphComp = activeSkillTreeObject.GetComponent<GraphicalSkillTree>();

        string skillTreeName = activeSkillTreeObject.name;
        Guid id = graphComp.GetSkillSet().UniqueId;

        if (!entitySkillListsInit.ContainsKey(skillTreeName))
            entitySkillListsInit.Add(skillTreeName, id);

        Globals.SaveEntitySkillData(graphComp.GetEntityNameReference(), graphComp.GetSkillSet());
    }
    /// <summary>
    /// Shows the newly indexed skill tree
    /// </summary>
    private void ShowSkillTree()
    {
        // Destroys the prior skill tree
        if (activeSkillTreeObject != null)
            Destroy(activeSkillTreeObject);

        activeSkillTreeObject = Instantiate(skillTreeObjects[index], contentFieldTransform);
        CheckForExistingData();
    }
    /// <summary>
    /// Saves the current skill tree on screen before switching off components to go back to the general screen
    /// </summary>
    public void BackToGeneralStatusScreen()
    {
        // Save skill tree data and destroy the object instanced
        RetainSkillTreeData();
        Destroy(activeSkillTreeObject);

        GameObject[] allyObjects = GameManager.Instance.GetAllyObjects();
        Ally[] allies = new Ally[allyObjects.Length];
        for (int i = 0; i < allies.Length; ++i)
            allies[i] = allyObjects[i].GetComponent<Ally>();

        AbilityBrain.ModificationChecks(GameManager.Instance.GetPlayerObject().GetComponent<Player>(), allies);

        // Then go back to the general status screen
        GetComponent<Image>().enabled = false;
        objectToInitialize.SetActive(false);
        objectToSwitchBackOn.SetActive(true);
        statusComp.UpdateStatusScreen();
    }
    public void SignalChoice(SkillSO data, object choices)
    {
        choiceObjectsInstanced?.ForEach(choice => Destroy(choice));
        choiceObjectsInstanced = new List<GameObject>();

        choiceObject.SetActive(true);

        if (choices is List<Vector2Int> statChoices)
        {
            foreach (Vector2Int choice in statChoices)
            {
                GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);
                string choiceName = DetermineStatChoiceString(choice);
                instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[choice.x], choiceName, choice, data);

                choiceObjectsInstanced.Add(instancedChoice);
            }
        }
        else if (choices is List<Spell> spellChoices)
        {
            int index = 0;
            foreach (Spell spell in spellChoices)
            {
                GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);
                instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[index++], spell.SpellName, spell, data);

                choiceObjectsInstanced.Add(instancedChoice);
            }
        }
        else if (choices is List<Ability> abilityChoices)
        {
            int index = 0;
            foreach (Ability ability in abilityChoices)
            {
                GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);
                instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[index++], ability.AbilityName, ability, data);

                choiceObjectsInstanced.Add(instancedChoice);
            }
        }
        else if (choices is List<WeaponSO.Type> weaponResistChoices)
        {
            int index = 0;
            foreach (WeaponSO.Type type in weaponResistChoices)
            {
                GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);
                string choiceName = DetermineWeaponResistChoiceString(type);
                instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[index++], choiceName, type, data);

                choiceObjectsInstanced.Add(instancedChoice);
            }
        }
        else if (choices is List<SpellSO.Element> elementResistChoices)
        {
            int index = 0;
            foreach (SpellSO.Element element in elementResistChoices)
            {
                GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);
                string choiceName = DetermineElementResistChoiceString(element);
                instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[index++], choiceName, element, data);

                choiceObjectsInstanced.Add(instancedChoice);
            }
        }
        else if (choices is List<int> flags)
            DecipherFlagValues(data, flags, choiceObjectsInstanced);
        else
            throw new TypeLoadException();

        string DetermineStatChoiceString(Vector2Int statModifier)
        {
            char sign = statModifier.y > 0 ? '+' : '-';

            return statModifier.x switch
            {
                0 => $"{sign}{statModifier.y} STR",
                1 => $"{sign}{statModifier.y} INT",
                2 => $"{sign}{statModifier.y} CON",
                3 => $"{sign}{statModifier.y} DEX",
                4 => $"{sign}{statModifier.y} CHA",
                5 => $"{sign}{statModifier.y} WIS",
                6 => $"{sign}{statModifier.y} LCK",

                _ => "Error Detected",
            };
        }
        string DetermineWeaponResistChoiceString(WeaponSO.Type resistType)
        {
            return resistType switch
            {
                WeaponSO.Type.Blunt         => "Blunt",
                WeaponSO.Type.Sharp         => "Sharp",
                WeaponSO.Type.Ranged        => "Ranged",
                WeaponSO.Type.Magic         => "Magic",
                WeaponSO.Type.Extended      => "Extended",
                WeaponSO.Type.Firearm       => "Firearm",

                _ => "Error Detected",
            };
        }
        string DetermineElementResistChoiceString(SpellSO.Element resistType)
        {
            return resistType switch
            {
                SpellSO.Element.Fire        => "Fire",
                SpellSO.Element.Wind        => "Wind",
                SpellSO.Element.Normal      => "Normal",
                SpellSO.Element.Water       => "Water",
                SpellSO.Element.Earth       => "Earth",
                SpellSO.Element.Poison      => "Poison",
                SpellSO.Element.Light       => "Light",
                SpellSO.Element.Darkness    => "Darkness",
                SpellSO.Element.Necro       => "Necro",
                SpellSO.Element.Strange     => "Strange",
                SpellSO.Element.Energy      => "Energy",
                SpellSO.Element.Ice         => "Ice",

                _ => "Error Detected",
            };
        }
    }
    private void DecipherFlagValues(SkillSO data, List<int> flags, List<GameObject> choiceObjectsInstanced)
    {
        switch (data.SkillName)
        {
            case "Favored Enemy":               // Flag values convert to different enemy types
                foreach (int flag in flags)
                {
                    GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);

                    string choiceName = flag switch
                    {
                        0 => "Golem Enemies",
                        1 => "Undead Enemies",
                        2 => "Beast Enemies",
                        3 => "Humanoid Enemies",
                        4 => "Demonic Enemies",

                        _ => string.Empty,
                    };

                    instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[flag], choiceName, flag, data);

                    choiceObjectsInstanced.Add(instancedChoice);
                }
                break;
            case "Field Focus":
                foreach (int flag in flags)
                {
                    GameObject instancedChoice = Instantiate(choicePrefab, choiceFieldTransform);
                    string choiceName = flag switch
                    {
                        0 => "Normal Element",
                        1 => "Fire Element",
                        2 => "Water Element",
                        3 => "Earth Element",
                        4 => "Wind Element",
                        5 => "Poison Element",
                        6 => "Ice Element",
                        7 => "Light Element",
                        8 => "Darkness Element",
                        9 => "Necro Element",
                        10 => "Energy Element",
                        11 => "Strange Element",

                        _ => string.Empty,
                    };

                    instancedChoice.GetComponent<SkillChoiceHandler>().InjectInformation(data.ChoiceSprites[flag], choiceName, flag, data);
                    choiceObjectsInstanced.Add(instancedChoice);
                }
                break;
            default:
                Debug.LogError($"Unknown Flag Choice Data Set: ({data.SkillName})");
                break;
        }
    }
    public void ChoiceMade(object data, SkillSO skillData = null)
    {
        if (data is Vector2Int statMod)
        {
            EntityStats.Stat filteredStat = GetStatMod(statMod);
            int newStatValue = entityReference.GetBaseStat(filteredStat) + statMod.y;

            string statReference = filteredStat switch
            {
                EntityStats.Stat.Strength           => "STR",
                EntityStats.Stat.Intelligence       => "INT",
                EntityStats.Stat.Wisdom             => "WIS",
                EntityStats.Stat.Dexterity          => "DEX",
                EntityStats.Stat.Constitution       => "CON",
                EntityStats.Stat.Luck               => "LCK",
                EntityStats.Stat.Charisma           => "CHA",

                _ => throw new InvalidOperationException(),
            };

            entityReference.GatherStats().SetStat(statReference, newStatValue);
        }
        else if (data is Ability ability)
        {
            entityReference.GetInventory().Add(ability);
            ManipulateSecondaryDetails(ability);
        }
        else if (data is Spell spell)
            entityReference.GetInventory().Add(spell);
        else if (data is WeaponSO.Type weaponResist)
            entityReference.GatherStats().AddTypeResistance(weaponResist);
        else if (data is SpellSO.Element elementResist)
            entityReference.GatherStats().AddElementalResistance(elementResist);
        else if (data is int value)
        {
            if (skillData.SkillName is "Favored Enemy")
            {
                if (entityReference is not Player)
                    throw new InvalidOperationException();

                string enemyType = value switch
                {
                    0 => "Golem",
                    1 => "Undead",
                    2 => "Beast",
                    3 => "Humanoid",
                    4 => "Demonic",

                    _ => throw new InvalidOperationException(),
                };

                // List object goes ---> Type Name, Added Damage, Added To Hit
                entityReference.SignalPlayerModification("Favored Enemy", new List<object> { enemyType, new DSix(), new DFour() });
            }
            else if (skillData.SkillName is "Field Focus")
            {
                Ability newFieldAbility = new Ability(Resources.Load<AbilitySO>("Abilities/93_Field_Focus"))
                {
                    ReferenceObject = new List<object>() 
                    {   value switch
                        {
                            0  => SpellSO.Element.Normal,
                            1  => SpellSO.Element.Fire,
                            2  => SpellSO.Element.Water,
                            3  => SpellSO.Element.Earth,
                            4  => SpellSO.Element.Wind,
                            5  => SpellSO.Element.Poison,
                            6  => SpellSO.Element.Ice,
                            7  => SpellSO.Element.Light,
                            8  => SpellSO.Element.Darkness,
                            9  => SpellSO.Element.Necro,
                            10 => SpellSO.Element.Energy,
                            11 => SpellSO.Element.Strange,

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

                entityReference.GetInventory().Add(newFieldAbility);
            }
        }

        TurnOffChoicePanel();
        static EntityStats.Stat GetStatMod(Vector2Int modification)
        {
            return modification.x switch
            {
                0 => EntityStats.Stat.Strength,
                1 => EntityStats.Stat.Intelligence,
                2 => EntityStats.Stat.Constitution,
                3 => EntityStats.Stat.Dexterity,
                4 => EntityStats.Stat.Charisma,
                5 => EntityStats.Stat.Wisdom,
                6 => EntityStats.Stat.Luck,

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

    private void ManipulateSecondaryDetails(Ability ability)
    {
        switch (ability.AbilityID)
        {
            case 91:            // Rapid Blasts  
                Spell rapidBlast = entityReference.GetInventory().Spells.Find(spell => spell.SpellID is 2);
                rapidBlast.AlterHelperEffect(0, "3D6 Energy");
                rapidBlast.AlterDescription("A blast of magic to a single target that deals 3D6 Energy damage.");
                break;
            case 92:            // Concentrated Blasts
                Spell concentratedBlast = entityReference.GetInventory().Spells.Find(spell => spell.SpellID is 2);
                concentratedBlast.AlterHelperEffect(0, "2D8 Energy");
                concentratedBlast.AlterDescription("A blast of magic to a single target that deals 2D8 Energy damage.");
                break;
            default:
                break;
        }
    }
    private void TurnOffChoicePanel()
    {
        choiceObject.SetActive(false);
    }
    /// <summary>
    /// Public call for updating the skill points available in the skill tree screen
    /// </summary>
    public void SignalPointUsage()
        => IndicateSkillPointsRemaining();
}