Пример #1
0
        public void UseBushidoStance()
        {
            Spell spell = null;

            if (this.m_Mobile.Debug)
            {
                this.m_Mobile.Say(2117, "Using a samurai stance");
            }

            BaseWeapon weapon = this.m_Mobile.Weapon as BaseWeapon;

            if (weapon == null)
            {
                return;
            }

            int whichone = Utility.RandomMinMax(1, 3);

            if (whichone == 3 && this.m_Mobile.Skills[SkillName.Bushido].Value >= 60.0)
            {
                spell = new Evasion(this.m_Mobile, null);
            }
            else if (whichone >= 2 && this.m_Mobile.Skills[SkillName.Bushido].Value >= 40.0)
            {
                spell = new CounterAttack(this.m_Mobile, null);
            }
            else if (whichone >= 1 && this.m_Mobile.Skills[SkillName.Bushido].Value >= 25.0)
            {
                spell = new Confidence(this.m_Mobile, null);
            }

            if (spell != null)
            {
                spell.Cast();
            }
        }
Пример #2
0
        public virtual double GetDamageScalar(Mobile target)
        {
            double scalar = 1.0;

            if (target == null)
            {
                return(scalar);
            }

            if (!Core.AOS)             //EvalInt stuff for AoS is handled elsewhere
            {
                double casterEI = m_Caster.Skills[DamageSkill].Value;
                double targetRS = target.Skills[SkillName.MagicResist].Value;

                /*
                 * if( Core.AOS )
                 * targetRS = 0;
                 */

                //m_Caster.CheckSkill( DamageSkill, 0.0, 120.0 );

                if (casterEI > targetRS)
                {
                    scalar = (1.0 + ((casterEI - targetRS) / 500.0));
                }
                else
                {
                    scalar = (1.0 + ((casterEI - targetRS) / 200.0));
                }

                // magery damage bonus, -25% at 0 skill, +0% at 100 skill, +5% at 120 skill
                scalar += (m_Caster.Skills[CastSkill].Value - 100.0) / 400.0;

                if (!target.Player && !target.Body.IsHuman /*&& !Core.AOS*/)
                {
                    scalar *= 2.0;                     // Double magery damage to monsters/animals if not AOS
                }
            }

            if (target is BaseCreature)
            {
                ((BaseCreature)target).AlterDamageScalarFrom(m_Caster, ref scalar);
            }

            if (m_Caster is BaseCreature)
            {
                ((BaseCreature)m_Caster).AlterDamageScalarTo(target, ref scalar);
            }

            if (Core.SE)
            {
                scalar *= GetSlayerDamageScalar(target);
            }

            target.Region.SpellDamageScalar(m_Caster, target, ref scalar);

            if (Evasion.CheckSpellEvasion(target))             //Only single target spells an be evaded
            {
                scalar = 0;
            }

            return(scalar);
        }
Пример #3
0
 public void BushidoPower()
 {
     if (0.5 > Utility.RandomDouble() && !(Confidence.IsConfident(m_Mobile) || CounterAttack.IsCountering(m_Mobile) || Evasion.IsEvading(m_Mobile)))
     {
         UseBushidoStance();
     }
     else
     {
         UseBushidoMove();
     }
 }
Пример #4
0
 string BuildEvasionCounts(Evasion evadedCounts)
 {
     return(string.Join(Environment.NewLine, evadedCounts.Counts.Select(pair => pair.Key + ": " + pair.Value)));
 }
