Files
PathOfBuilding/Modules/ItemTools.lua
Openarl 4e49df32fc Release 1.0.14
- Stop the melee/ranged weapon flag overrides from messing with skills that don't require it
 - Fixed ES on Sin Trek (and possibly other similar cases)
 - Enabled Dual Wielding mods
2016-09-02 00:29:47 +10:00

336 lines
12 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Path of Building
--
-- Module: Item Tools
-- Various functions for dealing with items.
--
local t_insert = table.insert
local m_min = math.min
local m_floor = math.floor
itemLib = { }
-- Apply range value (0 to 1) to a modifier that has a range: (x to x) or (x-x to x-x)
function itemLib.applyRange(line, range)
return line:gsub("%((%d+)%-(%d+) to (%d+)%-(%d+)%)",
function(lowMin, lowMax, highMin, highMax)
return string.format("%d to %d", m_floor(tonumber(lowMin) + range * (tonumber(lowMax) - tonumber(lowMin)) + 0.5), m_floor(tonumber(highMin) + range * (tonumber(highMax) - tonumber(highMin)) + 0.5))
end)
:gsub("(+?)%((%-?%d+) to (%-?%d+)%)",
function(plus, min, max)
local numVal = m_floor(tonumber(min) + range * (tonumber(max) - tonumber(min)) + 0.5)
if numVal < 0 then
if plus == "+" then
plus = ""
end
end
return plus .. tostring(numVal)
end)
:gsub("%-(%d+%%) increased", function(num) return num.." reduced" end)
end
-- Make an item from raw data
function itemLib.makeItemFromRaw(raw)
local newItem = {
-- Something something unicode support something grumble
raw = raw:gsub("^%s+",""):gsub("%s+$",""):gsub("\r\n","\n"):gsub("%b<>",""):gsub("","-"):gsub("\226\128\147","-"):gsub("\226\136\146","-"):gsub("ö","o"):gsub("\195\182","o")
}
itemLib.parseItemRaw(newItem)
if newItem.baseName then
return newItem
end
end
-- Parse raw item data and extract item name, base type, quality, and modifiers
function itemLib.parseItemRaw(item)
item.name = "?"
item.rarity = "UNIQUE"
item.quality = 0
item.rawLines = { }
for line in string.gmatch(item.raw .. "\r\n", "([^\r\n]*)\r?\n") do
line = line:gsub("^%s+",""):gsub("%s+$","")
if #line > 0 then
t_insert(item.rawLines, line)
end
end
local mode = "WIKI"
local l = 1
if item.rawLines[l] then
local rarity = item.rawLines[l]:match("^Rarity: (%a+)")
if rarity then
mode = "GAME"
item.rarity = rarity:upper()
l = l + 1
end
end
if item.rawLines[l] then
item.name = item.rawLines[l]
l = l + 1
end
if item.rarity == "NORMAL" or item.rarity == "MAGIC" then
for baseName, baseData in pairs(data.itemBases) do
if item.name:find(baseName, 1, true) then
item.baseName = baseName
item.type = baseData.type
break
end
end
elseif item.rawLines[l] and not item.rawLines[l]:match("^%-") and data.itemBases[item.rawLines[l]] then
item.baseName = item.rawLines[l]
item.title = item.name
item.name = item.title .. ", " .. item.baseName
item.type = data.itemBases[item.baseName].type
end
item.base = data.itemBases[item.baseName]
item.modLines = { }
item.implicitLines = 0
local gameModeStage = "FINDIMPLICIT"
local gameModeSection = 1
local foundExplicit
while item.rawLines[l] do
local line = item.rawLines[l]
if line == "--------" then
gameModeSection = gameModeSection + 1
if gameModeStage == "IMPLICIT" then
item.implicitLines = #item.modLines
gameModeStage = "FINDEXPLICIT"
elseif gameModeStage == "EXPLICIT" then
gameModeStage = "DONE"
end
elseif line == "Corrupted" then
item.corrupted = true
elseif data.weaponTypeInfo[line] then
item.weaponType = line
else
local specName, specVal = line:match("^([%a ]+): %+?([%d%-%.]+)")
if not specName then
specName, specVal = line:match("^([%a ]+): (.+)")
end
if specName then
if specName == "Radius" and item.type == "Jewel" then
for index, data in pairs(data.jewelRadius) do
if specVal == data.label then
item.jewelRadiusIndex = index
break
end
end
elseif specName == "Limited to" and item.type == "Jewel" then
item.limit = tonumber(specVal)
elseif specName == "Quality" then
item.quality = tonumber(specVal)
elseif specName == "Variant" then
if not item.variantList then
item.variantList = { }
end
t_insert(item.variantList, specVal)
elseif specName == "League" then
item.league = specVal
elseif specName == "Implicits" then
item.implicitLines = tonumber(specVal)
elseif specName == "Unreleased" then
item.unreleased = (specVal == "true")
end
end
if line == "Prefixes:" then
foundExplicit = true
gameModeStage = "EXPLICIT"
end
if not specName or foundExplicit then
local varSpec = line:match("^{variant:([%d,]+)}")
local variantList
if varSpec then
variantList = { }
for varId in varSpec:gmatch("%d+") do
variantList[tonumber(varId)] = true
end
end
local rangeSpec = line:match("^{range:([%d.]+)}")
line = line:gsub("%b{}", "")
local rangedLine
if line:match("%(%d+%-%d+ to %d+%-%d+%)") or line:match("%(%-?%d+ to %-?%d+%)") then
rangedLine = itemLib.applyRange(line, 1)
end
local modList, extra = modLib.parseMod(rangedLine or line)
if modList then
t_insert(item.modLines, { line = line, extra = extra, mods = modList, variantList = variantList, range = rangedLine and (tonumber(rangeSpec) or 0.5) })
if mode == "GAME" then
if gameModeStage == "FINDIMPLICIT" then
gameModeStage = "IMPLICIT"
elseif gameModeStage == "FINDEXPLICIT" then
foundExplicit = true
gameModeStage = "EXPLICIT"
end
else
foundExplicit = true
end
elseif mode == "GAME" then
if gameModeStage == "IMPLICIT" or gameModeStage == "EXPLICIT" then
t_insert(item.modLines, { line = line, extra = line, mods = { }, variantList = variantList })
elseif gameModeStage == "FINDEXPLICIT" then
gameModeStage = "DONE"
end
elseif foundExplicit then
t_insert(item.modLines, { line = line, extra = line, mods = { }, variantList = variantList })
end
end
end
l = l + 1
end
if item.base and item.base.implicit then
if item.implicitLines == 0 then
item.implicitLines = 1
end
elseif mode == "GAME" and not foundExplicit then
item.implicitLines = 0
end
if not item.corrupted then
item.quality = 20
end
if item.variantList then
item.variant = m_min(#item.variantList, item.variant or #item.variantList)
end
itemLib.buildItemModList(item)
end
-- Return the name of the slot this item is equipped in
function itemLib.getPrimarySlotForItem(item)
if item.base.weapon then
return "Weapon 1"
elseif item.type == "Quiver" or item.type == "Shield" then
return "Weapon 2"
elseif item.type == "Ring" then
return "Ring 1"
else
return item.type
end
end
-- Build list of modifiers for an item in a given slot number (1 or 2) while applying local modifers and adding quality
function itemLib.buildItemModListForSlotNum(item, baseList, slotNum)
local modList = { }
-- Process slot-specific modifiers
for k, v in pairs(baseList) do
if k:match("SocketedIn:X") then
local slotName = itemLib.getPrimarySlotForItem(item)
if slotNum then
slotName = slotName:gsub("1", slotNum)
end
k = k:gsub("SocketedIn:X","SocketedIn:"..slotName)
end
local slot = tonumber(k:match("IfSlot:(%d)_"))
if slot then
if slot == slotNum then
k = k:gsub("IfSlot:%d_","")
modLib.listMerge(modList, k, v)
end
else
if slotNum then
k = k:gsub("weaponX_","weapon"..slotNum.."_")
end
modLib.listMerge(modList, k, v)
end
end
if item.base.weapon then
local weaponPrefix = "weapon"..slotNum.."_"
modList[weaponPrefix.."type"] = item.base.type
modList[weaponPrefix.."name"] = item.name
modList[weaponPrefix.."attackRate"] = m_floor(item.base.weapon.attackRateBase * (1 + (modList.attackSpeedInc or 0) / 100) * 100 + 0.5) / 100
modList[weaponPrefix.."attackSpeedInc"] = modList.attackSpeedInc
modList.attackSpeedInc = nil
for _, dmgType in pairs({"physical","lightning","cold","fire","chaos"}) do
local min = (item.base.weapon[dmgType.."Min"] or 0) + (modList["attack_"..dmgType.."Min"] or 0)
local max = (item.base.weapon[dmgType.."Max"] or 0) + (modList["attack_"..dmgType.."Max"] or 0)
if dmgType == "physical" then
if modList.weaponLocal_noPhysical then
min, max = 0, 0
else
min = m_floor(min * (1 + ((modList["physicalInc"] or 0) + item.quality) / 100) + 0.5)
max = m_floor(max * (1 + ((modList["physicalInc"] or 0) + item.quality) / 100) + 0.5)
end
modList["physicalInc"] = nil
end
if min > 0 and max > 0 then
modList[weaponPrefix..dmgType.."Min"] = min
modList[weaponPrefix..dmgType.."Max"] = max
local dps = (min + max) / 2 * modList[weaponPrefix.."attackRate"]
modList[weaponPrefix..dmgType.."DPS"] = dps
modList[weaponPrefix.."damageDPS"] = (modList[weaponPrefix.."damageDPS"] or 0) + dps
if dmgType ~= "physical" and dmgType ~= "chaos" then
modList[weaponPrefix.."elementalDPS"] = (modList[weaponPrefix.."elementalDPS"] or 0) + dps
end
end
modList["attack_"..dmgType.."Min"] = nil
modList["attack_"..dmgType.."Max"] = nil
end
if modList.weaponLocal_alwaysCrit then
modList[weaponPrefix.."critChanceBase"] = 100
else
modList[weaponPrefix.."critChanceBase"] = m_floor(item.base.weapon.critChanceBase * (1 + (modList.critChanceInc or 0) / 100) * 100 + 0.5) / 100
end
modList.critChanceInc = nil
elseif item.base.armour then
if item.base.type == "Shield" then
modList.weapon2_type = "Shield"
end
local basePrefix = "slot:"..item.base.type.."_"
modList[basePrefix.."armourBase"] = m_floor(((item.base.armour.armourBase or 0) + (modList.armourBase or 0)) * (1 + ((modList.armourInc or 0) + (modList.armourAndEvasionInc or 0) + (modList.armourAndEnergyShieldInc or 0) + (modList.defencesInc or 0) + item.quality) / 100) + 0.5)
modList[basePrefix.."evasionBase"] = m_floor(((item.base.armour.evasionBase or 0) + (modList.evasionBase or 0)) * (1 + ((modList.evasionInc or 0) + (modList.armourAndEvasionInc or 0) + (modList.evasionAndEnergyShieldInc or 0) + (modList.defencesInc or 0) + item.quality) / 100) + 0.5)
modList[basePrefix.."energyShieldBase"] = m_floor(((item.base.armour.energyShieldBase or 0) + (modList.energyShieldBase or 0)) * (1 + ((modList.energyShieldInc or 0) + (modList.armourAndEnergyShieldInc or 0) + (modList.evasionAndEnergyShieldInc or 0) + (modList.defencesInc or 0) + item.quality) / 100) + 0.5)
if item.base.armour.blockChance then
if modList.shieldLocal_noBlock then
modList.blockChance = 0
else
modList.blockChance = item.base.armour.blockChance + (modList.blockChance or 0)
end
end
modList.armourBase = nil
modList.armourInc = nil
modList.evasionBase = nil
modList.evasionInc = nil
modList.energyShieldBase = nil
modList.energyShieldInc = nil
modList.armourAndEvasionInc = nil
modList.armourAndEnergyShieldInc = nil
modList.evasionAndEnergyShieldInc = nil
modList.defencesInc = nil
elseif item.type == "Jewel" then
item.jewelFunc = modList.jewelFunc
modList.jewelFunc = nil
end
return modList
end
-- Build lists of modifiers for each slot an item can occupy
function itemLib.buildItemModList(item)
if not item.base then
return
end
local baseList = { }
item.rangeLineList = { }
for _, modLine in ipairs(item.modLines) do
if not modLine.extra and (not modLine.variantList or modLine.variantList[item.variant]) then
if modLine.range then
local line = itemLib.applyRange(modLine.line, modLine.range)
local list, extra = modLib.parseMod(line)
if list and not extra then
modLine.mods = list
t_insert(item.rangeLineList, modLine)
end
end
for k, v in pairs(modLine.mods) do
modLib.listMerge(baseList, k, v)
end
end
end
item.baseModList = baseList
if item.base.weapon or item.type == "Ring" then
item.slotModList = { }
for i = 1, 2 do
item.slotModList[i] = itemLib.buildItemModListForSlotNum(item, baseList, i)
end
else
item.modList = itemLib.buildItemModListForSlotNum(item, baseList)
end
end