Exemple #1
0
    public void EquipArmor(Armor armor)
    {
        switch (armor.MyArmorType)
        {
        case ArmorType.Head:
            Head.EquipArmor(armor);
            break;

        case ArmorType.Chest:
            Chest.EquipArmor(armor);
            break;

        case ArmorType.MainHand:
            MainHand.EquipArmor(armor);
            break;

        case ArmorType.Offhand:
            Offhand.EquipArmor(armor);
            break;

        case ArmorType.Charm:
            Charm1.EquipArmor(armor);
            break;
        }
    }
        public bool?IsBetterThanHand(SortedHand secondHand)
        // Compare the main hand and kicker hand of two hands of the same type (e.g. flush, pair, etc...)
        {
            for (int i = 0; i < MainHand.CardCount(); i++)
            {
                if (MainHand.GetCards()[i].Value > secondHand.MainHand.GetCards()[i].Value)
                {
                    return(true);
                }
                else if (MainHand.GetCards()[i].Value < secondHand.MainHand.GetCards()[i].Value)
                {
                    return(false);
                }
            }

            if (KickerHand != null)
            {
                for (int i = 0; i < KickerHand.CardCount(); i++)
                {
                    if (KickerHand.GetCards()[i].Value > secondHand.KickerHand.GetCards()[i].Value)
                    {
                        return(true);
                    }
                    else if (KickerHand.GetCards()[i].Value < secondHand.KickerHand.GetCards()[i].Value)
                    {
                        return(false);
                    }
                }
            }

            return(null);
        }
        public string BuildSortedHandString()
        {
            string sortedHandString = "(";

            if (MainHand != null && MainHand.CardCount() != 0)
            {
                foreach (Card card in MainHand.GetCards())
                {
                    sortedHandString += String.Format("{0}{1}, ", FriendlyValueNames[card.Value], Constants.FriendlySuitNames[card.Suit]);
                }
                sortedHandString = sortedHandString.Remove(sortedHandString.Length - 2);
            }
            sortedHandString += "), (";
            if (KickerHand != null && KickerHand.CardCount() != 0)
            {
                foreach (Card card in KickerHand.GetCards())
                {
                    sortedHandString += String.Format("{0}{1}, ", FriendlyValueNames[card.Value], Constants.FriendlySuitNames[card.Suit]);
                }
                sortedHandString = sortedHandString.Remove(sortedHandString.Length - 2);
            }
            sortedHandString += ")";

            return(sortedHandString);
        }
 public void ChangeMainHand(Item newMainHand)
 {
     mainHand.GetComponent <SpriteResolver>().SetCategoryAndLabel(InventoryCategory.Main_Hand.ToString(), newMainHand.name.Replace(" ", "_"));
     inventory.Remove(newMainHand);
     inventory.Add(currentMainHand);
     currentMainHand = (MainHand)newMainHand;
     Player.instance.UpdateStats();
 }
Exemple #5
0
        public bool FixMainHandMisfire()    //TODO: remark that we only ever do this for MH, so we make that assumption
        {
            ValidateFixMainHandMisfire();

            ActionAvailable             = false;
            NumberOfShotsLostToMisfire += PlayerBase.NumberOfAttacks;
            return(MainHand.FixMisfire(PlayerBase.Proficiency));
        }
Exemple #6
0
        private AttackSummary DoMainHandShoot(IEnemy enemy) //young would make fun of me if he saw this
        {
            IModifier finalHitMod = (spellHitMod != null)
                ? PlayerBase.HitModifier.Add(spellHitMod)
                : PlayerBase.HitModifier;

            IModifier finalDmgMod = (spellDmgMod != null)
                ? PlayerBase.MainHandDamageModifier.Add(spellDmgMod)
                : PlayerBase.MainHandDamageModifier;

            CombatStats combatStats = new CombatStats(finalHitMod, finalDmgMod, PlayerBase.CritValue);

            return(MainHand.Shoot(enemy, combatStats));
        }
Exemple #7
0
 public override void ReadPacket()
 {
     Locale                    = Read <string>();
     ViewDistance              = Read <SByte>();
     ChatMode                  = (ChatMode)(int)Read <VarInt>();
     ChatColors                = Read <bool>();
     displayedSkinParts        = (DisplayedSkinParts)Read <byte>();
     mainHand                  = (MainHand)(int)Read <VarInt>();
     Player.Locale             = Locale;
     Player.ViewDistance       = ViewDistance;
     Player.ChatMode           = ChatMode;
     Player.ChatColors         = ChatColors;
     Player.DisplayedSkinParts = displayedSkinParts;
     Player.MainHand           = mainHand;
 }
Exemple #8
0
 public void Dispose()
 {
     Armor.Dispose();
     MainHand.Dispose();
     OffHand.Dispose();
     Ring.Dispose();
     Ring2.Dispose();
     Amulet.Dispose();
     Helm.Dispose();
     Gloves.Dispose();
     Boots.Dispose();
     Belt.Dispose();
     Flasks.ForEach(vm => vm.Dispose());
     TreeJewels.ForEach(vm => vm.Dispose());
     ItemJewels.Values.Flatten().ForEach(vm => vm.Dispose());
 }
Exemple #9
0
        private bool AttemptMhShoot(IEnemy enemy)
        {
            bool shot = false;

            if (MainHand.HasShotLoaded())
            {
                MainHandShoot(enemy);
                shot = true;
            }
            else
            {
                MainHand.ReloadChamber();
            }

            return(shot);
        }
Exemple #10
0
 protected IConditionBuilder MainHandAttackWith(Tags tags) =>
 MainHandAttack.And(MainHand.Has(tags));
Exemple #11
0
 protected IConditionBuilder MainHandAttackWith(Tags tags) =>
 Condition.AttackWith(AttackDamageHand.MainHand).And(MainHand.Has(tags));