Пример #5
0
 protected override IEnumerable <MatcherData> CreateCollection() =>
 new FormAndStatMatcherCollection(_modifierBuilder, ValueFactory)
 {
     // attributes
     // offense
     // - damage
     {
         @"adds # to # ({DamageTypeMatchers}) damage",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]), Reference.AsDamageType.Damage.WithHits
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to unarmed attacks",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Attack),
         And(Not(MainHand.HasItem), With(Keyword.Melee))
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to spells",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Spell)
     },
     {
         @"# to # additional ({DamageTypeMatchers}) damage",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]), Reference.AsDamageType.Damage.WithHits
     },
     {
         @"adds # maximum ({DamageTypeMatchers}) damage",
         BaseAdd, Value.MaximumOnly, Reference.AsDamageType.Damage.WithHits
     },
     { "deal no ({DamageTypeMatchers}) damage", TotalOverride, 0, Reference.AsDamageType.Damage },
     // - conversion and gain
     {
         "(gain )?#% of ({DamageTypeMatchers}) damage (gained |added )?as (extra )?({DamageTypeMatchers}) damage",
         BaseAdd, Value, References[0].AsDamageType.Damage.WithHitsAndAilments
         .GainAs(References[1].AsDamageType.Damage.WithHitsAndAilments)
     },
     {
         "gain #% of ({DamageTypeMatchers}) damage as extra damage of a random element",
         BaseAdd, Value, Reference.AsDamageType.Damage.WithHitsAndAilments
         .GainAs(RandomElement.Damage.WithHitsAndAilments)
     },
     {
         "gain #% of wand ({DamageTypeMatchers}) damage as extra ({DamageTypeMatchers}) damage",
         BaseAdd, Value,
         References[0].AsDamageType.Damage.With(AttackDamageHand.MainHand)
         .GainAs(References[1].AsDamageType.Damage.With(AttackDamageHand.MainHand))
         .WithCondition(MainHand.Has(Tags.Wand)),
         References[0].AsDamageType.Damage.With(AttackDamageHand.OffHand)
         .GainAs(References[1].AsDamageType.Damage.With(AttackDamageHand.OffHand))
         .WithCondition(OffHand.Has(Tags.Wand))
     },
     {
         "#% of ({DamageTypeMatchers}) damage converted to ({DamageTypeMatchers}) damage",
         BaseAdd, Value, References[0].AsDamageType.Damage.WithHitsAndAilments
         .ConvertTo(References[1].AsDamageType.Damage.WithHitsAndAilments)
     },
     // - penetration
     {
         "damage penetrates #% (of enemy )?({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, Reference.AsDamageType.Penetration
     },
     {
         "damage (?<inner>with .*|dealt by .*) penetrates #% ({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, Reference.AsDamageType.Penetration, "${inner}"
     },
     {
         "penetrate #% ({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, Reference.AsDamageType.Penetration
     },
     // - crit
     { @"\+#% critical strike chance", BaseAdd, Value, CriticalStrike.Chance },
     {
         "no critical strike multiplier, no damage multiplier for ailments from critical strikes",
         TotalOverride, 0, CriticalStrike.Multiplier
     },
     { "never deal critical strikes", TotalOverride, 0, CriticalStrike.Chance },
     // - speed
     // - projectiles
     { "skills fire an additional projectile", BaseAdd, 1, Projectile.Count },
     { "pierces # additional targets", BaseAdd, Value, Projectile.PierceCount },
     { "projectiles pierce an additional target", BaseAdd, 1, Projectile.PierceCount },
     { "projectiles pierce # (additional )?targets", BaseAdd, Value, Projectile.PierceCount },
     {
         "projectiles pierce all nearby targets",
         TotalOverride, double.PositiveInfinity, Projectile.PierceCount, Enemy.IsNearby
     },
     { @"skills chain \+# times", BaseAdd, Value, Projectile.ChainCount },
     // - other
     { "your hits can't be evaded", TotalOverride, 100, Stat.ChanceToHit },
     // defense
     // - life, mana, defences
     { "maximum life becomes #", TotalOverride, Value, Life },
     { "removes all mana", TotalOverride, 0, Mana },
     { "converts all evasion rating to armour", TotalOverride, 100, Evasion.ConvertTo(Armour) },
     { "cannot evade enemy attacks", TotalOverride, 0, Evasion.Chance },
     { @"\+# evasion rating", BaseAdd, Value, Evasion },
     // - resistances
     { "immune to ({DamageTypeMatchers}) damage", TotalOverride, 100, Reference.AsDamageType.Resistance },
     { @"\+#% elemental resistances", BaseAdd, Value, Elemental.Resistance },
     { @"\+?#% physical damage reduction", BaseAdd, Value, Physical.Resistance },
     // - leech
     {
         "life leech is applied to energy shield instead",
         TotalOverride, (int)Pool.EnergyShield, Life.Leech.TargetPool
     },
     { "gain life from leech instantly", TotalOverride, 1, Life.InstantLeech },
     { "leech #% of damage as life", BaseAdd, Value, Life.Leech.Of(Damage) },
     // - block
     {
         "#% of block chance applied to spells",
         BaseAdd, Value.PercentOf(Block.AttackChance), Block.SpellChance
     },
     // - other
     {
         "chaos damage does not bypass energy shield",
         TotalOverride, 100, Chaos.DamageTakenFrom(EnergyShield).Before(Life)
     },
     {
         "#% of chaos damage does not bypass energy shield",
         BaseAdd, Value, Chaos.DamageTakenFrom(EnergyShield).Before(Life)
     },
     {
         "#% of physical damage bypasses energy shield",
         BaseSubtract, Value, Physical.DamageTakenFrom(EnergyShield).Before(Life)
     },
     {
         "you take #% reduced extra damage from critical strikes",
         PercentReduce, Value, CriticalStrike.ExtraDamageTaken
     },
     {
         "you take no extra damage from critical strikes",
         PercentLess, 100, CriticalStrike.ExtraDamageTaken
     },
     // regen and recharge
     // (need to be FormAndStatMatcher because they also exist with flat values)
     {
         "#%( of)? ({PoolStatMatchers}) regenerated per second",
         BaseAdd, Value, Reference.AsPoolStat.Regen.Percent
     },
     {
         "#% of ({PoolStatMatchers}) and ({PoolStatMatchers}) regenerated per second",
         BaseAdd, Value, References[0].AsPoolStat.Regen.Percent, References[1].AsPoolStat.Regen.Percent
     },
     {
         "regenerate #%( of)?( their| your)? ({PoolStatMatchers}) per second",
         BaseAdd, Value, Reference.AsPoolStat.Regen.Percent
     },
     {
         "# ({PoolStatMatchers}) regenerated per second", BaseAdd, Value,
         Reference.AsPoolStat.Regen
     },
     {
         "#% faster start of energy shield recharge", PercentIncrease, Value,
         EnergyShield.Recharge.Start
     },
     { "life regeneration has no effect", PercentLess, 100, Life.Regen },
     {
         "life regeneration is applied to energy shield instead",
         TotalOverride, (int)Pool.EnergyShield, Life.Regen.TargetPool
     },
     // gain (need to be FormAndStatMatcher because they also exist with flat values)
     {
         "#% of ({PoolStatMatchers}) gained",
         BaseAdd, Value.PercentOf(Reference.AsStat), Reference.AsPoolStat.Gain
     },
     {
         "recover #% of( their)? ({PoolStatMatchers})",
         BaseAdd, Value.PercentOf(Reference.AsStat), Reference.AsPoolStat.Gain
     },
     {
         "removes #% of ({PoolStatMatchers})",
         BaseSubtract, Value.PercentOf(Reference.AsStat), Reference.AsPoolStat.Gain
     },
     { @"\+# ({PoolStatMatchers}) gained", BaseAdd, Value, Reference.AsPoolStat.Gain },
     { @"gain \+# ({PoolStatMatchers})", BaseAdd, Value, Reference.AsPoolStat.Gain },
     // charges
     {
         "#% chance to gain a power, frenzy or endurance charge",
         BaseAdd, Value / 3,
         Charge.Power.ChanceToGain, Charge.Frenzy.ChanceToGain, Charge.Endurance.ChanceToGain
     },
     {
         "(?<!chance to |when you )gain an? ({ChargeTypeMatchers})",
         BaseAdd, 100, Reference.AsChargeType.ChanceToGain
     },
     // skills
     // traps, mines, totems
     {
         "detonating mines is instant",
         TotalOverride, double.PositiveInfinity, Stat.CastRate, With(Skills.DetonateMines)
     },
     // minions
     // buffs
     {
         "(?<!while |chance to )you have ({BuffMatchers})",
         TotalOverride, 1, Reference.AsBuff.NotAsBuffOn(Self)
     },
     {
         "(?<!while |chance to )gain ({BuffMatchers})",
         TotalOverride, 1, Reference.AsBuff.On(Self)
     },
     {
         "you can have one additional curse",
         BaseAdd, 1, Buff.CurseLimit
     },
     {
         "enemies can have # additional curse",
         BaseAdd, Value, Buff.CurseLimit.For(Enemy)
     },
     { "unaffected by curses", PercentLess, 100, Buffs(targets: Self).With(Keyword.Curse).Effect },
     { "grants fortify", TotalOverride, 1, Buff.Fortify.On(Self) },
     { "gain elemental conflux", TotalOverride, 1, Buff.Conflux.Elemental.On(Self) },
     // flags
     // ailments
     { "causes bleeding", TotalOverride, 100, Ailment.Bleed.Chance },
     { "always poison", TotalOverride, 100, Ailment.Poison.Chance },
     {
         "(you )?can afflict an additional ignite on an enemy",
         BaseAdd, 1, Ailment.Ignite.InstancesOn(Enemy).Maximum
     },
     { "(you are )?immune to ({AilmentMatchers})", TotalOverride, 100, Reference.AsAilment.Avoidance },
     { "cannot be ({AilmentMatchers})", TotalOverride, 100, Reference.AsAilment.Avoidance },
     {
         "cannot be ({AilmentMatchers}) or ({AilmentMatchers})",
         TotalOverride, 100, References[0].AsAilment.Avoidance, References[1].AsAilment.Avoidance
     },
     {
         "(immune to|cannot be affected by) elemental ailments",
         TotalOverride, 100, Ailment.Elemental.Select(a => a.Avoidance)
     },
     {
         "poison you inflict with critical strikes deals #% more damage",
         PercentMore, Value, CriticalStrike.Multiplier.With(Ailment.Poison)
     },
     // stun
     { "(you )?cannot be stunned", TotalOverride, 100, Effect.Stun.Avoidance },
     // item quantity/quality
     // range and area of effect
     // other
     { "knocks back enemies", TotalOverride, 100, Effect.Knockback.Chance },
 };
