Files
PathOfBuilding/Classes/Item.lua
Openarl 2806cbac3d 1.4.118 Initial commit
- Added/updated skill gems and bases
- Fixed curse stats with wrong sign
- Fixed wrong sources on quality mods
2018-12-09 16:29:54 +13:00

911 lines
32 KiB
Lua

-- Path of Building
--
-- Class: Item
-- Equippable item class
--
local t_insert = table.insert
local t_remove = table.remove
local m_min = math.min
local m_max = math.max
local m_floor = math.floor
local dmgTypeList = {"Physical", "Lightning", "Cold", "Fire", "Chaos"}
local ItemClass = newClass("Item", function(self, targetVersion, raw)
self.targetVersion = targetVersion
if raw then
self:ParseRaw(itemLib.sanitiseItemText(raw))
end
end)
-- Parse raw item data and extract item name, base type, quality, and modifiers
function ItemClass:ParseRaw(raw)
self.raw = raw
local verData = data[self.targetVersion]
self.name = "?"
self.rarity = "UNIQUE"
self.quality = nil
self.rawLines = { }
for line in string.gmatch(self.raw .. "\r\n", "([^\r\n]*)\r?\n") do
line = line:gsub("^%s+",""):gsub("%s+$","")
if #line > 0 then
t_insert(self.rawLines, line)
end
end
local mode = "WIKI"
local l = 1
if self.rawLines[l] then
local rarity = self.rawLines[l]:match("^Rarity: (%a+)")
if rarity then
mode = "GAME"
if colorCodes[rarity:upper()] then
self.rarity = rarity:upper()
end
if self.rarity == "NORMAL" then
-- Hack for relics
for _, line in ipairs(self.rawLines) do
if line == "Relic Unique" then
self.rarity = "RELIC"
break
end
end
end
l = l + 1
end
end
if self.rawLines[l] then
self.name = self.rawLines[l]
l = l + 1
end
self.namePrefix = ""
self.nameSuffix = ""
if self.rarity == "NORMAL" or self.rarity == "MAGIC" then
for baseName, baseData in pairs(verData.itemBases) do
local s, e = self.name:find(baseName, 1, true)
if s then
self.baseName = baseName
self.namePrefix = self.name:sub(1, s - 1)
self.nameSuffix = self.name:sub(e + 1)
self.type = baseData.type
break
end
end
if not self.baseName then
local s, e = self.name:find("Two-Toned Boots", 1, true)
if s then
-- Hack for Two-Toned Boots
self.baseName = "Two-Toned Boots (Armour/Energy Shield)"
self.namePrefix = self.name:sub(1, s - 1)
self.nameSuffix = self.name:sub(e + 1)
self.type = "Boots"
end
end
self.name = self.name:gsub(" %(.+%)","")
elseif self.rawLines[l] and not self.rawLines[l]:match("^%-") then
if self.rawLines[l] == "Two-Toned Boots" then
self.rawLines[l] = "Two-Toned Boots (Armour/Energy Shield)"
end
if verData.itemBases[self.rawLines[l]] then
self.baseName = self.rawLines[l]
self.title = self.name
self.name = self.title .. ", " .. self.baseName:gsub(" %(.+%)","")
self.type = verData.itemBases[self.baseName].type
l = l + 1
end
end
self.base = verData.itemBases[self.baseName]
self.sockets = { }
self.modLines = { }
self.implicitLines = 0
self.buffLines = 0
if self.base then
self.affixes = (self.base.subType and verData.itemMods[self.base.type..self.base.subType])
or verData.itemMods[self.base.type]
or verData.itemMods.Item
self.enchantments = verData.enchantments[self.base.type]
self.corruptable = self.base.type ~= "Flask"
self.shaperElderTags = data.specialBaseTags[self.type]
self.canBeShaperElder = self.shaperElderTags
end
self.variantList = nil
self.prefixes = { }
self.suffixes = { }
self.requirements = { }
if self.base then
self.requirements.str = self.base.req.str or 0
self.requirements.dex = self.base.req.dex or 0
self.requirements.int = self.base.req.int or 0
local maxReq = m_max(self.requirements.str, self.requirements.dex, self.requirements.int)
self.defaultSocketColor = (maxReq == self.requirements.dex and "G") or (maxReq == self.requirements.int and "B") or "R"
end
local importedLevelReq
local flaskBuffLines = { }
if self.base and self.base.flask and self.base.flask.buff then
self.buffLines = #self.base.flask.buff
for _, line in ipairs(self.base.flask.buff) do
flaskBuffLines[line] = true
local modList, extra = modLib.parseMod[self.targetVersion](line)
t_insert(self.modLines, { line = line, extra = extra, modList = modList or { }, buff = true })
end
end
local gameModeStage = "FINDIMPLICIT"
local gameModeSection = 1
local foundExplicit
while self.rawLines[l] do
local line = self.rawLines[l]
if flaskBuffLines[line] then
flaskBuffLines[line] = nil
elseif line == "--------" then
gameModeSection = gameModeSection + 1
if gameModeStage == "IMPLICIT" then
self.implicitLines = #self.modLines - self.buffLines
gameModeStage = "FINDEXPLICIT"
elseif gameModeStage == "EXPLICIT" then
gameModeStage = "DONE"
end
elseif line == "Corrupted" then
self.corrupted = true
elseif line == "Shaper Item" then
self.shaper = true
elseif line == "Elder Item" then
self.elder = true
else
local specName, specVal = line:match("^([%a ]+): (%x+)$")
if not specName then
specName, specVal = line:match("^([%a ]+): %+?([%d%-%.]+)")
if not tonumber(specVal) then
specName = nil
end
end
if not specName then
specName, specVal = line:match("^([%a ]+): (.+)$")
end
if not specName then
specName, specVal = line:match("^(Requires) (.+)$")
end
if specName then
if specName == "Unique ID" then
self.uniqueID = specVal
elseif specName == "Item Level" then
self.itemLevel = tonumber(specVal)
elseif specName == "Quality" then
self.quality = tonumber(specVal)
elseif specName == "Sockets" then
local group = 0
for c in specVal:gmatch(".") do
if c:match("[RGBWA]") then
t_insert(self.sockets, { color = c, group = group })
elseif c == " " then
group = group + 1
end
end
elseif specName == "Radius" and self.type == "Jewel" then
for index, data in pairs(verData.jewelRadius) do
if specVal:match("^%a+") == data.label then
self.jewelRadiusIndex = index
break
end
end
elseif specName == "Limited to" and self.type == "Jewel" then
self.limit = tonumber(specVal)
elseif specName == "Variant" then
if not self.variantList then
self.variantList = { }
end
local ver, name = specVal:match("{([%w_]+)}(.+)")
if ver then
t_insert(self.variantList, name)
if ver == self.targetVersion then
self.defaultVariant = #self.variantList
end
else
t_insert(self.variantList, specVal)
end
elseif specName == "Requires" then
self.requirements.level = tonumber(specVal:match("Level (%d+)"))
elseif specName == "Level" then
-- Requirements from imported items can't always be trusted
importedLevelReq = tonumber(specVal)
elseif specName == "LevelReq" then
self.requirements.level = tonumber(specVal)
elseif specName == "Has Alt Variant" then
self.hasAltVariant = true
elseif specName == "Selected Variant" then
self.variant = tonumber(specVal)
elseif specName == "Selected Alt Variant" then
self.variantAlt = tonumber(specVal)
elseif specName == "League" then
self.league = specVal
elseif specName == "Crafted" then
self.crafted = true
elseif specName == "Prefix" then
local range, affix = specVal:match("{range:([%d.]+)}(.+)")
t_insert(self.prefixes, {
modId = affix or specVal,
range = tonumber(range),
})
elseif specName == "Suffix" then
local range, affix = specVal:match("{range:([%d.]+)}(.+)")
t_insert(self.suffixes, {
modId = affix or specVal,
range = tonumber(range),
})
elseif specName == "Implicits" then
self.implicitLines = tonumber(specVal) or 0
gameModeStage = "EXPLICIT"
elseif specName == "Unreleased" then
self.unreleased = (specVal == "true")
elseif specName == "Upgrade" then
self.upgradePaths = self.upgradePaths or { }
t_insert(self.upgradePaths, specVal)
elseif specName == "Source" then
self.source = specVal
elseif specName == "Evasion Rating" then
if self.baseName == "Two-Toned Boots (Armour/Energy Shield)" then
-- Another hack for Two-Toned Boots
self.baseName = "Two-Toned Boots (Armour/Evasion)"
self.base = verData.itemBases[self.baseName]
end
elseif specName == "Energy Shield" then
if self.baseName == "Two-Toned Boots (Armour/Evasion)" then
-- Yet another hack for Two-Toned Boots
self.baseName = "Two-Toned Boots (Evasion/Energy Shield)"
self.base = verData.itemBases[self.baseName]
end
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.]+)}")
local crafted = line:match("{crafted}")
local custom = line:match("{custom}")
line = line:gsub("%b{}", "")
local rangedLine
if line:match("%(%d+%-%d+ to %d+%-%d+%)") or line:match("%(%-?[%d%.]+ to %-?[%d%.]+%)") or line:match("%(%-?[%d%.]+%-[%d%.]+%)") then
rangedLine = itemLib.applyRange(line, 1)
end
local modList, extra = modLib.parseMod[self.targetVersion](rangedLine or line)
if (not modList or extra) and self.rawLines[l+1] then
-- Try to combine it with the next line
local combLine = line.." "..self.rawLines[l+1]
if combLine:match("%(%d+%-%d+ to %d+%-%d+%)") or combLine:match("%(%-?[%d%.]+ to %-?[%d%.]+%)") or combLine:match("%(%-?[%d%.]+%-[%d%.]+%)") then
rangedLine = itemLib.applyRange(combLine, 1)
end
modList, extra = modLib.parseMod[self.targetVersion](rangedLine or combLine, true)
if modList and not extra then
line = line.."\n"..self.rawLines[l+1]
l = l + 1
else
modList, extra = modLib.parseMod[self.targetVersion](rangedLine or line)
end
end
if modList then
t_insert(self.modLines, { line = line, extra = extra, modList = modList, variantList = variantList, crafted = crafted, custom = custom, 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"
elseif gameModeStage == "EXPLICIT" then
foundExplicit = true
end
else
foundExplicit = true
end
elseif mode == "GAME" then
if gameModeStage == "IMPLICIT" or gameModeStage == "EXPLICIT" then
t_insert(self.modLines, { line = line, extra = line, modList = { }, variantList = variantList, crafted = crafted, custom = custom })
elseif gameModeStage == "FINDEXPLICIT" then
gameModeStage = "DONE"
end
elseif foundExplicit then
t_insert(self.modLines, { line = line, extra = line, modList = { }, variantList = variantList, crafted = crafted, custom = custom })
end
end
end
l = l + 1
end
if self.base and not self.requirements.level then
if importedLevelReq and #self.sockets == 0 then
-- Requirements on imported items can only be trusted for items with no sockets
self.requirements.level = importedLevelReq
else
self.requirements.level = self.base.req.level
end
end
if self.base and self.base.implicit then
if self.implicitLines == 0 then
self.implicitLines = 1 + #self.base.implicit:gsub("[^\n]","")
end
elseif mode == "GAME" and not foundExplicit then
self.implicitLines = 0
end
self.affixLimit = 0
if self.crafted then
if not self.affixes then
self.crafted = false
elseif self.rarity == "MAGIC" then
self.affixLimit = 2
elseif self.rarity == "RARE" then
self.affixLimit = (self.type == "Jewel" and 4 or 6)
else
self.crafted = false
end
if self.crafted then
for _, list in ipairs({self.prefixes,self.suffixes}) do
for i = 1, self.affixLimit/2 do
if not list[i] then
list[i] = { modId = "None" }
elseif list[i].modId ~= "None" and not self.affixes[list[i].modId] then
for modId, mod in pairs(self.affixes) do
if list[i].modId == mod.affix then
list[i].modId = modId
break
end
end
if not self.affixes[list[i].modId] then
list[i].modId = "None"
end
end
end
end
end
end
if self.base and self.base.socketLimit then
if #self.sockets == 0 then
for i = 1, self.base.socketLimit do
t_insert(self.sockets, {
color = self.defaultSocketColor,
group = 0,
})
end
end
end
self.abyssalSocketCount = 0
if self.variantList then
self.variant = m_min(#self.variantList, self.variant or self.defaultVariant or #self.variantList)
if self.hasAltVariant then
self.variantAlt = m_min(#self.variantList, self.variantAlt or self.defaultVariant or #self.variantList)
end
end
if not self.quality then
self:NormaliseQuality()
end
self:BuildModList()
end
function ItemClass:NormaliseQuality()
if self.base and (self.base.armour or self.base.weapon or self.base.flask) then
if not self.quality then
self.quality = self.corrupted and 0 or 20
elseif not self.uniqueID and not self.corrupted then
self.quality = 20
end
end
end
function ItemClass:GetModSpawnWeight(mod, extraTags)
if self.base then
for i, key in ipairs(mod.weightKey) do
if self.base.tags[key] or (extraTags and extraTags[key]) or (self.shaperElderTags and (self.shaper and self.shaperElderTags.shaper == key) or (self.elder and self.shaperElderTags.elder == key)) then
return mod.weightVal[i]
end
end
end
return 0
end
function ItemClass:BuildRaw()
local rawLines = { }
t_insert(rawLines, "Rarity: "..self.rarity)
if self.title then
t_insert(rawLines, self.title)
t_insert(rawLines, self.baseName)
else
t_insert(rawLines, (self.namePrefix or "")..self.baseName..(self.nameSuffix or ""))
end
if self.uniqueID then
t_insert(rawLines, "Unique ID: "..self.uniqueID)
end
if self.league then
t_insert(rawLines, "League: "..self.league)
end
if self.unreleased then
t_insert(rawLines, "Unreleased: true")
end
if self.shaper then
t_insert(rawLines, "Shaper Item")
end
if self.elder then
t_insert(rawLines, "Elder Item")
end
if self.crafted then
t_insert(rawLines, "Crafted: true")
for i, affix in ipairs(self.prefixes or { }) do
t_insert(rawLines, "Prefix: "..(affix.range and ("{range:"..round(affix.range,3).."}") or "")..affix.modId)
end
for i, affix in ipairs(self.suffixes or { }) do
t_insert(rawLines, "Suffix: "..(affix.range and ("{range:"..round(affix.range,3).."}") or "")..affix.modId)
end
end
if self.itemLevel then
t_insert(rawLines, "Item Level: "..self.itemLevel)
end
if self.variantList then
for _, variantName in ipairs(self.variantList) do
t_insert(rawLines, "Variant: "..variantName)
end
t_insert(rawLines, "Selected Variant: "..self.variant)
if self.hasAltVariant then
t_insert(rawLines, "Has Alt Variant: true")
t_insert(rawLines, "Selected Alt Variant: "..self.variantAlt)
end
end
if self.quality then
t_insert(rawLines, "Quality: "..self.quality)
end
if self.sockets and #self.sockets > 0 then
local line = "Sockets: "
for i, socket in pairs(self.sockets) do
line = line .. socket.color
if self.sockets[i+1] then
line = line .. (socket.group == self.sockets[i+1].group and "-" or " ")
end
end
t_insert(rawLines, line)
end
if self.requirements and self.requirements.level then
t_insert(rawLines, "LevelReq: "..self.requirements.level)
end
if self.jewelRadiusIndex then
t_insert(rawLines, "Radius: "..data.jewelRadius[self.jewelRadiusIndex].label)
end
if self.limit then
t_insert(rawLines, "Limited to: "..self.limit)
end
t_insert(rawLines, "Implicits: "..self.implicitLines)
for _, modLine in ipairs(self.modLines) do
if not modLine.buff then
local line = modLine.line
if modLine.range then
line = "{range:"..round(modLine.range,3).."}" .. line
end
if modLine.crafted then
line = "{crafted}" .. line
end
if modLine.custom then
line = "{custom}" .. line
end
if modLine.variantList then
local varSpec
for varId in pairs(modLine.variantList) do
varSpec = (varSpec and varSpec.."," or "") .. varId
end
line = "{variant:"..varSpec.."}"..line
end
t_insert(rawLines, line)
end
end
if self.corrupted then
t_insert(rawLines, "Corrupted")
end
return table.concat(rawLines, "\n")
end
function ItemClass:BuildAndParseRaw()
local raw = self:BuildRaw()
self:ParseRaw(raw)
end
-- Rebuild explicit modifiers using the item's affixes
function ItemClass:Craft()
local custom = { }
for l = self.buffLines + self.implicitLines + 1, #self.modLines do
local modLine = self.modLines[l]
if modLine.custom or modLine.crafted then
t_insert(custom, modLine)
end
self.modLines[l] = nil
end
self.namePrefix = ""
self.nameSuffix = ""
self.requirements.level = self.base.req.level
local statOrder = { }
for _, list in ipairs({self.prefixes,self.suffixes}) do
for i = 1, self.affixLimit/2 do
local affix = list[i]
if not affix then
list[i] = { modId = "None" }
end
local mod = self.affixes[affix.modId]
if mod then
if mod.type == "Prefix" then
self.namePrefix = mod.affix .. " "
elseif mod.type == "Suffix" then
self.nameSuffix = " " .. mod.affix
end
self.requirements.level = m_max(self.requirements.level or 0, m_floor(mod.level * 0.8))
for i, line in ipairs(mod) do
line = itemLib.applyRange(line, affix.range or 0.5)
local order = mod.statOrder[i]
if statOrder[order] then
-- Combine stats
local start = 1
statOrder[order].line = statOrder[order].line:gsub("%d+", function(num)
local s, e, other = line:find("(%d+)", start)
start = e + 1
return tonumber(num) + tonumber(other)
end)
else
local modLine = { line = line, order = order }
for l = self.buffLines + self.implicitLines + 1, #self.modLines + 1 do
if not self.modLines[l] or self.modLines[l].order > order then
t_insert(self.modLines, l, modLine)
break
end
end
statOrder[order] = modLine
end
end
end
end
end
for _, line in ipairs(custom) do
t_insert(self.modLines, line)
end
self:BuildAndParseRaw()
end
function ItemClass:CheckModLineVariant(modLine)
return not modLine.variantList
or modLine.variantList[self.variant]
or (self.hasAltVariant and modLine.variantList[self.variantAlt])
end
-- Return the name of the slot this item is equipped in
function ItemClass:GetPrimarySlot()
if self.base.weapon then
return "Weapon 1"
elseif self.type == "Quiver" or self.type == "Shield" then
return "Weapon 2"
elseif self.type == "Ring" then
return "Ring 1"
elseif self.type == "Flask" then
return "Flask 1"
else
return self.type
end
end
-- Add up local modifiers, and removes them from the modifier list
-- To be considered local, a modifier must be an exact flag match, and cannot have any tags (e.g conditions, multipliers)
-- Only the InSlot tag is allowed (for Adds x to x X Damage in X Hand modifiers)
local function sumLocal(modList, name, type, flags)
local result
if type == "FLAG" then
result = false
else
result = 0
end
local i = 1
while modList[i] do
local mod = modList[i]
if mod.name == name and mod.type == type and mod.flags == flags and mod.keywordFlags == 0 and (not mod[1] or mod[1].type == "InSlot") then
if type == "FLAG" then
result = result or mod.value
else
result = result + mod.value
end
t_remove(modList, i)
else
i = i + 1
end
end
return result
end
-- Build list of modifiers in a given slot number (1 or 2) while applying local modifers and adding quality
function ItemClass:BuildModListForSlotNum(baseList, slotNum)
local slotName = self:GetPrimarySlot()
if slotNum == 2 then
slotName = slotName:gsub("1", "2")
end
local modList = new("ModList")
for _, baseMod in ipairs(baseList) do
local mod = copyTable(baseMod)
local add = true
for _, tag in ipairs(mod) do
if tag.type == "SlotNumber" or tag.type == "InSlot" then
if tag.num ~= slotNum then
add = false
break
end
end
for k, v in pairs(tag) do
if type(v) == "string" then
tag[k] = v:gsub("{SlotName}", slotName)
:gsub("{Hand}", (slotNum == 1) and "MainHand" or "OffHand")
:gsub("{OtherSlotNum}", slotNum == 1 and "2" or "1")
end
end
end
if add then
mod.sourceSlot = slotName
modList:AddMod(mod)
end
end
if #self.sockets > 0 then
local multiName = {
R = "Multiplier:RedSocketIn"..slotName,
G = "Multiplier:GreenSocketIn"..slotName,
B = "Multiplier:BlueSocketIn"..slotName,
W = "Multiplier:WhiteSocketIn"..slotName,
}
for _, socket in ipairs(self.sockets) do
if multiName[socket.color] then
modList:NewMod(multiName[socket.color], "BASE", 1, "Item Sockets")
end
end
end
if self.base.weapon then
local weaponData = { }
self.weaponData[slotNum] = weaponData
weaponData.type = self.base.type
weaponData.name = self.name
weaponData.AttackSpeedInc = sumLocal(modList, "Speed", "INC", ModFlag.Attack)
weaponData.AttackRate = round(self.base.weapon.AttackRateBase * (1 + weaponData.AttackSpeedInc / 100), 2)
for _, dmgType in pairs(dmgTypeList) do
local min = (self.base.weapon[dmgType.."Min"] or 0) + sumLocal(modList, dmgType.."Min", "BASE", 0)
local max = (self.base.weapon[dmgType.."Max"] or 0) + sumLocal(modList, dmgType.."Max", "BASE", 0)
if dmgType == "Physical" then
local physInc = sumLocal(modList, "PhysicalDamage", "INC", 0)
min = round(min * (1 + (physInc + self.quality) / 100))
max = round(max * (1 + (physInc + self.quality) / 100))
end
if min > 0 and max > 0 then
weaponData[dmgType.."Min"] = min
weaponData[dmgType.."Max"] = max
local dps = (min + max) / 2 * weaponData.AttackRate
weaponData[dmgType.."DPS"] = dps
if dmgType ~= "Physical" and dmgType ~= "Chaos" then
weaponData.ElementalDPS = (weaponData.ElementalDPS or 0) + dps
end
end
end
weaponData.CritChance = round(self.base.weapon.CritChanceBase * (1 + sumLocal(modList, "CritChance", "INC", 0) / 100), 2)
for _, value in ipairs(modList:List(nil, "WeaponData")) do
weaponData[value.key] = value.value
end
if self.targetVersion == "2_6" then
local accuracyInc = sumLocal(modList, "Accuracy", "INC", 0)
if accuracyInc > 0 then
modList:NewMod("Accuracy", "MORE", accuracyInc, self.modSource, { type = "Condition", var = (slotNum == 1) and "MainHandAttack" or "OffHandAttack" })
end
end
if data[self.targetVersion].weaponTypeInfo[self.base.type].range then
weaponData.range = data[self.targetVersion].weaponTypeInfo[self.base.type].range + sumLocal(modList, "WeaponRange", "BASE", 0)
end
for _, mod in ipairs(modList) do
-- Convert accuracy, L/MGoH and PAD Leech modifiers to local
if (
(mod.name == "Accuracy" and mod.flags == 0) or
((mod.name == "LifeOnHit" or mod.name == "ManaOnHit") and mod.flags == ModFlag.Attack) or
((mod.name == "PhysicalDamageLifeLeech" or mod.name == "PhysicalDamageManaLeech") and mod.flags == ModFlag.Attack)
) and mod.keywordFlags == 0 and not mod[1] then
mod[1] = { type = "Condition", var = (slotNum == 1) and "MainHandAttack" or "OffHandAttack" }
elseif self.targetVersion ~= "2_6" and (mod.name == "PoisonChance" or mod.name == "BleedChance") and (not mod[1] or (mod[1].type == "Condition" and mod[1].var == "CriticalStrike" and not mod[2])) then
t_insert(mod, { type = "Condition", var = (slotNum == 1) and "MainHandAttack" or "OffHandAttack" })
end
end
weaponData.TotalDPS = 0
for _, dmgType in pairs(dmgTypeList) do
weaponData.TotalDPS = weaponData.TotalDPS + (weaponData[dmgType.."DPS"] or 0)
end
elseif self.base.armour then
local armourData = self.armourData
local armourBase = sumLocal(modList, "Armour", "BASE", 0) + (self.base.armour.ArmourBase or 0)
local armourEvasionBase = sumLocal(modList, "ArmourAndEvasion", "BASE", 0)
local evasionBase = sumLocal(modList, "Evasion", "BASE", 0) + (self.base.armour.EvasionBase or 0)
local evasionEnergyShieldBase = sumLocal(modList, "EvasionAndEnergyShield", "BASE", 0)
local energyShieldBase = sumLocal(modList, "EnergyShield", "BASE", 0) + (self.base.armour.EnergyShieldBase or 0)
local armourEnergyShieldBase = sumLocal(modList, "ArmourAndEnergyShield", "BASE", 0)
local armourInc = sumLocal(modList, "Armour", "INC", 0)
local armourEvasionInc = sumLocal(modList, "ArmourAndEvasion", "INC", 0)
local evasionInc = sumLocal(modList, "Evasion", "INC", 0)
local evasionEnergyShieldInc = sumLocal(modList, "EvasionAndEnergyShield", "INC", 0)
local energyShieldInc = sumLocal(modList, "EnergyShield", "INC", 0)
local armourEnergyShieldInc = sumLocal(modList, "ArmourAndEnergyShield", "INC", 0)
local defencesInc = sumLocal(modList, "Defences", "INC", 0)
armourData.Armour = round((armourBase + armourEvasionBase + armourEnergyShieldBase) * (1 + (armourInc + armourEvasionInc + armourEnergyShieldInc + defencesInc + self.quality) / 100))
armourData.Evasion = round((evasionBase + armourEvasionBase + evasionEnergyShieldBase) * (1 + (evasionInc + armourEvasionInc + evasionEnergyShieldInc + defencesInc + self.quality) / 100))
armourData.EnergyShield = round((energyShieldBase + evasionEnergyShieldBase + armourEnergyShieldBase) * (1 + (energyShieldInc + armourEnergyShieldInc + evasionEnergyShieldInc + defencesInc + self.quality) / 100))
if self.base.armour.BlockChance then
armourData.BlockChance = self.base.armour.BlockChance + sumLocal(modList, "BlockChance", "BASE", 0)
end
if self.base.armour.MovementPenalty then
modList:NewMod("MovementSpeed", "INC", -self.base.armour.MovementPenalty, self.modSource, { type = "Condition", var = "IgnoreMovementPenalties", neg = true })
end
for _, value in ipairs(modList:List(nil, "ArmourData")) do
armourData[value.key] = value.value
end
elseif self.base.flask then
local flaskData = self.flaskData
local durationInc = sumLocal(modList, "Duration", "INC", 0)
if self.base.flask.life or self.base.flask.mana then
-- Recovery flask
flaskData.instantPerc = sumLocal(modList, "FlaskInstantRecovery", "BASE", 0)
local recoveryMod = 1 + sumLocal(modList, "FlaskRecovery", "INC", 0) / 100
local rateMod = 1 + sumLocal(modList, "FlaskRecoveryRate", "INC", 0) / 100
flaskData.duration = self.base.flask.duration * (1 + durationInc / 100) / rateMod
if self.base.flask.life then
flaskData.lifeBase = self.base.flask.life * (1 + self.quality / 100) * recoveryMod
flaskData.lifeInstant = flaskData.lifeBase * flaskData.instantPerc / 100
flaskData.lifeGradual = flaskData.lifeBase * (1 - flaskData.instantPerc / 100) * (1 + durationInc / 100)
flaskData.lifeTotal = flaskData.lifeInstant + flaskData.lifeGradual
end
if self.base.flask.mana then
flaskData.manaBase = self.base.flask.mana * (1 + self.quality / 100) * recoveryMod
flaskData.manaInstant = flaskData.manaBase * flaskData.instantPerc / 100
flaskData.manaGradual = flaskData.manaBase * (1 - flaskData.instantPerc / 100) * (1 + durationInc / 100)
flaskData.manaTotal = flaskData.manaInstant + flaskData.manaGradual
end
else
-- Utility flask
flaskData.duration = self.base.flask.duration * (1 + (durationInc + self.quality) / 100)
end
flaskData.chargesMax = self.base.flask.chargesMax + sumLocal(modList, "FlaskCharges", "BASE", 0)
flaskData.chargesUsed = m_floor(self.base.flask.chargesUsed * (1 + sumLocal(modList, "FlaskChargesUsed", "INC", 0) / 100))
flaskData.gainMod = 1 + sumLocal(modList, "FlaskChargeRecovery", "INC", 0) / 100
flaskData.effectInc = sumLocal(modList, "FlaskEffect", "INC", 0)
for _, value in ipairs(modList:List(nil, "FlaskData")) do
flaskData[value.key] = value.value
end
elseif self.type == "Jewel" then
local jewelData = self.jewelData
for _, func in ipairs(modList:List(nil, "JewelFunc")) do
jewelData.funcList = jewelData.funcList or { }
t_insert(jewelData.funcList, func)
end
for _, value in ipairs(modList:List(nil, "JewelData")) do
jewelData[value.key] = value.value
end
end
return { unpack(modList) }
end
-- Build lists of modifiers for each slot the item can occupy
function ItemClass:BuildModList()
if not self.base then
return
end
local baseList = new("ModList")
if self.base.weapon then
self.weaponData = { }
elseif self.base.armour then
self.armourData = { }
elseif self.base.flask then
self.flaskData = { }
self.buffModList = { }
elseif self.type == "Jewel" then
self.jewelData = { }
end
self.baseModList = baseList
self.rangeLineList = { }
self.modSource = "Item:"..(self.id or -1)..":"..self.name
for _, modLine in ipairs(self.modLines) do
if not modLine.extra and self:CheckModLineVariant(modLine) then
if modLine.range then
local line = itemLib.applyRange(modLine.line:gsub("\n"," "), modLine.range)
local list, extra = modLib.parseMod[self.targetVersion](line)
if list and not extra then
modLine.modList = list
t_insert(self.rangeLineList, modLine)
end
end
for _, mod in ipairs(modLine.modList) do
mod.source = self.modSource
if type(mod.value) == "table" and mod.value.mod then
mod.value.mod.source = mod.source
end
if modLine.buff then
t_insert(self.buffModList, mod)
else
baseList:AddMod(mod)
end
end
end
end
if sumLocal(baseList, "NoAttributeRequirements", "FLAG", 0) then
self.requirements.strMod = 0
self.requirements.dexMod = 0
self.requirements.intMod = 0
else
self.requirements.strMod = m_floor((self.requirements.str + sumLocal(baseList, "StrRequirement", "BASE", 0)) * (1 + sumLocal(baseList, "StrRequirement", "INC", 0) / 100))
self.requirements.dexMod = m_floor((self.requirements.dex + sumLocal(baseList, "DexRequirement", "BASE", 0)) * (1 + sumLocal(baseList, "DexRequirement", "INC", 0) / 100))
self.requirements.intMod = m_floor((self.requirements.int + sumLocal(baseList, "IntRequirement", "BASE", 0)) * (1 + sumLocal(baseList, "IntRequirement", "INC", 0) / 100))
end
self.grantedSkills = { }
for _, skill in ipairs(baseList:List(nil, "ExtraSkill")) do
if skill.name ~= "Unknown" then
t_insert(self.grantedSkills, {
skillId = skill.skillId,
level = skill.level,
noSupports = skill.noSupports,
source = self.modSource,
})
end
end
local socketCount = sumLocal(baseList, "SocketCount", "BASE", 0)
self.abyssalSocketCount = sumLocal(baseList, "AbyssalSocketCount", "BASE", 0)
self.selectableSocketCount = m_max(self.base.socketLimit or 0, #self.sockets) - self.abyssalSocketCount
if sumLocal(baseList, "NoSockets", "FLAG", 0) then
-- Remove all sockets
wipeTable(self.sockets)
self.selectableSocketCount = 0
elseif socketCount > 0 then
-- Force the socket count to be equal to the stated number
self.selectableSocketCount = socketCount
local group = 0
for i = 1, m_max(socketCount, #self.sockets) do
if i > socketCount then
self.sockets[i] = nil
elseif not self.sockets[i] then
self.sockets[i] = {
color = self.defaultSocketColor,
group = group
}
else
group = self.sockets[i].group
end
end
elseif self.abyssalSocketCount > 0 then
-- Ensure that there are the correct number of abyssal sockets present
local newSockets = { }
local group = 0
if self.sockets then
for i, socket in ipairs(self.sockets) do
if socket.color ~= "A" then
t_insert(newSockets, socket)
group = socket.group
if #newSockets >= self.selectableSocketCount then
break
end
end
end
end
for i = 1, self.abyssalSocketCount do
group = group + 1
t_insert(newSockets, {
color = "A",
group = group
})
end
self.sockets = newSockets
end
self.socketedJewelEffectModifier = 1 + sumLocal(baseList, "SocketedJewelEffect", "INC", 0) / 100
if self.name == "Tabula Rasa, Simple Robe" or self.name == "Skin of the Loyal, Simple Robe" or self.name == "Skin of the Lords, Simple Robe" then
-- Hack to remove the energy shield
baseList:NewMod("ArmourData", "LIST", { key = "EnergyShield", value = 0 })
end
if self.base.weapon or self.type == "Ring" then
self.slotModList = { }
for i = 1, 2 do
self.slotModList[i] = self:BuildModListForSlotNum(baseList, i)
end
else
self.modList = self:BuildModListForSlotNum(baseList)
end
end