Exemple #12
0
 protected override IEnumerable <MatcherData> CreateCollection() =>
 new ConditionMatcherCollection(_modifierBuilder)
 {
     // actions
     // - generic
     { "on ({ActionMatchers})", Reference.AsAction.On() },
     { "if you've ({ActionMatchers}) recently", Reference.AsAction.Recently },
     { "if you haven't ({ActionMatchers}) recently", Not(Reference.AsAction.Recently) },
     {
         "when you ({ActionMatchers}) a rare or unique enemy",
         And(Reference.AsAction.Against(Enemy).On(), Enemy.IsRareOrUnique)
     },
     // - kill
     { "on ({KeywordMatchers}) kill", Kill.On(Reference.AsKeyword) },
     { "when you kill an enemy,", Kill.Against(Enemy).On() },
     {
         "if you've killed a maimed enemy recently",
         And(Kill.Against(Enemy).Recently, Buff.Maim.IsOn(Enemy))
     },
     {
         "if you've killed a bleeding enemy recently",
         And(Kill.Against(Enemy).Recently, Ailment.Bleed.IsOn(Enemy))
     },
     {
         "if you've killed a cursed enemy recently",
         And(Kill.Against(Enemy).Recently, Buffs(target: Enemy).With(Keyword.Curse).Any())
     },
     {
         "if you or your totems have killed recently",
         Or(Kill.Recently, Kill.By(Entity.Totem).Recently)
     },
     { "if you or your totems kill an enemy", Or(Kill.On(), Kill.By(Entity.Totem).On()) },
     // - block
     { "when they block", Block.On() },
     { "when you block", Block.On() },
     {
         "if you've blocked a hit from a unique enemy recently",
         And(Block.Against(Enemy).Recently, Enemy.IsUnique)
     },
     // - hit
     { "(from|with) hits", Hit.On() },
     { "hits deal", Hit.On() },
     { "when you are hit", Hit.Taken.On() },
     { "if you've been hit recently", Hit.Taken.Recently },
     { "if you haven't been hit recently", Not(Hit.Taken.Recently) },
     { "if you were damaged by a hit recently", Hit.Taken.Recently },
     { "if you've taken no damage from hits recently", Not(Hit.Taken.Recently) },
     // - other
     { "if you've taken a savage hit recently", Action.SavageHit.Taken.Recently },
     { "when you deal a critical strike", CriticalStrike.On() },
     { "if you've crit in the past # seconds", CriticalStrike.InPastXSeconds(Value) },
     { "if you've shattered an enemy recently", Action.Shatter.Against(Enemy).Recently },
     { "when you stun an enemy", Effect.Stun.On() },
     { "after spending # mana", Action.SpendMana(Value).On() },
     { "if you have consumed a corpse recently", Action.ConsumeCorpse.Recently },
     { "when you gain a ({ChargeTypeMatchers})", Reference.AsChargeType.GainAction.On() },
     // damage
     // - by item tag
     { "with weapons", Damage.With(Tags.Weapon) },
     { "weapon", Damage.With(Tags.Weapon) },
     { "with bows", Damage.With(Tags.Bow) },
     { "bow", Damage.With(Tags.Bow) },
     { "with swords", Damage.With(Tags.Sword) },
     { "with claws", Damage.With(Tags.Claw) },
     { "claw", Damage.With(Tags.Claw) },
     { "with daggers", Damage.With(Tags.Dagger) },
     { "with wands", Damage.With(Tags.Wand) },
     { "wand", Damage.With(Tags.Wand) },
     { "with axes", Damage.With(Tags.Axe) },
     { "with staves", Damage.With(Tags.Staff) },
     { "with maces", Or(Damage.With(Tags.Mace), Damage.With(Tags.Sceptre)) },
     { "with one handed weapons", Damage.With(Tags.OneHandWeapon) },
     {
         "with one handed melee weapons",
         And(Damage.With(Tags.OneHandWeapon), Not(Damage.With(Tags.Ranged)))
     },
     { "with two handed weapons", Damage.With(Tags.TwoHandWeapon) },
     {
         "with two handed melee weapons",
         And(Damage.With(Tags.TwoHandWeapon), Not(Damage.With(Tags.Ranged)))
     },
     // - by item slot
     { "with the main-hand weapon", Damage.With(ItemSlot.MainHand) },
     { "with main hand", Damage.With(ItemSlot.MainHand) },
     { "with off hand", Damage.With(ItemSlot.OffHand) },
     // - by source
     { "attacks have", Damage.With(Source.Attack) },
     { "with attacks", Damage.With(Source.Attack) },
     { "from damage over time", Damage.With(Source.DamageOverTime) },
     // - by ailment
     { "with ({AilmentMatchers})", Damage.With(Reference.AsAilment) },
     { "with ailments", Ailment.All.Any(Damage.With) },
     // action and damage combinations
     // - by item tag
     { "if you get a critical strike with a bow", And(CriticalStrike.On(), Damage.With(Tags.Bow)) },
     { "if you get a critical strike with a staff", And(CriticalStrike.On(), Damage.With(Tags.Staff)) },
     { "critical strikes with daggers have a", And(CriticalStrike.On(), Damage.With(Tags.Dagger)) },
     // - by item slot
     // - by source
     { "for each enemy hit by your attacks", And(Hit.Against(Enemy).On(), Damage.With(Source.Attack)) },
     // - by ailment
     { "with hits and ailments", Or(Hit.On(), Ailment.All.Any(Damage.With)) },
     {
         "poison you inflict with critical strikes deals",
         And(Damage.With(Ailment.Poison), CriticalStrike.On())
     },
     // equipment
     { "while unarmed", Not(MainHand.HasItem) },
     { "while wielding a staff", MainHand.Has(Tags.Staff) },
     { "while wielding a dagger", MainHand.Has(Tags.Dagger) },
     { "while wielding a bow", MainHand.Has(Tags.Bow) },
     { "while wielding a sword", MainHand.Has(Tags.Sword) },
     { "while wielding a claw", MainHand.Has(Tags.Claw) },
     { "while wielding an axe", MainHand.Has(Tags.Axe) },
     { "while wielding a mace", Or(MainHand.Has(Tags.Mace), MainHand.Has(Tags.Sceptre)) },
     { "while wielding a melee weapon", And(MainHand.Has(Tags.Weapon), Not(MainHand.Has(Tags.Ranged))) },
     { "while wielding a one handed weapon", MainHand.Has(Tags.OneHandWeapon) },
     { "while wielding a two handed weapon", MainHand.Has(Tags.TwoHandWeapon) },
     { "while dual wielding", OffHand.Has(Tags.Weapon) },
     { "while holding a shield", OffHand.Has(Tags.Shield) },
     { "while dual wielding or holding a shield", Or(OffHand.Has(Tags.Weapon), OffHand.Has(Tags.Shield)) },
     { "with shields", OffHand.Has(Tags.Shield) },
     { "from equipped shield", And(Condition.BaseValueComesFrom(OffHand), OffHand.Has(Tags.Shield)) },
     { "with # corrupted items equipped", Equipment.Count(e => e.IsCorrupted) >= Value },
     // stats
     // - pool
     { "when on low life", Life.IsLow },
     { "when not on low life", Not(Life.IsLow) },
     { "while no mana is reserved", Mana.Reservation.Value == 0 },
     { "while energy shield is full", EnergyShield.IsFull },
     { "while on full energy shield", EnergyShield.IsFull },
     { "while not on full energy shield", Not(EnergyShield.IsFull) },
     { "if energy shield recharge has started recently", EnergyShield.Recharge.StartedRecently },
     // - charges
     { "while you have no ({ChargeTypeMatchers})", Reference.AsChargeType.Amount.Value == 0 },
     {
         "while (at maximum|on full) ({ChargeTypeMatchers})",
         Reference.AsChargeType.Amount.Value == Reference.AsChargeType.Amount.Maximum.Value
     },
     // - flags
     { "while you have ({FlagMatchers})", Reference.AsFlagStat.IsSet },
     { "during onslaught", Flag.Onslaught.IsSet },
     { "while phasing", Flag.Phasing.IsSet },
     // - other
     { "if you have # primordial jewels,", Stat.PrimordialJewelsSocketed.Value >= Value },
     // - on enemy
     { "(against enemies )?that are on low life", Enemy.Stat(Life).IsLow },
     { "(against enemies )?that are on full life", Enemy.Stat(Life).IsFull },
     { "against rare and unique enemies", Enemy.IsRareOrUnique },
     // buffs
     { "while you have fortify", Buff.Fortify.IsOn(Self) },
     { "if you've taunted an enemy recently", Buff.Taunt.Action.Recently },
     { "enemies you taunt( deal| take)?", And(For(Enemy), Buff.Taunt.IsOn(Enemy)) },
     { "enemies you curse (take|have)", And(For(Enemy), Buffs(Self, Enemy).With(Keyword.Curse).Any()) },
     { "(against|from) blinded enemies", Buff.Blind.IsOn(Enemy) },
     { "from taunted enemies", Buff.Taunt.IsOn(Enemy) },
     {
         "you and allies affected by your auras have",
         Or(For(Entity.ModifierSource), And(For(Ally), Buffs(target: Ally).With(Keyword.Aura).Any()))
     },
     {
         "you and allies deal while affected by auras you cast",
         Or(For(Entity.ModifierSource), And(For(Ally), Buffs(target: Ally).With(Keyword.Aura).Any()))
     },
     // buff and damage combinations
     {
         "bleeding you inflict on maimed enemies deals",
         And(Damage.With(Ailment.Bleed), Buff.Maim.IsOn(Enemy))
     },
     // ailments
     { "while ({AilmentMatchers})", Reference.AsAilment.IsOn(Self) },
     { "(against|from) ({AilmentMatchers}) enemies", Reference.AsAilment.IsOn(Enemy) },
     {
         "against frozen, shocked or ignited enemies",
         Or(Ailment.Freeze.IsOn(Enemy), Ailment.Shock.IsOn(Enemy), Ailment.Ignite.IsOn(Enemy))
     },
     { "enemies which are ({AilmentMatchers})", Reference.AsAilment.IsOn(Enemy) },
     {
         "against enemies( that are)? affected by elemental ailments",
         Ailment.Elemental.Any(a => a.IsOn(Enemy))
     },
     {
         "against enemies( that are)? affected by no elemental ailments",
         Not(Ailment.Elemental.Any(a => a.IsOn(Enemy)))
     },
     // ground effects
     { "while on consecrated ground", Ground.Consecrated.IsOn(Self) },
     // other effects
     { "against burning enemies", Fire.DamageOverTimeIsOn(Enemy) },
     // skills
     // - by keyword
     { "vaal( skill)?", With(Skills[Keyword.Vaal]) },
     { "({KeywordMatchers})", With(Skills[Reference.AsKeyword]) },
     {
         "({KeywordMatchers}) and ({KeywordMatchers})",
         Or(With(Skills[References[0].AsKeyword]), With(Skills[References[1].AsKeyword]))
     },
     { "(with|of|for|from) ({KeywordMatchers})( skills)?", With(Skills[Reference.AsKeyword]) },
     { "({KeywordMatchers}) skills (have|deal)", With(Skills[Reference.AsKeyword]) },
     // - by damage type
     { "with ({DamageTypeMatchers}) skills", With(Skills[Reference.AsDamageType]) },
     // - by item slot
     { "skills (in|from) your ({ItemSlotMatchers})(can have| have)?", With(Skills[Reference.AsItemSlot]) },
     // - by single skill
     { "({SkillMatchers})('|s)?( fires| has a| have a| has| deals| gain)?", With(Reference.AsSkill) },
     { "(dealt by) ({SkillMatchers})", With(Reference.AsSkill) },
     // - cast recently/in past x seconds
     { "if you've cast a spell recently", Skills[Keyword.Spell].Cast.Recently },
     { "if you've attacked recently", Skills[Keyword.Attack].Cast.Recently },
     { "if you've used a movement skill recently", Skills[Keyword.Movement].Cast.Recently },
     { "if you've used a warcry recently", Skills[Keyword.Warcry].Cast.Recently },
     {
         "if you've used a ({DamageTypeMatchers}) skill in the past # seconds",
         Skills[Reference.AsDamageType].Cast.InPastXSeconds(Value)
     },
     // skill and action combinations
     {
         "projectiles have against targets they pierce",
         And(Projectile.Pierce.On(), With(Skills[Keyword.Projectile]))
     },
     // traps and mines
     { "with traps", With(Traps) },
     { "with mines", With(Mines) },
     { "traps and mines (deal|have a)", Or(With(Traps), With(Mines)) },
     { "from traps and mines", Or(With(Traps), With(Mines)) },
     { "for throwing traps", With(Traps) },
     { "if you detonated mines recently", Skill.DetonateMines.Cast.Recently },
     { "if you've placed a mine or thrown a trap recently", Or(Traps.Cast.Recently, Mines.Cast.Recently) },
     // totems
     { "totems", For(Entity.Totem) },
     { "totems (fire|gain|have)", With(Totems) },
     { "(spells cast|attacks used|skills used) by totems (have a|have)", With(Totems) },
     { "of totem skills that cast an aura", With(Skills[Keyword.Totem, Keyword.Aura]) },
     { "while you have a totem", Totems.Any(s => s.HasInstance) },
     { "if you've summoned a totem recently", Totems.Cast.Recently },
     { "when you place a totem", Totems.Cast.On() },
     // minions
     { "minions", For(Entity.Minion) },
     { "minions (deal|have|gain)", For(Entity.Minion) },
     { "you and your minions have", For(Entity.Minion, Entity.ModifierSource) },
     { "golem", For(Entity.Minion.With(Keyword.Golem)) },
     { "golems have", For(Entity.Minion.With(Keyword.Golem)) },
     { "spectres have", For(Entity.Minion.From(Skill.RaiseSpectre)) },
     {
         // Technically this would be separate for each minion summoned by that skill, but DPS will
         // only be calculated for a single minion anyway.
         "golems summoned in the past # seconds deal",
         With(Golems.Where(s => s.Cast.InPastXSeconds(Value)))
     },
     { "if you Summoned a golem in the past # seconds", Golems.Cast.InPastXSeconds(Value) },
     // flasks
     { "while using a flask", Flask.IsAnyActive },
     { "during any flask effect", Flask.IsAnyActive },
     // other
     { "while leeching", Condition.WhileLeeching },
     { "(you )?gain", Condition.True },     // may be left over at the end, does nothing
     // unique
     {
         "when your trap is triggered by an enemy",
         Condition.Unique("When your Trap is triggered by an Enemy")
     },
     {
         "when your mine is detonated targeting an enemy",
         Condition.Unique("When your Mine is detonated targeting an Enemy")
     },
     {
         "if you've killed an enemy affected by your damage over time recently",
         Condition.Unique("Have you recently killed an Enemy affected by your Damage over Time?")
     },
 };
