-- Path of Building -- -- Module: Mod Parser -- Parser function for modifier names -- local t_insert = table.insert local band = bit.band local bor = bit.bor local bnot = bit.bnot -- List of modifier forms local formList = { ["^(%d+)%% increased"] = "INC", ["^(%d+)%% faster"] = "INC", ["^(%d+)%% reduced"] = "RED", ["^(%d+)%% slower"] = "RED", ["^(%d+)%% more"] = "MORE", ["^(%d+)%% less"] = "LESS", ["^([%+%-][%d%.]+)%%?"] = "BASE", ["^([%+%-][%d%.]+)%%? to"] = "BASE", ["^([%+%-][%d%.]+)%%? base"] = "BASE", ["^([%+%-]?[%d%.]+)%% additional"] = "BASE", ["^you gain ([%d%.]+)"] = "BASE", ["^([%+%-]?%d+)%% chance"] = "CHANCE", ["^([%+%-]?%d+)%% additional chance"] = "CHANCE", ["^([%d%.]+)%% of"] = "CONV", ["^gain ([%d%.]+)%% of"] = "CONV", ["penetrates (%d+)%%"] = "PEN", ["penetrates (%d+)%% of enemy"] = "PEN", ["^([%d%.]+)%% of (.+) regenerated per second"] = "REGENPERCENT", ["^([%d%.]+) (.+) regenerated per second"] = "REGENFLAT", ["^regenerate ([%d%.]+) (.+) per second"] = "REGENFLAT", ["(%d+) to (%d+) additional (%a+) damage"] = "DMG", ["adds (%d+)%-(%d+) (%a+) damage"] = "DMGATTACKS", ["adds (%d+) to (%d+) (%a+) damage"] = "DMGATTACKS", ["adds (%d+)%-(%d+) (%a+) damage to attacks"] = "DMGATTACKS", ["adds (%d+) to (%d+) (%a+) damage to attacks"] = "DMGATTACKS", ["adds (%d+)%-(%d+) (%a+) damage to spells"] = "DMGSPELLS", ["adds (%d+) to (%d+) (%a+) damage to spells"] = "DMGSPELLS", } -- Map of modifier names local modNameList = { -- Attributes ["strength"] = "Str", ["dexterity"] = "Dex", ["intelligence"] = "Int", ["strength and dexterity"] = { "Str", "Dex" }, ["strength and intelligence"] = { "Str", "Int" }, ["dexterity and intelligence"] = { "Dex", "Int" }, ["attributes"] = { "Str", "Dex", "Int" }, ["all attributes"] = { "Str", "Dex", "Int" }, -- Life/mana ["life"] = "Life", ["maximum life"] = "Life", ["mana"] = "Mana", ["maximum mana"] = "Mana", ["mana regeneration rate"] = "ManaRegen", ["mana cost"] = "ManaCost", ["mana cost of skills"] = "ManaCost", ["mana reserved"] = "ManaReserved", ["mana reservation"] = "ManaReserved", -- Primary defences ["maximum energy shield"] = "EnergyShield", ["energy shield recharge rate"] = "EnergyShieldRecharge", ["energy shield recovery rate"] = "EnergyShieldRecovery", ["start of energy shield recharge"] = "EnergyShieldRechargeFaster", ["armour"] = "Armour", ["evasion"] = "Evasion", ["evasion rating"] = "Evasion", ["energy shield"] = "EnergyShield", ["armour and evasion"] = "ArmourAndEvasion", ["armour and evasion rating"] = "ArmourAndEvasion", ["evasion rating and armour"] = "ArmourAndEvasion", ["armour and energy shield"] = "ArmourAndEnergyShield", ["evasion and energy shield"] = "EvasionAndEnergyShield", ["armour, evasion and energy shield"] = "Defences", ["defences"] = "Defences", -- Resistances ["fire resistance"] = "FireResist", ["maximum fire resistance"] = "FireResistMax", ["cold resistance"] = "ColdResist", ["maximum cold resistance"] = "ColdResistMax", ["lightning resistance"] = "LightningResist", ["maximum lightning resistance"] = "LightningResistMax", ["chaos resistance"] = "ChaosResist", ["fire and cold resistances"] = { "FireResist", "ColdResist" }, ["fire and lightning resistances"] = { "FireResist", "LightningResist" }, ["cold and lightning resistances"] = { "ColdResist", "LightningResist" }, ["elemental resistances"] = "ElementalResist", ["all elemental resistances"] = "ElementalResist", ["all maximum resistances"] = { "FireResistMax", "ColdResistMax", "LightningResistMax", "ChaosResistMax" }, -- Other defences ["to dodge attacks"] = "AttackDodgeChance", ["to dodge spells"] = "SpellDodgeChance", ["to dodge spell damage"] = "SpellDodgeChance", ["to block"] = "BlockChance", ["block chance"] = "BlockChance", ["to block spells"] = "SpellBlockChance", ["chance to block attacks and spells"] = { "BlockChance", "SpellBlockChance" }, ["maximum block chance"] = "BlockChanceMax", ["to avoid being stunned"] = "AvoidStun", ["to avoid being shocked"] = "AvoidShock", ["to avoid being frozen"] = "AvoidFrozen", ["to avoid being chilled"] = "AvoidChilled", ["to avoid being ignited"] = "AvoidIgnite", ["to avoid elemental status ailments"] = { "AvoidShock", "AvoidFrozen", "AvoidChilled", "AvoidIgnite" }, -- Stun modifiers ["stun recovery"] = "StunRecovery", ["stun and block recovery"] = "StunRecovery", ["stun threshold"] = "StunThreshold", ["block recovery"] = "BlockRecovery", ["enemy stun threshold"] = "EnemyStunThreshold", ["stun duration on enemies"] = "EnemyStunDuration", -- Auras/curses ["effect of non-curse auras you cast"] = "AuraEffect", ["effect of your curses"] = "CurseEffect", ["curse effect"] = "CurseEffect", ["curse duration"] = { "Duration", keywordFlags = KeywordFlag.Curse }, ["radius of auras"] = { "AreaRadius", keywordFlags = KeywordFlag.Aura }, ["radius of curses"] = { "AreaRadius", keywordFlags = KeywordFlag.Curse }, -- Charges ["maximum power charge"] = "PowerChargesMax", ["maximum power charges"] = "PowerChargesMax", ["power charge duration"] = "PowerChargesDuration", ["maximum frenzy charge"] = "FrenzyChargesMax", ["maximum frenzy charges"] = "FrenzyChargesMax", ["frenzy charge duration"] = "FrenzyChargesDuration", ["maximum endurance charge"] = "EnduranceChargesMax", ["maximum endurance charges"] = "EnduranceChargesMax", ["endurance charge duration"] = "EnduranceChargesDuration", ["endurance, frenzy and power charge duration"] = { "PowerChargesDuration", "FrenzyChargesDuration", "EnduranceChargesDuration" }, -- On hit/kill effects ["life gained on kill"] = "LifeOnKill", ["mana gained on kill"] = "ManaOnKill", ["life gained for each enemy hit by attacks"] = { "LifeOnHit", flags = ModFlag.Attack }, ["life gained for each enemy hit by your attacks"] = { "LifeOnHit", flags = ModFlag.Attack }, ["life gained for each enemy hit by spells"] = { "LifeOnHit", flags = ModFlag.Spell }, ["life gained for each enemy hit by your spells"] = { "LifeOnHit", flags = ModFlag.Spell }, ["mana gained for each enemy hit by attacks"] = { "ManaOnHit", flags = ModFlag.Attack }, ["mana gained for each enemy hit by your attacks"] = { "ManaOnHit", flags = ModFlag.Attack }, ["energy shield gained for each enemy hit by attacks"] = { "EnergyShieldOnHit", flags = ModFlag.Attack }, ["energy shield gained for each enemy hit by your attacks"] = { "EnergyShieldOnHit", flags = ModFlag.Attack }, ["life and mana gained for each enemy hit"] = { "LifeOnHit", "ManaOnHit", flags = ModFlag.Attack }, -- Projectile modifiers ["pierce chance"] = "PierceChance", ["of projectiles piercing"] = "PierceChance", ["of arrows piercing"] = { "PierceChance", flags = ModFlag.Bow }, ["projectile speed"] = "ProjectileSpeed", ["arrow speed"] = { "ProjectileSpeed", flags = ModFlag.Bow }, -- Totem/trap/mine modifiers ["totem placement speed"] = "TotemPlacementSpeed", ["totem life"] = "TotemLife", ["totem duration"] = "TotemDuration", ["trap throwing speed"] = "TrapThrowingSpeed", ["trap trigger radius"] = "TrapTriggerRadius", ["trap duration"] = "TrapDuration", ["cooldown recovery speed for throwing traps"] = "TrapCooldownRecovery", ["mine laying speed"] = "MineLayingSpeed", ["mine detonation radius"] = "MineDetonationRadius", ["mine duration"] = "MineDuration", -- Other skill modifiers ["radius"] = "AreaRadius", ["radius of area skills"] = "AreaRadius", ["area of effect radius"] = "AreaRadius", ["area of effect"] = "AreaRadius", ["duration"] = "Duration", ["skill effect duration"] = "Duration", ["chaos skill effect duration"] = { "Duration", keywordFlags = KeywordFlag.Chaos }, -- Buffs ["onslaught effect"] = "OnslaughtEffect", ["fortify duration"] = "FortifyDuration", ["effect of fortify on you"] = "FortifyEffect", -- Basic damage types ["damage"] = "Damage", ["physical damage"] = "PhysicalDamage", ["lightning damage"] = "LightningDamage", ["cold damage"] = "ColdDamage", ["fire damage"] = "FireDamage", ["chaos damage"] = "ChaosDamage", ["elemental damage"] = "ElementalDamage", -- Other damage forms ["attack damage"] = { "Damage", flags = ModFlag.Attack }, ["physical attack damage"] = { "PhysicalDamage", flags = ModFlag.Attack }, ["physical weapon damage"] = { "PhysicalDamage", flags = ModFlag.Weapon }, ["physical melee damage"] = { "PhysicalDamage", flags = ModFlag.Melee }, ["melee physical damage"] = { "PhysicalDamage", flags = ModFlag.Melee }, ["wand damage"] = { "Damage", flags = ModFlag.Wand }, ["wand physical damage"] = { "PhysicalDamage", flags = ModFlag.Wand }, ["claw physical damage"] = { "PhysicalDamage", flags = ModFlag.Claw }, ["damage over time"] = { "Damage", flags = ModFlag.Dot }, ["physical damage over time"] = { "PhysicalDamage", flags = ModFlag.Dot }, ["burning damage"] = { "FireDamage", flags = ModFlag.Dot }, ["righteous fire damage"] = { "Damage", tag = { type = "SkillName", skillName = "Righteous Fire" } }, -- Parser mis-interprets this one (Righteous/Fire Damage instead of Righteous Fire/Damage) -- Crit/accuracy/speed modifiers ["critical strike chance"] = "CritChance", ["critical strike multiplier"] = "CritMultiplier", ["accuracy rating"] = "Accuracy", ["attack speed"] = { "Speed", flags = ModFlag.Attack }, ["cast speed"] = { "Speed", flags = ModFlag.Spell }, ["attack and cast speed"] = "Speed", -- Elemental status ailments ["to shock"] = "EnemyShockChance", ["shock chance"] = "EnemyShockChance", ["to freeze"] = "EnemyFreezeChance", ["freeze chance"] = "EnemyFreezeChance", ["to ignite"] = "EnemyIgniteChance", ["ignite chance"] = "EnemyIgniteChance", ["to freeze, shock and ignite"] = { "EnemyFreezeChance", "EnemyShockChance", "EnemyIgniteChance" }, ["shock duration on enemies"] = "EnemyShockDuration", ["freeze duration on enemies"] = "EnemyFreezeDuration", ["chill duration on enemies"] = "EnemyChillDuration", ["ignite duration on enemies"] = "EnemyIgniteDuration", ["duration of elemental status ailments on enemies"] = { "EnemyShockDuration", "EnemyFreezeDuration", "EnemyChillDuration", "EnemyIgniteDuration" }, -- Other debuffs ["to poison"] = "PoisonChance", ["to poison on hit"] = "PoisonChance", ["poison duration"] = { "Duration", keywordFlags = KeywordFlag.Poison }, ["to cause bleeding"] = "BleedChance", ["to cause bleeding on hit"] = "BleedChance", ["bleed duration"] = { "Duration", keywordFlags = KeywordFlag.Bleed }, -- Misc modifiers ["movement speed"] = "MovementSpeed", ["light radius"] = "LightRadius", ["rarity of items found"] = "LootRarity", ["quantity of items found"] = "LootQuantity", } -- List of modifier flags local modFlagList = { -- Weapon types ["with axes"] = { flags = ModFlag.Axe }, ["with bows"] = { flags = ModFlag.Bow }, ["with claws"] = { flags = ModFlag.Claw }, ["with daggers"] = { flags = ModFlag.Dagger }, ["with maces"] = { flags = ModFlag.Mace }, ["with staves"] = { flags = ModFlag.Staff }, ["with swords"] = { flags = ModFlag.Sword }, ["with wands"] = { flags = ModFlag.Wand }, ["unarmed"] = { flags = ModFlag.Unarmed }, ["with one handed weapons"] = { flags = ModFlag.Weapon1H }, ["with one handed melee weapons"] = { flags = bor(ModFlag.Weapon1H, ModFlag.WeaponMelee) }, ["with two handed weapons"] = { flags = ModFlag.Weapon2H }, ["with two handed melee weapons"] = { flags = bor(ModFlag.Weapon2H, ModFlag.WeaponMelee) }, ["with ranged weapons"] = { flags = ModFlag.WeaponRanged }, -- Skill types ["spell"] = { flags = ModFlag.Spell }, ["for spells"] = { flags = ModFlag.Spell }, ["with attacks"] = { flags = ModFlag.Attack }, ["for attacks"] = { flags = ModFlag.Attack }, ["weapon"] = { flags = ModFlag.Weapon }, ["with weapons"] = { flags = ModFlag.Weapon }, ["melee"] = { flags = ModFlag.Melee }, ["with melee attacks"] = { flags = ModFlag.Melee }, ["on melee hit"] = { flags = ModFlag.Melee }, ["with poison"] = { keywordFlags = KeywordFlag.Poison }, ["projectile"] = { flags = ModFlag.Projectile }, ["area"] = { flags = ModFlag.Area }, ["mine"] = { keywordFlags = KeywordFlag.Mine }, ["with mines"] = { keywordFlags = KeywordFlag.Mine }, ["trap"] = { keywordFlags = KeywordFlag.Trap }, ["with traps"] = { keywordFlags = KeywordFlag.Trap }, ["totem"] = { keywordFlags = KeywordFlag.Totem }, ["with totem skills"] = { keywordFlags = KeywordFlag.Totem }, ["minion"] = { keywordFlags = KeywordFlag.Minion }, ["of aura skills"] = { keywordFlags = KeywordFlag.Aura }, ["of curse skills"] = { keywordFlags = KeywordFlag.Curse }, ["for curses"] = { keywordFlags = KeywordFlag.Curse }, ["warcry"] = { keywordFlags = KeywordFlag.Warcry }, ["vaal"] = { keywordFlags = KeywordFlag.Vaal }, ["vaal skill"] = { keywordFlags = KeywordFlag.Vaal }, ["with movement skills"] = { keywordFlags = KeywordFlag.Movement }, ["with lightning skills"] = { keywordFlags = KeywordFlag.Lightning }, ["with cold skills"] = { keywordFlags = KeywordFlag.Cold }, ["with fire skills"] = { keywordFlags = KeywordFlag.Fire }, ["with elemental skills"] = { keywordFlags = bor(KeywordFlag.Lightning, KeywordFlag.Cold, KeywordFlag.Fire) }, ["with chaos skills"] = { keywordFlags = KeywordFlag.Chaos }, -- Other ["global"] = { tag = { type = "Global" } }, ["from equipped shield"] = { tag = { type = "SlotName", slotName = "Weapon 2" } }, } -- List of modifier flags/tags that appear at the start of a line local preFlagList = { ["^hits deal "] = { flags = ModFlag.Hit }, ["^minions have "] = { keywordFlags = KeywordFlag.Minion }, ["^minions deal "] = { keywordFlags = KeywordFlag.Minion }, ["^attacks used by totems have "] = { keywordFlags = KeywordFlag.Totem }, ["^spells cast by totems have "] = { keywordFlags = KeywordFlag.Totem }, ["^attacks have "] = { flags = ModFlag.Attack }, ["^melee attacks have "] = { flags = ModFlag.Melee }, ["^left ring slot: "] = { tag = { type = "SlotNumber", num = 1 } }, ["^right ring slot: "] = { tag = { type = "SlotNumber", num = 2 } }, ["^socketed gems have "] = { tag = { type = "SocketedIn" } }, ["^socketed gems deal "] = { tag = { type = "SocketedIn" } }, ["^socketed curse gems have "] = { tag = { type = "SocketedIn", keyword = "curse" } }, ["^socketed melee gems have "] = { tag = { type = "SocketedIn", keyword = "melee" } }, ["^your flasks grant "] = { }, } -- List of modifier tags local modTagList = { -- Multipliers ["per power charge"] = { tag = { type = "Multiplier", var = "PowerCharge" } }, ["per frenzy charge"] = { tag = { type = "Multiplier", var = "FrenzyCharge" } }, ["per endurance charge"] = { tag = { type = "Multiplier", var = "EnduranceCharge" } }, ["per level"] = { tag = { type = "Multiplier", var = "Level" } }, ["for each normal item you have equipped"] = { tag = { type = "Multiplier", var = "NormalItem" } }, ["for each magic item you have equipped"] = { tag = { type = "Multiplier", var = "MagicItem" } }, ["for each rare item you have equipped"] = { tag = { type = "Multiplier", var = "RareItem" } }, ["for each unique item you have equipped"] = { tag = { type = "Multiplier", var = "UniqueItem" } }, -- Per stat ["per (%d+) strength"] = function(num) return { tag = { type = "PerStat", stat = "Str", div = num } } end, ["per (%d+) dexterity"] = function(num) return { tag = { type = "PerStat", stat = "Dex", div = num } } end, ["per (%d+) intelligence"] = function(num) return { tag = { type = "PerStat", stat = "Int", div = num } } end, ["per (%d+) evasion rating"] = function(num) return { tag = { type = "PerStat", stat = "Evasion", div = num } } end, ["per (%d+) accuracy rating"] = function(num) return { tag = { type = "PerStat", stat = "Accuracy", div = num } } end, ["per (%d+)%% block chance"] = function(num) return { tag = { type = "PerStat", stat = "BlockChance", div = num } } end, -- Slot conditions ["in main hand"] = { tag = { type = "SlotNumber", num = 1 } }, ["when in main hand"] = { tag = { type = "SlotNumber", num = 1 } }, ["in off hand"] = { tag = { type = "SlotNumber", num = 2 } }, ["when in off hand"] = { tag = { type = "SlotNumber", num = 2 } }, -- Equipment conditions ["while holding a shield"] = { tag = { type = "Condition", var = "UsingShield" } }, ["with shields"] = { tag = { type = "Condition", var = "UsingShield" } }, ["while dual wielding"] = { tag = { type = "Condition", var = "DualWielding" } }, ["while wielding a staff"] = { tag = { type = "Condition", var = "UsingStaff" } }, ["while unarmed"] = { tag = { type = "Condition", var = "Unarmed" } }, ["with a normal item equipped"] = { tag = { type = "Condition", var = "UsingNormalItem" } }, ["with a magic item equipped"] = { tag = { type = "Condition", var = "UsingMagicItem" } }, ["with a rare item equipped"] = { tag = { type = "Condition", var = "UsingRareItem" } }, ["with a unique item equipped"] = { tag = { type = "Condition", var = "UsingUniqueItem" } }, ["if you wear no corrupted items"] = { tag = { type = "Condition", var = "NotUsingCorruptedItem" } }, ["with main hand"] = { tag = { type = "Condition", var = "MainHandAttack" } }, ["with off hand"] = { tag = { type = "Condition", var = "OffHandAttack" } }, -- Player status conditions ["when on low life"] = { tag = { type = "Condition", var = "LowLife" } }, ["while on low life"] = { tag = { type = "Condition", var = "LowLife" } }, ["when not on low life"] = { tag = { type = "Condition", var = "NotLowLife" } }, ["while not on low life"] = { tag = { type = "Condition", var = "NotLowLife" } }, ["when on full life"] = { tag = { type = "Condition", var = "FullLife" } }, ["when not on full life"] = { tag = { type = "Condition", var = "NotFullLife" } }, ["while no mana is reserved"] = { tag = { type = "Condition", var = "NoManaReserved" } }, ["while at maximum power charges"] = { tag = { type = "Condition", var = "AtMaxPowerCharges" } }, ["while at maximum frenzy charges"] = { tag = { type = "Condition", var = "AtMaxFrenzyCharges" } }, ["while at maximum endurance charges"] = { tag = { type = "Condition", var = "AtMaxEnduranceCharges" } }, ["while you have fortify"] = { tag = { type = "Condition", var = "Fortify" } }, ["during onslaught"] = { tag = { type = "Condition", var = "Onslaught" } }, ["while you have onslaught"] = { tag = { type = "Condition", var = "Onslaught" } }, ["while phasing"] = { tag = { type = "Condition", var = "Phasing" } }, ["while using a flask"] = { tag = { type = "Condition", var = "UsingFlask" } }, ["during flask effect"] = { tag = { type = "Condition", var = "UsingFlask" } }, ["while on consecrated ground"] = { tag = { type = "Condition", var = "OnConsecratedGround" } }, ["if you have hit recently"] = { tag = { type = "Condition", var = "HitRecently" } }, ["if you've crit recently"] = { tag = { type = "Condition", var = "CritRecently" } }, ["if you've dealt a critical strike recently"] = { tag = { type = "Condition", var = "CritRecently" } }, ["if you haven't crit recently"] = { tag = { type = "Condition", var = "NotCritRecently" } }, ["if you've killed recently"] = { tag = { type = "Condition", var = "KilledRecently" } }, ["if you haven't killed recently"] = { tag = { type = "Condition", var = "NotKilledRecently" } }, ["if you or your totems have killed recently"] = { tag = { type = "Condition", varList = {"KilledRecently","TotemsKilledRecently"} } }, ["if you've been hit recently"] = { tag = { type = "Condition", var = "BeenHitRecently" } }, ["if you were hit recently"] = { tag = { type = "Condition", var = "BeenHitRecently" } }, ["if you were damaged by a hit recently"] = { tag = { type = "Condition", var = "BeenHitRecently" } }, ["if you haven't been hit recently"] = { tag = { type = "Condition", var = "NotBeenHitRecently" } }, ["if you've taken no damage from hits recently"] = { tag = { type = "Condition", var = "NotBeenHitRecently" } }, ["if you've attacked recently"] = { tag = { type = "Condition", var = "AttackedRecently" } }, ["if you've cast a spell recently"] = { tag = { type = "Condition", var = "CastSpellRecently" } }, ["if you've used a fire skill in the past 10 seconds"] = { tag = { type = "Condition", var = "UsedFireSkillInPast10Sec" } }, ["if you've used a cold skill in the past 10 seconds"] = { tag = { type = "Condition", var = "UsedColdSkillInPast10Sec" } }, ["if you've used a lightning skill in the past 10 seconds"] = { tag = { type = "Condition", var = "UsedLightningSkillInPast10Sec" } }, ["if you've summoned a totem recently"] = { tag = { type = "Condition", var = "SummonedTotemRecently" } }, ["if you've used a movement skill recently"] = { tag = { type = "Condition", var = "UsedMovementSkillRecently" } }, ["if you detonated mines recently"] = { tag = { type = "Condition", var = "DetonatedMinesRecently" } }, ["if you've crit in the past 8 seconds"] = { tag = { type = "Condition", var = "CritInPast8Sec" } }, ["if energy shield recharge has started recently"] = { tag = { type = "Condition", var = "EnergyShieldRechargeRecently" } }, -- Enemy status conditions ["against enemies on full life"] = { tag = { type = "Condition", var = "EnemyFullLife" }, flags = ModFlag.Hit }, ["against enemies that are on full life"] = { tag = { type = "Condition", var = "EnemyFullLife" }, flags = ModFlag.Hit }, ["against enemies on low life"] = { tag = { type = "Condition", var = "EnemyLowLife" }, flags = ModFlag.Hit }, ["against enemies that are on low life"] = { tag = { type = "Condition", var = "EnemyLowLife" }, flags = ModFlag.Hit }, ["against bleeding enemies"] = { tag = { type = "Condition", var = "EnemyBleeding" }, flags = ModFlag.Hit }, ["against poisoned enemies"] = { tag = { type = "Condition", var = "EnemyPoisoned" }, flags = ModFlag.Hit }, ["against burning enemies"] = { tag = { type = "Condition", var = "EnemyBurning" }, flags = ModFlag.Hit }, ["against ignited enemies"] = { tag = { type = "Condition", var = "EnemyIgnited" }, flags = ModFlag.Hit }, ["against shocked enemies"] = { tag = { type = "Condition", var = "EnemyShocked" }, flags = ModFlag.Hit }, ["against frozen enemies"] = { tag = { type = "Condition", var = "EnemyFrozen" }, flags = ModFlag.Hit }, ["against chilled enemies"] = { tag = { type = "Condition", var = "EnemyChilled" }, flags = ModFlag.Hit }, ["enemies which are chilled"] = { tag = { type = "Condition", var = "EnemyChilled" }, flags = ModFlag.Hit }, ["against frozen, shocked or ignited enemies"] = { tag = { type = "Condition", var = "EnemyFrozenShockedIgnited" }, flags = ModFlag.Hit }, ["against enemies that are affected by elemental status ailments"] = { tag = { type = "Condition", var = "EnemyElementalStatus" }, flags = ModFlag.Hit }, ["against enemies that are affected by no elemental status ailments"] = { tag = { type = "Condition", var = "NotEnemyElementalStatus" }, flags = ModFlag.Hit }, } local mod = modLib.createMod local function flag(name, ...) return mod(name, "FLAG", true, ...) end local gemNameLookup = { } for name in pairs(data.gems) do gemNameLookup[name:lower()] = name end -- List of special modifiers local specialModList = { -- Keystones ["your hits can't be evaded"] = { flag("CannotBeEvaded") }, ["never deal critical strikes"] = { flag("NeverCrit") }, ["no critical strike multiplier"] = { flag("NoCritMultiplier") }, ["the increase to physical damage from strength applies to projectile attacks as well as melee attacks"] = { flag("IronGrip") }, ["converts all evasion rating to armour%. dexterity provides no bonus to evasion rating"] = { flag("IronReflexes") }, ["30%% chance to dodge attacks%. 50%% less armour and energy shield, 30%% less chance to block spells and attacks"] = { mod("AttackDodgeChance", "BASE", 30), mod("Armour", "MORE", -50), mod("EnergyShield", "MORE", -50), mod("BlockChance", "MORE", -30), mod("SpellBlockChance", "MORE", -30) }, ["maximum life becomes 1, immune to chaos damage"] = { flag("ChaosInoculation") }, ["life regeneration is applied to energy shield instead"] = { flag("ZealotsOath") }, ["life leech applies instantly%. life regeneration has no effect%."] = { flag("VaalPact"), flag("NoLifeRegen") }, ["deal no non%-fire damage"] = { flag("DealNoPhysical"), flag("DealNoLightning"), flag("DealNoCold"), flag("DealNoChaos") }, ["removes all mana%. spend life instead of mana for skills"] = { mod("Mana", "MORE", -100), flag("BloodMagic") }, ["enemies you hit with elemental damage temporarily get (%+%d+)%% resistance to those elements and (%-%d+)%% resistance to other elements"] = function(plus, _, minus) minus = tonumber(minus) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("FireResist", "BASE", plus, { type = "Condition", var = "HitByFireDamage" }) }), mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("FireResist", "BASE", minus, { type = "Condition", var = "HitByFireDamage", neg = true }, { type = "Condition", varList={"HitByColdDamage","HitByLightningDamage"} }) }), mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("ColdResist", "BASE", plus, { type = "Condition", var = "HitByColdDamage" }) }), mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("ColdResist", "BASE", minus, { type = "Condition", var = "HitByColdDamage", neg = true }, { type = "Condition", varList={"HitByFireDamage","HitByLightningDamage"} }) }), mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("LightningResist", "BASE", plus, { type = "Condition", var = "HitByLightningDamage" }) }), mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("LightningResist", "BASE", minus, { type = "Condition", var = "HitByLightningDamage", neg = true }, { type = "Condition", varList={"HitByFireDamage","HitByColdDamage"} }) }), } end, -- Ascendancy notables ["movement skills cost no mana"] = { mod("ManaCost", "MORE", -100, nil, 0, KeywordFlag.Movement) }, ["projectiles have 100%% additional chance to pierce targets at the start of their movement, losing this chance as the projectile travels farther"] = { mod("PierceChance", "BASE", 100) }, ["projectile critical strike chance increased by arrow pierce chance"] = { mod("CritChance", "INC", 100, nil, ModFlag.Projectile) }, ["always poison on hit while using a flask"] = { mod("PoisonChance", "BASE", 100, { type = "Condition", var = "UsingFlask" }) }, ["armour received from body armour is doubled"] = { flag("Unbreakable") }, ["gain (%d+)%% of maximum mana as extra maximum energy shield"] = function(num) return { mod("ManaGainAsEnergyShield", "BASE", num) } end, ["you have fortify"] = { mod("Misc", "LIST", { type = "Condition", var = "Fortify"}) }, ["(%d+)%% increased damage of each damage type for which you have a matching golem"] = function(num) return { mod("PhysicalDamage", "INC", num, { type = "Condition", var = "HavePhysicalGolem"}), mod("LightningDamage", "INC", num, { type = "Condition", var = "HaveLightningGolem"}), mod("ColdDamage", "INC", num, { type = "Condition", var = "HaveColdGolem"}), mod("FireDamage", "INC", num, { type = "Condition", var = "HaveFireGolem"}), mod("ChaosDamage", "INC", num, { type = "Condition", var = "HaveChaosGolem"}) } end, ["100%% increased effect of buffs granted by your elemental golems"] = { flag("LiegeOfThePrimordial") }, ["every 10 seconds, gain (%d+)%% increased elemental damage for 4 seconds"] = function(num) return { mod("ElementalDamage", "INC", num, { type = "Condition", var = "PendulumOfDestruction" }) } end, ["every 10 seconds, gain (%d+)%% increased radius of area skills for 4 seconds"] = function(num) return { mod("AreaRadius", "INC", num, { type = "Condition", var = "PendulumOfDestruction" }) } end, ["enemies you curse take (%d+)%% increased damage"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("DamageTaken", "INC", num) }, { type = "Condition", var = "EnemyCursed" }) } end, ["enemies you curse have (%-%d+)%% to chaos resistance"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("ChaosResist", "BASE", num) }, { type = "Condition", var = "EnemyCursed" }) } end, ["nearby enemies have (%-%d+)%% to chaos resistance"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("ChaosResist", "BASE", num) }) } end, ["nearby enemies take (%d+)%% increased elemental damage"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("ElementalDamageTaken", "INC", num) }) } end, ["enemies near your totems take (%d+)%% increased physical and fire damage"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("PhysicalDamageTaken", "INC", num) }), mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("FireDamageTaken", "INC", num) }) } end, ["grants armour equal to (%d+)%% of your reserved life to you and nearby allies"] = function(num) return { mod("Armour", "BASE", num/100, { type = "PerStat", stat = "LifeReserved", div = 1 }) } end, ["grants maximum energy shield equal to (%d+)%% of your reserved mana to you and nearby allies"] = function(num) return { mod("EnergyShield", "BASE", num/100, { type = "PerStat", stat = "ManaReserved", div = 1 }) } end, ["you and nearby allies deal (%d+)%% increased damage"] = function(num) return { mod("Damage", "INC", num) } end, ["you and nearby allies have (%d+)%% increased movement speed"] = function(num) return { mod("MovementSpeed", "INC", num) } end, ["skills from your helmet penetrate (%d+)%% elemental resistances"] = function(num) return { mod("ElementalPenetration", "BASE", num, { type = "SocketedIn", slotName = "Helmet" }) } end, ["skills from your gloves have (%d+)%% increased area of effect"] = function(num) return { mod("AreaRadius", "INC", num, { type = "SocketedIn", slotName = "Gloves" }) } end, ["(%d+)%% less totem damage per totem"] = function(num) return { mod("Damage", "MORE", -num, nil, 0, KeywordFlag.Totem, { type = "PerStat", stat = "ActiveTotemLimit", div = 1 }) } end, ["poison you inflict with critical strikes deals (%d+)%% more damage"] = function(num) return { mod("PoisonDamageOnCrit", "MORE", 100) } end, ["bleeding you inflict on maimed enemies deals (%d+)%% more damage"] = function(num) return { mod("Damage", "MORE", num, nil, 0, KeywordFlag.Bleed, { type = "Condition", var = "EnemyMaimed"}) } end, -- Special node types ["(%d+)%% of block chance applied to spells"] = function(num) return { mod("BlockChanceConv", "BASE", num) } end, ["(%d+)%% additional block chance with staves"] = function(num) return { mod("BlockChance", "BASE", num, { type = "Condition", var = "UsingStaff" }) } end, ["(%d+)%% additional block chance while dual wielding or holding a shield"] = function(num) return { mod("BlockChance", "BASE", num, { type = "Condition", var = "DualWielding"}), mod("BlockChance", "BASE", num, { type = "Condition", var = "UsingShield"}) } end, ["can have up to (%d+) additional traps? placed at a time"] = function(num) return { mod("ActiveTrapLimit", "BASE", num) } end, ["can have up to (%d+) additional remote mines? placed at a time"] = function(num) return { mod("ActiveMineLimit", "BASE", num) } end, ["can have up to (%d+) additional totems? summoned at a time"] = function(num) return { mod("ActiveTotemLimit", "BASE", num) } end, -- Other modifiers ["cannot be stunned"] = { mod("AvoidStun", "BASE", 100) }, ["cannot be shocked"] = { mod("AvoidShock", "BASE", 100) }, ["cannot be frozen"] = { mod("AvoidFreeze", "BASE", 100) }, ["cannot be chilled"] = { mod("AvoidChill", "BASE", 100) }, ["cannot be ignited"] = { mod("AvoidIgnite", "BASE", 100) }, ["cannot evade enemy attacks"] = { flag("CannotEvade") }, ["deal no physical damage"] = { flag("DealNoPhysical") }, ["deal no elemental damage"] = { flag("DealNoLightning"), flag("DealNoCold"), flag("DealNoFire") }, ["your critical strikes do not deal extra damage"] = { flag("NoCritMultiplier") }, ["iron will"] = { flag("IronWill") }, ["adds an additional arrow"] = { mod("ProjectileCount", "BASE", 1, nil, ModFlag.Attack) }, ["(%d+) additional arrows"] = function(num) return { mod("ProjectileCount", "BASE", num, nil, ModFlag.Attack) } end, ["skills fire an additional projectile"] = { mod("ProjectileCount", "BASE", 1) }, ["spells have an additional projectile"] = { mod("ProjectileCount", "BASE", 1, nil, ModFlag.Spell) }, ["skills chain %+(%d) times"] = function(num) return { mod("ChainCount", "BASE", num) } end, ["reflects (%d+) physical damage to melee attackers"] = { }, -- Special item local modifiers ["no physical damage"] = { mod("Misc", "LIST", { type = "WeaponData", key = "PhysicalMin" }), mod("Misc", "LIST", { type = "WeaponData", key = "PhysicalMax" }), mod("Misc", "LIST", { type = "WeaponData", key = "PhysicalDPS" }) }, ["all attacks with this weapon are critical strikes"] = { mod("Misc", "LIST", { type = "WeaponData", key = "critChance", value = 100 }) }, ["hits can't be evaded"] = { mod("Misc", "LIST", { type = "WeaponData", key = "CannotBeEvaded", value = true }) }, ["no block chance"] = { mod("Misc", "LIST", { type = "ArmourData", key = "BlockChance", value = 0 }) }, ["causes bleeding on hit"] = { mod("BleedChance", "BASE", 100, nil, ModFlag.Attack) }, ["poisonous hit"] = { mod("PoisonChance", "BASE", 100, nil, ModFlag.Attack) }, ["has no sockets"] = { }, ["has 1 socket"] = { }, ["attacks have blood magic"] = { flag("SkillBloodMagic", nil, ModFlag.Attack) }, ["%+(%d+) to level of socketed gems"] = function(num) return { mod("GemProperty", "LIST", { keyword = "all", key = "level", value = num }, { type = "SocketedIn" }) } end, ["%+(%d+) to level of socketed (%a+) gems"] = function(num, _, type) return { mod("GemProperty", "LIST", { keyword = type, key = "level", value = num }, { type = "SocketedIn" }) } end, ["%+(%d+)%% to quality of socketed (%a+) gems"] = function(num, _, type) return { mod("GemProperty", "LIST", { keyword = type, key = "quality", value = num }, { type = "SocketedIn" }) } end, ["%+(%d+) to level of active socketed skill gems"] = function(num) return { mod("GemProperty", "LIST", { keyword = "active_skill", key = "level", value = num }, { type = "SocketedIn" }) } end, ["grants level (%d+) (.+)"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, ["curse enemies with (%D+) on %a+"] = function(_, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill] or "Unknown", level = 1, noSupports = true }, { type = "SocketedIn" }) } end, ["curse enemies with level (%d+) (.+) on %a+"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill] or "Unknown", level = num, noSupports = true }, { type = "SocketedIn" }) } end, ["socketed .*gems are supported by level (%d+) (.+)"] = function(num, _, support) return { mod("ExtraSupport", "LIST", { name = gemNameLookup[support] or "Unknown", level = num }, { type = "SocketedIn" }) } end, ["socketed curse gems supported by level (%d+) (.+)"] = function(num, _, support) return { mod("ExtraSupport", "LIST", { name = gemNameLookup[support] or "Unknown", level = num }, { type = "SocketedIn" }) } end, ["socketed gems fire an additional projectile"] = { mod("ProjectileCount", "BASE", 1, { type = "SocketedIn" }) }, ["socketed gems fire (%d+) additional projectiles"] = function(num) return { mod("ProjectileCount", "BASE", num, { type = "SocketedIn" }) } end, ["socketed gems reserve no mana"] = { mod("ManaReserved", "MORE", -100, { type = "SocketedIn" }) }, ["socketed gems have blood magic"] = { flag("SkillBloodMagic", { type = "SocketedIn" }) }, ["socketed gems gain (%d+)%% of physical damage as extra lightning damage"] = function(num) return { mod("PhysicalDamageGainAsLightning", "BASE", num, { type = "SocketedIn" }) } end, ["socketed red gems get (%d+)%% physical damage as extra fire damage"] = function(num) return { mod("PhysicalDamageGainAsFire", "BASE", num, { type = "SocketedIn", keyword = "strength" }) } end, -- Unique item modifiers ["your cold damage can ignite"] = { flag("ColdCanIgnite") }, ["your fire damage can shock but not ignite"] = { flag("FireCanShock"), flag("FireCannotIgnite") }, ["your cold damage can ignite but not freeze or chill"] = { flag("ColdCanIgnite"), flag("ColdCannotFreeze"), flag("ColdCannotChill") }, ["your lightning damage can freeze but not shock"] = { flag("LightningCanFreeze"), flag("LightningCannotShock") }, ["your chaos damage can shock"] = { flag("ChaosCanShock") }, ["your physical damage can chill"] = { flag("PhysicalCanChill") }, ["your physical damage can shock"] = { flag("PhysicalCanShock") }, ["your chaos damage poisons enemies"] = { mod("PoisonChance", "BASE", 100) }, ["melee attacks cause bleeding"] = { mod("BleedChance", "BASE", 100, nil, ModFlag.Melee) }, ["melee attacks poison on hit"] = { mod("PoisonChance", "BASE", 100, nil, ModFlag.Melee) }, ["traps and mines deal (%d+)%-(%d+) additional physical damage"] = function(_, min, max) return { mod("PhysicalMin", "BASE", tonumber(min), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)), mod("PhysicalMax", "BASE", tonumber(max), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)) } end, ["traps and mines deal (%d+) to (%d+) additional physical damage"] = function(_, min, max) return { mod("PhysicalMin", "BASE", tonumber(min), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)), mod("PhysicalMax", "BASE", tonumber(max), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)) } end, ["traps and mines have a (%d+)%% chance to poison on hit"] = function(num) return { mod("PoisonChance", "BASE", num, nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)) } end, ["poison cursed enemies on hit"] = { mod("PoisonChance", "BASE", 100, { type = "Condition", var = "EnemyCursed" }) }, ["projectile damage increased by arrow pierce chance"] = { mod("Damage", "INC", 1, nil, ModFlag.Projectile, 0, { type = "PerStat", stat = "PierceChance", div = 1 }) }, ["gain (%d+) armour per grand spectrum"] = function(num) return { mod("Armour", "BASE", num, { type = "Multiplier", var = "GrandSpectrum" }), mod("Misc", "LIST", { type = "Multiplier", var = "GrandSpectrum", value = 1}) } end, ["gain (%d+) mana per grand spectrum"] = function(num) return { mod("Mana", "BASE", num, { type = "Multiplier", var = "GrandSpectrum" }), mod("Misc", "LIST", { type = "Multiplier", var = "GrandSpectrum", value = 1}) } end, ["(%d+)%% increased elemental damage per grand spectrum"] = function(num) return { mod("ElementalDamage", "INC", num, { type = "Multiplier", var = "GrandSpectrum" }), mod("Misc", "LIST", { type = "Multiplier", var = "GrandSpectrum", value = 1}) } end, ["counts as dual wielding"] = { mod("Misc", "LIST", { type = "Condition", var = "DualWielding"}) }, ["counts as all one handed melee weapon types"] = { mod("Misc", "LIST", { type = "WeaponData", key = "flag", value = bor(ModFlag.Axe, ModFlag.Claw, ModFlag.Dagger, ModFlag.Mace, ModFlag.Sword)}) }, ["gain (%d+)%% of bow physical damage as extra damage of each element"] = function(num) return { mod("PhysicalDamageGainAsLightning", "BASE", num, nil, ModFlag.Bow), mod("PhysicalDamageGainAsCold", "BASE", num, nil, ModFlag.Bow), mod("PhysicalDamageGainAsFire", "BASE", num, nil, ModFlag.Bow) } end, ["totems fire (%d+) additional projectiles"] = function(num) return { mod("ProjectileCount", "BASE", num, nil, 0, KeywordFlag.Totem) } end, ["when at maximum frenzy charges, attacks poison enemies"] = { mod("PoisonChance", "BASE", 100, nil, ModFlag.Attack, { type = "Condition", var = "AtMaxFrenzyCharges" }) }, ["skills chain an additional time while at maximum frenzy charges"] = { mod("ChainCount", "BASE", 1, { type = "Condition", var = "AtMaxFrenzyCharges" }) }, ["you cannot be shocked while at maximum endurance charges"] = { mod("AvoidShock", "BASE", 100, { type = "Condition", var = "AtMaxEnduranceCharges" }) }, ["you have no life regeneration"] = { flag("NoLifeRegen") }, ["cannot block attacks"] = { flag("CannotBlockAttacks") }, ["projectiles pierce while phasing"] = { mod("PierceChance", "BASE", 100, { type = "Condition", var = "Phasing" }) }, ["increases and reductions to minion damage also affects you"] = { flag("MinionDamageAppliesToPlayer") }, ["armour is increased by uncapped fire resistance"] = { mod("Armour", "INC", 1, { type = "PerStat", stat = "FireResistTotal", div = 1 }) }, ["critical strike chance is increased by uncapped lightning resistance"] = { mod("CritChance", "INC", 1, { type = "PerStat", stat = "LightningResistTotal", div = 1 }) }, ["critical strikes deal no damage"] = { flag("NoCritDamage") }, ["enemies chilled by you take (%d+)%% increased burning damage"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("BurningDamageTaken", "INC", num) }, { type = "Condition", var = "EnemyChilled" }) } end, ["attacks with this weapon penetrate (%d+)%% elemental resistances"] = function(num) return { mod("ElementalPenetration", "BASE", num, nil, ModFlag.Weapon) } end, } local keystoneList = { -- List of keystones that can be found on uniques "Zealot's Oath", "Pain Attunement", "Blood Magic", "Unwavering Stance", "Ghost Reaver", "Conduit", "Mind Over Matter", "Acrobatics", "Avatar of Fire", } for _, name in pairs(keystoneList) do specialModList[name:lower()] = { mod("Keystone", "LIST", name) } end -- Special lookups used for various modifier forms local convTypes = { ["as extra lightning damage"] = "GainAsLightning", ["added as lightning damage"] = "GainAsLightning", ["gained as extra lightning damage"] = "GainAsLightning", ["as extra cold damage"] = "GainAsCold", ["added as cold damage"] = "GainAsCold", ["gained as extra cold damage"] = "GainAsCold", ["as extra fire damage"] = "GainAsFire", ["added as fire damage"] = "GainAsFire", ["gained as extra fire damage"] = "GainAsFire", ["as extra chaos damage"] = "GainAsChaos", ["added as chaos damage"] = "GainAsChaos", ["gained as extra chaos damage"] = "GainAsChaos", ["converted to lightning damage"] = "ConvertToLightning", ["converted to cold damage"] = "ConvertToCold", ["converted to fire damage"] = "ConvertToFire", ["converted to chaos damage"] = "ConvertToChaos", } local dmgTypes = { ["physical"] = "Physical", ["lightning"] = "Lightning", ["cold"] = "Cold", ["fire"] = "Fire", ["chaos"] = "Chaos", } local penTypes = { ["lightning resistance"] = "LightningPenetration", ["cold resistance"] = "ColdPenetration", ["fire resistance"] = "FirePenetration", ["elemental resistance"] = "ElementalPenetration", ["elemental resistances"] = "ElementalPenetration", } local regenTypes = { ["life"] = "LifeRegen", ["maximum life"] = "LifeRegen", ["mana"] = "ManaRegen", ["energy shield"] = "EnergyShieldRegen", ["maximum mana and energy shield"] = { "ManaRegen", "EnergyShieldRegen" }, } -- Build active skill name lookup local skillNameList = { } for skillName, data in pairs(data.gems) do if not data.support then skillNameList[" "..skillName:lower().." "] = { tag = { type = "SkillName", skillName = skillName } } end end local function getSimpleConv(src, dst, type, factor) return function(nodeMods, out, data) if nodeMods then local nodeVal = nodeMods:Sum(type, nil, src) if nodeVal ~= 0 then out:NewMod(src, type, -nodeVal, "Tree:Jewel") out:NewMod(dst, type, nodeVal * factor, "Tree:Jewel") end end end end local function getMatchConv(others, dst, type) return function(nodeMods, out, data) if nodeMods then for _, mod in ipairs(nodeMods) do for _, other in pairs(others) do if mod.name:match(other) then out:NewMod(mod.name, type, -mod.value, "Tree:Jewel") out:NewMod(mod.name:gsub(other, dst), type, mod.value, "Tree:Jewel") end end end end end end local function getPerStat(dst, type, flags, stat, factor) return function(nodeMods, out, data) if nodeMods then data[stat] = (data[stat] or 0) + nodeMods:Sum("BASE", nil, stat) else out:NewMod(dst, type, math.floor(data[stat] * factor + 0.5), "Tree:Jewel", flags) end end end -- List of radius jewel functions local jewelFuncs = { ["Strength from Passives in Radius is Transformed to Dexterity"] = getSimpleConv("Str", "Dex", "BASE", 1), ["Dexterity from Passives in Radius is Transformed to Strength"] = getSimpleConv("Dex", "Str", "BASE", 1), ["Strength from Passives in Radius is Transformed to Intelligence"] = getSimpleConv("Str", "Int", "BASE", 1), ["Intelligence from Passives in Radius is Transformed to Strength"] = getSimpleConv("Int", "Str", "BASE", 1), ["Dexterity from Passives in Radius is Transformed to Intelligence"] = getSimpleConv("Dex", "Int", "BASE", 1), ["Intelligence from Passives in Radius is Transformed to Dexterity"] = getSimpleConv("Int", "Dex", "BASE", 1), ["Increases and Reductions to Life in Radius are Transformed to apply to Energy Shield"] = getSimpleConv("Life", "EnergyShield", "INC", 1), ["Increases and Reductions to Energy Shield in Radius are Transformed to apply to Armour at 200% of their value"] = getSimpleConv("EnergyShield", "Armour", "INC", 2), ["Increases and Reductions to Life in Radius are Transformed to apply to Mana at 200% of their value"] = getSimpleConv("Life", "Mana", "INC", 2), ["Increases and Reductions to Physical Damage in Radius are Transformed to apply to Cold Damage"] = getMatchConv({"PhysicalDamage"}, "ColdDamage", "INC"), ["Increases and Reductions to Cold Damage in Radius are Transformed to apply to Physical Damage"] = getMatchConv({"ColdDamage"}, "PhysicalDamage", "INC"), ["Increases and Reductions to other Damage Types in Radius are Transformed to apply to Fire Damage"] = getMatchConv({"PhysicalDamage","ColdDamage","LightningDamage","ChaosDamage"}, "FireDamage", "INC"), ["Melee and Melee Weapon Type modifiers in Radius are Transformed to Bow Modifiers"] = function(nodeMods, out, data) if nodeMods then local mask1 = bor(ModFlag.Axe, ModFlag.Claw, ModFlag.Dagger, ModFlag.Mace, ModFlag.Staff, ModFlag.Sword, ModFlag.Melee) local mask2 = bor(ModFlag.Weapon1H, ModFlag.WeaponMelee) local mask3 = bor(ModFlag.Weapon2H, ModFlag.WeaponMelee) for _, mod in ipairs(nodeMods) do if band(mod.flags, mask1) ~= 0 or band(mod.flags, mask2) == mask2 or band(mod.flags, mask3) == mask3 then out:NewMod(mod.name, mod.type, -mod.value, "Tree:Jewel", mod.flags, mod.keywordFlags, unpack(mod.tagList)) out:NewMod(mod.name, mod.type, mod.value, "Tree:Jewel", bor(band(mod.flags, bnot(bor(mask1, mask2, mask3))), ModFlag.Bow), mod.keywordFlags, unpack(mod.tagList)) elseif mod.tagList[1] then for _, tag in ipairs(mod.tagList) do if tag.type == "Condition" and tag.var == "UsingStaff" then local newTagList = copyTable(mod.tagList) for _, tag in ipairs(newTagList) do if tag.type == "Condition" and tag.var == "UsingStaff" then tag.var = "UsingBow" break end end out:NewMod(mod.name, mod.type, -mod.value, "Tree:Jewel", mod.flags, mod.keywordFlags, unpack(mod.tagList)) out:NewMod(mod.name, mod.type, mod.value, "Tree:Jewel", mod.flags, mod.keywordFlags, unpack(newTagList)) break end end end end end end, ["Adds 1 to maximum Life per 3 Intelligence in Radius"] = getPerStat("Life", "BASE", 0, "Int", 1 / 3), ["Adds 1 to Maximum Life per 3 Intelligence Allocated in Radius"] = getPerStat("Life", "BASE", 0, "Int", 1 / 3), ["1% increased Evasion Rating per 3 Dexterity Allocated in Radius"] = getPerStat("Evasion", "INC", 0, "Dex", 1 / 3), ["1% increased Claw Physical Damage per 3 Dexterity Allocated in Radius"] = getPerStat("PhysicalDamage", "INC", ModFlag.Claw, "Dex", 1 / 3), ["1% increased Melee Physical Damage while Unarmed per 3 Dexterity Allocated in Radius"] = getPerStat("PhysicalDamage", "INC", ModFlag.Unarmed, "Dex", 1 / 3), ["3% increased Totem Life per 10 Strength in Radius"] = getPerStat("TotemLife", "INC", 0, "Str", 3 / 10), ["3% increased Totem Life per 10 Strength Allocated in Radius"] = getPerStat("TotemLife", "INC", 0, "Str", 3 / 10), ["Adds 1 maximum Lightning Damage to Attacks per 1 Dexterity Allocated in Radius"] = getPerStat("LightningMax", "BASE", ModFlag.Attack, "Dex", 1), ["5% increased Chaos damage per 10 Intelligence from Allocated Passives in Radius"] = getPerStat("ChaosDamage", "INC", 0, "Int", 5 / 10), ["Dexterity and Intelligence from passives in Radius count towards Strength Melee Damage bonus"] = function(nodeMods, out, data) if nodeMods then data.Dex = (data.Dex or 0) + nodeMods:Sum("BASE", nil, "Dex") data.Int = (data.Int or 0) + nodeMods:Sum("BASE", nil, "Int") else out:NewMod("DexIntToMeleeBonus", "BASE", data.Dex + data.Int, "Tree:Jewel") end end, } -- Scan a line for the earliest and longest match from the pattern list -- If a match is found, returns the corresponding value from the pattern list, plus the remainder of the line and a table of captures local function scan(line, patternList, plain) local bestIndex, bestEndIndex local bestMatch = { nil, line, nil } for pattern, patternVal in pairs(patternList) do local index, endIndex, cap1, cap2, cap3, cap4, cap5 = line:lower():find(pattern, 1, plain) if index and (not bestIndex or index < bestIndex or (index == bestIndex and endIndex > bestEndIndex)) then bestIndex = index bestEndIndex = endIndex bestMatch = { patternVal, line:sub(1, index - 1)..line:sub(endIndex + 1, -1), { cap1, cap2, cap3, cap4, cap5 } } end end return bestMatch[1], bestMatch[2], bestMatch[3] end local function parseMod(line) -- Check if this is a special modifier local specialMod, specialLine, cap = scan(line, specialModList) if specialMod and #specialLine == 0 then if type(specialMod) == "function" then return specialMod(tonumber(cap[1]), unpack(cap)) else return copyTable(specialMod) end end for desc, func in pairs(jewelFuncs) do if desc:lower() == line:lower() then return { mod("Misc", "LIST", { type = "JewelFunc", func = func }) } end end -- Check for a flag/tag specification at the start of the line local modFlag modFlag, line = scan(line, preFlagList) -- Scan for modifier form local modForm, formCap modForm, line, formCap = scan(line, formList) if not modForm then return end local num = tonumber(formCap[1]) -- Check for tags (per-charge, conditionals) local modTag, modTag2 modTag, line, cap = scan(line, modTagList) if type(modTag) == "function" then modTag = modTag(tonumber(cap[1]), unpack(cap)) end if modTag then modTag2, line, cap = scan(line, modTagList) if type(modTag2) == "function" then modTag2 = modTag2(tonumber(cap[1]), unpack(cap)) end end -- Scan for modifier name local modName modName, line = scan(line, modNameList, true) -- Scan for skill name local skillTag skillTag, line = scan(line, skillNameList, true) -- Scan for flags if one hasn't been found already if not modFlag then modFlag, line = scan(line, modFlagList, true) end -- Find modifier value and type according to form local modValue = num local modType = "BASE" local modSuffix = "" if modForm == "INC" then modType = "INC" elseif modForm == "RED" then modValue = -num modType = "INC" elseif modForm == "MORE" then modType = "MORE" elseif modForm == "LESS" then modValue = -num modType = "MORE" elseif modForm == "BASE" then elseif modForm == "CHANCE" then elseif modForm == "CONV" then modSuffix, line = scan(line, convTypes, true) if not modSuffix then return { }, line end elseif modForm == "PEN" then modName, line = scan(line, penTypes, true) if not modName then return { }, line end elseif modForm == "REGENPERCENT" then modName = regenTypes[formCap[2]] if not modName then return { }, line end modSuffix = "Percent" elseif modForm == "REGENFLAT" then modName = regenTypes[formCap[2]] if not modName then return { }, line end elseif modForm == "DMG" then local damageType = dmgTypes[formCap[3]] if not damageType then return { }, line end modValue = { tonumber(formCap[1]), tonumber(formCap[2]) } modName = { damageType.."Min", damageType.."Max" } elseif modForm == "DMGATTACKS" then local damageType = dmgTypes[formCap[3]] if not damageType then return { }, line end modValue = { tonumber(formCap[1]), tonumber(formCap[2]) } modName = { damageType.."Min", damageType.."Max" } modFlag = modFlag or { flags = ModFlag.Attack } elseif modForm == "DMGSPELLS" then local damageType = dmgTypes[formCap[3]] if not damageType then return { }, line end modValue = { tonumber(formCap[1]), tonumber(formCap[2]) } modName = { damageType.."Min", damageType.."Max" } modFlag = modFlag or { flags = ModFlag.Spell } end -- Combine flags and tags local flags = 0 local keywordFlags = 0 local tagList = { } for _, data in pairs({ modName, modFlag, modTag, modTag2, skillTag }) do if type(data) == "table" then flags = bor(flags, data.flags or 0) keywordFlags = bor(keywordFlags, data.keywordFlags or 0) if data.tag then t_insert(tagList, copyTable(data.tag)) end end end -- Generate modifier list local nameList = modName or "" local modList = { } for i, name in ipairs(type(nameList) == "table" and nameList or { nameList }) do modList[i] = { name = name .. modSuffix, type = modType, value = type(modValue) == "table" and modValue[i] or modValue, flags = flags, keywordFlags = keywordFlags, tagList = tagList, } end return modList, line:match("%S") and line end local cache = { } local unsupported = { } local count = 0 return function(line) if not cache[line] then cache[line] = { parseMod(line) } --[[if not cache[line][1] then local form = line:gsub("[%+%-]?%d+%.?%d*","{num}") if not unsupported[form] then unsupported[form] = true count = count + 1 ConPrintf("%d %s", count, form) end end]] end return unpack(copyTable(cache[line])) end