Core/Spells: Fixed calculation spell optional power cost when any modifiers are applied

Port From (https://github.com/TrinityCore/TrinityCore/commit/0e3666bfbc88bdcc9344dd3ebcfed0f205d122b2)
This commit is contained in:
hondacrx
2021-03-04 15:26:57 -05:00
parent 923def9f4a
commit 166da57c6b
2 changed files with 246 additions and 376 deletions
+24 -186
View File
@@ -2687,94 +2687,9 @@ namespace Game.Entities
float totalmul = 1.0f; float totalmul = 1.0f;
int totalflat = 0; int totalflat = 0;
// Drop charges for triggering spells instead of triggered ones GetSpellModValues(spellInfo, op, spell, basevalue, ref totalflat, ref totalmul);
if (m_spellModTakingSpell != null)
spell = m_spellModTakingSpell;
switch (op) basevalue = (int)(((float)basevalue + totalflat) * totalmul);
{
// 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);
} }
public void ApplySpellMod(SpellInfo spellInfo, SpellModOp op, ref uint basevalue, Spell spell = null) public void ApplySpellMod(SpellInfo spellInfo, SpellModOp op, ref uint basevalue, Spell spell = null)
@@ -2782,94 +2697,9 @@ namespace Game.Entities
float totalmul = 1.0f; float totalmul = 1.0f;
int totalflat = 0; int totalflat = 0;
// Drop charges for triggering spells instead of triggered ones GetSpellModValues(spellInfo, op, spell, basevalue, ref totalflat, ref totalmul);
if (m_spellModTakingSpell != null)
spell = m_spellModTakingSpell;
switch (op) basevalue = (uint)(((float)basevalue + totalflat) * totalmul);
{
// 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);
} }
public void ApplySpellMod(SpellInfo spellInfo, SpellModOp op, ref float basevalue, Spell spell = null) public void ApplySpellMod(SpellInfo spellInfo, SpellModOp op, ref float basevalue, Spell spell = null)
@@ -2877,8 +2707,18 @@ namespace Game.Entities
float totalmul = 1.0f; float totalmul = 1.0f;
int totalflat = 0; int totalflat = 0;
GetSpellModValues(spellInfo, op, spell, basevalue, ref totalflat, ref totalmul);
basevalue = (float)(basevalue + totalflat) * totalmul;
}
public void GetSpellModValues<T>(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 // Drop charges for triggering spells instead of triggered ones
if (m_spellModTakingSpell != null) if (m_spellModTakingSpell)
spell = m_spellModTakingSpell; spell = m_spellModTakingSpell;
switch (op) switch (op)
@@ -2892,7 +2732,7 @@ namespace Game.Entities
if (!IsAffectedBySpellmod(spellInfo, mod, spell)) if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue; continue;
if (basevalue < 10000f && mod.value <= -100) if (baseValue.CompareTo(10000) < 0 && mod.value <= -100)
{ {
modInstantSpell = mod; modInstantSpell = mod;
break; break;
@@ -2902,7 +2742,7 @@ namespace Game.Entities
if (modInstantSpell != null) if (modInstantSpell != null)
{ {
ApplyModToSpell(modInstantSpell, spell); ApplyModToSpell(modInstantSpell, spell);
basevalue = 0f; pct = 0.0f;
return; return;
} }
break; break;
@@ -2926,7 +2766,7 @@ namespace Game.Entities
if (modCritical != null) if (modCritical != null)
{ {
ApplyModToSpell(modCritical, spell); ApplyModToSpell(modCritical, spell);
basevalue = 100f; flat = 100;
return; return;
} }
break; break;
@@ -2935,36 +2775,34 @@ namespace Game.Entities
break; 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)) if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue; continue;
totalflat += mod.value; flat += mod.value;
ApplyModToSpell(mod, spell); 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)) if (!IsAffectedBySpellmod(spellInfo, mod, spell))
continue; continue;
// skip percent mods for null basevalue (most important for spell mods with charges) // skip percent mods for null basevalue (most important for spell mods with charges)
if (basevalue + totalflat == 0) if (baseValue + (dynamic)flat == 0)
continue; continue;
// special case (skip > 10sec spell casts for instant cast setting) // special case (skip > 10sec spell casts for instant cast setting)
if (op == SpellModOp.CastingTime) if (op == SpellModOp.CastingTime)
{ {
if (basevalue >= 10000 && mod.value <= -100) if (baseValue.CompareTo(10000) > 0 && mod.value <= -100)
continue; continue;
} }
totalmul *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); pct *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value);
ApplyModToSpell(mod, spell); ApplyModToSpell(mod, spell);
} }
basevalue = (basevalue + totalflat) * totalmul;
} }
bool IsAffectedBySpellmod(SpellInfo spellInfo, SpellModifier mod, Spell spell) bool IsAffectedBySpellmod(SpellInfo spellInfo, SpellModifier mod, Spell spell)
+138 -106
View File
@@ -2675,19 +2675,22 @@ namespace Game.Spells
return RecoveryTime > CategoryRecoveryTime ? RecoveryTime : CategoryRecoveryTime; return RecoveryTime > CategoryRecoveryTime ? RecoveryTime : CategoryRecoveryTime;
} }
public List<SpellPowerCost> CalcPowerCost(Unit caster, SpellSchoolMask schoolMask, Spell spell = null) SpellPowerCost CalcPowerCost(PowerType powerType, bool optionalCost, Unit caster, SpellSchoolMask schoolMask, Spell spell = null)
{ {
List<SpellPowerCost> costs = new List<SpellPowerCost>(); var spellPowerRecord = PowerCosts.First(spellPowerEntry =>
int healthCost = 0;
foreach (SpellPowerRecord power in PowerCosts)
{ {
if (power == null) return spellPowerEntry.PowerType == powerType;
continue; });
if (power.RequiredAuraSpellID != 0 && !caster.HasAura(power.RequiredAuraSpellID)) if (spellPowerRecord == null)
continue; 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) // Spell drain all exist power on cast (Only paladin lay of Hands)
if (HasAttribute(SpellAttr1.DrainAllPower)) if (HasAttribute(SpellAttr1.DrainAllPower))
@@ -2695,26 +2698,27 @@ namespace Game.Spells
// If power type - health drain all // If power type - health drain all
if (power.PowerType == PowerType.Health) if (power.PowerType == PowerType.Health)
{ {
healthCost = (int)caster.GetHealth(); cost.Power = PowerType.Health;
continue; cost.Amount = (int)caster.GetHealth();
return cost;
} }
// Else drain all power // Else drain all power
if (power.PowerType < PowerType.Max) if (power.PowerType < PowerType.Max)
{ {
SpellPowerCost cost = new SpellPowerCost();
cost.Power = power.PowerType; cost.Power = power.PowerType;
cost.Amount = caster.GetPower(cost.Power); cost.Amount = caster.GetPower(cost.Power);
costs.Add(cost); return cost;
continue;
} }
Log.outError(LogFilter.Spells, "SpellInfo.GetCostDataList: Unknown power type '{0}' in spell {1}", power.PowerType, Id); Log.outError(LogFilter.Spells, $"SpellInfo.CalcPowerCost: Unknown power type '{power.PowerType}' in spell {Id}");
continue; return default;
} }
// Base powerCost // Base powerCost
int powerCost = power.ManaCost; int powerCost = 0;
bool initiallyNegative = powerCost < 0; if (!optionalCost)
{
powerCost = power.ManaCost;
// PCT cost from total amount // PCT cost from total amount
if (power.PowerCostPct != 0) if (power.PowerCostPct != 0)
{ {
@@ -2722,60 +2726,42 @@ namespace Game.Spells
{ {
// health as power used // health as power used
case PowerType.Health: 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); powerCost += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.PowerCostPct);
break; break;
case PowerType.Mana: case PowerType.Mana:
powerCost += (int)MathFunctions.CalculatePct(caster.GetCreateMana(), power.PowerCostPct); powerCost += (int)MathFunctions.CalculatePct(caster.GetCreateMana(), power.PowerCostPct);
break; break;
case PowerType.Rage: case PowerType.AlternatePower:
case PowerType.Focus: Log.outError(LogFilter.Spells, $"SpellInfo.CalcPowerCost: Unknown power type '{power.PowerType}' in spell {Id}");
case PowerType.Energy: return null;
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: 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 PowerTypeRecord powerTypeEntry = Global.DB2Mgr.GetPowerTypeEntry(power.PowerType);
&& aurEff.IsAffectingSpell(this); 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);
}); });
if (optionalCost != 0)
{
int remainingPower = caster.GetPower(power.PowerType) - powerCost;
powerCost += MathFunctions.RoundToInterval(ref remainingPower, 0, optionalCost);
} }
if (power.PowerType != PowerType.Health) bool initiallyNegative = powerCost < 0;
{
// 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) // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost)
if (HasAttribute(SpellAttr4.SpellVsExtendCost)) if (HasAttribute(SpellAttr4.SpellVsExtendCost))
@@ -2793,27 +2779,72 @@ namespace Game.Spells
speed = caster.GetBaseAttackTime(slot); speed = caster.GetBaseAttackTime(slot);
} }
powerCost += (int)(speed / 100); 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 // Apply cost mod by spell
Player modOwner = caster.GetSpellModOwner(); Player modOwner = caster.GetSpellModOwner();
if (modOwner) if (modOwner != null)
{ {
SpellModOp mod = SpellModOp.Max;
switch (power.OrderIndex) switch (power.OrderIndex)
{ {
case 0: case 0:
modOwner.ApplySpellMod(this, SpellModOp.Cost, ref powerCost, spell); mod = SpellModOp.Cost;
break; break;
case 1: case 1:
modOwner.ApplySpellMod(this, SpellModOp.SpellCost2, ref powerCost, spell); mod = SpellModOp.SpellCost2;
break; break;
case 2: case 2:
modOwner.ApplySpellMod(this, SpellModOp.SpellCost3, ref powerCost, spell); mod = SpellModOp.SpellCost3;
break; break;
default: default:
break; 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 (!caster.IsControlledByPlayer() && MathFunctions.fuzzyEq(power.PowerCostPct, 0.0f) && SpellLevel != 0 && power.PowerType == PowerType.Mana)
@@ -2827,57 +2858,58 @@ namespace Game.Spells
} }
} }
// 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) if (power.PowerType == PowerType.Mana)
powerCost = (int)((float)powerCost * (1.0f + caster.m_unitData.ManaCostMultiplier)); 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 // power cost cannot become negative if initially positive
if (initiallyNegative != (powerCost < 0)) if (initiallyNegative != (powerCost < 0))
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.Power = power.PowerType;
cost.Amount = powerCost; cost.Amount = powerCost;
costs.Add(cost); return cost;
}
} }
if (healthCost > 0) public List<SpellPowerCost> CalcPowerCost(Unit caster, SpellSchoolMask schoolMask, Spell spell = null)
{ {
SpellPowerCost cost = new SpellPowerCost(); List<SpellPowerCost> costs = new List<SpellPowerCost>();
cost.Power = PowerType.Health;
cost.Amount = healthCost; SpellPowerCost getOrCreatePowerCost(PowerType powerType)
{
var itr = costs.Find(cost =>
{
return cost.Power == powerType;
});
if (itr != null)
return itr;
SpellPowerCost cost = new();
cost.Power = powerType;
cost.Amount = 0;
costs.Add(cost); costs.Add(cost);
return costs.Last();
}
foreach (SpellPowerRecord power in PowerCosts)
{
if (power == null)
continue;
if (power.RequiredAuraSpellID != 0 && !caster.HasAura(power.RequiredAuraSpellID))
continue;
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)
{
SpellPowerCost cost1 = getOrCreatePowerCost(optionalCost.Power);
int remainingPower = caster.GetPower(optionalCost.Power) - cost1.Amount;
if (remainingPower > 0)
cost1.Amount += Math.Min(optionalCost.Amount, remainingPower);
}
} }
return costs; return costs;