Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 57 additions & 7 deletions spec/System/TestItemParse_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,63 @@ describe("TestItemParse", function()
assert.are.equals("~price 1 chaos", item.note)
end)

it("Attribute Requirements", function()
local item = new("Item", raw("Dex: 100"))
assert.are.equals(100, item.requirements.dex)
item = new("Item", raw("Int: 101"))
assert.are.equals(101, item.requirements.int)
item = new("Item", raw("Str: 102"))
assert.are.equals(102, item.requirements.str)
it("Rune level requirements", function()
local item = new("Item", [[
Test Wand
Runic Fork
Sockets: S
Rune: Perfect Storm Rune
LevelReq: 1
Implicits: 1
{enchant}{rune}Gain 12% of Damage as Extra Lightning Damage
]])
assert.are.equals(50, item.requirements.level)
end)

it("Unique mod level requirements", function()
local foundAnvil
for _, rawUnique in ipairs(data.uniques.amulet) do
if rawUnique:match("The Anvil") then
local item = new("Item", rawUnique)
assert.are.equals(18, item.requirements.level)
assert.is_nil(rawUnique:match("Requires Level 18"))
foundAnvil = true
break
end
end
assert(foundAnvil, "The Anvil not found")

local foundChoirOfTheStorm
for _, rawUnique in ipairs(data.uniques.amulet) do
if rawUnique:match("Choir of the Storm") then
assert(rawUnique:find("Grants Skill: Level (1-20) Lightning Bolt", 1, true))
assert(rawUnique:find("Trigger Lightning Bolt Skill on Critical Hit", 1, true))
foundChoirOfTheStorm = true
break
end
end
assert(foundChoirOfTheStorm, "Choir of the Storm not found")

local foundSylvansEffigy
for _, rawUnique in ipairs(data.uniques.sceptre) do
if rawUnique:match("Sylvan's Effigy") then
local item = new("Item", rawUnique)
assert.are.equals(62, item.requirements.level)
foundSylvansEffigy = true
break
end
end
assert(foundSylvansEffigy, "Sylvan's Effigy not found")

for _, rawUnique in ipairs(data.uniques.amulet) do
if rawUnique:match("Hinekora's Sight") then
local item = new("Item", rawUnique)
assert.are.equals(44, item.requirements.level)
assert(rawUnique:find("Grants Skill: Level (1-20) Future-Past", 1, true))
return
end
end
assert(false, "Hinekora's Sight not found")
end)

it("Requires Class", function()
Expand Down
10 changes: 5 additions & 5 deletions src/Classes/ImportTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(
self.controls.logoutApiButton.shown = function()
return (self.charImportMode == "SELECTCHAR" or self.charImportMode == "GETACCOUNTNAME") and main.api.authToken ~= nil
end

self.controls.characterImportAnchor = new("Control", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, {6, 40, 200, 16})
self.controls.sectionCharImport.height = function() return self.charImportMode == "AUTHENTICATION" and 60 or 200 end

Expand Down Expand Up @@ -442,7 +442,7 @@ function ImportTabClass:DownloadCharacterList()
return "Standard"
end
end

self.charImportMode = "DOWNLOADCHARLIST"
self.charImportStatus = "Retrieving character list..."
local realm = realmList[self.controls.accountRealm.selIndex]
Expand Down Expand Up @@ -954,10 +954,10 @@ function ImportTabClass:ImportItemsAndSkills(charData)
local funcGetGemInstance = function(skillData)
local typeLine = sanitiseText(skillData.typeLine) .. (skillData.support and " Support" or "")
local gemId = self.build.data.gemForBaseName[typeLine:lower()]

if typeLine:match("^Spectre:") then
gemId = "Metadata/Items/Gems/SkillGemSummonSpectre"
end
end
if typeLine:match("^Companion:") then
gemId = "Metadata/Items/Gems/SkillGemSummonBeast"
end
Expand Down Expand Up @@ -1061,7 +1061,7 @@ function ImportTabClass:ImportItemsAndSkills(charData)
end
for _, skillData in pairs(charData.skills) do
local gemInstance = funcGetGemInstance(skillData)

if gemInstance then
local group = { label = "", enabled = true, gemList = { } }
t_insert(group.gemList, gemInstance )
Expand Down
88 changes: 18 additions & 70 deletions src/Classes/Item.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ local catalystTags = {
{ "attribute" },
}

local minimumReqLevel = { }

local function getCatalystScalar(catalystId, mod, quality)
if mod.unscalable then
return 1
Expand Down Expand Up @@ -506,6 +504,11 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
end
self.checkSection = false
end
local levelReq = line:match("^Requires:? Level (%d+)")
if levelReq then
self.requirements.level = tonumber(levelReq)
goto continue
end
local specName, specVal = line:match("^([%a %(%)]+:?): (.+)$")
if specName then
if specName == "Class:" then
Expand Down Expand Up @@ -548,16 +551,6 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
self.itemSocketCount = #self.sockets
elseif specName == "Rune" then
t_insert(self.runes, specVal)
local runeLevel = 0
local runeData = data.itemMods.Runes[specVal]
if runeData then
for _, slotData in pairs(runeData) do
runeLevel = math.max(runeLevel, slotData.rank[1])
end
end
if runeLevel > 0 and (not self.requirements.runeLevel or runeLevel > self.requirements.runeLevel) then
self.requirements.runeLevel = runeLevel
end
elseif specName == "Radius" and self.type == "Jewel" then
self.jewelRadiusLabel = specVal:match("^[%a ]+")
if specVal:match("^%a+") == "Variable" then
Expand Down Expand Up @@ -604,13 +597,11 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
end
self.armourData = self.armourData or { }
self.armourData[specName] = specToNumber(specVal)
elseif specName == "Requires Level" then
self.requirements.level = specToNumber(specVal)
minimumReqLevel = minimumReqLevel or {}
table.insert(minimumReqLevel, { name = self.name, level = specVal })
elseif specName == "Level" then
-- Requirements from imported items can't always be trusted
importedLevelReq = specToNumber(specVal)
elseif specName == "Requires Level" then
self.requirements.level = specToNumber(specVal)
elseif specName == "LevelReq" then
self.requirements.level = specToNumber(specVal)
elseif specName == "Has Alt Variant" then
Expand Down Expand Up @@ -690,9 +681,6 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
self.catalystQuality = specToNumber(specVal)
elseif specName == "Note" then
self.note = specVal
elseif specName == "Str" or specName == "Strength" or specName == "Dex" or specName == "Dexterity" or
specName == "Int" or specName == "Intelligence" then
self.requirements[specName:sub(1,3):lower()] = specToNumber(specVal)
elseif specName == "Critical Hit Range" or specName == "Attacks per Second" or specName == "Weapon Range" or
specName == "Critical Hit Chance" or specName == "Physical Damage" or specName == "Elemental Damage" or
specName == "Chaos Damage" or specName == "Fire Damage" or specName == "Cold Damage" or specName == "Lightning Damage" or
Expand Down Expand Up @@ -1202,6 +1190,14 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
end
end

for _, runeName in ipairs(self.runes) do
local runeData = data.itemMods.Runes[runeName]
if runeData then
for _, slotData in pairs(runeData) do
self.requirements.runeLevel = m_max(self.requirements.runeLevel or 0, slotData.rank[1])
end
end
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
Expand All @@ -1210,18 +1206,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
self.requirements.level = self.base.req.level
end
end
if self.base and not self.requirements.baseLevel then
-- Add only if not already present, to prevent overwriting original value.
local exists = false
for _, entry in ipairs(minimumReqLevel) do
if entry.name == self.title then
exists = true
break
end
end
if not exists then
self.requirements.baseLevel = self.base.req.level
end
if self.base then
self.requirements.level = m_max(self.requirements.level or 0, self.requirements.runeLevel or 0)
end
self.affixLimit = 0
if self.crafted then
Expand Down Expand Up @@ -1651,7 +1637,7 @@ function ItemClass:Craft()
elseif mod.type == "Suffix" then
self.nameSuffix = self.nameSuffix .. " " .. mod.affix
end
self.requirements.level = m_max(self.requirements.level or 0, m_floor(mod.level * 0.8))
self.requirements.level = m_max(self.requirements.level or 0, m_floor(mod.level * 0.8), self.requirements.runeLevel or 0)
local rangeScalar = getCatalystScalar(self.catalyst, mod, self.catalystQuality)
for i, line in ipairs(mod) do
line = itemLib.applyRange(line, affix.range or 0.5, rangeScalar)
Expand Down Expand Up @@ -2153,44 +2139,6 @@ function ItemClass:BuildModList()
self.canSocketJewelBase["Ruby"] = calcLocal(baseList, "CanSocketJewelBaseRuby", "FLAG", 0)
end

local reqLevel = 0
local minReqLevel

for _, entry in ipairs(minimumReqLevel) do
if entry.name == self.title then
minReqLevel = tonumber(entry.level)
break
end
end

if #self.grantedSkills >= 1 then
local skillDef = data.skills[self.grantedSkills[1].skillId]
local gemId = data.gemForSkill[skillDef]
local gem = data.gems[gemId]

local skillLevel = self.grantedSkills[1].level or #skillDef.levels
local chosenLevel = skillDef.levels[skillLevel] or skillDef.levels[#skillDef.levels]
local gemLevelReq = chosenLevel.levelRequirement

reqLevel = m_max(gemLevelReq, minReqLevel or 0, self.requirements.runeLevel or 0, self.requirements.baseLevel or 0)

-- Rune level and unique base level don't scale attribute requirements. Example, Cursecarver has 33 minimum required level
-- but the intelligence requirement will be 21 at level 4 skill.
local attrLevel = m_max(gemLevelReq, self.requirements.baseLevel or 0)

if self.base.type == "Sceptre" or self.base.type == "Wand" or self.base.type == "Staff" then
self.requirements.int = calcLib.getGemStatRequirement(attrLevel, gem.reqInt)
self.requirements.dex = calcLib.getGemStatRequirement(attrLevel, gem.reqDex)
self.requirements.str = calcLib.getGemStatRequirement(attrLevel, gem.reqStr)
end
else
-- If no granted skills, we want to use the "Requires Level" from the unique instead of the base armour type level requirement.
-- Currently there are no Uniques that use a lower level than the base, but maybe in the future.
reqLevel = m_max(minReqLevel or 0, self.requirements.runeLevel or 0, self.requirements.baseLevel or 0)
end

self.requirements.level = reqLevel

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" or self.name == "The Apostate, Cabalist Regalia" then
-- Hack to remove the energy shield and base int requirement
baseList:NewMod("ArmourData", "LIST", { key = "EnergyShield", value = 0 })
Expand Down
2 changes: 1 addition & 1 deletion src/Data/Bases/amulet.lua
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ itemBases["Absent Amulet"] = {
variantList = { "Cast on Elemental Ailment", "Cast on Critical", "Cast on Dodge", "Rhoa Mount", "Archmage", "Trinity", "Eternal Rage", },
implicit = "-1 Prefix Modifier allowed\n-1 Suffix Modifier allowed\n{variant:1}Grants Skill: Level (1-20) Cast on Elemental Ailment\n{variant:2}Grants Skill: Level (1-20) Cast on Critical\n{variant:3}Grants Skill: Level (1-20) Cast on Dodge\n{variant:4}Grants Skill: Level (1-20) Rhoa Mount\n{variant:5}Grants Skill: Level (1-20) Archmage\n{variant:6}Grants Skill: Level (1-20) Trinity\n{variant:7}Grants Skill: Level (1-20) Eternal Rage",
implicitModTypes = { { }, { }, },
req = { level = 49, },
req = { level = 50, },
}
itemBases["Corona Amulet"] = {
type = "Amulet",
Expand Down
Loading