Пример #6
0
        public static void Damage(Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage, int phys, int fire, int cold, int pois, int nrgy, DFAlgorithm dfa)
        {
            int iDamage = (int)damage;

            #region Taran: Spell damage is based on armor difference

            /*
             * double newDamage = damage;
             *
             * if (target is PlayerMobile && from is PlayerMobile)
             * {
             *  World.Broadcast(25, true, "Old damage is: " + iDamage);
             *
             *  double defar = ((PlayerMobile) target).BaseArmorRatingSpells;
             *  double attar = ((PlayerMobile) from).BaseArmorRatingSpells;
             *
             *  double diff = defar - attar;
             *
             *  World.Broadcast(50, true, "Difference in armor rating is: " + diff);
             *
             *  if (defar - attar > 0)
             *      newDamage = iDamage*(1.00 - ((diff * 1.6) / 100));
             *
             *  if (newDamage / iDamage < 0.6) //Don't deal less than 60% damage
             *      newDamage = iDamage*0.6;
             *
             *  iDamage = (int) newDamage;
             *
             *  World.Broadcast(25, true, "New damage is: " + iDamage);
             * }
             */
            #endregion

            //Maka);
            if (target == from && from.MagicDamageAbsorb > 0)
            {
                from.FixedEffect(0x37B9, 10, 5);
                from.MagicDamageAbsorb = 0;
                iDamage = 0;
                DefensiveSpell.Nullify(from);
            }

            if (Evasion.CheckSpellEvasion(target))
            {
                iDamage = 0;
            }

            if (delay == TimeSpan.Zero)
            {
                if (from is BaseCreature)
                {
                    ((BaseCreature)from).AlterSpellDamageTo(target, ref iDamage);
                }

                if (target is BaseCreature)
                {
                    ((BaseCreature)target).AlterSpellDamageFrom(from, ref iDamage);
                }

                //WeightOverloading.DFA = dfa;

                int damageGiven = AOS.Damage(target, from, iDamage, phys, fire, cold, pois, nrgy);
                DoLeech(damageGiven, from, target);

                //WeightOverloading.DFA = DFAlgorithm.Standard;
            }
            else
            {
                new SpellDamageTimerAOS(spell, target, from, iDamage, phys, fire, cold, pois, nrgy, delay, dfa).Start();
            }

            if (target is BaseCreature && from != null && delay == TimeSpan.Zero)
            {
                BaseCreature c = (BaseCreature)target;

                c.OnHarmfulSpell(from);
                c.OnDamagedBySpell(from);
            }
        }
