using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using UnityEngine; public class Entity : MonoBehaviour { public enum Entities { Companion, Companions, Player, Ally, Allies, Minion, Minions, Enemy, Enemies, Boss } public enum EntityType { Golem, Undead, Humanoid, Demonic, Beast } [Header("Entity Common Values")] [SerializeField] protected string Entity_Name; [SerializeField] protected Sprite entity_Sprite; [SerializeField] protected EntityType entityType; [SerializeField] protected List<Dice.DiceR> HealthDice; [SerializeField] protected int baseHealth; [SerializeField] protected List<Dice.DiceR> ManaDice; [SerializeField] protected int baseMana; [SerializeField] protected Vector2Int ArmorRange; [SerializeField] protected List<GameObject> availableSkillTrees; [SerializeField] protected bool IsFlying; [SerializeField] [Range(0,2)] [Tooltip("0 is the smallest entity sprite while 2 is the largest entity sprite")] protected int sizeIndic; [SerializeField] protected Collider2D secondaryCollider; [SerializeField] protected SpellSO.Element entityElement; [SerializeField] protected List<SpellSO.Element> elementWeaknesses; [SerializeField] protected List<WeaponSO.Type> weaponWeaknesses; [SerializeField] protected List<SpellSO.Element> elementResistances; [SerializeField] protected List<WeaponSO.Type> weaponResistances; protected int Health; protected int Mana; protected int Armor; protected int maxHealth; protected int maxMana; protected int maxArmor; private int numberOfActions; private int numberOfAttacks; protected Dictionary<string, object> ClassModifications; public int ActionCount { get => numberOfActions; set => numberOfActions = value; } public int AttackCount { get => numberOfAttacks; set => numberOfAttacks = value; } protected readonly Dictionary<int, int> levelMapExpRequirements = new Dictionary<int, int>() { { 2, 150 }, // 150 total exp needed { 3, 300 }, // 450 total exp needed { 4, 550 }, // 1000 total exp needed { 5, 800 }, // 1800 total exp needed { 6, 1250 }, // 3050 total exp needed { 7, 1700 }, // 4750 total exp needed { 8, 2250 }, // 7000 total exp needed { 9, 2500 }, // 9500 total exp needed { 10, 3000 }, // 12500 total exp needed { 11, 3500 }, // 16000 total exp needed { 12, 4000 }, // 20000 total exp needed { 13, 5000 }, // 25000 total exp needed { 14, 6500 }, // 31500 total exp needed { 15, 8000 }, // 39500 total exp needed { 16, 10000 }, // 49500 total exp needed { 17, 12500 }, // 62000 total exp needed { 18, 15000 }, // 77000 total exp needed { 19, 17500 }, // 94500 total exp needed { 20, 20000 } // 114500 total exp needed }; protected List<string> skillTreeNames; protected int CurrentLevelExperience; protected int TotalExperience; protected int Level; protected const int MaxLevel = 20; protected int LastLevelIntegrated; protected bool HasLeveledUp; protected int SkillPoints; public bool HasDied { get => this.Health is 0; } public bool IsHidden { get; protected set; } public bool Pillaged { get; set; } public bool ForcedMiss { get; set; } public bool TrueDeath { get; set; } public (bool, (SpellManagement.PreparationCalls, object)) IsPreparing { get; set; } private float manaRegenRate = .1f; protected EntityStats stats; protected EntityModList modList; protected EntityAnimation animator; protected bool genStatsChanged = false; protected bool statusAfflicted = false; protected Dictionary<Modifier, int> dotModifierValues; protected bool dotApplied = false; protected int turnsLeft = 0; protected int dotDamagePerTurn = 0; private Dictionary<Modifier, (int, EntityStats.Stat)> modifiersWithChance; private List<Modifier> implementedMods; protected int UniqueID; // The Instance ID of the entity public List<string> EntityFlags; // This flag system will be used to determine the state of the Entity, such as access to Dream Master unique effects public Transform spawnedPosition; public int GetSkillPoints() => this.SkillPoints; public bool HasSkillPoints() => this.SkillPoints != 0; public bool TryUseSkillPoint(int skillCost) { if (skillCost > this.SkillPoints) return false; else { this.SkillPoints -= skillCost; return true; } } public void HideEntity() => this.IsHidden = true; public void ShowEntity() => this.IsHidden = false; /// <summary> /// Retrieval for the Instance ID of the entity, since there can be multiple entities with the same name /// </summary> /// <returns>Unique Entity ID</returns> public int GetUniqueID() => this.UniqueID; /// <summary> /// Triggers the update method to update the HP /// </summary> public void StatusChanged() => genStatsChanged = true; public void AfflictedStatus() => statusAfflicted = true; public EntityModList GatherModList() => modList; public EntityStats GatherStats() => stats; public SpellSO.Element GetEntityType() => entityElement; public int GetSizeIndic() => this.sizeIndic; private void Awake() { this.stats = stats != null ? stats : GetComponent<EntityStats>(); this.modList = new EntityModList(); this.dotModifierValues = new Dictionary<Modifier, int>(); this.implementedMods = new List<Modifier>(); this.EntityFlags = new List<string>(); this.ClassModifications = new Dictionary<string, object>(); this.SkillPoints = 0; // If skill point cheat is turned on if (Globals.Add100SkillPointsCheat) this.SkillPoints += 100; this.Level = 1; this.ActionCount = 1; this.AttackCount = 1; this.LastLevelIntegrated = 1; this.HasLeveledUp = false; this.skillTreeNames = new List<string>(); foreach (GameObject skillTree in this.availableSkillTrees) skillTreeNames.Add(skillTree.name); UniqueID = GetInstanceID(); } protected virtual void OnEnable() { // Generates random values based on the dice indicators for the enemy if (this is Enemy || this is Ally) { if (this is Ally && Globals.LargeHealthCheat) this.Health = UtilityHandler.RollValueWithBase(baseHealth + 300, HealthDice.ToArray()); else this.Health = UtilityHandler.RollValueWithBase(baseHealth, HealthDice.ToArray()); if (this is Ally && Globals.LargeManaCheat) this.Mana = UtilityHandler.RollValueWithBase(baseMana + 300, ManaDice.ToArray()); else this.Mana = UtilityHandler.RollValueWithBase(baseMana, ManaDice.ToArray()); this.Armor = UnityEngine.Random.Range(ArmorRange.x, ArmorRange.y + 1); } // Sets the max values for all of the entity components this.maxHealth = this.Health; this.maxMana = this.Mana; this.maxArmor = this.Armor; if (this.spawnedPosition == null) this.spawnedPosition = transform.parent; this.stats = this.stats != null ? this.stats : GetComponent<EntityStats>(); this.modList = new EntityModList(); } protected void Update() { if (genStatsChanged) UpdateHealthVis(); else if (statusAfflicted) UpdateStatusFields(); } /// <summary> /// Retrieves the name of the enemy object /// </summary> /// <returns>Enemy name</returns> public string GetName() => Entity_Name; /// <summary> /// Decreases health of the enemy and signals if the enemy has lost all life points /// </summary> /// <param name="damage">Amount of damage that the enemy is taking</param> /// <returns>True if the enemy has lost all health, false if otherwise</returns> public bool DecreaseHealth(int damage, bool mercied = false) { bool stateCheck = this.Health - damage <= 0; if (this is Enemy && damage is not 0 && !stateCheck) animator.PlayTakeHitAnimation(); if (stateCheck) { if (mercied) this.Health = 1; else { this.Health = 0; if (this is Enemy || this is Player) { animator.PlayDeathAnimation(); if (this.IsFlying) ActivateSecondaryColliderFall(); } return true; } } this.Health -= damage; PhyllSurpriseDamagedCheck(); return false; void PhyllSurpriseDamagedCheck() { if (GetName().Equals("Phyll") && GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[57])) { Ability abRef = GetInventory().Abilities.Where(ability => ability.AbilityID is 57).FirstOrDefault(); abRef.active = false; this.IsHidden = false; } } } /// <summary> /// Helps better align the flying enemy deaths with a secondary collider to detect the final sprites collision box /// </summary> private void ActivateSecondaryColliderFall() { GetComponent<BoxCollider2D>().enabled = false; GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic; secondaryCollider.enabled = true; } /// <summary> /// Heals the health of the enemy within the range set by the max health /// </summary> /// <param name="restoredAmount">Heal amount</param> public void IncreaseHealth(int restoredAmount, bool isFirst = true) { if (isFirst) { Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); if (this.GetInventory().Abilities.Contains(abilityList[108]) || this.GetInventory().limitedAbilities.Contains(abilityList[108])) this.IncreaseMana((int)Math.Floor(restoredAmount * .33f), false); } if (this.Health + restoredAmount > this.maxHealth) { this.Health = this.maxHealth; return; } this.Health += restoredAmount; } /// <summary> /// Increases the max health of the entity by the indicated amount /// </summary> /// <param name="increaseAmount">The amount to increase the max health by</param> public void IncreaseMaxHealth(int increaseAmount) { if (this.maxHealth + increaseAmount <= 3) { this.maxHealth = 3; return; } this.maxHealth += increaseAmount; } /// <summary> /// The mana used is taken from the mana pool of the enemy /// </summary> /// <param name="mana_Used">The amount to be taken from the enemies mana</param> public void DecreaseMana(int mana_Used) { if (this.Mana - mana_Used <= 0) { this.Mana = 0; return; } this.Mana -= mana_Used; } /// <summary> /// The mana is refilled based on the parameter passed with a limit to the max mana /// </summary> /// <param name="refill">The amount of mana to refill</param> public void IncreaseMana(int refill, bool isFirst = true) { if (isFirst) { Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); if (this.GetInventory().Abilities.Contains(abilityList[108]) || this.GetInventory().limitedAbilities.Contains(abilityList[108])) this.IncreaseHealth((int)Math.Floor(refill * .33f), false); } if (this.Mana + refill > this.maxMana) { this.Mana = this.maxMana; return; } this.Mana += refill; } /// <summary> /// Increases the max mana of the entity by the indicated amount /// </summary> /// <param name="increaseAmount">The amount to increase the max mana by</param> public void IncreaseMaxMana(int increaseAmount) { if (this.maxMana + increaseAmount <= 0) { this.maxMana = 1; return; } this.maxMana += increaseAmount; } /// <summary> /// Handles stats such as health and mana in terms of their visual updates /// </summary> protected virtual void UpdateHealthVis() { // DOT damage is applied the same throughout all entities by applying all listed DOT modifiers, // when all decrement their turn counters to 0 and are removed, then the DOT application is switched off if (dotApplied && dotModifierValues.Count > 0) ApplyDOTDamage(); else dotApplied = false; } private void ApplyDOTDamage() { for (int i = 0; i < dotModifierValues.Count; i++) { // Specific values are set up here as reference points for unique modifiers KeyValuePair<Modifier, int> col = dotModifierValues.ElementAt(i); int damageAmount = 0; // If the DOT modifier is 'Experiencing Nightmare', then check to make sure the sleep mod is also active // otherwise remove this specific modifier from both the DOT Dictionary and the Mod List Dictionary if (col.Key.Name.Equals("Experiencing Nightmare")) { if (!this.modList.SmartModCheck("Sleeping")) { dotModifierValues.Remove(col.Key); this.modList.RemoveModifier(col.Key); continue; } // Experiencing Nightmare increments its damage each turn damageAmount = col.Value; damageAmount += UtilityHandler.RollValue(col.Key.GetDiceValues().ToArray()); } // When the damage value hasn't been affected simply retrieve the damage value indicated in the Tuple if (damageAmount is 0) damageAmount = col.Value; // Then decrease this entities health and update the DOT modifier values in the Dictionary DecreaseHealth(damageAmount); dotModifierValues.Remove(col.Key); dotModifierValues.Add(col.Key, damageAmount); } } /// <summary> /// Clears out all of the modifiers gained from the battle /// </summary> public void ClearModifiers() { dotModifierValues.Clear(); dotDamagePerTurn = 0; dotApplied = false; modList.ClearModifiers(); this.AfflictedStatus(); } protected virtual void UpdateStatusFields() { this.UpdateModsOfEntity(); statusAfflicted = false; } protected void UpdateModsOfEntity() { List<Modifier> modsToRemove = new List<Modifier>(); foreach (KeyValuePair<Modifier, int> mod in modList) { if (mod.Value is 0) { modsToRemove.Add(mod.Key); RemoveModBonus(mod); } else { if (implementedMods.Contains(mod.Key)) continue; else ImplementModBonus(mod); } } foreach (Modifier modRemoving in modsToRemove) modList.RemoveModifier(modRemoving); if (this is Enemy enemy) { Transform modifierTransform = enemy.GetModifierField(); foreach (Transform mod in modifierTransform) { ModifierHelper modHelper = mod.GetComponent<ModifierHelper>(); modHelper.CallTurnDecrement(); } } } /// <summary> /// When the entity is cured, all of the debuff and DOT modifiers are removed from their mod list /// </summary> public void CleanseAilments() { List<Modifier> modsToRemove = new List<Modifier>(); foreach (KeyValuePair<Modifier, int> mod in modList) { if (mod.Key.Type is ModifierSO.Type.Debuff || mod.Key.Effect is ModifierSO.Effect.DOT) { modsToRemove.Add(mod.Key); RemoveModBonus(mod); } } foreach (Modifier modRemoving in modsToRemove) modList.RemoveModifier(modRemoving); this.AfflictedStatus(); } private void ImplementModBonus(KeyValuePair<Modifier, int> mod) { switch (mod.Key.Effect) { case ModifierSO.Effect.Stat: foreach (EntityStats.Stat statValue in mod.Key.GetStatsModified()) { if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(statValue, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(statValue, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(statValue, mod.Key.GetDiceValues()); } break; case ModifierSO.Effect.HPRegen: if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(EntityStats.Stat.HPRegen, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(EntityStats.Stat.HPRegen, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(EntityStats.Stat.HPRegen, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.ManaRegen: if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(EntityStats.Stat.MPRegen, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(EntityStats.Stat.MPRegen, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(EntityStats.Stat.MPRegen, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.ArmorModifier: if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(EntityStats.Stat.Armor, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(EntityStats.Stat.Armor, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(EntityStats.Stat.Armor, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.Accuracy: if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(EntityStats.Stat.ToHit, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(EntityStats.Stat.ToHit, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(EntityStats.Stat.ToHit, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.Crit: if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(EntityStats.Stat.CritDamage, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(EntityStats.Stat.CritDamage, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(EntityStats.Stat.CritDamage, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.DamageModifier: if (mod.Key.IsValues[0]) // Base Values stats.SetModifierToStat(EntityStats.Stat.Damage, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.SetModifierToStat(EntityStats.Stat.Damage, mod.Key.GetPercentageValue()); else // Dice Values stats.SetModifierToStat(EntityStats.Stat.Damage, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.DOT: if (mod.Key.IsValues[0]) // Is Value dotModifierValues.Add(mod.Key, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Is Percent dotModifierValues.Add(mod.Key, (int)Math.Floor((double)this.maxHealth * mod.Key.GetPercentageValue())); else // Is Dice dotModifierValues.Add(mod.Key, UtilityHandler.RollValue(mod.Key.GetDiceValues().ToArray())); dotApplied = true; break; case ModifierSO.Effect.Other: switch (mod.Key.Name) { case "Unquenched": // Limit INT by 2 this.GatherStats().SetModifierToStat(EntityStats.Stat.Intelligence, -2); this.GetInventory().limitedAbilities.Add(AbilityBrain.GetAbilityList()[86]); this.GetInventory().limitedSpells.Add(AbilityBrain.GetSpellList()[83]); break; } break; } implementedMods.Add(mod.Key); } private async void RemoveModBonus(KeyValuePair<Modifier, int> mod) { implementedMods.Remove(mod.Key); switch (mod.Key.Effect) { case ModifierSO.Effect.Stat: foreach (EntityStats.Stat statValue in mod.Key.GetStatsModified()) { if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(statValue, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(statValue, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(statValue, mod.Key.GetDiceValues()); } break; case ModifierSO.Effect.HPRegen: if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(EntityStats.Stat.HPRegen, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(EntityStats.Stat.HPRegen, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(EntityStats.Stat.HPRegen, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.ManaRegen: if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(EntityStats.Stat.MPRegen, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(EntityStats.Stat.MPRegen, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(EntityStats.Stat.MPRegen, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.ArmorModifier: if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(EntityStats.Stat.Armor, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(EntityStats.Stat.Armor, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(EntityStats.Stat.Armor, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.Accuracy: if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(EntityStats.Stat.ToHit, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(EntityStats.Stat.ToHit, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(EntityStats.Stat.ToHit, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.Crit: if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(EntityStats.Stat.CritDamage, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(EntityStats.Stat.CritDamage, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(EntityStats.Stat.CritDamage, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.DamageModifier: if (mod.Key.IsValues[0]) // Base Values stats.RemoveModifierToStat(EntityStats.Stat.Damage, mod.Key.GetNormalValue()); else if (mod.Key.IsValues[1]) // Percentage Values stats.RemoveModifierToStat(EntityStats.Stat.Damage, mod.Key.GetPercentageValue()); else // Dice Values stats.RemoveModifierToStat(EntityStats.Stat.Damage, mod.Key.GetDiceValues()); break; case ModifierSO.Effect.DOT: try { dotModifierValues.Remove(mod.Key); } catch { } break; case ModifierSO.Effect.Other: await DecipherUniqueEffectRemoved(mod.Key); break; } } private async Task DecipherUniqueEffectRemoved(Modifier mod) { switch (mod.Name) { case "Life Contract": List<int> retainedDamage = mod.RetentionObject as List<int>; // Deal damage to random target if damage accumulated exceeds the heal value if (retainedDamage[1] >= retainedDamage[0]) { if (this is Enemy) await BattleHandler.Instance.DealDamageToLeftRandom(retainedDamage[0]); else await BattleHandler.Instance.DealDamageToRightRandom(retainedDamage[0]); } // Otherwise take the health regenerated back else { int damageValue = retainedDamage[0]; if (this is Enemy enemy) { BattleHandler.Instance.GetUIHandler().RetrieveActionText().UpdateActionTextUnique(15, damageValue, enemy); BattleHandler.Instance.DamageEnemy(enemy, damageValue, false); } else if (this is Player player) { BattleHandler.Instance.GetUIHandler().RetrieveActionText().UpdateActionTextUnique(15, damageValue, player); BattleHandler.Instance.DamagePlayer(damageValue, false); } else { BattleHandler.Instance.GetUIHandler().RetrieveActionText().UpdateActionTextUnique(15, damageValue, this as Ally); BattleHandler.Instance.DamageAlly(this as Ally, damageValue, false); } } break; case "Unquenched": this.GatherStats().RemoveModifierToStat(EntityStats.Stat.Intelligence, -2); this.GetInventory().Remove(AbilityBrain.GetAbilityList()[86]); this.GetInventory().Remove(AbilityBrain.GetSpellList()[83]); break; } } /// <summary> /// Call for the Entities order initiative value for the order of combat class /// </summary> /// <returns></returns> public int RollOrderValue() => DiceRoller.RollDice(new DTwenty()) + stats.GetStat(EntityStats.Stat.Dexterity); /// <summary> /// Getter for the current health of this entity /// </summary> /// <returns>This entities current health</returns> public int RetrieveHealth() => this.Health; /// <summary> /// Getter for Max Health /// </summary> /// <returns>This entities Max Health</returns> public int RetrieveMaxHealth() => this.maxHealth; /// <summary> /// Retrieves the health percentage of the entity for comparisons of others /// </summary> /// <returns>This entities health percentage</returns> public int GetHealthPercentage() { float currentHealth = (float)this.Health; float maxHP = (float)this.maxHealth; return (int)(currentHealth / maxHP * 100); } /// <summary> /// Retrieves this entities current mana value /// </summary> /// <returns>This entities current mana</returns> public int RetrieveMana() => this.Mana; /// <summary> /// Getter for the max mana of this entity /// </summary> /// <returns>This entities Max Mana</returns> public int RetrieveMaxMana() => this.maxMana; /// <summary> /// Retrieval for the percentage of missing mana this entity has /// which is then used for comparisons /// </summary> /// <returns>Percentage of missing mana</returns> public int GetManaPercentage() { float currentMana = (float)this.Mana; float maxMA = (float)this.maxMana; return (int)(currentMana / maxMA * 100); } public int RetrieveArmor() => this.Armor; public int RetrieveMaxArmor() => this.maxArmor; public void SetArmor(int newArmor) { this.maxArmor = newArmor; this.Armor = newArmor; } public int GetStat(EntityStats.Stat stat) => stats.GetStat(stat); public int GetBaseStat(EntityStats.Stat stat) => stats.GetBaseStat(stat); /// <summary> /// Retrieval for the entities level /// </summary> /// <returns>The current level of the entity</returns> public int GetLevel() => this.Level; public void Revive(bool hasFainted = false, int healAmount = 1) { if (TrueDeath) return; if (!hasFainted) { this.Health = this.maxHealth / 2; GameManager.Instance.GetComponent<BattleHandler>().Revive(this); } else this.Health = healAmount; if (this is Ally ally) { ally.GetGUI().UpdateStats(ally); ally.StatusChanged(); ally.ReactivateVisually(); } else if (this is Player player) { player.StatusChanged(); GeneralUI.Instance.SetInformation(false); player.ReactivateVisually(); } else { Enemy enemy = this as Enemy; enemy.UpdateHealthVis(); } Debug.Log($"{this.GetName()} has been revived!"); } public void AddModToList(Modifier mod, int baseTurnValue = 0, int substituteTurnValue = 0) { // Resilient Nature Chcek if (mod.Name.Equals("Envenomated") && this is Enemy enemy && enemy.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[18])) return; // Check to make sure that the mod isn't already present, if it is just increment the turn counter if (modList.Contains(mod)) { modList.IncreaseModifierTime(mod, this); return; } int turns; if (substituteTurnValue is 0) { if (mod.LengthOfEffect.Count is not 0) { if (mod.LengthOfEffect.Contains(Dice.DiceR.Hundred)) turns = 999; else turns = UtilityHandler.RollValue(mod.LengthOfEffect.ToArray()) + baseTurnValue; } else { turns = mod.LengthOfEffectNoDice + baseTurnValue; // If Smoldering Rage is available in this entities abilities if (mod.Name is "Rage" && GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[83])) turns += 2; } } else turns = substituteTurnValue; // Sticky Aura Ability Check if (mod.Type is ModifierSO.Type.Buff && this.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[106])) turns += 2; modList.AddModifier(mod, turns); AfflictedStatus(); } public void DecrementTurnsOnMods() { modList.DecrementTurnsForMods(); AfflictedStatus(); } /// <summary> /// Retrieval for the sprite icon of the entity /// </summary> /// <returns>Sprite Icon, meant for Party GUI</returns> public virtual Sprite GetSprite() => entity_Sprite; /// <summary> /// Sets the modifiers that can potentially happen from the AbilityBrain when checking status' /// </summary> /// <param name="m">The Modifier to add</param> /// <param name="chance">The chance of the modifier occurred to a target FLOAT VALUE SHOULD BE 0 < | x | < 1 for percentage values</param> public void SetModifierAndChance(Modifier m, Vector2Int savingThrow, bool reset = false) { if (reset) modifiersWithChance = new(); if (modifiersWithChance.ContainsKey(m)) { (int currentValue, EntityStats.Stat statConsidered) = modifiersWithChance[m]; int addedValue = this.GatherStats().GetStat(statConsidered); // Guard to prevent lowering of the saving throw if (addedValue < 0) return; int newCurrentValue = currentValue + addedValue; modifiersWithChance[m] = (newCurrentValue, statConsidered); } else { switch (savingThrow.x) { case 0: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Strength)); break; case 1: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Intelligence)); break; case 2: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Constitution)); break; case 3: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Dexterity)); break; case 4: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Charisma)); break; case 5: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Wisdom)); break; case 6: modifiersWithChance.Add(m, (savingThrow.y, EntityStats.Stat.Luck)); break; } } } /// <summary> /// Gathers all of the mods from the mod chance dictionary and calculates if they happen for each, then /// creates a new dictionary with true for activated and false for not activated /// </summary> /// <returns>Dictionary of activate Modifiers</returns> public Dictionary<Modifier, bool> CallMods(Entity target) { Dictionary<Modifier, bool> modTriggers = new(); if (modifiersWithChance == null || modifiersWithChance.Count is 0) return modTriggers; foreach (KeyValuePair<Modifier, (int, EntityStats.Stat)> pair in modifiersWithChance) { int savingThrow = UtilityHandler.RollSavingThrow(pair.Value.Item2, target); if (savingThrow >= pair.Value.Item1) modTriggers.Add(pair.Key, true); } return modTriggers; } /// <summary> /// Before a turn is taken the entity will receive DOT damage if applicable /// </summary> public (bool, bool)CallTurnModifierEffects() { bool turnSkipped = false; bool confused = false; this.UpdateHealthVis(); // This check just checks to make sure that modifiers that skip turns will be called after the DOT systems are checked if (this.modList.SmartModCheck("Sleeping")) turnSkipped = true; if (this.modList.SmartModCheck("Paralyzed")) turnSkipped = true; if (this.modList.SmartModCheck("Mind Controlled") || this.modList.SmartModCheck("Confusion")) { int savingThrow = UtilityHandler.RollSavingThrow(EntityStats.Stat.Intelligence, this); if (savingThrow < 10) confused = true; } return (turnSkipped, confused); } public EntityAnimation GetAnimationHandler() => animator; public void AlterManaRegenerationRate(float regenerationRate) => manaRegenRate += (manaRegenRate * regenerationRate); public float GetManaRegen() => manaRegenRate; /// <summary> /// Retrieval for the Inventory attached to this entity /// </summary> /// <returns>This entity's inventory component</returns> public virtual Inventory GetInventory() { return null; } /// <summary> /// Increases the experience level of this entity, handles leveling up internally /// </summary> /// <param name="expVal">The experience value the entity has gained</param> /// <returns>True if the entity has leveled up, false otherwise</returns> public bool IncreaseExperience(int expVal) { bool hasLeveledUp = false; // If level is already 20. simply return if (this.Level is MaxLevel) { this.TotalExperience += expVal; return false; } // Get next level requirements int levelThreshhold = levelMapExpRequirements[this.Level + 1]; this.CurrentLevelExperience += expVal; this.TotalExperience += expVal; // If the current experience for the level the entity is at reaches the threshold, the // entity levels up until the current experience level is not above the threshold while (this.CurrentLevelExperience >= levelThreshhold) { Debug.Log($"{this.Entity_Name} has leveled up!"); this.CurrentLevelExperience -= levelThreshhold; ++this.Level; IncreaseSecondaryStatMaxes(); // If Desire for Power is an ability this entity has, double the skill point increase if (this.GetInventory().Abilities.Contains(AbilityBrain.GetAbilityList()[96])) ++this.SkillPoints; ++this.SkillPoints; levelThreshhold = levelMapExpRequirements[this.Level + 1]; this.HasLeveledUp = true; hasLeveledUp = true; } return hasLeveledUp; } /// <summary> /// Increases the health and mana of the entity when leveling up /// </summary> private void IncreaseSecondaryStatMaxes() { if (this is Player player) { Player.PlayerType playerType = player.GetPlayerType(); switch (playerType) { case Player.PlayerType.Fighter: // Increase Health by D6, Mana by D6 IncreaseEntityStats(new DSix(), new DSix()); break; case Player.PlayerType.Berserker: // Increase Health by D8, Mana by D4 IncreaseEntityStats(new DEight(), new DFour()); break; case Player.PlayerType.Wizard: // Increase Health by D4, Mana by D10 IncreaseEntityStats(new DFour(), new DTen()); break; case Player.PlayerType.Sorcerer: // Increase Health by D6, Mana by D8 IncreaseEntityStats(new DSix(), new DEight()); break; case Player.PlayerType.Ranger: // Increase Health by D6, Mana by D6 IncreaseEntityStats(new DSix(), new DSix()); break; case Player.PlayerType.Gunslinger: // Increase Health by D6, Mana by D4 IncreaseEntityStats(new DSix(), new DFour()); break; } } else { // Can't use local function since the dice params are an enum type and not the class itself int rolledHealth = UtilityHandler.RollValue(HealthDice[0]); int rolledMana = UtilityHandler.RollValue(ManaDice[0]); IncreaseMaxHealth(rolledHealth); IncreaseHealth(rolledHealth); IncreaseMaxMana(rolledMana); IncreaseMana(rolledMana); this.StatusChanged(); } void IncreaseEntityStats(Dice hpDice, Dice mpDice) { Dictionary<int, Ability> abilityList = AbilityBrain.GetAbilityList(); int rolledHP = DiceRoller.RollDice(hpDice) + this.GatherStats().GetStat(EntityStats.Stat.Constitution); // Inspiring Courage if (this.GetInventory().Abilities.Contains(abilityList[102]) || this.GetInventory().limitedAbilities.Contains(abilityList[102])) rolledHP += DiceRoller.RollDice(hpDice) + this.GatherStats().GetStat(EntityStats.Stat.Constitution); int rolledMP = DiceRoller.RollDice(mpDice) + this.GatherStats().GetStat(EntityStats.Stat.Intelligence); // Mana Growth if (this.GetInventory().Abilities.Contains(abilityList[142]) || this.GetInventory().limitedAbilities.Contains(abilityList[142])) rolledMP += DiceRoller.RollDice(mpDice) + this.GatherStats().GetStat(EntityStats.Stat.Intelligence); this.IncreaseMaxHealth(rolledHP); this.IncreaseHealth(rolledHP); this.IncreaseMaxMana(rolledMP); this.IncreaseMana(rolledMP); this.StatusChanged(); } } /// <summary> /// Signal method to indicate that the entity has leveled up their components /// </summary> public void HasIntegratedLevel() { this.LastLevelIntegrated = this.Level; this.HasLeveledUp = false; } /// <summary> /// Retrieval for the signal if the entity has leveled up and can access their skill tree /// and improve their stats in the inventory manipulator component /// </summary> /// <returns>Signal for if the skill tree and stats can be modified</returns> public bool HasEntityLeveledUp() => this.HasLeveledUp; public void SignalPlayerModification(string damageModificationName, object damageModifier) => ClassModifications.Add(damageModificationName, damageModifier); public int ParseClassDamageModifiers(Entity target, Dictionary<string, (List<string>, List<object>)> triggers = null, SortedDictionary<int, (int, Dice)> damageRolls = null) { int additionalDamage = 0; foreach (KeyValuePair<string, object> modification in ClassModifications) { switch (modification.Key) { case "Favored Enemy": string enemyType = (string)((List<object>)modification.Value)[0]; if (enemyType.Equals(target.RetrieveEnemyType())) { Dice dice = (Dice)((List<object>)modification.Value)[1]; int damageValue = DiceRoller.RollDice(dice); if (damageRolls is not null && triggers is not null) { int currentDice = damageRolls.Last().Key; damageRolls.Add(++currentDice, (damageValue, dice)); if (triggers.ContainsKey("AddDamageValue")) { triggers["AddDamageValue"].Item1.Add("Favored Enemy"); triggers["AddDamageValue"].Item2.Add(new List<object> { damageValue, dice }); } else triggers.Add("AddDamageValue", (new List<string> { "Favored Enemy" }, new List<object> { new List<object> { damageValue, dice } })); } additionalDamage += damageValue; } break; default: break; } } return additionalDamage; } public int ParseToHitClassModifiers(Entity target, Dictionary<string, (List<string>, List<object>)> triggers = null, SortedDictionary<int, (int, Dice)> toHitRolls = null) { int additionalToHit = 0; foreach (KeyValuePair<string, object> modification in ClassModifications) { switch (modification.Key) { case "Favored Enemy": string enemyType = (string)((List<object>)modification.Value)[0]; if (enemyType.Equals(target.RetrieveEnemyType())) { Dice dice = (Dice)((List<object>)modification.Value)[2]; int toHitValue = DiceRoller.RollDice(dice); if (toHitRolls is not null && triggers is not null) { int currentDice = toHitRolls.Last().Key; toHitRolls.Add(++currentDice, (toHitValue, dice)); } if (triggers.ContainsKey("ToHitAdd")) { triggers["ToHitAdd"].Item1.Add("Favored Enemy"); triggers["ToHitAdd"].Item2.Add(new List<object> { toHitValue, dice }); } else triggers.Add("ToHitAdd", (new List<string> { "Favored Enemy" }, new List<object> { new List<object> { toHitValue, dice } })); additionalToHit += toHitValue; } break; default: break; } } return additionalToHit; } public (List<SpellSO.Element>, List<SpellSO.Element>) RetrieveStartingElementalTypes() => (elementWeaknesses, elementResistances); public (List<WeaponSO.Type>, List<WeaponSO.Type>) RetrieveStartingWeaponTypes() => (weaponWeaknesses, weaponResistances); /// <summary> /// Easier retrieval for this entity's weapon weaknesses /// </summary> /// <returns></returns> public Dictionary<WeaponSO.Type, int> GetWeaponWeaknesses() { this.stats.WeaponWeaknesses ??= new Dictionary<WeaponSO.Type, int>(); return this.stats.WeaponWeaknesses; } /// <summary> /// Easier retrieval for this entity's weapon resistances /// </summary> /// <returns></returns> public Dictionary<WeaponSO.Type, int> GetWeaponResists() { this.stats.WeaponResistances ??= new Dictionary<WeaponSO.Type, int>(); return this.stats.WeaponResistances; } /// <summary> /// Easier retrieval for this entity's elemental weaknesses /// </summary> /// <returns></returns> public Dictionary<SpellSO.Element, int> GetElementWeaknesses() { this.stats.ElementWeaknesses ??= new Dictionary<SpellSO.Element, int>(); return this.stats.ElementWeaknesses; } /// <summary> /// Easier retrieval for this entity's elemental resistances /// </summary> /// <returns></returns> public Dictionary<SpellSO.Element, int> GetElementResists() { this.stats.ElementResistances ??= new Dictionary<SpellSO.Element, int>(); return this.stats.ElementResistances; } /// <summary> /// Deactivates the entities sprite component to give off the illusion of destruction /// </summary> public void DeactivateVisually() => transform.GetChild(0).gameObject.SetActive(false); /// <summary> /// Reactivates the entities sprite component to give off the illusion of instantiation /// </summary> public void ReactivateVisually() => transform.GetChild(0).gameObject.SetActive(true); /// <summary> /// Retrieval for the skill tree objects /// </summary> /// <returns></returns> public List<GameObject> GetSkillTrees() => this.availableSkillTrees; /// <summary> /// Adds a skill tree object to the entities list of available trees to them /// </summary> /// <param name="skillTreeObject">The whole game object for the entities new skill tree</param> public void AddSkillTree(GameObject skillTreeObject) { // Make sure the skill tree isn't already present in the current trees available to the player if (!skillTreeNames.Contains(skillTreeObject.name)) availableSkillTrees.Add(skillTreeObject); } /// <summary> /// Removes a skill tree from the entities list of available ones /// </summary> /// <param name="skillTreeName">The name of which the object that needs to be removed</param> public void RemoveSkillTree(string skillTreeName) { // Guard for if the names doesn't contain the name indicated if (!skillTreeNames.Contains(skillTreeName)) return; // Cycles through the skill trees to remove the matching skill tree object to the name given // Then simply removes the item at the designated index for (int i = 0; i < availableSkillTrees.Count; ++i) { if (availableSkillTrees[i].name == skillTreeName) availableSkillTrees.RemoveAt(i); } } /// <summary> /// A catch function for if trying to activate a skill indicates requirements not fulfilled, refunds the points since points were paid already /// </summary> /// <param name="skillPointCost">Cost of the skill</param> public void RefundSkillPoints(int skillPointCost) => this.SkillPoints += skillPointCost; public string RetrieveEnemyType() { return entityType switch { EntityType.Golem => "Golem", EntityType.Undead => "Undead", EntityType.Humanoid => "Humanoid", EntityType.Demonic => "Demonic", EntityType.Beast => "Beast", _ => throw new InvalidEnumArgumentException(), }; } public static bool operator ==(Entity left, Entity right) => left?.GetUniqueID() == right?.GetUniqueID(); public static bool operator !=(Entity left, Entity right) => !(left == right); public override bool Equals(object other) { if (other is not Entity) return false; else return other as Entity == this; } public override int GetHashCode() => base.GetHashCode(); public IEnumerator GetEnumerator() { yield return this; } public override string ToString() => Entity_Name; }