Core/Spells: rework part 1: Improved positive detection logic

Port From (https://github.com/TrinityCore/TrinityCore/commit/1e1415a49128d034c8d48aa8cbb5d157200371b0)
This commit is contained in:
hondacrx
2021-08-28 13:56:32 -04:00
parent e35094f0a3
commit 9123dec4b9
3 changed files with 404 additions and 217 deletions
+2 -1
View File
@@ -2281,7 +2281,8 @@ namespace Game.Spells
{ {
for (uint i = 0; i < SpellConst.MaxEffects; ++i) for (uint i = 0; i < SpellConst.MaxEffects; ++i)
{ {
if ((effectMask & (1 << (int)i)) != 0 && !m_spellInfo.IsPositiveEffect(i)) // mod duration only for effects applying aura!
if ((aura_effmask & (1 << (int)i)) != 0 && !m_spellInfo.IsPositiveEffect(i))
{ {
positive = false; positive = false;
break; break;
+400 -212
View File
@@ -3251,22 +3251,42 @@ namespace Game.Spells
ExplicitTargetMask = (uint)targetMask; ExplicitTargetMask = (uint)targetMask;
} }
public bool _IsPositiveEffect(uint effIndex, bool deep) public bool _isPositiveTarget(SpellInfo spellInfo, uint effIndex)
{ {
// not found a single positive spell with this attribute SpellEffectInfo effect = spellInfo.GetEffect(effIndex);
if (HasAttribute(SpellAttr0.Negative1)) if (effect == null || !effect.IsEffect())
return true;
return (effect.TargetA.GetCheckType() != SpellTargetCheckTypes.Enemy &&
effect.TargetB.GetCheckType() != SpellTargetCheckTypes.Enemy);
}
bool _isPositiveEffectImpl(SpellInfo spellInfo, uint effIndex, List<Tuple<SpellInfo, uint>> visited)
{
SpellEffectInfo effect = spellInfo.GetEffect(effIndex);
if (effect == null || !effect.IsEffect())
return true;
// attribute may be already set in DB
if (!spellInfo.IsPositiveEffect(effIndex))
return false; return false;
switch (SpellFamilyName) // passive auras like talents are all positive
if (spellInfo.IsPassive())
return true;
// not found a single positive spell with this attribute
if (spellInfo.HasAttribute(SpellAttr0.Negative1))
return false;
visited.Add(Tuple.Create(spellInfo, effIndex));
int bp = effect.CalcValue();
switch (spellInfo.SpellFamilyName)
{ {
case SpellFamilyNames.Generic: case SpellFamilyNames.Generic:
switch (Id) switch (spellInfo.Id)
{ {
case 29214: // Wrath of the Plaguebringer
case 34700: // Allergic Reaction
case 41914: // Parasitic Shadowfiend (Illidan)
case 41917: // Parasitic Shadowfiend (Illidan)
case 54836: // Wrath of the Plaguebringer
case 61987: // Avenging Wrath Marker case 61987: // Avenging Wrath Marker
case 61988: // Divine Shield exclude aura case 61988: // Divine Shield exclude aura
return false; return false;
@@ -3278,253 +3298,421 @@ namespace Game.Spells
case 61834: // Manabonked! (minigob) case 61834: // Manabonked! (minigob)
case 73523: // Rigor Mortis case 73523: // Rigor Mortis
return true; return true;
default:
break;
} }
break; break;
case SpellFamilyNames.Mage: case SpellFamilyNames.Warrior:
// Arcane Missiles // Slam, Execute
if (SpellFamilyFlags[0] == 0x00000800) if ((spellInfo.SpellFamilyFlags[0] & 0x20200000) != 0)
return false; return false;
break; break;
case SpellFamilyNames.Priest:
switch (Id)
{
case 64844: // Divine Hymn
case 64904: // Hymn of Hope
case 47585: // Dispersion
return true;
}
break;
case SpellFamilyNames.Rogue: case SpellFamilyNames.Rogue:
switch (Id) switch (spellInfo.Id)
{ {
// Envenom must be considered as a positive effect even though it deals damage // Envenom must be considered as a positive effect even though it deals damage
case 32645: // Envenom case 32645: // Envenom
return true; return true;
default:
break;
} }
break; break;
default:
break;
} }
if (Mechanic == Mechanics.ImmuneShield) switch (spellInfo.Mechanic)
return true; {
case Mechanics.ImmuneShield:
return true;
default:
break;
}
// Special case: effects which determine positivity of whole spell // Special case: effects which determine positivity of whole spell
foreach (SpellEffectInfo effectInfo in _effects) if (spellInfo.HasAttribute(SpellAttr1.DontRefreshDurationOnRecast))
if (effectInfo != null && effectInfo.IsAura() && effectInfo.ApplyAuraName == AuraType.ModStealth)
return true;
SpellEffectInfo effect = GetEffect(effIndex);
if (effect != null)
{ {
switch (effect.Effect) // check for targets, there seems to be an assortment of dummy triggering spells that should be negative
foreach (SpellEffectInfo otherEffect in spellInfo.GetEffects())
if (otherEffect != null && !_isPositiveTarget(spellInfo, otherEffect.EffectIndex))
return false;
}
foreach (SpellEffectInfo otherEffect in spellInfo.GetEffects())
{
if (otherEffect == null)
continue;
switch (otherEffect.Effect)
{ {
case SpellEffectName.Dummy:
// some explicitly required dummy effect sets
switch (Id)
{
case 28441:
return false; // AB Effect 000
}
break;
// always positive effects (check before target checks that provided non-positive result in some case for positive effects)
case SpellEffectName.Heal: case SpellEffectName.Heal:
case SpellEffectName.LearnSpell: case SpellEffectName.LearnSpell:
case SpellEffectName.SkillStep: case SpellEffectName.SkillStep:
case SpellEffectName.HealPct: case SpellEffectName.HealPct:
case SpellEffectName.EnergizePct: case SpellEffectName.EnergizePct:
return true; return true;
case SpellEffectName.ApplyAreaAuraEnemy: case SpellEffectName.Instakill:
return false; if (otherEffect.EffectIndex != effIndex && // for spells like 38044: instakill effect is negative but auras on target must count as buff
otherEffect.TargetA.GetTarget() == effect.TargetA.GetTarget() &&
otherEffect.TargetB.GetTarget() == effect.TargetB.GetTarget())
return false;
break;
default:
break;
}
// non-positive aura use if (otherEffect.IsAura())
case SpellEffectName.ApplyAura: {
case SpellEffectName.ApplyAreaAuraFriend: switch (otherEffect.ApplyAuraName)
{ {
switch (effect.ApplyAuraName) case AuraType.ModStealth:
{ case AuraType.ModUnattackable:
case AuraType.ModDamageDone: // dependent from bas point sign (negative . negative) return true;
case AuraType.ModStat: case AuraType.SchoolHealAbsorb:
case AuraType.ModSkill: case AuraType.ChannelDeathItem:
case AuraType.ModSkill2: case AuraType.Empathy:
case AuraType.ModDodgePercent: return false;
case AuraType.ModHealingPct: default:
case AuraType.ModHealingDone: break;
case AuraType.ModHealingDonePercent: }
if (effect.CalcValue() < 0) }
return false; }
break;
case AuraType.ModDamageTaken: // dependent from bas point sign (positive . negative)
if (effect.CalcValue() > 0)
return false;
break;
case AuraType.ModCritPct:
case AuraType.ModSpellCritChance:
if (effect.CalcValue() > 0)
return true; // some expected positive spells have SPELL_ATTR1_NEGATIVE
break;
case AuraType.AddTargetTrigger:
return true;
case AuraType.PeriodicTriggerSpellWithValue:
case AuraType.PeriodicTriggerSpell:
if (!deep)
{
var spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell, Difficulty);
if (spellTriggeredProto != null)
{
// negative targets of main spell return early
foreach (SpellEffectInfo eff in spellTriggeredProto._effects)
{
if (eff == null || eff.Effect == 0)
continue;
// if non-positive trigger cast targeted to positive target this main cast is non-positive
// this will place this spell auras as debuffs
if (_IsPositiveTarget(eff.TargetA.GetTarget(), eff.TargetB.GetTarget()) && !spellTriggeredProto._IsPositiveEffect(eff.EffectIndex, true))
return false;
}
}
}
break;
case AuraType.ProcTriggerSpell:
// many positive auras have negative triggered spells at damage for example and this not make it negative (it can be canceled for example)
break;
case AuraType.ModStun: //have positive and negative spells, we can't sort its correctly at this moment.
bool more = false;
foreach (SpellEffectInfo eff in _effects)
{
if (eff != null && eff.EffectIndex != 0)
{
more = true;
break;
}
}
if (effIndex == 0 && !more) switch (effect.Effect)
return false; // but all single stun aura spells is negative {
break; case SpellEffectName.WeaponDamage:
case AuraType.ModPacifySilence: case SpellEffectName.WeaponDamageNoSchool:
if (Id == 24740) // Wisp Costume case SpellEffectName.NormalizedWeaponDmg:
return true; case SpellEffectName.WeaponPercentDamage:
case SpellEffectName.SchoolDamage:
case SpellEffectName.EnvironmentalDamage:
case SpellEffectName.HealthLeech:
case SpellEffectName.Instakill:
case SpellEffectName.PowerDrain:
case SpellEffectName.StealBeneficialBuff:
case SpellEffectName.InterruptCast:
case SpellEffectName.Pickpocket:
case SpellEffectName.GameObjectDamage:
case SpellEffectName.DurabilityDamage:
case SpellEffectName.DurabilityDamagePct:
case SpellEffectName.ApplyAreaAuraEnemy:
case SpellEffectName.Tamecreature:
case SpellEffectName.Distract:
return false;
case SpellEffectName.Energize:
case SpellEffectName.EnergizePct:
case SpellEffectName.HealPct:
case SpellEffectName.HealMaxHealth:
case SpellEffectName.HealMechanical:
return true;
case SpellEffectName.KnockBack:
case SpellEffectName.Charge:
case SpellEffectName.PersistentAreaAura:
case SpellEffectName.AttackMe:
case SpellEffectName.PowerBurn:
// check targets
if (!_isPositiveTarget(spellInfo, effIndex))
return false;
break;
case SpellEffectName.Dispel:
// non-positive dispel
switch ((DispelType)effect.MiscValue)
{
case DispelType.Stealth:
case DispelType.Invisibility:
case DispelType.Enrage:
return false;
default:
break;
}
// also check targets
if (!_isPositiveTarget(spellInfo, effIndex))
return false;
break;
case SpellEffectName.DispelMechanic:
if (!_isPositiveTarget(spellInfo, effIndex))
{
// non-positive mechanic dispel on negative target
switch ((Mechanics)effect.MiscValue)
{
case Mechanics.Bandage:
case Mechanics.Shield:
case Mechanics.Mount:
case Mechanics.Invulnerability:
return false; return false;
case AuraType.ModRoot: default:
case AuraType.ModRoot2:
case AuraType.ModSilence:
case AuraType.Ghost:
case AuraType.PeriodicLeech:
case AuraType.ModStalked:
case AuraType.PeriodicDamagePercent:
case AuraType.PreventResurrection:
case AuraType.Empathy:
return false;
case AuraType.PeriodicDamage: // used in positive spells also.
// part of negative spell if casted at self (prevent cancel)
if (effect.TargetA.GetTarget() == Framework.Constants.Targets.UnitCaster)
return false;
break; break;
case AuraType.ModDecreaseSpeed: // used in positive spells also }
// part of positive spell if casted at self }
if (effect.TargetA.GetTarget() != Framework.Constants.Targets.UnitCaster) break;
return false; case SpellEffectName.Threat:
// but not this if this first effect (didn't find better check) case SpellEffectName.ModifyThreatPercent:
if (HasAttribute(SpellAttr0.Negative1) && effIndex == 0) // check targets AND basepoints
return false; if (!_isPositiveTarget(spellInfo, effIndex) && bp > 0)
break; return false;
case AuraType.MechanicImmunity: break;
default:
break;
}
if (effect.IsAura())
{
// non-positive aura use
switch (effect.ApplyAuraName)
{
case AuraType.ModDamageDone: // dependent from basepoint sign (negative . negative)
case AuraType.ModStat:
case AuraType.ModSkill:
case AuraType.ModSkill2:
case AuraType.ModDodgePercent:
case AuraType.ModHealingPct:
case AuraType.ModHealingDone:
case AuraType.ModDamageDoneCreature:
case AuraType.ObsModHealth:
case AuraType.ObsModPower:
case AuraType.ModCritPct:
case AuraType.ModHitChance:
case AuraType.ModSpellHitChance:
case AuraType.ModSpellCritChance:
case AuraType.ModRangedHaste:
case AuraType.ModCastingSpeedNotStack:
case AuraType.HasteSpells:
case AuraType.ModResistance:
case AuraType.ModRecoveryRateBySpellLabel:
case AuraType.ModDetectRange:
case AuraType.ModIncreaseHealthPercent:
case AuraType.ModTotalStatPercentage:
case AuraType.ModIncreaseSwimSpeed:
if (bp < 0)
return false;
break;
case AuraType.ModAttackspeed: // some buffs have negative bp, check both target and bp
case AuraType.ModMeleeHaste:
case AuraType.ModResistancePct:
case AuraType.ModRating:
case AuraType.ModAttackPower:
case AuraType.ModRangedAttackPower:
case AuraType.ModDamagePercentDone:
if (!_isPositiveTarget(spellInfo, effIndex) && bp < 0)
return false;
break;
case AuraType.ModDamageTaken: // dependent from basepoint sign (positive . negative)
case AuraType.ModMeleeDamageTaken:
case AuraType.ModMeleeDamageTakenPct:
case AuraType.ModCooldown:
case AuraType.ModChargeCooldown:
case AuraType.ModPowerCostSchool:
case AuraType.ModPowerCostSchoolPct:
if (bp > 0)
return false;
break;
case AuraType.ModDamagePercentTaken: // check targets and basepoints (ex Recklessness)
if (!_isPositiveTarget(spellInfo, effIndex) && bp > 0)
return false;
break;
case AuraType.AddTargetTrigger:
return true;
case AuraType.PeriodicTriggerSpellWithValue:
case AuraType.PeriodicTriggerSpell:
if (!_isPositiveTarget(spellInfo, effIndex))
{
SpellInfo spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell, spellInfo.Difficulty);
if (spellTriggeredProto != null)
{ {
// non-positive immunities // negative targets of main spell return early
switch ((Mechanics)effect.MiscValue) foreach (SpellEffectInfo spellTriggeredEffect in spellTriggeredProto.GetEffects())
{ {
case Mechanics.Bandage: if (spellTriggeredEffect == null)
case Mechanics.Shield: continue;
case Mechanics.Mount:
case Mechanics.Invulnerability: // already seen this
if (visited.Contains(Tuple.Create(spellTriggeredProto, spellTriggeredEffect.EffectIndex)))
continue;
if (!spellTriggeredEffect.IsEffect())
continue;
// if non-positive trigger cast targeted to positive target this main cast is non-positive
// this will place this spell auras as debuffs
if (_isPositiveTarget(spellTriggeredProto, spellTriggeredEffect.EffectIndex) && !_isPositiveEffectImpl(spellTriggeredProto, spellTriggeredEffect.EffectIndex, visited))
return false; return false;
} }
break;
}
case AuraType.AddFlatModifier: // mods
case AuraType.AddPctModifier:
{
// non-positive mods
switch ((SpellModOp)effect.MiscValue)
{
case SpellModOp.PowerCost0: // dependent from bas point sign (negative . positive)
case SpellModOp.PowerCost1:
case SpellModOp.PowerCost2:
if (effect.CalcValue() > 0)
{
if (!deep)
{
bool negative = true;
for (uint i = 0; i < SpellConst.MaxEffects; ++i)
{
if (i != effIndex)
{
if (_IsPositiveEffect(i, true))
{
negative = false;
break;
}
}
}
if (negative)
return false;
}
}
break;
}
break;
} }
} }
break; break;
} case AuraType.ModStun:
} case AuraType.Transform:
case AuraType.ModDecreaseSpeed:
// non-positive targets case AuraType.ModFear:
if (!_IsPositiveTarget(effect.TargetA.GetTarget(), effect.TargetB.GetTarget())) case AuraType.ModTaunt:
return false; // special auras: they may have non negative target but still need to be marked as debuff
// checked again after all effects (SpellInfo::_InitializeSpellPositivity)
// negative spell if triggered spell is negative case AuraType.ModPacify:
if (!deep && effect.ApplyAuraName == 0 && effect.TriggerSpell != 0) case AuraType.ModPacifySilence:
{ case AuraType.ModDisarm:
SpellInfo spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell, Difficulty); case AuraType.ModDisarmOffhand:
if (spellTriggeredProto != null) case AuraType.ModDisarmRanged:
if (!spellTriggeredProto._IsPositiveSpell()) case AuraType.ModCharm:
case AuraType.AoeCharm:
case AuraType.ModPossess:
case AuraType.ModLanguage:
case AuraType.DamageShield:
case AuraType.ProcTriggerSpell:
case AuraType.ModAttackerMeleeHitChance:
case AuraType.ModAttackerRangedHitChance:
case AuraType.ModAttackerSpellHitChance:
case AuraType.ModAttackerMeleeCritChance:
case AuraType.ModAttackerRangedCritChance:
case AuraType.ModAttackerSpellAndWeaponCritChance:
// have positive and negative spells, check target
if (!_isPositiveTarget(spellInfo, effIndex))
return false; return false;
break;
case AuraType.ModConfuse:
case AuraType.ModRoot:
case AuraType.ModRoot2:
case AuraType.ModSilence:
case AuraType.ModDetaunt:
case AuraType.Ghost:
case AuraType.ModLeech:
case AuraType.PeriodicManaLeech:
case AuraType.ModStalked:
case AuraType.PreventResurrection:
case AuraType.PeriodicDamage:
case AuraType.PeriodicWeaponPercentDamage:
case AuraType.PeriodicDamagePercent:
case AuraType.MeleeAttackPowerAttackerBonus:
case AuraType.RangedAttackPowerAttackerBonus:
return false;
case AuraType.MechanicImmunity:
{
// non-positive immunities
switch ((Mechanics)effect.MiscValue)
{
case Mechanics.Bandage:
case Mechanics.Shield:
case Mechanics.Mount:
case Mechanics.Invulnerability:
return false;
default:
break;
}
break;
}
case AuraType.AddFlatModifier: // mods
case AuraType.AddPctModifier:
case AuraType.AddFlatModifierBySpellLabel:
case AuraType.AddPctModifierBySpellLabel:
{
switch ((SpellModOp)effect.MiscValue)
{
case SpellModOp.ChangeCastTime: // dependent from basepoint sign (positive . negative)
case SpellModOp.Period:
case SpellModOp.PowerCostOnMiss:
case SpellModOp.StartCooldown:
if (bp > 0)
return false;
break;
case SpellModOp.Cooldown:
case SpellModOp.PowerCost0:
case SpellModOp.PowerCost1:
case SpellModOp.PowerCost2:
if (!spellInfo.IsPositive() && bp > 0) // dependent on prev effects too (ex Arcane Power)
return false;
break;
case SpellModOp.PointsIndex0: // always positive
case SpellModOp.PointsIndex1:
case SpellModOp.PointsIndex2:
case SpellModOp.PointsIndex3:
case SpellModOp.PointsIndex4:
case SpellModOp.Points:
case SpellModOp.Hate:
case SpellModOp.ChainAmplitude:
case SpellModOp.Amplitude:
return true;
case SpellModOp.Duration:
case SpellModOp.CritChance:
case SpellModOp.HealingAndDamage:
case SpellModOp.ChainTargets:
if (!spellInfo.IsPositive() && bp < 0) // dependent on prev effects too
return false;
break;
default: // dependent from basepoint sign (negative . negative)
if (bp < 0)
return false;
break;
}
break;
}
default:
break;
} }
} }
// negative spell if triggered spell is negative
if (effect.ApplyAuraName == 0 && effect.TriggerSpell != 0)
{
SpellInfo spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell, spellInfo.Difficulty);
if (spellTriggeredProto != null)
{
// spells with at least one negative effect are considered negative
// some self-applied spells have negative effects but in self casting case negative check ignored.
foreach (SpellEffectInfo spellTriggeredEffect in spellTriggeredProto.GetEffects())
{
if (spellTriggeredEffect == null)
continue;
// already seen this
if (visited.Contains(Tuple.Create(spellTriggeredProto, spellTriggeredEffect.EffectIndex)))
continue;
if (!spellTriggeredEffect.IsEffect())
continue;
if (!_isPositiveEffectImpl(spellTriggeredProto, spellTriggeredEffect.EffectIndex, visited))
return false;
}
}
}
// ok, positive // ok, positive
return true; return true;
} }
public bool _IsPositiveSpell() public void InitializeSpellPositivity()
{ {
// spells with at least one negative effect are considered negative List<Tuple<SpellInfo, uint>> visited = new();
// some self-applied spells have negative effects but in self casting case negative check ignored.
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
if (!_IsPositiveEffect(i, true))
return false;
return true;
}
bool _IsPositiveTarget(Targets targetA, Targets targetB) for (byte i = 0; i < SpellConst.MaxEffects; ++i)
{ if (!_isPositiveEffectImpl(this, i, visited))
// non-positive targets NegativeEffects[i] = true;
switch (targetA)
// additional checks after effects marked
foreach (SpellEffectInfo effect in GetEffects())
{ {
case Framework.Constants.Targets.UnitNearbyEnemy: if (effect == null || !effect.IsEffect() || !IsPositiveEffect(effect.EffectIndex))
case Framework.Constants.Targets.UnitTargetEnemy: continue;
case Framework.Constants.Targets.UnitSrcAreaEnemy:
case Framework.Constants.Targets.UnitDestAreaEnemy: switch (effect.ApplyAuraName)
case Framework.Constants.Targets.UnitConeEnemy24: {
case Framework.Constants.Targets.UnitConeEnemy104: // has other non positive effect?
case Framework.Constants.Targets.DestDynobjEnemy: // then it should be marked negative despite of targets (ex 8510, 8511, 8893, 10267)
case Framework.Constants.Targets.DestTargetEnemy: case AuraType.Dummy:
return false; case AuraType.ModStun:
default: case AuraType.ModFear:
break; case AuraType.ModTaunt:
case AuraType.Transform:
case AuraType.ModAttackspeed:
case AuraType.ModDecreaseSpeed:
if (!IsPositive())
NegativeEffects[(int)effect.EffectIndex] = true;
break;
default:
break;
}
} }
if (targetB != 0)
return _IsPositiveTarget(targetB, 0);
return true;
} }
public void _UnloadImplicitTargetConditionLists() public void _UnloadImplicitTargetConditionLists()
+2 -4
View File
@@ -2615,7 +2615,6 @@ namespace Game.Entities
foreach (SpellInfo spellInfo in spells) foreach (SpellInfo spellInfo in spells)
{ {
// TODO: validate attributes
if (attributes.HasAnyFlag((uint)SpellCustomAttributes.ShareDamage)) if (attributes.HasAnyFlag((uint)SpellCustomAttributes.ShareDamage))
{ {
if (!spellInfo.HasEffect(SpellEffectName.SchoolDamage)) if (!spellInfo.HasEffect(SpellEffectName.SchoolDamage))
@@ -2763,9 +2762,6 @@ namespace Game.Entities
break; break;
} }
} }
if (!spellInfo._IsPositiveEffect(effect.EffectIndex, false))
spellInfo.NegativeEffects[(int)effect.EffectIndex] = true;
} }
// spells ignoring hit result should not be binary // spells ignoring hit result should not be binary
@@ -2854,6 +2850,8 @@ namespace Game.Entities
spellInfo.AttributesCu |= SpellCustomAttributes.SchoolmaskNormalWithMagic; spellInfo.AttributesCu |= SpellCustomAttributes.SchoolmaskNormalWithMagic;
} }
spellInfo.InitializeSpellPositivity();
if (talentSpells.Contains(spellInfo.Id)) if (talentSpells.Contains(spellInfo.Id))
spellInfo.AttributesCu |= SpellCustomAttributes.IsTalent; spellInfo.AttributesCu |= SpellCustomAttributes.IsTalent;