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)
{
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;
break;
+400 -212
View File
@@ -3251,22 +3251,42 @@ namespace Game.Spells
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
if (HasAttribute(SpellAttr0.Negative1))
SpellEffectInfo effect = spellInfo.GetEffect(effIndex);
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;
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:
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 61988: // Divine Shield exclude aura
return false;
@@ -3278,253 +3298,421 @@ namespace Game.Spells
case 61834: // Manabonked! (minigob)
case 73523: // Rigor Mortis
return true;
default:
break;
}
break;
case SpellFamilyNames.Mage:
// Arcane Missiles
if (SpellFamilyFlags[0] == 0x00000800)
case SpellFamilyNames.Warrior:
// Slam, Execute
if ((spellInfo.SpellFamilyFlags[0] & 0x20200000) != 0)
return false;
break;
case SpellFamilyNames.Priest:
switch (Id)
{
case 64844: // Divine Hymn
case 64904: // Hymn of Hope
case 47585: // Dispersion
return true;
}
break;
case SpellFamilyNames.Rogue:
switch (Id)
switch (spellInfo.Id)
{
// Envenom must be considered as a positive effect even though it deals damage
case 32645: // Envenom
case 32645: // Envenom
return true;
default:
break;
}
break;
default:
break;
}
if (Mechanic == Mechanics.ImmuneShield)
return true;
switch (spellInfo.Mechanic)
{
case Mechanics.ImmuneShield:
return true;
default:
break;
}
// Special case: effects which determine positivity of whole spell
foreach (SpellEffectInfo effectInfo in _effects)
if (effectInfo != null && effectInfo.IsAura() && effectInfo.ApplyAuraName == AuraType.ModStealth)
return true;
SpellEffectInfo effect = GetEffect(effIndex);
if (effect != null)
if (spellInfo.HasAttribute(SpellAttr1.DontRefreshDurationOnRecast))
{
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.LearnSpell:
case SpellEffectName.SkillStep:
case SpellEffectName.HealPct:
case SpellEffectName.EnergizePct:
return true;
case SpellEffectName.ApplyAreaAuraEnemy:
return false;
case SpellEffectName.Instakill:
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
case SpellEffectName.ApplyAura:
case SpellEffectName.ApplyAreaAuraFriend:
if (otherEffect.IsAura())
{
switch (otherEffect.ApplyAuraName)
{
switch (effect.ApplyAuraName)
{
case AuraType.ModDamageDone: // dependent from bas point sign (negative . negative)
case AuraType.ModStat:
case AuraType.ModSkill:
case AuraType.ModSkill2:
case AuraType.ModDodgePercent:
case AuraType.ModHealingPct:
case AuraType.ModHealingDone:
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;
}
}
case AuraType.ModStealth:
case AuraType.ModUnattackable:
return true;
case AuraType.SchoolHealAbsorb:
case AuraType.ChannelDeathItem:
case AuraType.Empathy:
return false;
default:
break;
}
}
}
if (effIndex == 0 && !more)
return false; // but all single stun aura spells is negative
break;
case AuraType.ModPacifySilence:
if (Id == 24740) // Wisp Costume
return true;
switch (effect.Effect)
{
case SpellEffectName.WeaponDamage:
case SpellEffectName.WeaponDamageNoSchool:
case SpellEffectName.NormalizedWeaponDmg:
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;
case AuraType.ModRoot:
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;
default:
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)
return false;
// but not this if this first effect (didn't find better check)
if (HasAttribute(SpellAttr0.Negative1) && effIndex == 0)
return false;
break;
case AuraType.MechanicImmunity:
}
}
break;
case SpellEffectName.Threat:
case SpellEffectName.ModifyThreatPercent:
// check targets AND basepoints
if (!_isPositiveTarget(spellInfo, effIndex) && bp > 0)
return false;
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
switch ((Mechanics)effect.MiscValue)
// negative targets of main spell return early
foreach (SpellEffectInfo spellTriggeredEffect in spellTriggeredProto.GetEffects())
{
case Mechanics.Bandage:
case Mechanics.Shield:
case Mechanics.Mount:
case Mechanics.Invulnerability:
if (spellTriggeredEffect == null)
continue;
// 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;
}
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;
}
}
// non-positive targets
if (!_IsPositiveTarget(effect.TargetA.GetTarget(), effect.TargetB.GetTarget()))
return false;
// negative spell if triggered spell is negative
if (!deep && effect.ApplyAuraName == 0 && effect.TriggerSpell != 0)
{
SpellInfo spellTriggeredProto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell, Difficulty);
if (spellTriggeredProto != null)
if (!spellTriggeredProto._IsPositiveSpell())
case AuraType.ModStun:
case AuraType.Transform:
case AuraType.ModDecreaseSpeed:
case AuraType.ModFear:
case AuraType.ModTaunt:
// special auras: they may have non negative target but still need to be marked as debuff
// checked again after all effects (SpellInfo::_InitializeSpellPositivity)
case AuraType.ModPacify:
case AuraType.ModPacifySilence:
case AuraType.ModDisarm:
case AuraType.ModDisarmOffhand:
case AuraType.ModDisarmRanged:
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;
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
return true;
}
public bool _IsPositiveSpell()
public void InitializeSpellPositivity()
{
// 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.
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
if (!_IsPositiveEffect(i, true))
return false;
return true;
}
List<Tuple<SpellInfo, uint>> visited = new();
bool _IsPositiveTarget(Targets targetA, Targets targetB)
{
// non-positive targets
switch (targetA)
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
if (!_isPositiveEffectImpl(this, i, visited))
NegativeEffects[i] = true;
// additional checks after effects marked
foreach (SpellEffectInfo effect in GetEffects())
{
case Framework.Constants.Targets.UnitNearbyEnemy:
case Framework.Constants.Targets.UnitTargetEnemy:
case Framework.Constants.Targets.UnitSrcAreaEnemy:
case Framework.Constants.Targets.UnitDestAreaEnemy:
case Framework.Constants.Targets.UnitConeEnemy24:
case Framework.Constants.Targets.UnitConeEnemy104:
case Framework.Constants.Targets.DestDynobjEnemy:
case Framework.Constants.Targets.DestTargetEnemy:
return false;
default:
break;
if (effect == null || !effect.IsEffect() || !IsPositiveEffect(effect.EffectIndex))
continue;
switch (effect.ApplyAuraName)
{
// has other non positive effect?
// then it should be marked negative despite of targets (ex 8510, 8511, 8893, 10267)
case AuraType.Dummy:
case AuraType.ModStun:
case AuraType.ModFear:
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()
+2 -4
View File
@@ -2615,7 +2615,6 @@ namespace Game.Entities
foreach (SpellInfo spellInfo in spells)
{
// TODO: validate attributes
if (attributes.HasAnyFlag((uint)SpellCustomAttributes.ShareDamage))
{
if (!spellInfo.HasEffect(SpellEffectName.SchoolDamage))
@@ -2763,9 +2762,6 @@ namespace Game.Entities
break;
}
}
if (!spellInfo._IsPositiveEffect(effect.EffectIndex, false))
spellInfo.NegativeEffects[(int)effect.EffectIndex] = true;
}
// spells ignoring hit result should not be binary
@@ -2854,6 +2850,8 @@ namespace Game.Entities
spellInfo.AttributesCu |= SpellCustomAttributes.SchoolmaskNormalWithMagic;
}
spellInfo.InitializeSpellPositivity();
if (talentSpells.Contains(spellInfo.Id))
spellInfo.AttributesCu |= SpellCustomAttributes.IsTalent;