using System; using UnityEngine; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif /// <summary> /// Handles base stats and modifiers for the stats of an entity /// </summary> public class EntityStats : MonoBehaviour { public enum Stat { Strength, Wisdom, Intelligence, Dexterity, Charisma, Constitution, Luck, ToHit, Armor, HPRegen, MPRegen, Damage, CritDamage, ToCritMod } [SerializeField][Range(0, 30)] private int Strength; // Base STR [SerializeField][Range(0, 30)] private int Wisdom; // Base WIS [SerializeField][Range(0, 30)] private int Intelligence; // Base INT [SerializeField][Range(0, 30)] private int Dexterity; // Base DEX [SerializeField][Range(0, 30)] private int Charisma; // Base CHA [SerializeField][Range(0, 30)] private int Constitution; // Base CON [SerializeField][Range(0, 30)] private int Luck; // Base LCK public int StrMod; // Modifier for the stat STR public float StrMultiplierMod; public List<Dice.DiceR> StrModDice; public int WisMod; // Modifier for the stat WIS public float WisMultiplierMod; public List<Dice.DiceR> WisModDice; public int IntMod; // Modifier for the stat INT public float IntMultiplierMod; public List<Dice.DiceR> IntModDice; public int DexMod; // Modifier for the stat DEX public float DexMultiplierMod; public List<Dice.DiceR> DexModDice; public int ChaMod; // Modifier for the stat CHA public float ChaMultiplierMod; public List<Dice.DiceR> ChaModDice; public int ConMod; // Modifier for the stat CON public float ConMultiplierMod; public List<Dice.DiceR> ConModDice; public int LckMod; // Modifier for the stat LCK public float LckMultiplierMod; public List<Dice.DiceR> LckModDice; public int BaseToHitMod; // Modifier for the To Hit Value public float BaseToHitMultiplierMod; public List<Dice.DiceR> BaseToHitModDice; public int ArmorMod; // Modifier for the Armor value public float ArmorMultiplierMod; public List<Dice.DiceR> ArmorModDice; public int HPRegenMod; // Modifier for health regeneration public float HPRegenMultiplierMod; public List<Dice.DiceR> HPRegenModDice; public int MPRegenMod; // Modifier for the mana regeneration public float MPRegenMultiplierMod; public List<Dice.DiceR> MPRegenModDice; public int DamageMod; // Modifier for damage public float DamageMultiplierMod; public List<Dice.DiceR> DamageModDice; public int ToCritMod; // Modifier for crit chance, 0 represents 20, 1 represents 19 public float ToCritMultiplierMod; public List<Dice.DiceR> ToCritModDice; public int CritDamageMod; // Modifier for crit damage public float CritDamageMultiplierMod; public List<Dice.DiceR> CritDamageModDice; public Dictionary<WeaponSO.Type, int> WeaponResistances; public Dictionary<SpellSO.Element, int> ElementResistances; public Dictionary<WeaponSO.Type, int> WeaponWeaknesses; public Dictionary<SpellSO.Element, int> ElementWeaknesses; // 0 is not affected, -1 is disadvantaged, 1 is advantaged public Dictionary<Stat, int> StatSavingThrowModifications = new Dictionary<Stat, int>() { { Stat.Strength, 0 }, { Stat.Intelligence, 0 }, { Stat.Dexterity, 0 }, { Stat.Constitution, 0 }, { Stat.Charisma, 0 }, { Stat.Wisdom, 0 }, { Stat.Luck, 0 }, }; /// <summary> /// Retrieves the stat based on the Stat enum passed for the entity this instance of EntityStats is attached to /// </summary> /// <param name="stat">The specific Stat to find</param> /// <returns>The stat value that is desired</returns> /// <exception cref="ArgumentNullException">When a Stat is not given an exception is thrown</exception> public int GetStat(Stat stat, int baseValue = 0) { StrModDice ??= new List<Dice.DiceR>(); WisModDice ??= new List<Dice.DiceR>(); IntModDice ??= new List<Dice.DiceR>(); DexModDice ??= new List<Dice.DiceR>(); ChaModDice ??= new List<Dice.DiceR>(); ConModDice ??= new List<Dice.DiceR>(); LckModDice ??= new List<Dice.DiceR>(); BaseToHitModDice ??= new List<Dice.DiceR>(); DamageModDice ??= new List<Dice.DiceR>(); ToCritModDice ??= new List<Dice.DiceR>(); CritDamageModDice ??= new List<Dice.DiceR>(); HPRegenModDice ??= new List<Dice.DiceR>(); MPRegenModDice ??= new List<Dice.DiceR>(); ArmorModDice ??= new List<Dice.DiceR>(); return stat switch { Stat.Strength => this.StrMod + (int)(this.Strength * this.StrMultiplierMod) + UtilityHandler.RollValue(StrModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Strength)), Stat.Wisdom => this.WisMod + (int)(this.Wisdom * this.WisMultiplierMod) + UtilityHandler.RollValue(WisModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Wisdom)), Stat.Intelligence => this.IntMod + (int)(this.Intelligence * this.IntMultiplierMod) + UtilityHandler.RollValue(IntModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Intelligence)), Stat.Dexterity => this.DexMod + (int)(this.Dexterity * this.DexMultiplierMod) + UtilityHandler.RollValue(DexModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Dexterity)), Stat.Charisma => this.ChaMod + (int)(this.Charisma * this.ChaMultiplierMod) + UtilityHandler.RollValue(ChaModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Charisma)), Stat.Constitution => this.ConMod + (int)(this.Constitution * this.ConMultiplierMod) + UtilityHandler.RollValue(ConModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Constitution)), Stat.Luck => this.LckMod + (int)(this.Luck * this.LckMultiplierMod) + UtilityHandler.RollValue(LckModDice.ToArray()) + Globals.GatherModifier(GetBaseStat(Stat.Luck)), Stat.ToHit => this.BaseToHitMod + (int)(baseValue * this.BaseToHitMultiplierMod) + UtilityHandler.RollValue(BaseToHitModDice.ToArray()), Stat.Damage => baseValue + this.DamageMod + (int)(baseValue * this.DamageMultiplierMod) + UtilityHandler.RollValue(DamageModDice.ToArray()), Stat.ToCritMod => this.ToCritMod + (int)(20 * this.ToCritMultiplierMod) + UtilityHandler.RollValue(ToCritModDice.ToArray()), Stat.CritDamage => this.CritDamageMod + (int)(baseValue * this.CritDamageMultiplierMod) + UtilityHandler.RollValue(CritDamageModDice.ToArray()), Stat.HPRegen => this.HPRegenMod + (int)(baseValue * this.HPRegenMultiplierMod) + UtilityHandler.RollValue(HPRegenModDice.ToArray()), Stat.MPRegen => this.MPRegenMod + (int)(baseValue * this.MPRegenMultiplierMod) + UtilityHandler.RollValue(MPRegenModDice.ToArray()), Stat.Armor => baseValue + this.ArmorMod + (int)(baseValue * this.ArmorMultiplierMod) + UtilityHandler.RollValue(ArmorModDice.ToArray()), _ => throw new ArgumentNullException("Unknown stat detected"), }; } public int GetBaseStat(Stat stat) { return stat switch { Stat.Strength => this.Strength, Stat.Wisdom => this.Wisdom, Stat.Intelligence => this.Intelligence, Stat.Dexterity => this.Dexterity, Stat.Charisma => this.Charisma, Stat.Constitution => this.Constitution, Stat.Luck => this.Luck, _ => throw new ArgumentNullException("Unknown stat detected"), }; } /// <summary> /// Sets the stats within the Entity Stats class, primarily meant for the Player Class to connect with the handler stats /// </summary> /// <param name="statName">Name of the stat to change</param> /// <param name="statValue">Value of the stat to set</param> /// <exception cref="ArgumentException">Exception is thrown when an unknown name is given</exception> public void SetStat(string statName, int statValue) { switch (statName) { case "STR": this.Strength = statValue; break; case "INT": this.Intelligence = statValue; break; case "DEX": this.Dexterity = statValue; break; case "WIS": this.Wisdom = statValue; break; case "CON": this.Constitution = statValue; break; case "CHA": this.Charisma = statValue; break; case "LCK": this.Luck = statValue; break; default: throw new ArgumentException("Unknown stat name indicated, please check that proper stat name was indicated ---> " + statName); } } public void SetModifierToStat(Stat stat, object modification) { if (modification is int iVal) { switch (stat) { case Stat.Strength: StrMod += iVal; break; case Stat.Wisdom: WisMod += iVal; break; case Stat.Intelligence: IntMod += iVal; break; case Stat.Dexterity: DexMod += iVal; break; case Stat.Charisma: ChaMod += iVal; break; case Stat.Constitution: ConMod += iVal; break; case Stat.Luck: LckMod += iVal; break; case Stat.Damage: DamageMod += iVal; break; case Stat.ToCritMod: ToCritMod += iVal; break; case Stat.CritDamage: CritDamageMod += iVal; break; case Stat.HPRegen: HPRegenMod += iVal; break; case Stat.MPRegen: MPRegenMod += iVal; break; case Stat.ToHit: BaseToHitMod += iVal; break; case Stat.Armor: ArmorMod += iVal; break; } } else if (modification is float fVal) { switch (stat) { case Stat.Strength: StrMultiplierMod += fVal; break; case Stat.Wisdom: WisMultiplierMod += fVal; break; case Stat.Intelligence: IntMultiplierMod += fVal; break; case Stat.Dexterity: DexMultiplierMod += fVal; break; case Stat.Charisma: ChaMultiplierMod += fVal; break; case Stat.Constitution: ConMultiplierMod += fVal; break; case Stat.Luck: LckMultiplierMod += fVal; break; case Stat.Damage: DamageMultiplierMod += fVal; break; case Stat.ToCritMod: ToCritMultiplierMod += fVal; break; case Stat.CritDamage: CritDamageMultiplierMod += fVal; break; case Stat.HPRegen: HPRegenMultiplierMod += fVal; break; case Stat.MPRegen: MPRegenMultiplierMod += fVal; break; case Stat.ToHit: BaseToHitMultiplierMod += fVal; break; case Stat.Armor: ArmorMultiplierMod += fVal; break; } } else if (modification is Dice.DiceR[] dices) { switch (stat) { case Stat.Strength: StrModDice ??= new List<Dice.DiceR>(); StrModDice.AddRange(dices); break; case Stat.Wisdom: WisModDice ??= new List<Dice.DiceR>(); WisModDice.AddRange(dices); break; case Stat.Intelligence: IntModDice ??= new List<Dice.DiceR>(); IntModDice.AddRange(dices); break; case Stat.Dexterity: DexModDice ??= new List<Dice.DiceR>(); DexModDice.AddRange(dices); break; case Stat.Charisma: ChaModDice ??= new List<Dice.DiceR>(); ChaModDice.AddRange(dices); break; case Stat.Constitution: ConModDice ??= new List<Dice.DiceR>(); ConModDice.AddRange(dices); break; case Stat.Luck: LckModDice ??= new List<Dice.DiceR>(); LckModDice.AddRange(dices); break; case Stat.Damage: DamageModDice ??= new List<Dice.DiceR>(); DamageModDice.AddRange(dices); break; case Stat.ToCritMod: ToCritModDice ??= new List<Dice.DiceR>(); ToCritModDice.AddRange(dices); break; case Stat.CritDamage: CritDamageModDice ??= new List<Dice.DiceR>(); CritDamageModDice.AddRange(dices); break; case Stat.HPRegen: HPRegenModDice ??= new List<Dice.DiceR>(); HPRegenModDice.AddRange(dices); break; case Stat.MPRegen: MPRegenModDice ??= new List<Dice.DiceR>(); MPRegenModDice.AddRange(dices); break; case Stat.ToHit: BaseToHitModDice ??= new List<Dice.DiceR>(); BaseToHitModDice.AddRange(dices); break; case Stat.Armor: ArmorModDice ??= new List<Dice.DiceR>(); ArmorModDice.AddRange(dices); break; } } } public void RemoveModifierToStat(Stat stat, object modification) { if (modification is int iVal) { switch (stat) { case Stat.Strength: StrMod -= iVal; break; case Stat.Wisdom: WisMod -= iVal; break; case Stat.Intelligence: IntMod -= iVal; break; case Stat.Dexterity: DexMod -= iVal; break; case Stat.Charisma: ChaMod -= iVal; break; case Stat.Constitution: ConMod -= iVal; break; case Stat.Luck: LckMod -= iVal; break; case Stat.Damage: DamageMod -= iVal; break; case Stat.ToCritMod: ToCritMod -= iVal; break; case Stat.CritDamage: CritDamageMod -= iVal; break; case Stat.HPRegen: HPRegenMod -= iVal; break; case Stat.MPRegen: MPRegenMod -= iVal; break; case Stat.ToHit: BaseToHitMod -= iVal; break; case Stat.Armor: ArmorMod -= iVal; break; } } else if (modification is float fVal) { switch (stat) { case Stat.Strength: StrMultiplierMod -= fVal; break; case Stat.Wisdom: WisMultiplierMod -= fVal; break; case Stat.Intelligence: IntMultiplierMod -= fVal; break; case Stat.Dexterity: DexMultiplierMod -= fVal; break; case Stat.Charisma: ChaMultiplierMod -= fVal; break; case Stat.Constitution: ConMultiplierMod -= fVal; break; case Stat.Luck: LckMultiplierMod -= fVal; break; case Stat.Damage: DamageMultiplierMod -= fVal; break; case Stat.ToCritMod: ToCritMultiplierMod -= fVal; break; case Stat.CritDamage: CritDamageMultiplierMod -= fVal; break; case Stat.HPRegen: HPRegenMultiplierMod -= fVal; break; case Stat.MPRegen: MPRegenMultiplierMod -= fVal; break; case Stat.ToHit: BaseToHitMultiplierMod -= fVal; break; case Stat.Armor: ArmorMultiplierMod -= fVal; break; } } else if (modification is Dice.DiceR[] dices) { switch (stat) { case Stat.Strength: foreach (Dice.DiceR dice in dices) StrModDice.Remove(dice); break; case Stat.Wisdom: foreach (Dice.DiceR dice in dices) WisModDice.Remove(dice); break; case Stat.Intelligence: foreach (Dice.DiceR dice in dices) IntModDice.Remove(dice); break; case Stat.Dexterity: foreach (Dice.DiceR dice in dices) DexModDice.Remove(dice); break; case Stat.Charisma: foreach (Dice.DiceR dice in dices) ChaModDice.Remove(dice); break; case Stat.Constitution: foreach (Dice.DiceR dice in dices) ConModDice.Remove(dice); break; case Stat.Luck: foreach (Dice.DiceR dice in dices) LckModDice.Remove(dice); break; case Stat.Damage: foreach (Dice.DiceR dice in dices) DamageModDice.Remove(dice); break; case Stat.ToCritMod: foreach (Dice.DiceR dice in dices) ToCritModDice.Remove(dice); break; case Stat.CritDamage: foreach (Dice.DiceR dice in dices) CritDamageModDice.Remove(dice); break; case Stat.HPRegen: foreach (Dice.DiceR dice in dices) HPRegenModDice.Remove(dice); break; case Stat.MPRegen: foreach (Dice.DiceR dice in dices) MPRegenModDice.Remove(dice); break; case Stat.ToHit: foreach (Dice.DiceR dice in dices) BaseToHitModDice.Remove(dice); break; case Stat.Armor: foreach (Dice.DiceR dice in dices) ArmorModDice.Remove(dice); break; } } } /// <summary> /// Resets a specific modification based on the Enum passed /// </summary> /// <param name="stat"></param> public void ResetMod(Stat stat) { switch (stat) { case Stat.Strength: this.StrMod = 0; break; case Stat.Wisdom: this.WisMod = 0; break; case Stat.Intelligence: this.IntMod = 0; break; case Stat.Dexterity: this.DexMod = 0; break; case Stat.Charisma: this.ChaMod = 0; break; case Stat.Constitution: this.ConMod = 0; break; case Stat.Luck: this.LckMod = 0; break; case Stat.ToHit: this.BaseToHitMod = 0; break; case Stat.Armor: this.ArmorMod = 0; break; case Stat.HPRegen: this.HPRegenMod = 0; break; case Stat.MPRegen: this.MPRegenMod = 0; break; case Stat.Damage: this.DamageMod = 0; break; case Stat.CritDamage: this.CritDamageMod = 0; break; } } /// <summary> /// Resets all of the mods attached to this entity /// </summary> public void ResetAllMods() { this.StrMod = 0; this.WisMod = 0; this.DexMod = 0; this.ChaMod = 0; this.ConMod = 0; this.IntMod = 0; this.LckMod = 0; this.BaseToHitMod = 0; this.ArmorMod = 0; this.HPRegenMod = 0; this.MPRegenMod = 0; this.DamageMod = 0; this.CritDamageMod = 0; } /// <summary> /// Adds an elemental resistance to this entity's stats /// </summary> /// <param name="newResistance">Resisted element to be recorded</param> public void AddElementalResistance(SpellSO.Element newResistance) { this.ElementResistances ??= new Dictionary<SpellSO.Element, int>(); if (!this.ElementResistances.ContainsKey(newResistance)) this.ElementResistances.Add(newResistance, 25); else this.ElementResistances[newResistance] += 25; } /// <summary> /// Remvoes an elemental resistance either completely or partially from this entity's stats /// </summary> /// <param name="removedResistance">The resistance to remove from this entity's stats</param> /// <param name="partially">Indicator if only a percentage of the resistance should be cut off</param> /// <exception cref="ArgumentOutOfRangeException"></exception> public void RemoveElementalResistance(SpellSO.Element removedResistance, bool partially = false) { if (this.ElementResistances.ContainsKey(removedResistance) && !partially) this.ElementResistances.Remove(removedResistance); else if (this.ElementResistances.ContainsKey(removedResistance) && partially) { int currentResist = this.ElementResistances[removedResistance]; currentResist -= 25; if (currentResist <= 0) { this.ElementResistances.Remove(removedResistance); return; } this.ElementResistances[removedResistance] = currentResist; } else throw new ArgumentOutOfRangeException($"Unable to remove ({removedResistance}) out of Elemental Resistance list due to list not containing resistance element."); } /// <summary> /// Adds an elemental weakness to this entity's stats /// </summary> /// <param name="newWeakness">The new elemental weakness to add</param> public void AddElementalWeakness(SpellSO.Element newWeakness) { this.ElementWeaknesses ??= new Dictionary<SpellSO.Element, int>(); if (!this.ElementWeaknesses.ContainsKey(newWeakness)) this.ElementWeaknesses.Add(newWeakness, 25); else this.ElementWeaknesses[newWeakness] += 25; } /// <summary> /// Removes an elemental weakness from this entity's stats /// </summary> /// <param name="removedWeakness">The elemental weakness to remove</param> /// <param name="partially">Indicator if the weakness should be completely or partially removed</param> /// <exception cref="ArgumentOutOfRangeException"></exception> public void RemoveElementalWeakness(SpellSO.Element removedWeakness, bool partially = false) { if (this.ElementWeaknesses.ContainsKey(removedWeakness) && !partially) this.ElementWeaknesses.Remove(removedWeakness); else if (this.ElementWeaknesses.ContainsKey(removedWeakness) && partially) { int currentResist = this.ElementWeaknesses[removedWeakness]; currentResist -= 25; if (currentResist <= 0) { this.ElementWeaknesses.Remove(removedWeakness); return; } this.ElementWeaknesses[removedWeakness] = currentResist; } else throw new ArgumentOutOfRangeException($"Unable to remove ({removedWeakness}) out of Elemental Resistance list due to list not containing resistance element."); } /// <summary> /// Adds a weapon type resistance to this entity's stats /// </summary> /// <param name="weaponType">The resistance to add to this entity's stats</param> public void AddTypeResistance(WeaponSO.Type weaponType) { this.WeaponResistances ??= new Dictionary<WeaponSO.Type, int>(); if (!this.WeaponResistances.ContainsKey(weaponType)) this.WeaponResistances.Add(weaponType, 25); else this.WeaponResistances[weaponType] += 25; } /// <summary> /// Removes a weapon type resistance either partially or completely /// </summary> /// <param name="weaponType">The weapon type resistance to remove</param> /// <param name="partially">Indicator if all of the resistance should be removed or only a percentage</param> /// <exception cref="ArgumentOutOfRangeException"></exception> public void RemoveTypeResistance(WeaponSO.Type weaponType, bool partially = false) { if (this.WeaponResistances.ContainsKey(weaponType) && !partially) this.WeaponResistances.Remove(weaponType); else if (this.WeaponResistances.ContainsKey(weaponType) && partially) { int currentResist = this.WeaponResistances[weaponType]; currentResist -= 25; if (currentResist <= 0) { this.WeaponResistances.Remove(weaponType); return; } this.WeaponResistances[weaponType] = currentResist; } else throw new ArgumentOutOfRangeException($"Unable to remove ({weaponType}) out of Elemental Resistance list due to list not containing resistance element."); } /// <summary> /// Adds a weapon type weakness to this entity's stats /// </summary> /// <param name="weaponType">The weapon weakness type to add</param> public void AddTypeWeakness(WeaponSO.Type weaponType) { this.WeaponWeaknesses ??= new Dictionary<WeaponSO.Type, int>(); if (!this.WeaponWeaknesses.ContainsKey(weaponType)) this.WeaponWeaknesses.Add(weaponType, 25); else this.WeaponWeaknesses[weaponType] += 25; } /// <summary> /// Removes a weapon weakness either completely or partially /// </summary> /// <param name="weaponType">The weapon weakness type to remove</param> /// <param name="partially">Indicator if the weakness is partially removed or completely removed</param> /// <exception cref="ArgumentOutOfRangeException"></exception> public void RemoveTypeWeakness(WeaponSO.Type weaponType, bool partially = false) { if (this.WeaponWeaknesses.ContainsKey(weaponType) && !partially) this.WeaponWeaknesses.Remove(weaponType); else if (this.WeaponWeaknesses.ContainsKey(weaponType) && partially) { int currentResist = this.WeaponWeaknesses[weaponType]; currentResist -= 25; if (currentResist <= 0) { this.WeaponWeaknesses.Remove(weaponType); return; } this.WeaponWeaknesses[weaponType] = currentResist; } else throw new ArgumentOutOfRangeException($"Unable to remove ({weaponType}) out of Elemental Resistance list due to list not containing resistance element."); } /// <summary> /// Changes a certain stat to incorporate disadvantages or advantages /// </summary> /// <param name="stat">The stat to modify</param> /// <param name="value"> /// 0 --> No Change /// 1 --> Advantaged /// -1 --> Disadvantaged /// </param> public void ModifySavingThrow(Stat stat, int value) { StatSavingThrowModifications[stat] = value; } public Dictionary<Stat, int> GetModifiedSaveThrows() => StatSavingThrowModifications; } #if UNITY_EDITOR [CustomEditor(typeof(EntityStats))] public class EntityStatsEditor : Editor { private EntityStats container; private void OnEnable() { container = target as EntityStats; } public override void OnInspectorGUI() { serializedObject.Update(); GUI.changed = false; EditorGUILayout.LabelField("Base Stats", EditorStyles.boldLabel); EditorGUILayout.Space(10); EditorGUILayout.PropertyField(serializedObject.FindProperty("Strength")); EditorGUILayout.PropertyField(serializedObject.FindProperty("Wisdom")); EditorGUILayout.PropertyField(serializedObject.FindProperty("Intelligence")); EditorGUILayout.PropertyField(serializedObject.FindProperty("Dexterity")); EditorGUILayout.PropertyField(serializedObject.FindProperty("Charisma")); EditorGUILayout.PropertyField(serializedObject.FindProperty("Constitution")); EditorGUILayout.PropertyField(serializedObject.FindProperty("Luck")); if (GUI.changed) EditorUtility.SetDirty(container); serializedObject.ApplyModifiedProperties(); } } #endif