Пример #7
0
        private DataDrivenMechanicCollection CreateCollection()
        => new DataDrivenMechanicCollection(_modifierBuilder, BuilderFactories)
        {
            // skill hit damage
            // - DPS
            { TotalOverride, _stat.SkillDpsWithHits, _stat.AverageHitDamage.Value *_stat.CastRate.Value },
            // - average damage
            {
                TotalOverride, _stat.AverageHitDamage,
                CombineSource(_stat.AverageDamage.WithHits, CombineHandsByAverage)
            },
            // - average damage per source
            {
                TotalOverride, _stat.AverageDamage.WithHits.With(AttackDamageHand.MainHand),
                _stat.AverageDamagePerHit.With(AttackDamageHand.MainHand).Value *
                Stat.ChanceToHit.With(AttackDamageHand.MainHand).Value.AsPercentage
            },
            {
                TotalOverride, _stat.AverageDamage.WithHits.With(AttackDamageHand.OffHand),
                _stat.AverageDamagePerHit.With(AttackDamageHand.OffHand).Value *
                Stat.ChanceToHit.With(AttackDamageHand.OffHand).Value.AsPercentage
            },
            {
                TotalOverride, _stat.AverageDamage.WithHits.With(DamageSource.Spell),
                _stat.AverageDamagePerHit.With(DamageSource.Spell).Value
            },
            {
                TotalOverride, _stat.AverageDamage.WithHits.With(DamageSource.Secondary),
                _stat.AverageDamagePerHit.With(DamageSource.Secondary).Value
            },
            // - average damage of a successful hit per source
            {
                TotalOverride, _stat.AverageDamagePerHit,
                _stat.DamageWithNonCrits().WithHits, _stat.DamageWithCrits().WithHits, _stat.EffectiveCritChance,
                (nonCritDamage, critDamage, critChance)
                => nonCritDamage.Value.Average * (1 - critChance.Value) +
                critDamage.Value.Average * critChance.Value
            },
            // - crit/non-crit damage per source and type
            {
                TotalOverride, dt => _stat.DamageWithNonCrits(dt).WithHits,
                dt => _stat.Damage(dt).WithHits,
                dt => _stat.EffectiveDamageMultiplierWithNonCrits(dt).WithHits,
                (_, damage, mult) => damage.Value * mult.Value
            },
            {
                TotalOverride, dt => _stat.DamageWithCrits(dt).WithHits,
                dt => _stat.Damage(dt).WithHits,
                dt => _stat.EffectiveDamageMultiplierWithCrits(dt).WithHits,
                (_, damage, mult) => damage.Value * mult.Value
            },
            // - effective crit/non-crit damage multiplier per source and type
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithNonCrits(dt).WithHits,
                dt => _stat.EnemyResistanceAgainstNonCrits(dt),
                dt => DamageTaken(dt).WithHits.For(Enemy),
                (_, resistance, damageTaken) => DamageTakenMultiplier(resistance, damageTaken)
            },
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithCrits(dt).WithHits,
                dt => _stat.EnemyResistanceAgainstCrits(dt),
                dt => DamageTaken(dt).WithHits.For(Enemy),
                _ => CriticalStrike.Multiplier.WithHits,
                (_, resistance, damageTaken, mult)
                => DamageTakenMultiplier(resistance, damageTaken) * mult.Value.AsPercentage
            },
            // - enemy resistance against crit/non-crit hits per source and type
            {
                TotalOverride, dt => _stat.EnemyResistanceAgainstNonCrits(dt),
                dt => DamageTypeBuilders.From(dt).IgnoreResistanceWithNonCrits,
                dt => DamageTypeBuilders.From(dt).PenetrationWithNonCrits,
                (dt, ignoreResistance, penetration)
                => ValueFactory.If(ignoreResistance.IsSet).Then(0)
                .Else(DamageTypeBuilders.From(dt).Resistance.For(Enemy).Value - penetration.Value)
            },
            {
                TotalOverride, dt => _stat.EnemyResistanceAgainstCrits(dt),
                dt => DamageTypeBuilders.From(dt).IgnoreResistanceWithCrits,
                dt => DamageTypeBuilders.From(dt).PenetrationWithCrits,
                (dt, ignoreResistance, penetration)
                => ValueFactory.If(ignoreResistance.Value.Eq(1)).Then(0)
                .Else(DamageTypeBuilders.From(dt).Resistance.For(Enemy).Value - penetration.Value)
            },

            // skill damage over time
            // - DPS = average damage = non-crit damage
            { TotalOverride, _stat.SkillDpsWithDoTs, _stat.AverageDamage.WithSkills(DamageSource.OverTime).Value },
            {
                TotalOverride, _stat.AverageDamage.WithSkills(DamageSource.OverTime),
                _stat.DamageWithNonCrits().WithSkills(DamageSource.OverTime).Value
            },
            // - damage per type
            {
                TotalOverride, dt => _stat.DamageWithNonCrits(dt).WithSkills(DamageSource.OverTime),
                dt => _stat.Damage(dt).WithSkills(DamageSource.OverTime).Value *
                _stat.EffectiveDamageMultiplierWithNonCrits(dt).WithSkills(DamageSource.OverTime).Value
            },
            // - effective damage multiplier per type
            {
                TotalOverride,
                dt => _stat.EffectiveDamageMultiplierWithNonCrits(dt).WithSkills(DamageSource.OverTime),
                dt => EnemyDamageTakenMultiplier(DamageTypeBuilders.From(dt),
                                                 DamageTaken(dt).WithSkills(DamageSource.OverTime))
            },

            // ignite damage
            // - average damage
            {
                TotalOverride, _stat.AverageAilmentDamage(Common.Builders.Effects.Ailment.Ignite),
                CombineSource(_stat.AverageDamage.With(Ailment.Ignite),
                              CombineHandsForAverageAilmentDamage(Common.Builders.Effects.Ailment.Ignite))
            },
            // - effective crit/non-crit damage multiplier per source and type
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithNonCrits(dt).With(Ailment.Ignite),
                _ => Fire.Damage.Taken.With(Ailment.Ignite),
                (_, damageTaken) => EnemyDamageTakenMultiplier(Fire, damageTaken)
            },
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithCrits(dt).With(Ailment.Ignite),
                _ => Fire.Damage.Taken.With(Ailment.Ignite),
                _ => CriticalStrike.Multiplier.With(Ailment.Ignite),
                (_, damageTaken, mult) => EnemyDamageTakenMultiplier(Fire, damageTaken) * mult.Value.AsPercentage
            },
            // bleed damage
            // - average damage
            {
                TotalOverride, _stat.AverageAilmentDamage(Common.Builders.Effects.Ailment.Bleed),
                CombineHandsForAverageAilmentDamage(Common.Builders.Effects.Ailment.Bleed)
                    (_stat.AverageDamage.With(Ailment.Bleed))
            },
            // - effective crit/non-crit damage multiplier per source and type
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithNonCrits(dt).With(Ailment.Bleed),
                _ => Physical.Damage.Taken.With(Ailment.Bleed),
                (_, damageTaken) => EnemyDamageTakenMultiplier(Physical, damageTaken)
            },
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithCrits(dt).With(Ailment.Bleed),
                _ => Physical.Damage.Taken.With(Ailment.Bleed),
                _ => CriticalStrike.Multiplier.With(Ailment.Bleed),
                (_, damageTaken, mult)
                => EnemyDamageTakenMultiplier(Physical, damageTaken) * mult.Value.AsPercentage
            },
            // poison damage
            // - average damage
            {
                TotalOverride, _stat.AverageAilmentDamage(Common.Builders.Effects.Ailment.Poison),
                CombineSource(_stat.AverageDamage.With(Ailment.Poison),
                              CombineHandsForAverageAilmentDamage(Common.Builders.Effects.Ailment.Poison))
            },
            // - effective crit/non-crit damage multiplier per source and type
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithNonCrits(dt).With(Ailment.Poison),
                _ => Chaos.Damage.Taken.With(Ailment.Poison),
                (_, damageTaken) => EnemyDamageTakenMultiplier(Chaos, damageTaken)
            },
            {
                TotalOverride, dt => _stat.EffectiveDamageMultiplierWithCrits(dt).With(Ailment.Poison),
                _ => Chaos.Damage.Taken.With(Ailment.Poison),
                _ => CriticalStrike.Multiplier.With(Ailment.Poison),
                (_, damageTaken, mult) => EnemyDamageTakenMultiplier(Chaos, damageTaken) * mult.Value.AsPercentage
            },
            // shared ailment damage
            // - DPS
            {
                TotalOverride, _stat.AilmentDps,
                ailment => _stat.AverageAilmentDamage(ailment).Value *
                _stat.AilmentEffectiveInstances(ailment).Value
            },
            // - lifetime damage of one instance
            {
                TotalOverride, _stat.AilmentInstanceLifetimeDamage,
                ailment => _stat.AverageAilmentDamage(ailment).Value *Ailment.From(ailment).Duration.Value
            },
            // - average damage per source
            {
                TotalOverride, ailment => _stat.AverageDamage.With(Ailment.From(ailment)),
                ailment => _stat.DamageWithNonCrits().With(Ailment.From(ailment)),
                ailment => _stat.DamageWithCrits().With(Ailment.From(ailment)),
                _ => _stat.EffectiveCritChance,
                ailment => Ailment.From(ailment).Chance,
                ailment => _stat.AilmentChanceWithCrits(ailment),
                AverageAilmentDamageFromCritAndNonCrit
            },
            // - crit/non-crit damage per source and type
            {
                TotalOverride, (a, dt) => _stat.DamageWithNonCrits(dt).With(Ailment.From(a)),
                (a, dt) => _stat.Damage(dt).With(Ailment.From(a)),
                (a, dt) => _stat.EffectiveDamageMultiplierWithNonCrits(dt).With(Ailment.From(a)),
                (damage, mult) => damage.Value * mult.Value
            },
            {
                TotalOverride, (a, dt) => _stat.DamageWithCrits(dt).With(Ailment.From(a)),
                (a, dt) => _stat.Damage(dt).With(Ailment.From(a)),
                (a, dt) => _stat.EffectiveDamageMultiplierWithCrits(dt).With(Ailment.From(a)),
                (damage, mult) => damage.Value * mult.Value
            },

            // speed
            { TotalOverride, _stat.CastRate, CombineSource(Stat.CastRate, CombineHandsByAverage) },
            { TotalOverride, _stat.CastTime, _stat.CastRate.Value.Invert },
            { PercentMore, Stat.MovementSpeed, ActionSpeedValueForPercentMore },
            {
                PercentMore, Stat.CastRate, ActionSpeedValueForPercentMore,
                Not(Or(With(Keyword.Totem), With(Keyword.Trap), With(Keyword.Mine)))
            },
            { PercentMore, Stat.Totem.Speed, ActionSpeedValueForPercentMore },
            { PercentMore, Stat.Trap.Speed, ActionSpeedValueForPercentMore },
            { PercentMore, Stat.Mine.Speed, ActionSpeedValueForPercentMore },
            // resistances/damage reduction
            { BaseSet, _stat.ResistanceAgainstHits(DamageType.Physical), Physical.Resistance.Value },
            {
                BaseAdd, _stat.ResistanceAgainstHits(DamageType.Physical),
                100 * Armour.Value /
                (Armour.Value + 10 * Physical.Damage.WithSkills.With(AttackDamageHand.MainHand).For(Enemy).Value)
            },
            { BaseSet, _stat.ResistanceAgainstHits(DamageType.Physical).Maximum, 90 },
            { TotalOverride, _stat.ResistanceAgainstHits(DamageType.Lightning), Lightning.Resistance.Value },
            { TotalOverride, _stat.ResistanceAgainstHits(DamageType.Cold), Cold.Resistance.Value },
            { TotalOverride, _stat.ResistanceAgainstHits(DamageType.Fire), Fire.Resistance.Value },
            { TotalOverride, _stat.ResistanceAgainstHits(DamageType.Chaos), Chaos.Resistance.Value },
            // damage mitigation (1 - (1 - resistance / 100) * damage taken)
            {
                TotalOverride, _stat.MitigationAgainstHits,
                dt => 1 - DamageTakenMultiplier(_stat.ResistanceAgainstHits(dt),
                                                DamageTaken(dt).WithSkills(DamageSource.Secondary))
            },
            {
                TotalOverride, _stat.MitigationAgainstDoTs,
                dt => 1 - DamageTakenMultiplier(DamageTypeBuilders.From(dt).Resistance,
                                                DamageTaken(dt).WithSkills(DamageSource.OverTime))
            },
            // chance to hit/evade
            {
                BaseSet, Evasion.Chance,
                100 - ChanceToHitValue(Stat.Accuracy.With(AttackDamageHand.MainHand).For(Enemy), Evasion,
                                       Buff.Blind.IsOn(Enemy))
            },
            {
                BaseSet, Stat.ChanceToHit.With(AttackDamageHand.MainHand),
                ChanceToHitValue(Stat.Accuracy.With(AttackDamageHand.MainHand), Evasion.For(Enemy),
                                 Buff.Blind.IsOn(Self))
            },
            {
                BaseSet, Stat.ChanceToHit.With(AttackDamageHand.OffHand),
                ChanceToHitValue(Stat.Accuracy.With(AttackDamageHand.OffHand), Evasion.For(Enemy),
                                 Buff.Blind.IsOn(Self))
            },
            // chance to avoid
            {
                TotalOverride, _stat.ChanceToAvoidMeleeAttacks,
                100 - 100 * (FailureProbability(Evasion.ChanceAgainstMeleeAttacks) *
                             FailureProbability(Stat.Dodge.AttackChance) * FailureProbability(Block.AttackChance))
            },
            {
                TotalOverride, _stat.ChanceToAvoidProjectileAttacks,
                100 - 100 * (FailureProbability(Evasion.ChanceAgainstProjectileAttacks) *
                             FailureProbability(Stat.Dodge.AttackChance) * FailureProbability(Block.AttackChance))
            },
            {
                TotalOverride, _stat.ChanceToAvoidSpells,
                100 - 100 * (FailureProbability(Stat.Dodge.SpellChance) * FailureProbability(Block.SpellChance))
            },
            // crit
            {
                TotalOverride, _stat.EffectiveCritChance.With(AttackDamageHand.MainHand),
                CriticalStrike.Chance.With(AttackDamageHand.MainHand).Value.AsPercentage *
                Stat.ChanceToHit.With(AttackDamageHand.MainHand).Value.AsPercentage
            },
            {
                TotalOverride, _stat.EffectiveCritChance.With(AttackDamageHand.OffHand),
                CriticalStrike.Chance.With(AttackDamageHand.OffHand).Value.AsPercentage *
                Stat.ChanceToHit.With(AttackDamageHand.OffHand).Value.AsPercentage
            },
            {
                TotalOverride, _stat.EffectiveCritChance.With(DamageSource.Spell),
                CriticalStrike.Chance.With(DamageSource.Spell).Value.AsPercentage
            },
            {
                TotalOverride, _stat.EffectiveCritChance.With(DamageSource.Secondary),
                CriticalStrike.Chance.With(DamageSource.Secondary).Value.AsPercentage
            },
            // pools
            {
                BaseAdd, p => p.Regen,
                p => _stat.RegenTargetPoolValue(p.BuildPool()) * p.Regen.Percent.Value.AsPercentage
            },
            { TotalOverride, _stat.EffectiveRegen, p => p.Regen.Value * p.RecoveryRate.Value },
            { TotalOverride, _stat.EffectiveRecharge, p => p.Recharge.Value * p.RecoveryRate.Value },
            { TotalOverride, _stat.RechargeStartDelay, p => 2 / p.Recharge.Start.Value },
            { TotalOverride, _stat.EffectiveLeechRate, p => p.Leech.Rate.Value * p.RecoveryRate.Value },
            {
                TotalOverride, _stat.AbsoluteLeechRate,
                p => _stat.LeechTargetPoolValue(p) * _stat.EffectiveLeechRate(p).Value.AsPercentage
            },
            {
                TotalOverride, _stat.AbsoluteLeechRateLimit,
                p => _stat.LeechTargetPoolValue(p.BuildPool()) * p.Leech.RateLimit.Value.AsPercentage
            },
            {
                TotalOverride, _stat.TimeToReachLeechRateLimit,
                p => p.Leech.RateLimit.Value / p.Leech.Rate.Value / _stat.CastRate.Value
            },
            // flasks
            { PercentMore, Flask.LifeRecovery, Flask.Effect.Value * 100 },
            { PercentMore, Flask.ManaRecovery, Flask.Effect.Value * 100 },
            { PercentMore, Flask.LifeRecovery, Flask.RecoverySpeed.Value * 100 },
            { PercentMore, Flask.ManaRecovery, Flask.RecoverySpeed.Value * 100 },
            { PercentMore, Flask.Duration, (100 / Flask.RecoverySpeed.Value) - 100 },
            // ailments
            {
                TotalOverride, _stat.AilmentDealtDamageType(Common.Builders.Effects.Ailment.Ignite),
                (int)DamageType.Fire
            },
            {
                TotalOverride, _stat.AilmentDealtDamageType(Common.Builders.Effects.Ailment.Bleed),
                (int)DamageType.Physical
            },
            {
                TotalOverride, _stat.AilmentDealtDamageType(Common.Builders.Effects.Ailment.Ignite),
                (int)DamageType.Chaos
            },
            {
                TotalOverride, _stat.AilmentCombinedEffectiveChance,
                ailment => CombineSource(_stat.AilmentEffectiveChance(ailment), CombineHandsByAverage)
            },
            {
                TotalOverride, _stat.AilmentEffectiveChance,
                ailment => Ailment.From(ailment).Chance,
                ailment => _stat.AilmentChanceWithCrits(ailment),
                _ => _stat.EffectiveCritChance,
                (ailment, ailmentChance, ailmentChanceWithCrits, critChance)
                => (ailmentChance.Value.AsPercentage * (1 - critChance.Value) +
                    ailmentChanceWithCrits.Value.AsPercentage * critChance.Value) *
                (1 - Ailment.From(ailment).Avoidance.For(Enemy).Value.AsPercentage)
            },
            {
                TotalOverride, _stat.AilmentChanceWithCrits,
                ailment => Ailment.From(ailment).Chance,
                (ailment, ailmentChance) => ValueFactory
                .If(Ailment.From(ailment).CriticalStrikesAlwaysInflict.IsSet).Then(100)
                .Else(ailmentChance.Value)
            },
            { TotalOverride, Ailment.Chill.On(Self), 1, Ailment.Freeze.IsOn(Self) },
            { PercentIncrease, Ailment.Shock.AddStat(Damage.Taken), _stat.IncreasedDamageTakenFromShocks.Value },
            { BaseSet, _stat.IncreasedDamageTakenFromShocks, 20 },
            { TotalOverride, _stat.IncreasedDamageTakenFromShocks.Maximum, 50 },
            { TotalOverride, _stat.IncreasedDamageTakenFromShocks.Minimum, 1 },
            {
                PercentReduce, Ailment.Chill.AddStat(Stat.ActionSpeed),
                _stat.ReducedActionSpeedFromChill.Value
            },
            { BaseSet, _stat.ReducedActionSpeedFromChill, 10 },
            { TotalOverride, _stat.ReducedActionSpeedFromChill.Maximum, 30 },
            { TotalOverride, _stat.ReducedActionSpeedFromChill.Minimum, 1 },
            // - AilmentEffectiveInstances
            {
                TotalOverride, _stat.AilmentEffectiveInstances(Common.Builders.Effects.Ailment.Ignite),
                Ailment.Ignite.InstancesOn(Enemy).Maximum.Value
            },
            {
                TotalOverride, _stat.AilmentEffectiveInstances(Common.Builders.Effects.Ailment.Bleed),
                Ailment.Bleed.InstancesOn(Enemy).Maximum.Value
            },
            {
                TotalOverride, _stat.AilmentEffectiveInstances(Common.Builders.Effects.Ailment.Poison),
                Ailment.Poison.Duration.Value *_stat.CastRate.Value *
                CombineSource(_stat.AilmentEffectiveChance(Common.Builders.Effects.Ailment.Poison),
                              s => CombineByWeightedAverage(
                                  s.With(AttackDamageHand.MainHand).Value *
                                  Stat.ChanceToHit.With(AttackDamageHand.MainHand).Value.AsPercentage,
                                  SkillUsesHandAsMultiplier(AttackDamageHand.MainHand),
                                  s.With(AttackDamageHand.OffHand).Value *
                                  Stat.ChanceToHit.With(AttackDamageHand.OffHand).Value.AsPercentage,
                                  SkillUsesHandAsMultiplier(AttackDamageHand.OffHand)))
            },
            // stun (see https://pathofexile.gamepedia.com/Stun)
            { PercentLess, Effect.Stun.Duration, Effect.Stun.Recovery.For(Enemy).Value * 100 },
            {
                TotalOverride, _stat.EffectiveStunThreshold,
                Effect.Stun.Threshold, EffectiveStunThresholdValue
            },
            {
                BaseSet, Effect.Stun.Chance,
                _stat.AverageDamage.WithHits, _stat.EffectiveStunThreshold,
                (damage, threshold)
                => 200 * damage.Value / (Life.For(Enemy).ValueFor(NodeType.Subtotal) * threshold.Value)
            },
            {
                TotalOverride, _stat.StunAvoidanceWhileCasting,
                1 -
                (1 - Effect.Stun.Avoidance.Value) * (1 - Effect.Stun.ChanceToAvoidInterruptionWhileCasting.Value)
            },
            // radius
            { PercentMore, Stat.Radius, Stat.AreaOfEffect.Value.Select(Math.Sqrt, v => $"Sqrt({v})") },
        };