Exemple #13
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 },
 };
 protected override IEnumerable <MatcherData> CreateCollection() =>
 new StatMatcherCollection <IStatBuilder>(_modifierBuilder)
 {
     // attributes
     { "strength", Attribute.Strength },
     { "strength damage bonus", Attribute.StrengthDamageBonus },
     { "dexterity", Attribute.Dexterity },
     { "dexterity evasion bonus", Attribute.DexterityEvasionBonus },
     { "intelligence", Attribute.Intelligence },
     { "strength and dexterity", ApplyOnce(Attribute.Strength, Attribute.Dexterity) },
     { "strength and intelligence", ApplyOnce(Attribute.Strength, Attribute.Intelligence) },
     { "dexterity and intelligence", ApplyOnce(Attribute.Dexterity, Attribute.Intelligence) },
     { "attributes", ApplyOnce(Attribute.Strength, Attribute.Dexterity, Attribute.Intelligence) },
     // offense
     // - damage: see also DamageStatMatchers
     { "chance to deal double damage", Damage.ChanceToDouble },
     {
         "({DamageTypeMatchers}) damage (gained |added )?as (extra )?({DamageTypeMatchers}) damage",
         References[0].AsDamageType.Damage.AddAs(References[1].AsDamageType.Damage)
     },
     {
         "({DamageTypeMatchers}) damage as extra damage of a random element",
         Reference.AsDamageType.Damage.AddAs(RandomElement.Damage)
     },
     {
         "({DamageTypeMatchers}) damage converted to ({DamageTypeMatchers}) damage",
         References[0].AsDamageType.Damage.ConvertTo(References[1].AsDamageType.Damage)
     },
     { "({DamageTypeMatchers}) damage taken", Reference.AsDamageType.Damage.Taken },
     { "take ({DamageTypeMatchers}) damage", Reference.AsDamageType.Damage.Taken },
     { "damage taken", Damage.Taken },
     // - penetration
     // - crit
     { "(global )?critical strike multiplier", CriticalStrike.Multiplier },
     { "(global )?critical strike chance", CriticalStrike.Chance },
     // - projectiles
     { "projectile speed", Projectile.Speed },
     { "arrow speed", Projectile.Speed, Damage.With(Tags.Bow) },
     // - other
     { "accuracy rating", Stat.Accuracy },
     // defense
     // - life, mana, defences; see also PoolStatMatchers
     { "armour", Armour },
     { "evasion( rating)?", Evasion },
     { "evasion rating and armour", ApplyOnce(Armour, Evasion) },
     { "armour and evasion( rating)?", ApplyOnce(Armour, Evasion) },
     { "armour and energy shield", ApplyOnce(Armour, EnergyShield) },
     { "(global )?defences", ApplyOnce(Armour, Evasion, EnergyShield) },
     // - resistances
     { "({DamageTypeMatchers}) resistance", Reference.AsDamageType.Resistance },
     { "all elemental resistances", Elemental.Resistance },
     { "maximum ({DamageTypeMatchers}) resistance", Reference.AsDamageType.Resistance.Maximum },
     { "all maximum resistances", Elemental.And(Chaos).Resistance.Maximum },
     { "physical damage reduction", Physical.Resistance },
     // - leech
     {
         @"(?<pool>({PoolStatMatchers})) per second to \k<pool> Leech rate",
         Reference.AsPoolStat.Leech.RateLimit
     },
     {
         "({DamageStatMatchers}) leeched as ({PoolStatMatchers})",
         References[1].AsPoolStat.Leech.Of(References[0].AsDamageStat)
     },
     {
         "({DamageStatMatchers}) leeched as ({PoolStatMatchers}) and ({PoolStatMatchers})",
         References[1].AsPoolStat.Leech.Of(References[0].AsDamageStat),
         References[2].AsPoolStat.Leech.Of(References[0].AsDamageStat)
     },
     {
         "damage dealt by your totems is leeched to you as life",
         Life.Leech.To(Entity.ModifierSource).Of(Damage), For(Entity.Totem)
     },
     { "({PoolStatMatchers}) leeched per second", Reference.AsPoolStat.Leech.Rate },
     // - block
     { "chance to block", Block.AttackChance },
     { "block chance", Block.AttackChance },
     { "maximum block chance", Block.AttackChance.Maximum },
     { "chance to block spells", Block.SpellChance },
     { "chance to block spells and attacks", ApplyOnce(Block.SpellChance, Block.AttackChance) },
     // - other
     { "chance to dodge attacks", Stat.Dodge.AttackChance },
     { "chance to dodge spell damage", Stat.Dodge.SpellChance },
     { "chance to evade( attacks)?", Evasion.Chance },
     { "chance to evade projectile attacks", Evasion.ChanceAgainstProjectileAttacks },
     { "chance to evade melee attacks", Evasion.ChanceAgainstMeleeAttacks },
     {
         "damage is taken from ({PoolStatMatchers}) before ({PoolStatMatchers})",
         Damage.TakenFrom(References[0].AsPoolStat).Before(References[1].AsPoolStat)
     },
     {
         "({DamageTypeMatchers}) damage is taken from ({PoolStatMatchers}) before ({PoolStatMatchers})",
         References[0].AsDamageType.Damage.TakenFrom(References[1].AsPoolStat)
         .Before(References[2].AsPoolStat)
     },
     // speed
     { "attack speed", Skills[Keyword.Attack].Speed },
     { "cast speed", Skills.Speed, Not(With(Skills[Keyword.Attack])) },
     { "movement speed", Stat.MovementSpeed },
     {
         // not the most elegant solution but by far the easiest
         @"movement speed \(hidden\)",
         Stat.MovementSpeed, Not(Flag.IgnoreMovementSpeedPenalties.IsSet)
     },
     { "attack and cast speed", Skills.Speed },
     { "attack, cast( speed)? and movement speed", ApplyOnce(Skills.Speed, Stat.MovementSpeed) },
     { "animation speed", Stat.AnimationSpeed },
     // regen and recharge
     { "({PoolStatMatchers}) regeneration rate", Reference.AsPoolStat.Regen },
     { "energy shield recharge rate", EnergyShield.Recharge },
     {
         "recovery rate of life, mana and energy shield",
         Life.RecoveryRate, Mana.RecoveryRate, EnergyShield.RecoveryRate
     },
     // gain
     // charges
     { "(?<!maximum )({ChargeTypeMatchers})", Reference.AsChargeType.Amount },
     { "(?<!while at )maximum ({ChargeTypeMatchers})", Reference.AsChargeType.Amount.Maximum },
     {
         "maximum ({ChargeTypeMatchers}) and maximum ({ChargeTypeMatchers})",
         ApplyOnce(References[0].AsChargeType.Amount.Maximum, References[1].AsChargeType.Amount.Maximum)
     },
     { "chance to (gain|grant) an? ({ChargeTypeMatchers})", Reference.AsChargeType.ChanceToGain },
     {
         "chance to (gain|grant) an? ({ChargeTypeMatchers}) and an? ({ChargeTypeMatchers})",
         References[0].AsChargeType.ChanceToGain, References[1].AsChargeType.ChanceToGain
     },
     { "({ChargeTypeMatchers}) duration", Reference.AsChargeType.Duration },
     {
         "endurance, frenzy and power charge duration",
         Charge.Endurance.Duration, Charge.Frenzy.Duration, Charge.Power.Duration
     },
     // skills
     { "cooldown recovery speed", Skills.CooldownRecoverySpeed },
     { "mana cost( of skills)?", Skills.Cost },
     { "skill effect duration", Skills.Duration },
     { "mana reserved", Skills.Reservation },
     { "({KeywordMatchers}) duration", Skills[Reference.AsKeyword].Duration },
     // traps, mines, totems
     { "traps? placed at a time", Traps.CombinedInstances.Maximum },
     { "remote mines? placed at a time", Mines.CombinedInstances.Maximum },
     { "totems? summoned at a time", Totems.CombinedInstances.Maximum },
     { "trap trigger area of effect", Stat.TrapTriggerAoE },
     { "mine detonation area of effect", Stat.MineDetonationAoE },
     { "trap throwing speed", Traps.Speed },
     { "mine laying speed", Mines.Speed },
     { "totem placement speed", Totems.Speed },
     { "totem life", Life, For(Entity.Totem) },
     // minions
     {
         "maximum number of skeletons",
         Combine(Skill.SummonSkeleton, Skill.VaalSummonSkeletons).CombinedInstances.Maximum
     },
     { "maximum number of spectres", Skill.RaiseSpectre.Instances.Maximum },
     { "maximum number of zombies", Skill.RaiseZombie.Instances.Maximum },
     { "skeleton duration", Skill.SummonSkeleton.Duration, Skill.VaalSummonSkeletons.Duration },
     { "golem at a time", Golems.CombinedInstances.Maximum },
     // buffs
     { "effect of buffs granted by your golems", Buffs(Entity.Minion.With(Keyword.Golem)).Effect },
     {
         "effect of buffs granted by your elemental golems",
         Buffs(Entity.Minion.With(Keyword.Golem, Elemental)).Effect
     },
     { "effect of your curses", Buffs(Self).With(Keyword.Curse).Effect },
     { "effect of curses on you", Buffs(target: Self).With(Keyword.Curse).Effect },
     { "effect of non-curse auras you cast", Buffs(Self).With(Keyword.Aura).Without(Keyword.Curse).Effect },
     { "chance to fortify", Buff.Fortify.ChanceOn(Self) },
     { "effect of fortify on you", Buff.Fortify.Effect },
     { "fortify duration", Buff.Fortify.Duration },
     { "chance for attacks to maim", Buff.Maim.ChanceOn(Enemy), Damage.With(Source.Attack) },
     { "chance to taunt", Buff.Taunt.ChanceOn(Enemy) },
     { "taunt duration", Buff.Taunt.Duration },
     { "chance to blind enemies", Buff.Blind.ChanceOn(Enemy) },
     { "blind duration", Buff.Blind.Duration },
     // flags
     {
         "chance to (gain|grant) ({FlagMatchers})",
         Reference.AsFlagStat     // chance is handled by StatManipulationMatchers
     },
     { "({FlagMatchers}) duration", Reference.AsFlagStat.Duration },
     { "({FlagMatchers}) effect", Reference.AsFlagStat.Effect },
     // ailments
     { "chance to ({AilmentMatchers})( the enemy)?", Reference.AsAilment.Chance },
     {
         "chance to freeze, shock and ignite",
         Ailment.Freeze.Chance, Ailment.Shock.Chance, Ailment.Ignite.Chance
     },
     { "chance to avoid being ({AilmentMatchers})", Reference.AsAilment.Avoidance },
     { "chance to avoid elemental ailments", Ailment.Elemental.Select(a => a.Avoidance) },
     { "({AilmentMatchers}) duration( on enemies)?", Reference.AsAilment.Duration },
     { "duration of elemental ailments on enemies", Ailment.Elemental.Select(a => a.Duration) },
     // stun
     { "chance to avoid being stunned", Effect.Stun.Avoidance },
     { "stun and block recovery", Effect.Stun.Recovery, Block.Recovery },
     { "block recovery", Block.Recovery },
     { "stun threshold", Effect.Stun.Threshold },
     { "enemy stun threshold", Enemy.Stat(Effect.Stun.Threshold) },
     { "stun duration( on enemies)?", Enemy.Stat(Effect.Stun.Duration) },
     { "stun duration (?<inner>with .*) on enemies", Enemy.Stat(Effect.Stun.Duration), "${inner}" },
     {
         "chance to avoid interruption from stuns while casting",
         Effect.Stun.ChanceToAvoidInterruptionWhileCasting
     },
     { "chance to double stun duration", Effect.Stun.Duration.ChanceToDouble },
     // flasks
     { "effect of flasks( on you)?", Flask.Effect },
     { "flask effect duration", Flask.Duration },
     { "life recovery from flasks", Flask.LifeRecovery },
     { "mana recovery from flasks", Flask.ManaRecovery },
     { "flask charges used", Flask.ChargesUsed },
     { "flask charges gained", Flask.ChargesGained },
     { "flask recovery (speed|rate)", Flask.RecoverySpeed },
     // item quantity/quality
     { "quantity of items found", Stat.ItemQuantity },
     { "rarity of items found", Stat.ItemRarity },
     // range and area of effect
     { "area of effect", Skills.AreaOfEffect },
     { "melee weapon and unarmed range", Stat.Range, Not(MainHand.Has(Tags.Ranged)) },
     { "melee weapon range", Stat.Range, And(MainHand.Has(Tags.Weapon), Not(MainHand.Has(Tags.Ranged))) },
     // other
     { "rampage stacks", Stat.RampageStacks },
     { "chance to knock enemies back", Effect.Knockback.ChanceOn(Enemy) },
     { "knockback distance", Effect.Knockback.Distance },
     // Not really anything that can be done with them (yet), but should still be summed up
     { "character size", Stat.Unique("Character Size") },
     { "reflected elemental damage taken", Stat.Unique("Reduced Reflected Elemental Damage taken") },
     { "reflected physical damage taken", Stat.Unique("Reduced Reflected Physical Damage taken") },
     {
         "damage taken gained as mana over 4 seconds when hit",
         Stat.Unique("#% of Damage taken gained as Mana over 4 seconds when Hit")
     },
     { "light radius", Stat.Unique("Light Radius") },
 };
