From 166da57c6be6fa9ffc83888552c7d0118d91417b Mon Sep 17 00:00:00 2001 From: hondacrx Date: Thu, 4 Mar 2021 15:26:57 -0500 Subject: [PATCH] Core/Spells: Fixed calculation spell optional power cost when any modifiers are applied Port From (https://github.com/TrinityCore/TrinityCore/commit/0e3666bfbc88bdcc9344dd3ebcfed0f205d122b2) --- Source/Game/Entities/Player/Player.Spells.cs | 212 ++-------- Source/Game/Spells/SpellInfo.cs | 410 ++++++++++--------- 2 files changed, 246 insertions(+), 376 deletions(-) diff --git a/Source/Game/Entities/Player/Player.Spells.cs b/Source/Game/Entities/Player/Player.Spells.cs index 51af16a61..0a1f4296e 100644 --- a/Source/Game/Entities/Player/Player.Spells.cs +++ b/Source/Game/Entities/Player/Player.Spells.cs @@ -2687,94 +2687,9 @@ namespace Game.Entities float totalmul = 1.0f; int totalflat = 0; - // Drop charges for triggering spells instead of triggered ones - if (m_spellModTakingSpell != null) - spell = m_spellModTakingSpell; + GetSpellModValues(spellInfo, op, spell, basevalue, ref totalflat, ref totalmul); - switch (op) - { - // special case, if a mod makes spell instant, only consume that mod - case SpellModOp.CastingTime: - { - SpellModifier modInstantSpell = null; - foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Pct]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - if (basevalue < 10000 && mod.value <= -100) - { - modInstantSpell = mod; - break; - } - } - - if (modInstantSpell != null) - { - ApplyModToSpell(modInstantSpell, spell); - basevalue = 0; - return; - } - break; - } - // special case if two mods apply 100% critical chance, only consume one - case SpellModOp.CriticalChance: - { - SpellModifier modCritical = null; - foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Flat]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - if (mod.value >= 100) - { - modCritical = mod; - break; - } - } - - if (modCritical != null) - { - ApplyModToSpell(modCritical, spell); - basevalue = 100; - return; - } - break; - } - default: - break; - } - - foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Flat]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - totalflat += mod.value; - ApplyModToSpell(mod, spell); - } - - foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Pct]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - // skip percent mods for null basevalue (most important for spell mods with charges) - if (basevalue + totalflat == 0) - continue; - - // special case (skip > 10sec spell casts for instant cast setting) - if (op == SpellModOp.CastingTime) - { - if (basevalue >= 10000 && mod.value <= -100) - continue; - } - - totalmul *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); - ApplyModToSpell(mod, spell); - } - - basevalue = (int)((float)(basevalue + totalflat) * totalmul); + basevalue = (int)(((float)basevalue + totalflat) * totalmul); } public void ApplySpellMod(SpellInfo spellInfo, SpellModOp op, ref uint basevalue, Spell spell = null) @@ -2782,94 +2697,9 @@ namespace Game.Entities float totalmul = 1.0f; int totalflat = 0; - // Drop charges for triggering spells instead of triggered ones - if (m_spellModTakingSpell != null) - spell = m_spellModTakingSpell; + GetSpellModValues(spellInfo, op, spell, basevalue, ref totalflat, ref totalmul); - switch (op) - { - // special case, if a mod makes spell instant, only consume that mod - case SpellModOp.CastingTime: - { - SpellModifier modInstantSpell = null; - foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Pct]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - if (basevalue < 10000 && mod.value <= -100) - { - modInstantSpell = mod; - break; - } - } - - if (modInstantSpell != null) - { - ApplyModToSpell(modInstantSpell, spell); - basevalue = 0; - return; - } - break; - } - // special case if two mods apply 100% critical chance, only consume one - case SpellModOp.CriticalChance: - { - SpellModifier modCritical = null; - foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Flat]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - if (mod.value >= 100) - { - modCritical = mod; - break; - } - } - - if (modCritical != null) - { - ApplyModToSpell(modCritical, spell); - basevalue = 100; - return; - } - break; - } - default: - break; - } - - foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Flat]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - totalflat += mod.value; - ApplyModToSpell(mod, spell); - } - - foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Pct]) - { - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) - continue; - - // skip percent mods for null basevalue (most important for spell mods with charges) - if (basevalue + totalflat == 0) - continue; - - // special case (skip > 10sec spell casts for instant cast setting) - if (op == SpellModOp.CastingTime) - { - if (basevalue >= 10000 && mod.value <= -100) - continue; - } - - totalmul *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); - ApplyModToSpell(mod, spell); - } - - basevalue = (uint)((float)(basevalue + totalflat) * totalmul); + basevalue = (uint)(((float)basevalue + totalflat) * totalmul); } public void ApplySpellMod(SpellInfo spellInfo, SpellModOp op, ref float basevalue, Spell spell = null) @@ -2877,8 +2707,18 @@ namespace Game.Entities float totalmul = 1.0f; int totalflat = 0; + GetSpellModValues(spellInfo, op, spell, basevalue, ref totalflat, ref totalmul); + + basevalue = (float)(basevalue + totalflat) * totalmul; + } + + public void GetSpellModValues(SpellInfo spellInfo, SpellModOp op, Spell spell, T baseValue, ref int flat, ref float pct) where T : IComparable + { + flat = 0; + pct = 1.0f; + // Drop charges for triggering spells instead of triggered ones - if (m_spellModTakingSpell != null) + if (m_spellModTakingSpell) spell = m_spellModTakingSpell; switch (op) @@ -2892,7 +2732,7 @@ namespace Game.Entities if (!IsAffectedBySpellmod(spellInfo, mod, spell)) continue; - if (basevalue < 10000f && mod.value <= -100) + if (baseValue.CompareTo(10000) < 0 && mod.value <= -100) { modInstantSpell = mod; break; @@ -2902,7 +2742,7 @@ namespace Game.Entities if (modInstantSpell != null) { ApplyModToSpell(modInstantSpell, spell); - basevalue = 0f; + pct = 0.0f; return; } break; @@ -2926,7 +2766,7 @@ namespace Game.Entities if (modCritical != null) { ApplyModToSpell(modCritical, spell); - basevalue = 100f; + flat = 100; return; } break; @@ -2935,38 +2775,36 @@ namespace Game.Entities break; } - foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Flat]) + foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Flat]) { if (!IsAffectedBySpellmod(spellInfo, mod, spell)) continue; - totalflat += mod.value; + flat += mod.value; ApplyModToSpell(mod, spell); } - foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Pct]) + foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Pct]) { if (!IsAffectedBySpellmod(spellInfo, mod, spell)) continue; // skip percent mods for null basevalue (most important for spell mods with charges) - if (basevalue + totalflat == 0) + if (baseValue + (dynamic)flat == 0) continue; // special case (skip > 10sec spell casts for instant cast setting) if (op == SpellModOp.CastingTime) { - if (basevalue >= 10000 && mod.value <= -100) + if (baseValue.CompareTo(10000) > 0 && mod.value <= -100) continue; } - totalmul *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); + pct *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); ApplyModToSpell(mod, spell); } - - basevalue = (basevalue + totalflat) * totalmul; } - + bool IsAffectedBySpellmod(SpellInfo spellInfo, SpellModifier mod, Spell spell) { if (mod == null || spellInfo == null) diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 23a468716..8275fd57e 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -2675,11 +2675,220 @@ namespace Game.Spells return RecoveryTime > CategoryRecoveryTime ? RecoveryTime : CategoryRecoveryTime; } + SpellPowerCost CalcPowerCost(PowerType powerType, bool optionalCost, Unit caster, SpellSchoolMask schoolMask, Spell spell = null) + { + var spellPowerRecord = PowerCosts.First(spellPowerEntry => + { + return spellPowerEntry.PowerType == powerType; + }); + + if (spellPowerRecord == null) + return null; + + return CalcPowerCost(spellPowerRecord, optionalCost, caster, schoolMask, spell); + } + + SpellPowerCost CalcPowerCost(SpellPowerRecord power, bool optionalCost, Unit caster, SpellSchoolMask schoolMask, Spell spell = null) + { + SpellPowerCost cost = new(); + + // Spell drain all exist power on cast (Only paladin lay of Hands) + if (HasAttribute(SpellAttr1.DrainAllPower)) + { + // If power type - health drain all + if (power.PowerType == PowerType.Health) + { + cost.Power = PowerType.Health; + cost.Amount = (int)caster.GetHealth(); + return cost; + } + // Else drain all power + if (power.PowerType < PowerType.Max) + { + cost.Power = power.PowerType; + cost.Amount = caster.GetPower(cost.Power); + return cost; + } + + Log.outError(LogFilter.Spells, $"SpellInfo.CalcPowerCost: Unknown power type '{power.PowerType}' in spell {Id}"); + return default; + } + + // Base powerCost + int powerCost = 0; + if (!optionalCost) + { + powerCost = power.ManaCost; + // PCT cost from total amount + if (power.PowerCostPct != 0) + { + switch (power.PowerType) + { + // health as power used + case PowerType.Health: + if (MathFunctions.fuzzyEq(power.PowerCostPct, 0.0f)) + powerCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.PowerCostMaxPct); + else + powerCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.PowerCostPct); + break; + case PowerType.Mana: + powerCost += (int)MathFunctions.CalculatePct(caster.GetCreateMana(), power.PowerCostPct); + break; + case PowerType.AlternatePower: + Log.outError(LogFilter.Spells, $"SpellInfo.CalcPowerCost: Unknown power type '{power.PowerType}' in spell {Id}"); + return null; + default: + { + PowerTypeRecord powerTypeEntry = Global.DB2Mgr.GetPowerTypeEntry(power.PowerType); + if (powerTypeEntry != null) + { + powerCost += MathFunctions.CalculatePct(powerTypeEntry.MaxBasePower, power.PowerCostPct); + break; + } + + Log.outError(LogFilter.Spells, $"SpellInfo.CalcPowerCost: Unknown power type '{power.PowerType}' in spell {Id}"); + return default; + } + } + } + } + else + { + powerCost = (int)power.OptionalCost; + powerCost += caster.GetTotalAuraModifier(AuraType.ModAdditionalPowerCost, aurEff => + { + return aurEff.GetMiscValue() == (int)power.PowerType && aurEff.IsAffectingSpell(this); + }); + } + + bool initiallyNegative = powerCost < 0; + + // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost) + if (HasAttribute(SpellAttr4.SpellVsExtendCost)) + { + uint speed = 0; + SpellShapeshiftFormRecord ss = CliDB.SpellShapeshiftFormStorage.LookupByKey(caster.GetShapeshiftForm()); + if (ss != null) + speed = ss.CombatRoundTime; + else + { + WeaponAttackType slot = WeaponAttackType.BaseAttack; + if (!HasAttribute(SpellAttr3.MainHand) && HasAttribute(SpellAttr3.ReqOffhand)) + slot = WeaponAttackType.OffAttack; + + speed = caster.GetBaseAttackTime(slot); + } + + powerCost += (int)speed / 100; + } + + if (power.PowerType != PowerType.Health) + { + if (!optionalCost) + { + // Flat mod from caster auras by spell school and power type + foreach (AuraEffect aura in caster.GetAuraEffectsByType(AuraType.ModPowerCostSchool)) + { + if ((aura.GetMiscValue() & (int)schoolMask) == 0) + continue; + + if ((aura.GetMiscValueB() & (1 << (int)power.PowerType)) == 0) + continue; + + powerCost += aura.GetAmount(); + } + } + + // PCT mod from user auras by spell school and power type + foreach (var schoolCostPct in caster.GetAuraEffectsByType(AuraType.ModPowerCostSchoolPct)) + { + if ((schoolCostPct.GetMiscValue() & (int)schoolMask) == 0) + continue; + + if ((schoolCostPct.GetMiscValueB() & (1 << (int)power.PowerType)) == 0) + continue; + + powerCost += MathFunctions.CalculatePct(powerCost, schoolCostPct.GetAmount()); + } + } + + // Apply cost mod by spell + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + { + SpellModOp mod = SpellModOp.Max; + switch (power.OrderIndex) + { + case 0: + mod = SpellModOp.Cost; + break; + case 1: + mod = SpellModOp.SpellCost2; + break; + case 2: + mod = SpellModOp.SpellCost3; + break; + default: + break; + } + + if (mod != SpellModOp.Max) + { + if (!optionalCost) + modOwner.ApplySpellMod(this, mod, ref powerCost, spell); + else + { + // optional cost ignores flat modifiers + int flatMod = 0; + float pctMod = 1.0f; + modOwner.GetSpellModValues(this, mod, spell, powerCost, ref flatMod, ref pctMod); + powerCost = (int)(powerCost * pctMod); + } + } + } + + if (!caster.IsControlledByPlayer() && MathFunctions.fuzzyEq(power.PowerCostPct, 0.0f) && SpellLevel != 0 && power.PowerType == PowerType.Mana) + { + if (HasAttribute(SpellAttr0.LevelDamageCalculation)) + { + GtNpcManaCostScalerRecord spellScaler = CliDB.NpcManaCostScalerGameTable.GetRow(SpellLevel); + GtNpcManaCostScalerRecord casterScaler = CliDB.NpcManaCostScalerGameTable.GetRow(caster.GetLevel()); + if (spellScaler != null && casterScaler != null) + powerCost *= (int)(casterScaler.Scaler / spellScaler.Scaler); + } + } + + if (power.PowerType == PowerType.Mana) + powerCost = (int)((float)powerCost * (1.0f + caster.m_unitData.ManaCostMultiplier)); + + // power cost cannot become negative if initially positive + if (initiallyNegative != (powerCost < 0)) + powerCost = 0; + + cost.Power = power.PowerType; + cost.Amount = powerCost; + return cost; + } + public List CalcPowerCost(Unit caster, SpellSchoolMask schoolMask, Spell spell = null) { List costs = new List(); + + SpellPowerCost getOrCreatePowerCost(PowerType powerType) + { + var itr = costs.Find(cost => + { + return cost.Power == powerType; + }); + if (itr != null) + return itr; - int healthCost = 0; + SpellPowerCost cost = new(); + cost.Power = powerType; + cost.Amount = 0; + costs.Add(cost); + return costs.Last(); + } foreach (SpellPowerRecord power in PowerCosts) { @@ -2689,197 +2898,20 @@ namespace Game.Spells if (power.RequiredAuraSpellID != 0 && !caster.HasAura(power.RequiredAuraSpellID)) continue; - // Spell drain all exist power on cast (Only paladin lay of Hands) - if (HasAttribute(SpellAttr1.DrainAllPower)) + SpellPowerCost cost = CalcPowerCost(power, false, caster, schoolMask, spell); + if (cost != null) + getOrCreatePowerCost(cost.Power).Amount += cost.Amount; + + SpellPowerCost optionalCost = CalcPowerCost(power, true, caster, schoolMask, spell); + if (optionalCost != null) { - // If power type - health drain all - if (power.PowerType == PowerType.Health) - { - healthCost = (int)caster.GetHealth(); - continue; - } - // Else drain all power - if (power.PowerType < PowerType.Max) - { - SpellPowerCost cost = new SpellPowerCost(); - cost.Power = power.PowerType; - cost.Amount = caster.GetPower(cost.Power); - costs.Add(cost); - continue; - } - - Log.outError(LogFilter.Spells, "SpellInfo.GetCostDataList: Unknown power type '{0}' in spell {1}", power.PowerType, Id); - continue; - } - - // Base powerCost - int powerCost = power.ManaCost; - bool initiallyNegative = powerCost < 0; - // PCT cost from total amount - if (power.PowerCostPct != 0) - { - switch (power.PowerType) - { - // health as power used - case PowerType.Health: - powerCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.PowerCostPct); - break; - case PowerType.Mana: - powerCost += (int)MathFunctions.CalculatePct(caster.GetCreateMana(), power.PowerCostPct); - break; - case PowerType.Rage: - case PowerType.Focus: - case PowerType.Energy: - powerCost += MathFunctions.CalculatePct(caster.GetMaxPower(power.PowerType), power.PowerCostPct); - break; - case PowerType.Runes: - case PowerType.RunicPower: - Log.outDebug(LogFilter.Spells, "GetCostDataList: Not implemented yet!"); - break; - default: - Log.outError(LogFilter.Spells, "GetCostDataList: Unknown power type '{0}' in spell {1}", power.PowerType, Id); - continue; - } - } - - if (power.PowerCostMaxPct != 0) - healthCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.PowerCostMaxPct); - - int optionalCost = (int)power.OptionalCost; - optionalCost += caster.GetTotalAuraModifier(AuraType.ModAdditionalPowerCost, aurEff => - { - return aurEff.GetMiscValue() == (int)power.PowerType - && aurEff.IsAffectingSpell(this); - }); - - if (optionalCost != 0) - { - int remainingPower = caster.GetPower(power.PowerType) - powerCost; - powerCost += MathFunctions.RoundToInterval(ref remainingPower, 0, optionalCost); - } - - if (power.PowerType != PowerType.Health) - { - // Flat mod from caster auras by spell school and power type - int flatMod = 0; - var auras = caster.GetAuraEffectsByType(AuraType.ModPowerCostSchool); - foreach (var eff in auras) - { - if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) - continue; - - if (!Convert.ToBoolean(eff.GetMiscValueB() & (1 << (int)power.PowerType))) - continue; - - flatMod += eff.GetAmount(); - } - - powerCost += flatMod; - } - - // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost) - if (HasAttribute(SpellAttr4.SpellVsExtendCost)) - { - uint speed = 0; - SpellShapeshiftFormRecord ss = CliDB.SpellShapeshiftFormStorage.LookupByKey(caster.GetShapeshiftForm()); - if (ss != null) - speed = ss.CombatRoundTime; - else - { - WeaponAttackType slot = WeaponAttackType.BaseAttack; - if (!HasAttribute(SpellAttr3.MainHand) && HasAttribute(SpellAttr3.ReqOffhand)) - slot = WeaponAttackType.OffAttack; - - speed = caster.GetBaseAttackTime(slot); - } - - powerCost += (int)(speed / 100); - } - - // Apply cost mod by spell - Player modOwner = caster.GetSpellModOwner(); - if (modOwner) - { - switch (power.OrderIndex) - { - case 0: - modOwner.ApplySpellMod(this, SpellModOp.Cost, ref powerCost, spell); - break; - case 1: - modOwner.ApplySpellMod(this, SpellModOp.SpellCost2, ref powerCost, spell); - break; - case 2: - modOwner.ApplySpellMod(this, SpellModOp.SpellCost3, ref powerCost, spell); - break; - default: - break; - } - } - - if (!caster.IsControlledByPlayer() && MathFunctions.fuzzyEq(power.PowerCostPct, 0.0f) && SpellLevel != 0 && power.PowerType == PowerType.Mana) - { - if (HasAttribute(SpellAttr0.LevelDamageCalculation)) - { - GtNpcManaCostScalerRecord spellScaler = CliDB.NpcManaCostScalerGameTable.GetRow(SpellLevel); - GtNpcManaCostScalerRecord casterScaler = CliDB.NpcManaCostScalerGameTable.GetRow(caster.GetLevel()); - if (spellScaler != null && casterScaler != null) - powerCost *= (int)(casterScaler.Scaler / spellScaler.Scaler); - } - } - - // PCT mod from user auras by spell school and power type - var aurasPct = caster.GetAuraEffectsByType(AuraType.ModPowerCostSchoolPct); - foreach (var eff in aurasPct) - { - if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) - continue; - - if (!Convert.ToBoolean(eff.GetMiscValueB() & (1 << (int)power.PowerType))) - continue; - - powerCost += MathFunctions.CalculatePct(powerCost, eff.GetAmount()); - } - - if (power.PowerType == PowerType.Mana) - powerCost = (int)((float)powerCost * (1.0f + caster.m_unitData.ManaCostMultiplier)); - else if (power.PowerType == PowerType.Health) - { - healthCost += powerCost; - continue; - } - - // power cost cannot become negative if initially positive - if (initiallyNegative != (powerCost < 0)) - powerCost = 0; - - bool found = false; - for (var i = 0; i < costs.Count; ++i) - { - var cost = costs[i]; - if (cost.Power == power.PowerType) - { - cost.Amount += powerCost; - found = true; - } - } - - if (!found) - { - SpellPowerCost cost = new SpellPowerCost(); - cost.Power = power.PowerType; - cost.Amount = powerCost; - costs.Add(cost); + SpellPowerCost cost1 = getOrCreatePowerCost(optionalCost.Power); + int remainingPower = caster.GetPower(optionalCost.Power) - cost1.Amount; + if (remainingPower > 0) + cost1.Amount += Math.Min(optionalCost.Amount, remainingPower); } } - - if (healthCost > 0) - { - SpellPowerCost cost = new SpellPowerCost(); - cost.Power = PowerType.Health; - cost.Amount = healthCost; - costs.Add(cost); - } - + return costs; }