Пример #8
0
 protected override IReadOnlyList <MatcherData> CreateCollection() =>
 new FormAndStatMatcherCollection(_modifierBuilder, ValueFactory)
 {
     // attributes
     // offense
     // - damage
     {
         @"adds # to # ({DamageTypeMatchers}) damage",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]), Reference.AsDamageType.Damage.WithHits
     },
     {
         @"# to # added ({DamageTypeMatchers}) damage",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]), Reference.AsDamageType.Damage.WithHits
     },
     {
         "# to # ({DamageTypeMatchers}) damage",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]), Reference.AsDamageType.Damage.WithHits
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to attacks",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Attack)
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to unarmed attacks",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Attack).With(Keyword.Melee),
         Not(MainHand.HasItem)
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to bow attacks",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Attack).With(Keyword.Melee),
         MainHand.Has(Tags.Bow)
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to spells",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Spell)
     },
     {
         @"adds # to # ({DamageTypeMatchers}) damage to spells and attacks",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Spell),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Attack)
     },
     {
         @"# to # additional ({DamageTypeMatchers}) damage",
         BaseAdd, ValueFactory.FromMinAndMax(Values[0], Values[1]), Reference.AsDamageType.Damage.WithHits
     },
     {
         @"adds # maximum ({DamageTypeMatchers}) damage",
         BaseAdd, Value.MaximumOnly, Reference.AsDamageType.Damage.WithHits
     },
     { "deal no ({DamageTypeMatchers}) damage", TotalOverride, 0, Reference.AsDamageType.Damage },
     {
         @"explosion deals (base )?({DamageTypeMatchers}) damage equal to #% of the (corpse|monster)'s maximum life",
         BaseSet, Value.AsPercentage *Life.For(Enemy).Value,
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Secondary)
     },
     {
         @"explosion deals # to # base ({DamageTypeMatchers}) damage",
         BaseSet, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Secondary)
     },
     {
         "deals # to # base ({DamageTypeMatchers}) damage",
         BaseSet, ValueFactory.FromMinAndMax(Values[0], Values[1]),
         Reference.AsDamageType.Damage.WithSkills(DamageSource.Spell)
     },
     // - damage taken
     {
         "cold damage taken increased by chill effect",
         PercentIncrease, 100 * (Ailment.ChillEffect.Value - 1),
         Cold.Damage.Taken.With(DamageSource.OverTime)
     },
     // - damage taken as
     // - conversion and gain
     {
         "(gain )?#% of ({DamageTypeMatchers}) damage (gained |added )?as (extra )?({DamageTypeMatchers}) damage",
         BaseAdd, Value, References[0].AsDamageType.Damage.WithHitsAndAilments
         .GainAs(References[1].AsDamageType.Damage.WithHitsAndAilments)
     },
     {
         "gain #% of ({DamageTypeMatchers}) damage as extra damage of a random element",
         BaseAdd, Value, Reference.AsDamageType.Damage.WithHitsAndAilments
         .GainAs(RandomElement.Damage.WithHitsAndAilments)
     },
     {
         "gain #% of wand ({DamageTypeMatchers}) damage as extra ({DamageTypeMatchers}) damage",
         BaseAdd, Value,
         References[0].AsDamageType.Damage.With(AttackDamageHand.MainHand)
         .GainAs(References[1].AsDamageType.Damage.With(AttackDamageHand.MainHand))
         .WithCondition(MainHand.Has(Tags.Wand)),
         References[0].AsDamageType.Damage.With(AttackDamageHand.OffHand)
         .GainAs(References[1].AsDamageType.Damage.With(AttackDamageHand.OffHand))
         .WithCondition(OffHand.Has(Tags.Wand))
     },
     {
         "#% of ({DamageTypeMatchers}) damage converted to ({DamageTypeMatchers}) damage",
         BaseAdd, Value, References[0].AsDamageType.Damage.WithHitsAndAilments
         .ConvertTo(References[1].AsDamageType.Damage.WithHitsAndAilments)
     },
     {
         "({DamageTypeMatchers}) spells have #% of ({DamageTypeMatchers}) damage converted to ({DamageTypeMatchers}) damage",
         BaseAdd, Value, References[1].AsDamageType.Damage.With(DamageSource.Spell)
         .ConvertTo(References[2].AsDamageType.Damage.With(DamageSource.Spell)),
         With(References[0].AsDamageType)
     },
     // - penetration
     {
         "damage penetrates #% (of enemy )?({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, Reference.AsDamageType.Penetration
     },
     {
         "damage (?<inner>with .*|dealt by .*) penetrates #% ({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, Reference.AsDamageType.Penetration, "${inner}"
     },
     {
         "penetrates? #% ({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, Reference.AsDamageType.Penetration
     },
     {
         "({KeywordMatchers}) damage penetrates #% ({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, References[1].AsDamageType.Penetration.With(References[0].AsKeyword)
     },
     {
         "({KeywordMatchers}) damage (?<inner>with .*|dealt by .*) penetrates #% ({DamageTypeMatchers}) resistances?",
         BaseAdd, Value, References[1].AsDamageType.Penetration.With(References[1].AsKeyword), "${inner}"
     },
     {
         "hits ignore enemy monster ({DamageTypeMatchers}) resistance",
         TotalOverride, 1, Reference.AsDamageType.IgnoreResistance
     },
     // - exposure
     {
         @"(?<damageType>({DamageTypeMatchers})) exposure applies #% to \k<damageType> resistance",
         BaseSet, Value, Reference.AsDamageType.Exposure
     },
     // - crit
     { @"\+#% critical strike chance", BaseAdd, Value, CriticalStrike.Chance },
     {
         "no critical strike multiplier, no damage multiplier for ailments from critical strikes",
         TotalOverride, 0, CriticalStrike.Multiplier
     },
     { "never deal critical strikes", TotalOverride, 0, CriticalStrike.Chance },
     // - speed
     { "actions are #% slower", PercentLess, Value, Stat.ActionSpeed },
     { @"\+# seconds to attack time", BaseAdd, Value, Stat.BaseCastTime.With(DamageSource.Attack) },
     // - projectiles
     { "fires # additional projectiles", BaseAdd, Value, Projectile.Count },
     { "fires # additional arrows", BaseAdd, Value, Projectile.Count, With(Keyword.Attack) },
     { "fires an additional projectile", BaseAdd, 1, Projectile.Count },
     { "fires an additional arrow", BaseAdd, 1, Projectile.Count, With(Keyword.Attack) },
     { "skills fire an additional projectile", BaseAdd, 1, Projectile.Count },
     { "skills fire # additional projectiles", BaseAdd, Value, Projectile.Count },
     { "supported skills fire # additional projectiles", BaseAdd, Value, Projectile.Count },
     { "pierces # additional targets", BaseAdd, Value, Projectile.PierceCount },
     { "projectiles pierce an additional target", BaseAdd, 1, Projectile.PierceCount },
     { "arrows pierce an additional target", BaseAdd, 1, Projectile.PierceCount, With(Keyword.Attack) },
     { "projectiles pierce # (additional )?targets", BaseAdd, Value, Projectile.PierceCount },
     {
         "arrows pierce # (additional )?targets",
         BaseAdd, Value, Projectile.PierceCount, With(Keyword.Attack)
     },
     {
         "projectiles from supported skills pierce # additional targets", BaseAdd, Value,
         Projectile.PierceCount
     },
     {
         "projectiles pierce all nearby targets",
         TotalOverride, double.PositiveInfinity, Projectile.PierceCount, Enemy.IsNearby
     },
     {
         "projectiles pierce all targets",
         TotalOverride, double.PositiveInfinity, Projectile.PierceCount
     },
     { @"chains \+# times", BaseAdd, Value, Projectile.ChainCount },
     { @"(supported )?skills chain \+# times", BaseAdd, Value, Projectile.ChainCount },
     // - other
     { "your hits can't be evaded", TotalOverride, 100, Stat.ChanceToHit },
     { "can't be evaded", TotalOverride, 100, Stat.ChanceToHit },
     // defense
     // - life, mana, defences
     { "maximum life becomes #", TotalOverride, Value, Life },
     { "removes all mana", TotalOverride, 0, Mana },
     { "converts all evasion rating to armour", TotalOverride, 100, Evasion.ConvertTo(Armour) },
     { "cannot evade enemy attacks", TotalOverride, 0, Evasion.Chance },
     { @"\+# evasion rating", BaseAdd, Value, Evasion },
     // - resistances
     { "immune to ({DamageTypeMatchers}) damage", TotalOverride, 100, Reference.AsDamageType.Resistance },
     { @"\+#% elemental resistances", BaseAdd, Value, Elemental.Resistance },
     { @"\+?#% physical damage reduction", BaseAdd, Value, Physical.Resistance },
     // - leech
     {
         "leech energy shield instead of life",
         TotalOverride, 100, Life.Leech.Of(Damage).ConvertTo(EnergyShield.Leech.Of(Damage))
     },
     { "gain life from leech instantly", TotalOverride, 1, Life.Leech.IsInstant },
     { "leech #% of damage as life", BaseAdd, Value, Life.Leech.Of(Damage) },
     { "cannot leech mana", TotalOverride, 0, Mana.Leech.Of(Damage) },
     // - block
     {
         "#% of block chance applied to spells",
         BaseAdd, Value.PercentOf(Block.AttackChance), Block.SpellChance
     },
     // - other
     {
         "chaos damage does not bypass energy shield",
         TotalOverride, 100, Chaos.DamageTakenFrom(EnergyShield).Before(Life)
     },
     {
         "#% of chaos damage does not bypass energy shield",
         BaseAdd, Value, Chaos.DamageTakenFrom(EnergyShield).Before(Life)
     },
     {
         "#% of physical damage bypasses energy shield",
         BaseSubtract, Value, Physical.DamageTakenFrom(EnergyShield).Before(Life)
     },
     {
         "you take #% reduced extra damage from critical strikes",
         PercentReduce, Value, CriticalStrike.ExtraDamageTaken
     },
     {
         "you take no extra damage from critical strikes",
         PercentLess, 100, CriticalStrike.ExtraDamageTaken
     },
     // regen and recharge
     // (need to be FormAndStatMatcher because they also exist with flat values)
     {
         "#%( of)? ({PoolStatMatchers}) regenerated per second",
         BaseAdd, Value, Reference.AsPoolStat.Regen.Percent
     },
     {
         "#% of ({PoolStatMatchers}) and ({PoolStatMatchers}) regenerated per second",
         BaseAdd, Value, References[0].AsPoolStat.Regen.Percent, References[1].AsPoolStat.Regen.Percent
     },
     {
         "regenerate #%( of)?( their| your)? ({PoolStatMatchers}) per second",
         BaseAdd, Value, Reference.AsPoolStat.Regen.Percent
     },
     {
         "# ({PoolStatMatchers}) regenerated per second", BaseAdd, Value,
         Reference.AsPoolStat.Regen
     },
     {
         "#% faster start of energy shield recharge", PercentIncrease, Value,
         EnergyShield.Recharge.Start
     },
     { "life regeneration has no effect", PercentLess, 100, Life.Regen },
     {
         "life regeneration is applied to energy shield instead",
         TotalOverride, (int)Pool.EnergyShield, Life.Regen.TargetPool
     },
     // gain (need to be FormAndStatMatcher because they also exist with flat values)
     {
         "#% of ({PoolStatMatchers}) gained",
         BaseAdd, Value.PercentOf(Reference.AsStat), Reference.AsPoolStat.Gain
     },
     {
         "recover #% of( their)? ({PoolStatMatchers})",
         BaseAdd, Value.PercentOf(Reference.AsStat), Reference.AsPoolStat.Gain
     },
     {
         "removes #% of ({PoolStatMatchers})",
         BaseSubtract, Value.PercentOf(Reference.AsStat), Reference.AsPoolStat.Gain
     },
     { @"\+# ({PoolStatMatchers}) gained", BaseAdd, Value, Reference.AsPoolStat.Gain },
     { @"gain \+# ({PoolStatMatchers})", BaseAdd, Value, Reference.AsPoolStat.Gain },
     { "replenishes energy shield by #% of armour", BaseAdd, Value.PercentOf(Armour), EnergyShield.Gain },
     {
         "recover ({PoolStatMatchers}) equal to #% of your evasion rating",
         BaseAdd, Value.PercentOf(Evasion), Reference.AsPoolStat.Gain
     },
     // charges
     {
         "#% chance to gain a power, frenzy or endurance charge",
         BaseAdd, Value / 3,
         Charge.Power.ChanceToGain, Charge.Frenzy.ChanceToGain, Charge.Endurance.ChanceToGain
     },
     {
         "(?<!chance to |when you )gain an? ({ChargeTypeMatchers})",
         BaseAdd, 100, Reference.AsChargeType.ChanceToGain
     },
     {
         "(?<!chance to |when you )gain a power or frenzy charge",
         BaseAdd, 50, Charge.Power.ChanceToGain, Charge.Frenzy.ChanceToGain
     },
     // skills
     { "base duration is # seconds", BaseSet, Value, Stat.Duration },
     { @"\+# seconds to base duration", BaseAdd, Value, Stat.Duration },
     { "base secondary duration is # seconds", BaseSet, Value, Stat.SecondaryDuration },
     {
         "#% increased duration(?! of)",
         PercentIncrease, Value, ApplyOnce(Stat.Duration, Stat.SecondaryDuration)
     },
     {
         "#% reduced duration(?! of)", PercentReduce, Value, ApplyOnce(Stat.Duration, Stat.SecondaryDuration)
     },
     { "skills cost no mana", TotalOverride, 0, Mana.Cost },
     { "you can cast an additional brand", BaseAdd, 1, Skills[Keyword.Brand].CombinedInstances },
     // traps, mines, totems
     { "trap lasts # seconds", BaseSet, Value, Stat.Trap.Duration },
     { "mine lasts # seconds", BaseSet, Value, Stat.Mine.Duration },
     { "totem lasts # seconds", BaseSet, Value, Stat.Totem.Duration },
     {
         "detonating mines is instant",
         TotalOverride, 0, Stat.BaseCastTime, With(Skills.DetonateMines)
     },
     // minions
     { "can summon up to # golem at a time", BaseSet, Value, Golems.CombinedInstances.Maximum },
     // buffs
     {
         "(?<!while |chance to )you have ({BuffMatchers})",
         TotalOverride, 1, Reference.AsBuff.NotAsBuffOn(Self)
     },
     {
         "(?<!while |chance to )gain ({BuffMatchers})",
         TotalOverride, 1, Reference.AsBuff.On(Self)
     },
     {
         "you can have one additional curse",
         BaseAdd, 1, Buff.CurseLimit
     },
     {
         "enemies can have # additional curse",
         BaseAdd, Value, Buff.CurseLimit.For(Enemy)
     },
     { "you can apply an additional curse", BaseAdd, 1, Buff.CurseLimit.For(Enemy) },
     { "unaffected by curses", PercentLess, 100, Buffs(targets: Self).With(Keyword.Curse).Effect },
     {
         "unaffected by ({SkillMatchers})",
         PercentLess, 100, Reference.AsSkill.Buff.EffectOn(Self).For(Entity.Any)
     },
     { "immun(e|ity) to curses", TotalOverride, 0, Buffs(targets: Self).With(Keyword.Curse).On },
     {
         "monsters are hexproof",
         TotalOverride, 0, Buffs(Self, Enemy).With(Keyword.Curse).On, Flag.IgnoreHexproof.IsSet.Not
     },
     { "grants? fortify", TotalOverride, 1, Buff.Fortify.On(Self) },
     { "gain elemental conflux", TotalOverride, 1, Buff.Conflux.Elemental.On(Self) },
     { "creates consecrated ground", TotalOverride, 1, Buff.Conflux.Elemental.On(Self) },
     { "(?<!chance to )impale enemies", TotalOverride, 100, Buff.Impale.Chance },
     { "({BuffMatchers}) lasts # seconds", BaseSet, Value, Reference.AsBuff.Duration },
     {
         "supported auras do not affect you",
         TotalOverride, 0, Skills.ModifierSourceSkill.Buff.EffectOn(Self)
     },
     { "totems cannot gain ({BuffMatchers})", TotalOverride, 0, Reference.AsBuff.On(Entity.Totem) },
     // flags
     // ailments
     { "causes bleeding", TotalOverride, 100, Ailment.Bleed.Chance },
     { "bleed is applied", TotalOverride, 100, Ailment.Bleed.Chance },
     { "always poison", TotalOverride, 100, Ailment.Poison.Chance },
     { "always ({AilmentMatchers}) enemies", TotalOverride, 100, Reference.AsAilment.Chance },
     { "cannot cause bleeding", TotalOverride, 0, Ailment.Bleed.Chance },
     { "cannot ignite", TotalOverride, 0, Ailment.Ignite.Chance },
     { "cannot (apply|inflict) shock", TotalOverride, 0, Ailment.Shock.Chance },
     { "cannot inflict elemental ailments", TotalOverride, 0, Ailment.Elemental.Select(s => s.Chance) },
     {
         "(you )?can afflict an additional ignite on an enemy",
         BaseAdd, 1, Ailment.Ignite.InstancesOn(Enemy).Maximum
     },
     { "(you are )?immune to ({AilmentMatchers})", TotalOverride, 100, Reference.AsAilment.Avoidance },
     { "cannot be ({AilmentMatchers})", TotalOverride, 100, Reference.AsAilment.Avoidance },
     {
         "cannot be ({AilmentMatchers}) or ({AilmentMatchers})",
         TotalOverride, 100, References[0].AsAilment.Avoidance, References[1].AsAilment.Avoidance
     },
     {
         "(immune to|cannot be affected by|immunity to) elemental ailments",
         TotalOverride, 100, Ailment.Elemental.Select(a => a.Avoidance)
     },
     {
         "poison you inflict with critical strikes deals #% more damage",
         PercentMore, Value, CriticalStrike.Multiplier.With(Ailment.Poison)
     },
     // stun
     { "(you )?cannot be stunned", TotalOverride, 100, Effect.Stun.Avoidance },
     { "additional #% chance to be stunned", BaseAdd, Value, Effect.Stun.Chance.For(Entity.OpponentOfSelf) },
     // item quantity/quality
     // range and area of effect
     // other
     { "knocks back enemies", TotalOverride, 100, Effect.Knockback.Chance },
     { "knocks enemies back", TotalOverride, 100, Effect.Knockback.Chance },
     { "knockback(?! distance)", TotalOverride, 100, Effect.Knockback.Chance },
 };