Exemple #15
0
 private bool ContinueUsingAttackActions(int attackNum)    //Note attackNum is 0-indexed; i.e. first attack is 0.
 {
     return(MainHand.CanFire() &&
            attackNum < PlayerBase.NumberOfAttacks);
 }
Exemple #16
0
 private void ValidateMainHandAttack(IEnemy enemy)
 {
     Assert.IsNotNull(enemy);
     Assert.IsTrue(ActionAvailable);
     Assert.IsTrue(MainHand.CanFire());
 }
Exemple #17
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 },
 };
Exemple #18
0
 protected override IReadOnlyList <MatcherData> CreateCollection() =>
 new StatMatcherCollection <IStatBuilder>(_modifierBuilder)
 {
     // attributes
     { "strength", Attribute.Strength },
     { "strength damage bonus", Attribute.StrengthDamageBonus },
     { "dexterity", Attribute.Dexterity },
     { "dexterity evasion bonus", Attribute.DexterityEvasionBonus },
     { "intelligence", Attribute.Intelligence },
     { "strength and dexterity", ApplyOnce(Attribute.Strength, Attribute.Dexterity) },
     { "strength and intelligence", ApplyOnce(Attribute.Strength, Attribute.Intelligence) },
     { "dexterity and intelligence", ApplyOnce(Attribute.Dexterity, Attribute.Intelligence) },
     { "(all )?attributes", ApplyOnce(Attribute.Strength, Attribute.Dexterity, Attribute.Intelligence) },
     // offense
     // - damage: see also DamageStatMatchers
     { "chance to deal double damage", Damage.ChanceToDouble },
     // - damage multiplier
     {
         "({DamageTypeMatchers}) damage over time multiplier",
         Reference.AsDamageType.DamageMultiplier.With(DamageSource.OverTime)
     },
     {
         "non-ailment ({DamageTypeMatchers}) damage over time multiplier",
         Reference.AsDamageType.DamageMultiplier.WithSkills(DamageSource.OverTime)
     },
     // - damage taken
     { "damage taken", Damage.Taken },
     { "({DamageTypeMatchers}) damage taken", Reference.AsDamageType.Damage.Taken },
     { "take ({DamageTypeMatchers}) damage", Reference.AsDamageType.Damage.Taken },
     { "damage taken from hits", Damage.Taken.WithHits },
     { "({DamageTypeMatchers}) damage taken from hits", Reference.AsDamageType.Damage.Taken.WithHits },
     { "damage taken from damage over time", Damage.Taken.With(DamageSource.OverTime) },
     {
         "({DamageTypeMatchers}) damage taken over time",
         Reference.AsDamageType.Damage.Taken.With(DamageSource.OverTime)
     },
     {
         "take ({DamageTypeMatchers}) damage over time",
         Reference.AsDamageType.Damage.Taken.With(DamageSource.OverTime)
     },
     { "damage taken from projectiles", Damage.Taken.With(Keyword.Projectile) },
     {
         "damage taken from trap or mine hits",
         Damage.Taken.With(Keyword.Trap).WithHits, Damage.Taken.With(Keyword.Mine).WithHits
     },
     // - damage taken as
     {
         "({DamageTypeMatchers}) damage from hits taken as lightning damage",
         Reference.AsDamageType.HitDamageTakenAs(DamageType.Lightning)
     },
     {
         "({DamageTypeMatchers}) damage from hits taken as cold damage",
         Reference.AsDamageType.HitDamageTakenAs(DamageType.Cold)
     },
     {
         "({DamageTypeMatchers}) damage from hits taken as fire damage",
         Reference.AsDamageType.HitDamageTakenAs(DamageType.Fire)
     },
     {
         "({DamageTypeMatchers}) damage from hits taken as chaos damage",
         Reference.AsDamageType.HitDamageTakenAs(DamageType.Chaos)
     },
     // - penetration
     // - crit
     { "(global )?critical strike multiplier", CriticalStrike.Multiplier.WithSkills },
     { "(global )?critical strike chance", CriticalStrike.Chance },
     {
         "({KeywordMatchers}) critical strike multiplier",
         CriticalStrike.Multiplier.WithSkills.With(Reference.AsKeyword)
     },
     { "({KeywordMatchers}) critical strike chance", CriticalStrike.Chance.With(Reference.AsKeyword) },
     { "projectiles have critical strike chance", CriticalStrike.Chance.With(Keyword.Projectile) },
     // - projectiles
     { "projectile speed", Projectile.Speed },
     { "arrow speed", Projectile.Speed, And(With(Keyword.Attack), MainHand.Has(Tags.Bow)) },
     // - other
     { "(global )?accuracy rating", Stat.Accuracy },
     { "minion accuracy rating", Stat.Accuracy.For(Entity.Minion) },
     // defense
     // - life, mana, defences; see also PoolStatMatchers
     { "armour", Armour },
     { "evasion( rating)?", Evasion },
     { "evasion rating and armour", ApplyOnce(Armour, Evasion) },
     { "armour and evasion( rating)?", ApplyOnce(Armour, Evasion) },
     { "armour and energy shield", ApplyOnce(Armour, EnergyShield) },
     { "(global )?defences", ApplyOnce(Armour, Evasion, EnergyShield) },
     { "minion maximum life", Life.For(Entity.Minion) },
     // - resistances
     { "({DamageTypeMatchers}) resistance", Reference.AsDamageType.Resistance },
     {
         "({DamageTypeMatchers}) and ({DamageTypeMatchers}) resistances",
         References[0].AsDamageType.Resistance, References[1].AsDamageType.Resistance
     },
     { "all elemental resistances", Elemental.Resistance },
     { "maximum ({DamageTypeMatchers}) resistance", Reference.AsDamageType.Resistance.Maximum },
     { "all maximum resistances", Elemental.And(Chaos).Resistance.Maximum },
     { "physical damage reduction", Physical.Resistance },
     // - leech
     {
         @"(?<pool>({PoolStatMatchers})) per second to \k<pool> Leech rate",
         Reference.AsPoolStat.Leech.RateLimit
     },
     {
         "damage leeched as ({PoolStatMatchers})",
         Reference.AsPoolStat.Leech.Of(Damage)
     },
     {
         "attack damage leeched as ({PoolStatMatchers})",
         Reference.AsPoolStat.Leech.Of(Damage.With(DamageSource.Attack))
     },
     {
         "spell damage leeched as ({PoolStatMatchers})",
         Reference.AsPoolStat.Leech.Of(Damage.With(DamageSource.Spell))
     },
     {
         "({DamageTypeMatchers}) damage leeched as ({PoolStatMatchers})",
         References[1].AsPoolStat.Leech.Of(References[0].AsDamageType.Damage)
     },
     {
         "({DamageTypeMatchers}) attack damage leeched as ({PoolStatMatchers})",
         References[1].AsPoolStat.Leech.Of(References[0].AsDamageType.Damage.With(DamageSource.Attack))
     },
     {
         "damage leeched as ({PoolStatMatchers}) and ({PoolStatMatchers})",
         References[0].AsPoolStat.Leech.Of(Damage), References[1].AsPoolStat.Leech.Of(Damage)
     },
     {
         "attack damage leeched as ({PoolStatMatchers}) and ({PoolStatMatchers})",
         References[0].AsPoolStat.Leech.Of(Damage.With(DamageSource.Attack)),
         References[1].AsPoolStat.Leech.Of(Damage.With(DamageSource.Attack))
     },
     {
         "damage dealt by your totems is leeched to you as life",
         Life.Leech.Of(Damage.For(Entity.Totem))
     },
     { "({PoolStatMatchers}) leeched per second", Reference.AsPoolStat.Leech.Rate },
     // - block
     { "chance to block", Block.AttackChance },
     { "chance to block attack damage", Block.AttackChance },
     { "chance to block spell damage", Block.SpellChance },
     { "chance to block spell and attack damage", Block.SpellChance, Block.AttackChance },
     { "enemy block chance", ApplyOnce(Block.SpellChance, Block.AttackChance).For(Enemy) },
     { "maximum chance to block attack damage", Block.AttackChance.Maximum },
     // - other
     { "chance to dodge attacks", Stat.Dodge.AttackChance },
     { "chance to dodge attack hits", Stat.Dodge.AttackChance },
     { "chance to dodge spell hits", Stat.Dodge.SpellChance },
     { "chance to dodge attack and spell hits", Stat.Dodge.AttackChance, Stat.Dodge.SpellChance },
     {
         "enemies have chance to dodge hits",
         ApplyOnce(Stat.Dodge.AttackChance, Stat.Dodge.SpellChance).For(Enemy)
     },
     { "chance to evade( attacks)?", Evasion.Chance },
     { "chance to evade projectile attacks", Evasion.ChanceAgainstProjectileAttacks },
     { "chance to evade melee attacks", Evasion.ChanceAgainstMeleeAttacks },
     {
         "damage is taken from ({PoolStatMatchers}) before ({PoolStatMatchers})",
         AnyDamageType.DamageTakenFrom(References[0].AsPoolStat).Before(References[1].AsPoolStat)
     },
     {
         "({DamageTypeMatchers}) damage is taken from ({PoolStatMatchers}) before ({PoolStatMatchers})",
         References[0].AsDamageType.DamageTakenFrom(References[1].AsPoolStat)
         .Before(References[2].AsPoolStat)
     },
     // speed
     { "attack speed", Stat.CastRate.With(DamageSource.Attack) },
     {
         "({KeywordMatchers}) attack speed",
         Stat.CastRate.With(DamageSource.Attack).With(Reference.AsKeyword)
     },
     { "cast speed", Stat.CastRate.With(DamageSource.Spell), Stat.CastRate.With(DamageSource.Secondary) },
     { "cast speed for curses", Stat.CastRate.With(DamageSource.Attack).With(Keyword.Curse) },
     { "movement speed", Stat.MovementSpeed },
     {
         // not the most elegant solution but by far the easiest
         @"movement speed \(hidden\)",
         Stat.MovementSpeed, Not(Flag.IgnoreMovementSpeedPenalties.IsSet)
     },
     { "attack and cast speed", Stat.CastRate },
     { "attack, cast( speed)? and movement speed", Stat.CastRate, Stat.MovementSpeed },
     { "hit rate", Stat.HitRate },
     { "brand activation frequency", Stat.HitRate, With(Keyword.Brand) },
     // regen and recharge
     { "({PoolStatMatchers}) regeneration rate", Reference.AsPoolStat.Regen },
     { "energy shield recharge rate", EnergyShield.Recharge },
     {
         "recovery rate of life, mana and energy shield",
         Life.RecoveryRate, Mana.RecoveryRate, EnergyShield.RecoveryRate
     },
     // gain
     // charges
     { "(?<!maximum |have an? )({ChargeTypeMatchers})", Reference.AsChargeType.Amount },
     { "(?<!while at )maximum ({ChargeTypeMatchers})", Reference.AsChargeType.Amount.Maximum },
     {
         "maximum ({ChargeTypeMatchers}) and maximum ({ChargeTypeMatchers})",
         ApplyOnce(References[0].AsChargeType.Amount.Maximum, References[1].AsChargeType.Amount.Maximum)
     },
     { "chance to (gain|grant) (an?|1) ({ChargeTypeMatchers})", Reference.AsChargeType.ChanceToGain },
     {
         "chance to (gain|grant) an? ({ChargeTypeMatchers}) and an? ({ChargeTypeMatchers})",
         References[0].AsChargeType.ChanceToGain, References[1].AsChargeType.ChanceToGain
     },
     {
         // Isn't really useful to parse, but if not, no damage related stat would be parsed, leading to
         // a ParseException because this is "on Hit with Claws".
         "chance to steal power, frenzy, and endurance charges",
         Charge.ChanceToSteal
     },
     { "({ChargeTypeMatchers}) duration", Reference.AsChargeType.Duration },
     {
         "endurance, frenzy and power charge duration",
         Charge.Endurance.Duration, Charge.Frenzy.Duration, Charge.Power.Duration
     },
     // skills
     { "cooldown recovery speed", Stat.CooldownRecoverySpeed },
     { "warcry cooldown recovery speed", Stat.CooldownRecoverySpeed, With(Keyword.Warcry) },
     { "cooldown recovery speed for throwing traps", Stat.CooldownRecoverySpeed, With(Keyword.Trap) },
     { "mana cost( of skills)?", Mana.Cost },
     {
         "mana cost of skills that place mines or throw traps",
         Mana.Cost, Or(With(Keyword.Mine), With(Keyword.Trap))
     },
     { "mana reserved", AllSkills.Reservation },
     { "mana reservation of skills", AllSkills.Reservation },
     { "mana reservation of ({KeywordMatchers}) skills", Skills[Reference.AsKeyword].Reservation },
     { "({SkillMatchers}) has mana reservation", Reference.AsSkill.Reservation },
     { "skill effect duration", Stat.Duration },
     { "skill duration", Stat.Duration },
     { "buff duration", Stat.Duration },
     { "warcry duration", Stat.Duration, With(Keyword.Warcry) },
     { "curse duration", Stat.Duration, With(Keyword.Curse) },
     // traps, mines, totems
     { "trap duration", Stat.Trap.Duration },
     { "mine duration", Stat.Mine.Duration },
     { "totem duration", Stat.Totem.Duration },
     { "traps? placed at a time", Traps.CombinedInstances.Maximum },
     { "remote mines? placed at a time", Mines.CombinedInstances.Maximum },
     { "totems? summoned at a time", Totems.CombinedInstances.Maximum },
     { "maximum number of summoned totems", Totems.CombinedInstances.Maximum },
     { "trap trigger area of effect", Stat.Trap.TriggerAoE },
     { "mine detonation area of effect", Stat.Mine.DetonationAoE },
     { "trap throwing speed", Stat.Trap.Speed },
     { "mine laying speed", Stat.Mine.Speed },
     { "totem placement speed", Stat.Totem.Speed },
     { "totem life", Life.For(Entity.Totem) },
     // minions
     {
         "maximum number of skeletons",
         Skills.SummonSkeleton.Instances.Maximum, Skills.VaalSummonSkeletons.Instances.Maximum
     },
     { "maximum number of spectres", Skills.RaiseSpectre.Instances.Maximum },
     { "maximum number of zombies", Skills.RaiseZombie.Instances.Maximum },
     { "minion duration", Stat.Duration, With(Keyword.Minion) },
     { "skeleton duration", Stat.Duration, WithSkeletonSkills },
     { "skeleton movement speed", Stat.MovementSpeed.For(Entity.Minion), WithSkeletonSkills },
     { "golem at a time", Golems.CombinedInstances.Maximum },
     // buffs
     // - effect
     { "({BuffMatchers}) effect", Reference.AsBuff.Effect },
     { "effect of ({BuffMatchers})", Reference.AsBuff.Effect },
     { "effect of ({BuffMatchers}) on you", Reference.AsBuff.EffectOn(Self) },
     { "({SkillMatchers}) has buff effect", Reference.AsSkill.Buff.Effect },
     { "effect of buffs granted by your golems", Buffs(Entity.Minion).With(Keyword.Golem).Effect },
     {
         "effect of buffs granted by your elemental golems",
         Buffs(Entity.Minion).With(Keyword.Golem, Fire).Effect,
         Buffs(Entity.Minion).With(Keyword.Golem, Cold).Effect,
         Buffs(Entity.Minion).With(Keyword.Golem, Lightning).Effect
     },
     { "effect of heralds on you", Buffs(targets: Self).With(Keyword.Herald).Effect },
     { "effect of your curses", Buffs(Self).With(Keyword.Curse).Effect },
     { "effect of curses on you", Buffs(targets: Self).With(Keyword.Curse).Effect },
     {
         "effect of non-curse auras from your skills",
         Buffs(Self).With(Keyword.Aura).Without(Keyword.Curse).Effect
     },
     { "warcry buff effect", Buffs(targets: Self).With(Keyword.Warcry).Effect },
     { "aura effect", Skills.ModifierSourceSkill.Buff.Effect },
     { "(?<!area of )effect of aura", Skills.ModifierSourceSkill.Buff.Effect },
     { "effect of supported curses", Skills.ModifierSourceSkill.Buff.Effect },
     { "non-curse auras from supported skills have effect", Skills.ModifierSourceSkill.Buff.Effect },
     { "effect of curse against players", Skills.ModifierSourceSkill.Buff.EffectOn(Entity.Character) },
     // - chance
     { "chance to (gain|grant) ({BuffMatchers})", Reference.AsBuff.Chance },
     { "chance to fortify", Buff.Fortify.Chance },
     { "chance to maim", Buff.Maim.Chance },
     { "chance for attacks to maim", Buff.Maim.Chance.With(DamageSource.Attack) },
     { "chance to taunt", Buff.Taunt.Chance },
     { "chance to blind( enemies)?", Buff.Blind.Chance },
     { "chance to cover rare or unique enemies in ash", Buff.CoveredInAsh.Chance, Enemy.IsRareOrUnique },
     { "chance to impale enemies", Buff.Impale.Chance },
     // - duration
     { "({BuffMatchers}) duration", Reference.AsBuff.Duration },
     { "blinding duration", Buff.Blind.Duration },
     // ailments
     { "chance to ({AilmentMatchers})( the enemy| enemies)?( on hit)?", Reference.AsAilment.Chance },
     {
         "chance to freeze, shock and ignite",
         Ailment.Freeze.Chance, Ailment.Shock.Chance, Ailment.Ignite.Chance
     },
     { "chance to cause bleeding( on hit)?", Ailment.Bleed.Chance.With(DamageSource.Attack) },
     { "chance to avoid being ({AilmentMatchers})", Reference.AsAilment.Avoidance },
     { "chance to avoid elemental ailments", Ailment.Elemental.Select(a => a.Avoidance) },
     { "({AilmentMatchers}) duration( on enemies)?", Reference.AsAilment.Duration },
     {
         "duration of ailments on enemies",
         Ailment.Elemental.Append(Ailment.Bleed, Ailment.Poison).Select(a => a.Duration)
     },
     { "duration of elemental ailments on enemies", Ailment.Elemental.Select(a => a.Duration) },
     { "effect of shock", Ailment.ShockEffect },
     { "effect of chill( on enemies)?", Ailment.ChillEffect },
     { "effect of non-damaging ailments on enemies", Ailment.ShockEffect, Ailment.ChillEffect },
     // stun
     { "chance to avoid being stunned", Effect.Stun.Avoidance },
     { "stun and block recovery", Effect.Stun.Recovery, Block.Recovery },
     { "block recovery", Block.Recovery },
     { "stun threshold", Effect.Stun.Threshold },
     { "enemy stun threshold", Effect.Stun.Threshold.For(Enemy) },
     { "stun duration( on enemies)?", Effect.Stun.Duration },
     { "stun duration (?<inner>with .*) on enemies", Effect.Stun.Duration, "${inner}" },
     {
         "chance to avoid interruption from stuns while casting",
         Effect.Stun.ChanceToAvoidInterruptionWhileCasting
     },
     { "chance to double stun duration", Effect.Stun.Duration.ChanceToDouble },
     // flasks
     { "effect of flasks( on you)?", Flask.Effect },
     { "flask effect duration", Flask.Duration },
     { "life recovery from flasks", Flask.LifeRecovery },
     { "mana recovery from flasks", Flask.ManaRecovery },
     { "flask charges used", Flask.ChargesUsed },
     { "flask charges gained", Flask.ChargesGained },
     { "flask recovery (speed|rate)", Flask.RecoverySpeed },
     // item quantity/quality
     { "quantity of items found", Stat.ItemQuantity },
     { "quantity of items dropped by enemies slain", Stat.ItemQuantity },
     { "rarity of items found", Stat.ItemRarity },
     { "rarity of items dropped by enemies slain", Stat.ItemRarity },
     // range and area of effect
     { "area of effect", Stat.AreaOfEffect },
     { "aura area of effect", Stat.AreaOfEffect, With(Keyword.Aura) },
     { "radius", Stat.Radius },
     { "explosion radius", Stat.Radius },
     { "melee weapon and unarmed( attack)? range", Stat.Range.With(Keyword.Melee) },
     { "melee range", Stat.Range.With(Keyword.Melee) },
     { "melee weapon range", Stat.Range.With(Keyword.Melee), MainHand.HasItem },
     // other
     { "rampage stacks", Stat.RampageStacks },
     { "chance to knock enemies back", Effect.Knockback.Chance },
     { "knockback distance", Effect.Knockback.Distance },
     { "character size", Stat.CharacterSize },
     { "reflected elemental damage taken", Elemental.ReflectedDamageTaken },
     { "reflected physical damage taken", Physical.ReflectedDamageTaken },
     { "damage taken gained as mana over 4 seconds when hit", Stat.DamageTakenGainedAsMana },
     { "light radius", Stat.LightRadius },
 };