using System; using ACE.Common.Extensions; using ACE.DatLoader; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Entity.Models; using ACE.Server.Entity; namespace ACE.Server.WorldObjects.Entity { public class CreatureVital { private readonly Creature creature; public readonly PropertyAttribute2nd Vital; // the underlying database record private readonly PropertiesAttribute2nd propertiesAttribute2nd; /// /// If the creature's biota does not contain this vital, a new record will be created. /// public CreatureVital(Creature creature, PropertyAttribute2nd vital) { this.creature = creature; Vital = vital; if (!creature.Biota.PropertiesAttribute2nd.TryGetValue(vital, out propertiesAttribute2nd)) { propertiesAttribute2nd = new PropertiesAttribute2nd(); creature.Biota.PropertiesAttribute2nd[vital] = propertiesAttribute2nd; } switch (Vital) { case PropertyAttribute2nd.MaxHealth: RegenRate = creature.GetProperty(PropertyFloat.HealthRate) ?? 0; break; case PropertyAttribute2nd.MaxStamina: RegenRate = creature.GetProperty(PropertyFloat.StaminaRate) ?? 0; break; case PropertyAttribute2nd.MaxMana: RegenRate = creature.GetProperty(PropertyFloat.ManaRate) ?? 0; break; } } public uint StartingValue { get => propertiesAttribute2nd.InitLevel; set => propertiesAttribute2nd.InitLevel = value; } /// /// Total Experience Spent on this vital /// public uint ExperienceSpent { get => propertiesAttribute2nd.CPSpent; set => propertiesAttribute2nd.CPSpent = value; } /// /// Returns the amount of vital experience remaining /// until max rank is reached /// public uint ExperienceLeft { get { var vitalXPTable = DatManager.PortalDat.XpTable.VitalXpList; return vitalXPTable[vitalXPTable.Count - 1] - ExperienceSpent; } } /// /// The number of levels a vital has been raised, /// derived from ExperienceSpent /// public uint Ranks { get => propertiesAttribute2nd.LevelFromCP; set => propertiesAttribute2nd.LevelFromCP = value; } /// /// Returns TRUE if this vital has been raised the maximum # of times /// public bool IsMaxRank { get { var vitalXPTable = DatManager.PortalDat.XpTable.VitalXpList; return Ranks >= (vitalXPTable.Count - 1); } } /// /// Returns the adjusted Value depending on the base attribute formula /// public uint Base { get { var attr = AttributeFormula.GetFormula(creature, Vital, false); var total = StartingValue + Ranks + attr; if (creature is Player player && Vital == PropertyAttribute2nd.MaxHealth) total += (uint)(player.Enlightenment * 2 + player.GetGearMaxHealth()); return total; } } public uint Current { get => propertiesAttribute2nd.CurrentLevel; set => propertiesAttribute2nd.CurrentLevel = value; } public uint MaxValue => GetMaxValue(true); public uint GetMaxValue(bool enchanted) { var attr = AttributeFormula.GetFormula(creature, Vital, enchanted); uint total = StartingValue + Ranks + attr; var player = creature as Player; if (player != null) { // Enlightenment and GearMaxHealth didn't work like other additives // most additives (ie. from enchantments) were added in *after* multipliers, // but Enlightenment and GearMaxHealth were an exception, and added in beforehand // this means Enlightenment and GearMaxHealth would get scaled by multipliers, // including Asheron's Benediction, and oddly enough, vitae as well // it's also possible these were considered "base" if (Vital == PropertyAttribute2nd.MaxHealth) total += (uint)(player.Enlightenment * 2 + player.GetGearMaxHealth()); } // apply multiplicative enchantments first var multiplier = enchanted ? creature.EnchantmentManager.GetVitalMod_Multiplier(this) : 1.0f; var fTotal = total * multiplier; if (player != null) { var vitae = player.Vitae; if (vitae != 1.0f) fTotal *= vitae; } // everything beyond this point does not get scaled by vitae var additives = enchanted ? creature.EnchantmentManager.GetVitalMod_Additives(this) : 0; var iTotal = (fTotal + additives).Round(); // a creature cannot fall below 5 MaxVital from enchantments / vitae normally, // or 1 MaxVital for creatures with very low starting vitals var minVital = total >= 5 ? 5 : 1; iTotal = Math.Max(minVital, iTotal); return (uint)iTotal; } public uint Missing => MaxValue - Current; public float Percent => (float)Current / MaxValue; public ModifierType ModifierType { get { var diff = (int)GetMaxValue(true) - (int)GetMaxValue(false); if (diff > 0) return ModifierType.Buffed; else if (diff < 0) return ModifierType.Debuffed; else return ModifierType.None; } } public double RegenRate { set; get; } /// /// For tracking partial regeneration between ticks /// public double PartialRegen { get; set; } public Vital ToEnum() { switch (Vital) { case PropertyAttribute2nd.MaxHealth: return ACE.Entity.Enum.Vital.Health; case PropertyAttribute2nd.MaxStamina: return ACE.Entity.Enum.Vital.Stamina; case PropertyAttribute2nd.MaxMana: return ACE.Entity.Enum.Vital.Mana; } return ACE.Entity.Enum.Vital.Undefined; } } }