diff --git a/spec/System/TestFoulborn_spec.lua b/spec/System/TestFoulborn_spec.lua new file mode 100644 index 00000000000..5bc5585fc29 --- /dev/null +++ b/spec/System/TestFoulborn_spec.lua @@ -0,0 +1,211 @@ +describe("TestFoulborn", function() + -- spell-checker: disable + -- Voll's Devotion has two foulborn-convertible mods in ModFoulbornMap.jsonc: + -- "30% reduced Power Charge Duration" -> "Gain a Power Charge every Second..." + -- "30% reduced Endurance Charge Duration" -> "Lose all Power Charges..." + local powerBase = "30% reduced Power Charge Duration" + local powerFoulborn = "Gain a Power Charge every Second if you haven't lost Power Charges Recently" + + local function vollsDevotion() + return new("Item", [[ + Rarity: Unique + Voll's Devotion + Agate Amulet + Implicits: 1 + +(16-24) to Strength and Intelligence + +(20-30) to maximum Energy Shield + +(30-40) to maximum Life + +(15-20)% to all Elemental Resistances + 30% reduced Endurance Charge Duration + 30% reduced Power Charge Duration + Gain an Endurance Charge when you lose a Power Charge + ]]) + end + + local function findByMutatedLine(item, replacement) + for _, modLine in ipairs(item.explicitModLines) do + if (modLine.mutatedLine == replacement) or (modLine.line == replacement) then + return modLine + end + end + end + + local function findByLine(item, text) + for _, modLine in ipairs(item.explicitModLines) do + if modLine.line == text then + return modLine + end + end + end + + it("matches convertible mods to their foulborn replacement", function() + local item = vollsDevotion() + local modLine = findByMutatedLine(item, powerFoulborn) + assert.is_not_nil(modLine) + assert.are.equals(powerBase, modLine.line) + -- present but inactive by default, so the item is not yet foulborn + assert.is_false(modLine.mutateActive) + assert.is_falsy(item.foulborn) + end) + + it("does not mark non-convertible mods", function() + local item = vollsDevotion() + local plainMod = findByLine(item, "Gain an Endurance Charge when you lose a Power Charge") + assert.is_not_nil(plainMod) + assert.is_nil(plainMod.mutatedLine) + end) + + it("converts the item to foulborn when a mod is activated", function() + local item = vollsDevotion() + findByMutatedLine(item, powerFoulborn).mutateActive = true + item:BuildAndParseRaw() + + assert.is_true(item.foulborn) + assert.are.equals("Foulborn Voll's Devotion", item.title) + + -- the original mod line is now shown as the foulborn text + local mutated = findByLine(item, powerFoulborn) + assert.is_not_nil(mutated) + assert.is_true(mutated.mutated) + assert.are.equals(powerBase, mutated.originalLine) + end) + + it("round-trips foulborn state through BuildRaw", function() + local item = vollsDevotion() + findByMutatedLine(item, powerFoulborn).mutateActive = true + item:BuildAndParseRaw() + + local raw = item:BuildRaw() + assert.is_truthy(raw:find("{mutateActive}", 1, true)) + assert.is_truthy(raw:find("{originalLine:" .. powerBase .. "}", 1, true)) + + -- re-importing the built text preserves the foulborn conversion + local reimported = new("Item", raw) + assert.is_true(reimported.foulborn) + assert.are.equals("Foulborn Voll's Devotion", reimported.title) + end) + + it("reverts to the base item when the mod is deactivated", function() + local item = vollsDevotion() + findByMutatedLine(item, powerFoulborn).mutateActive = true + item:BuildAndParseRaw() + assert.is_true(item.foulborn) + + findByMutatedLine(item, powerFoulborn).mutateActive = false + item:BuildAndParseRaw() + + assert.is_falsy(item.foulborn) + assert.are.equals("Voll's Devotion", item.title) + assert.is_not_nil(findByLine(item, powerBase)) + end) + + it("only converts one mod when several are convertible", function() + local item = vollsDevotion() + findByMutatedLine(item, powerFoulborn).mutateActive = true + item:BuildAndParseRaw() + + -- the endurance charge mod stays in its base form and is still offered + local endurance = findByLine(item, "30% reduced Endurance Charge Duration") + assert.is_not_nil(endurance) + assert.is_falsy(endurance.mutateActive) + assert.is_not_nil(endurance.mutatedLine) + end) + + it("leaves items without a foulborn mapping untouched", function() + -- same mod text, but on a rare: mutatedLines is only populated for uniques + local item = new("Item", "Rarity: Rare\nTest Subject\nAgate Amulet\n30% reduced Power Charge Duration") + assert.is_nil(item.mutatedLines) + assert.is_falsy(item.foulborn) + assert.is_nil(item.explicitModLines[1].mutatedLine) + end) + -- Meginord's Vise maps a single mod to a two-line foulborn replacement: + -- "+# to Strength" -> "+700 Strength Requirement\n(10-15)% chance to deal Triple Damage" + local viseFoulborn = "+700 Strength Requirement\n(10-15)% chance to deal Triple Damage" + local viseBase = "+50 to Strength" + + local function meginordsVise() + return new("Item", [[ + Rarity: Unique + Meginord's Vise + Steel Gauntlets + +50 to Strength + 100% increased Knockback Distance + ]]) + end + + local function linesAllByOriginal(item, originalLine) + local out = {} + for _, modLine in ipairs(item.explicitModLines) do + if modLine.originalLine == originalLine then + table.insert(out, modLine) + end + end + return out + end + + local function hasMod(item, name) + for _, modLine in ipairs(item.explicitModLines) do + for _, mod in ipairs(modLine.modList or {}) do + if mod.name == name then + return true + end + end + end + return false + end + + it("expands a single mod into two lines when a 1->2 foulborn mod is activated", function() + local item = meginordsVise() + + -- one base mod carries the whole multi-line replacement + local base = findByMutatedLine(item, viseFoulborn) + assert.is_not_nil(base) + assert.are.equals(viseBase, base.line) + assert.is_false(base.mutateActive) + assert.is_falsy(item.foulborn) + + base.mutateActive = true + item:BuildAndParseRaw() + + assert.is_true(item.foulborn) + assert.are.equals("Foulborn Meginord's Vise", item.title) + + -- the base line is gone, replaced by one modLine per stat line + assert.is_nil(findByLine(item, viseBase)) + local mutated = linesAllByOriginal(item, viseBase) + assert.are.equals(2, #mutated) + + local seen = {} + for _, modLine in ipairs(mutated) do + assert.is_true(modLine.mutated) + seen[modLine.line] = true + end + assert.is_true(seen["+700 Strength Requirement"]) + assert.is_true(seen["(10-15)% chance to deal Triple Damage"]) + + -- both stat lines contribute their own parsed mod + assert.is_true(hasMod(item, "StrRequirement")) + assert.is_true(hasMod(item, "TripleDamageChance")) + end) + + it("collapses the expanded lines back to the base mod when deactivated", function() + local item = meginordsVise() + findByMutatedLine(item, viseFoulborn).mutateActive = true + item:BuildAndParseRaw() + assert.are.equals(2, #linesAllByOriginal(item, viseBase)) + + linesAllByOriginal(item, viseBase)[1].mutateActive = false + item:BuildAndParseRaw() + + assert.is_falsy(item.foulborn) + assert.are.equals("Meginord's Vise", item.title) + + -- the two expanded lines collapse back into the single base mod + local restored = linesAllByOriginal(item, viseBase) + assert.are.equals(1, #restored) + assert.are.equals(viseBase, restored[1].line) + assert.is_falsy(restored[1].mutated) + assert.is_not_nil(findByLine(item, viseBase)) + assert.is_false(hasMod(item, "TripleDamageChance")) + end) +end) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 4803339c75e..f42773091e9 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -80,9 +80,19 @@ for _, curInfluenceInfo in ipairs(influenceInfo) do end local lineFlags = { - ["crafted"] = true, ["crucible"] = true, ["custom"] = true, ["eater"] = true, ["enchant"] = true, - ["exarch"] = true, ["fractured"] = true, ["implicit"] = true, ["scourge"] = true, ["synthesis"] = true, - ["mutated"] = true, ["unscalable"] = true + ["crafted"] = true, + ["crucible"] = true, + ["custom"] = true, + ["eater"] = true, + ["enchant"] = true, + ["exarch"] = true, + ["fractured"] = true, + ["implicit"] = true, + ["scourge"] = true, + ["synthesis"] = true, + ["mutated"] = true, + ["unscalable"] = true, + ["mutateActive"] = true } -- Special function to store unique instances of modifier on specific item slots @@ -377,6 +387,9 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self.requirements.dex = 0 self.requirements.int = 0 self.baseLines = { } + -- assume the item is not foulborn. if it is, this will be set to true when + -- a mod has the mutated tag + self.foulborn = false local importedLevelReq local flaskBuffLines local tinctureBuffLines @@ -695,6 +708,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) for tag in val:gmatch("[%a_]+") do t_insert(modLine.modTags, tag) end + elseif k == "originalLine" then + modLine.originalLine = val elseif k == "range" then modLine.range = tonumber(val) elseif lineFlags[k] then @@ -769,8 +784,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) if not (self.rarity == "NORMAL" or self.rarity == "MAGIC") then self.title = self.name end - if self.title and self.title:find("Foulborn") then - self.foulborn = true + if self.rarity == "UNIQUE" and self.title then + self.mutatedLines = data.foulbornMap[self.title:gsub("^[Ff]oulborn ", "")] end self.type = base.type self.base = base @@ -890,20 +905,19 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end local rangedLine = itemLib.applyRange(line, 1, catalystScalar) local modList, extra = modLib.parseMod(rangedLine) - if (not modList or extra) and self.rawLines[l+1] then + if (not modList or extra) and self.rawLines[l + 1] then -- Try to combine it with the next line - local nextLine = self.rawLines[l+1]:gsub("%b{}", ""):gsub(" ?%(%l+%)","") - local combLine = line.." "..nextLine + local nextLine = self.rawLines[l + 1]:gsub("%b{}", ""):gsub(" ?%(%l+%)", "") + local combLine = line .. " " .. nextLine rangedLine = itemLib.applyRange(combLine, 1, catalystScalar) modList, extra = modLib.parseMod(rangedLine, true) if modList and not extra then - line = line.."\n"..nextLine + line = line .. "\n" .. nextLine l = l + 1 else modList, extra = modLib.parseMod(rangedLine) end end - local lineLower = line:lower() if lineLower == "implicit modifiers cannot be changed" then self.implicitsCannotBeChanged = true @@ -1093,6 +1107,96 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self.variantAlt5 = m_min(#self.variantList, self.variantAlt5 or #self.variantList) end end + local function normalise(line) + return line:gsub("%d+%.?%d*", "#") + :gsub("%(%-?#%-#%)", "#"):lower() + :gsub("\n", " ") + end + local i = 1 + -- apply foulborn mod transformation + if self.mutatedLines then + while self.explicitModLines[i] do + local modLine = self.explicitModLines[i] + local function matchMutatedLine(lineText) + local normalised = normalise(lineText) + for pattern, replacement in pairs(self.mutatedLines) do + if normalised == pattern or + -- exception: timeclasp + (normalised:gsub("reduced", "increased") == pattern) then + modLine.mutatedLine = replacement + modLine.mutateActive = modLine.mutateActive or false + end + end + end + matchMutatedLine(modLine.line) + local doubleLine + -- pob parsing can be weird and result in arbitrarily splitting + -- lines which means we have to test two adjacent lines to be sure + -- it's not a match + if not modLine.mutatedLine and self.explicitModLines[i + 1] then + matchMutatedLine(modLine.line .. " " .. self.explicitModLines[i + 1].line) + if modLine.mutatedLine then + doubleLine = true + end + end + -- if we both find a matching mod and want to transform this, we + -- remove the original one and add a new modline for each literal + -- line in the string. this is because foulborn mods might map from + -- a single line to multiple (e.g. ventor's gamble) + if modLine.mutatedLine and modLine.mutateActive then + table.remove(self.explicitModLines, i) + if doubleLine then + local secondLine = table.remove(self.explicitModLines, i) + modLine.line = modLine.line .. " " .. secondLine.line + end + modLine.originalLine = modLine.line:gsub("\n", " ") + modLine.line = modLine.mutatedLine + modLine.mutatedLine = nil + modLine.mutated = true + local extraLines = -1 + for line in (modLine.line .. "\n"):gmatch("([^\n]+)") do + local copiedLine = copyTable(modLine) + table.insert(self.explicitModLines, i, copiedLine) + + copiedLine.line = line + local rangedLine = itemLib.applyRange(line, copiedLine.range or main.defaultItemAffixQuality, 1) + local modList, extra = modLib.parseMod(rangedLine) + copiedLine.modList = modList + copiedLine.extra = extra + + extraLines = extraLines + 1 + end + i = i + extraLines + -- restore original line by removing all lines which came from the original line + elseif modLine.originalLine and not modLine.mutateActive then + local i = 1 + while self.explicitModLines[i] do + if (self.explicitModLines[i] ~= modLine) and (self.explicitModLines[i].originalLine == modLine.originalLine) then + table.remove(self.explicitModLines, i) + else + i = i + 1 + end + end + modLine.line = modLine.originalLine + modLine.mutated = false + local rangedLine = itemLib.applyRange(modLine.line, modLine.range or main.defaultItemAffixQuality, 1) + local modList, extra = modLib.parseMod(rangedLine) + modLine.modList = modList + modLine.extra = extra + end + if modLine.mutated then + self.foulborn = true + end + i = i + 1 + end + end + -- ensure that foulborn items have the name prefix + local hasFoulbornPrefix = self.title and self.title:lower():find("^foulborn ") + if self.foulborn and not hasFoulbornPrefix then + self.title = "Foulborn " .. self.title + elseif not self.foulborn and hasFoulbornPrefix then + self.title = self.title:gsub("[Ff]oulborn ", "") + end if not self.quality then self:NormaliseQuality() if highQuality then @@ -1274,9 +1378,17 @@ function ItemClass:BuildRaw() if modLine.crucible then line = "{crucible}" .. line end + -- ggg tag for cultivated/foulborn mod if modLine.mutated then line = "{mutated}" .. line end + -- pob markers for application and removal of mutated mod + if modLine.mutateActive then + line = "{mutateActive}" .. line + end + if modLine.originalLine then + line = string.format("{originalLine:%s}", modLine.originalLine) .. line + end if modLine.fractured then line = "{fractured}" .. line end @@ -1856,22 +1968,24 @@ function ItemClass:BuildModList() self.classRestriction = modLine.line:gsub("{variant:([%d,]+)}", ""):match("Requires Class (.+)") end -- handle understood modifier variable properties - if not modLine.extra then - if modLine.range then - -- Check if line actually has a range - if modLine.line:find("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") then - local strippedModeLine = modLine.line:gsub("\n"," ") - local catalystScalar = getCatalystScalar(self.catalyst, modLine, self.catalystQuality) - -- Put the modified value into the string - local line = itemLib.applyRange(strippedModeLine, modLine.range, catalystScalar) - -- Check if we can parse it before adding the mods - local list, extra = modLib.parseMod(line) - if list and not extra then - modLine.modList = list - t_insert(self.rangeLineList, modLine) - end - end + if not modLine.extra and modLine.range and + -- Check if line actually has a range + modLine.line:find("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") then + local strippedModeLine = modLine.line:gsub("\n", " ") + local catalystScalar = getCatalystScalar(self.catalyst, modLine, self.catalystQuality) + -- Put the modified value into the string + local line = itemLib.applyRange(strippedModeLine, modLine.range, catalystScalar) + -- Check if we can parse it before adding the mods + local list, extra = modLib.parseMod(line) + if list and not extra then + modLine.modList = list + modLine.showSlider = true + t_insert(self.rangeLineList, modLine) end + elseif modLine.mutatedLine or modLine.originalLine then + t_insert(self.rangeLineList, modLine) + end + if not modLine.extra then for _, mod in ipairs(modLine.modList) do mod = modLib.setSource(mod, self.modSource) baseList:AddMod(mod) diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index 3136d1a8476..db941184426 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -137,7 +137,15 @@ function ItemDBClass:DoesItemMatchFilters(item) local found = false local mode = self.controls.searchMode.selIndex if mode == 1 or mode == 2 then - local err, match = PCall(string.matchOrPattern, item.name:lower(), searchStr) + -- check if any explicit mods have a mutated line + local searchName = item.name:lower() + for _, modLine in ipairs(item.explicitModLines) do + if modLine.mutatedLine then + searchName = "foulborn " .. searchName + break + end + end + local err, match = PCall(string.matchOrPattern, searchName, searchStr) if not err and match then found = true end @@ -158,7 +166,9 @@ function ItemDBClass:DoesItemMatchFilters(item) end end for _, line in pairs(item.explicitModLines) do - local err, match = PCall(string.matchOrPattern, line.line:lower(), searchStr) + -- concatenate alternate lines for mutated items + local searchLine = line.line:lower() .. (line.mutatedLine or ""):lower() + local err, match = PCall(string.matchOrPattern, searchLine, searchStr) if not err and match then found = true break diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index e6083ea7839..e42f6a6efa2 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -946,58 +946,173 @@ holding Shift will put it in the second.]]) end -- Section: Modifier Range - self.controls.displayItemSectionRange = new("Control", {"TOPLEFT",self.controls.displayItemSectionCustom,"BOTTOMLEFT"}, {0, 0, 0, function() + local labelFontSize = 14 + self.controls.displayItemSectionRange = new("Control", { "TOPLEFT", self.controls.displayItemSectionCustom, "BOTTOMLEFT" }, { 0, 0, 0, function() if not self.displayItem or not self.displayItem.rangeLineList[1] then return 0 end if main.showAllItemAffixes and self.displayItem.rarity == "UNIQUE" then - local count = #self.displayItem.rangeLineList - return count * 22 + 4 + local height = 0 + for i = 1, #self.displayItem.rangeLineList do + local label = self.controls["displayItemStackedRangeLine" .. i] + height = height + math.max(22, (label.lineCount or 0) * labelFontSize) + end + return height + 4 else return 28 end end}) + local foulbornIcon = NewImageHandle() + foulbornIcon:Load("Assets/breachicon.png") self.controls.displayItemRangeLine = new("DropDownControl", {"TOPLEFT",self.controls.displayItemSectionRange,"TOPLEFT"}, {0, 0, 350, 18}, nil, function(index, value) self.controls.displayItemRangeSlider.val = self.displayItem.rangeLineList[index].range end) self.controls.displayItemRangeLine.shown = function() return self.displayItem and self.displayItem.rangeLineList[1] ~= nil and not (main.showAllItemAffixes and self.displayItem.rarity == "UNIQUE") end - self.controls.displayItemRangeSlider = new("SliderControl", {"LEFT",self.controls.displayItemRangeLine,"RIGHT"}, {8, 0, 100, 18}, function(val) - self.displayItem.rangeLineList[self.controls.displayItemRangeLine.selIndex].range = val + local function getSelectedModLine() + return self.controls.displayItemRangeLine:GetSelValue() and self.controls.displayItemRangeLine:GetSelValue().modLine + end + local box = new("CheckBoxControl", { "LEFT", self.controls.displayItemRangeLine, "RIGHT", true }, { 0, 0, 18 }, nil, function(val) + local line = getSelectedModLine() + if line and (line.mutatedLine or line.originalLine) then + line.mutateActive = val + self.displayItem:BuildAndParseRaw() + self:UpdateDisplayItemTooltip() + self:UpdateCustomControls() + end + end) + box.RealDraw = box.Draw + function box:Draw(...) + local x, y = self:GetPos() + SetDrawColor(1, 1, 1) + DrawImage(foulbornIcon, x - 24, y - 1, 20, 20) + return box:RealDraw(...) + end + + box.shown = function() + local line = getSelectedModLine() + -- hack: set property according to item. the state is not a prop and so can't be set as a function value + if line and line.mutateActive then + box.state = true + else + box.state = false + end + return not main.showAllItemAffixes and line and ((line.mutatedLine ~= nil) or (line.originalLine ~= nil)) + end + -- fix spacing + box.x = function() + if box:IsShown() then + return 25 + else + return 8 + end + end + self.controls.displayItemMutatedCheckbox = box + local slider = new("SliderControl", { "LEFT", self.controls.displayItemMutatedCheckbox, "RIGHT", true }, { 4, 0, 100, 18 }, function(val) + local line = getSelectedModLine() + line.range = val self.displayItem:BuildAndParseRaw() self:UpdateDisplayItemTooltip() self:UpdateCustomControls() end) + slider.shown = function() + local modLine = not main.showAllItemAffixes and self.displayItem and self.displayItem.rarity == "UNIQUE" and self.displayItem.rangeLineList[self.controls.displayItemRangeLine.selIndex] + if modLine then + -- it's possible for the range to change while this slider is + -- hidden, so correct the slider to show the same value + slider.val = modLine.range or slider.val + return modLine.showSlider + else + return false + end + end + self.controls.displayItemRangeSlider = slider for i = 1, 20 do - local baseControl = i == 1 and self.controls.displayItemSectionRange or self.controls["displayItemStackedRangeSlider"..(i-1)] + local baseControl = self.controls.displayItemSectionRange - self.controls["displayItemStackedRangeSlider"..i] = new("SliderControl", {"TOPLEFT",baseControl,"TOPLEFT"}, {0, function() - return i == 1 and 2 or 22 - end, 100, 18}, function(val) - if self.displayItem and self.displayItem.rangeLineList[i] then - self.displayItem.rangeLineList[i].range = val + -- layout: [box] <- [slider] <- text + + local box = new("CheckBoxControl", { "TOPLEFT", baseControl, "TOPLEFT", true }, { 0, 0, 18 }, nil, function(val) + local line = self.displayItem and self.displayItem.rangeLineList[i] + if line and (line.mutatedLine or line.originalLine) then + line.mutateActive = val self.displayItem:BuildAndParseRaw() self:UpdateDisplayItemTooltip() self:UpdateCustomControls() end end) - self.controls["displayItemStackedRangeLine"..i] = new("LabelControl", {"LEFT",self.controls["displayItemStackedRangeSlider"..i],"RIGHT"}, {8, -2, 350, 14}, function() + -- fix spacing + box.x = function() + if box:IsShown() then + return 24 + else + return 0 + end + end + box.y = function() + local prevBox = self.controls["displayItemStackedMutatedCheckbox" .. (i - 1)] + local prevLabel = self.controls["displayItemStackedRangeLine" .. (i - 1)] + return i == 1 and 2 or prevBox:GetProperty("y") + math.max(22, (prevLabel.lineCount or 0) * 14) + end + box.shown = function() + local line = self.displayItem and self.displayItem.rangeLineList[i] + -- hack: set property according to item. the state is not a prop and so can't be set as a function value + if line and line.mutateActive then + box.state = true + else + box.state = false + end + return main.showAllItemAffixes and line and ((line.mutatedLine ~= nil) or (line.originalLine ~= nil)) + end + box.RealDraw = box.Draw + function box:Draw(...) + local x, y = self:GetPos() + SetDrawColor(1, 1, 1) + DrawImage(foulbornIcon, x - 24, y, 20, 20) + return box:RealDraw(...) + end + + self.controls["displayItemStackedMutatedCheckbox" .. i] = box + + local slider = new("SliderControl", { "LEFT", box, "RIGHT", true }, { 4, 0, 100, 18 }, function(val) if self.displayItem and self.displayItem.rangeLineList[i] then - return "^7" .. self.displayItem.rangeLineList[i].line + self.displayItem.rangeLineList[i].range = val + self.displayItem:BuildAndParseRaw() + self:UpdateDisplayItemTooltip() + self:UpdateCustomControls() end - return "" end) - self.controls["displayItemStackedRangeSlider"..i].shown = function() - return main.showAllItemAffixes and self.displayItem and self.displayItem.rarity == "UNIQUE" and self.displayItem.rangeLineList[i] ~= nil + slider.shown = function() + local modLine = main.showAllItemAffixes and self.displayItem and self.displayItem.rarity == "UNIQUE" and self.displayItem.rangeLineList[i] + if modLine then + -- it's possible for the range to change while this slider is + -- hidden, so correct the slider to show the same value + slider.val = modLine.range or slider.val + return modLine.showSlider + else + return false + end end - self.controls["displayItemStackedRangeLine"..i].shown = function() - return self.controls["displayItemStackedRangeSlider"..i]:IsShown() + self.controls["displayItemStackedRangeSlider" .. i] = slider + + self.controls["displayItemStackedRangeLine" .. i] = new("LabelControl", { "LEFT", slider, "RIGHT", true }, { 4, -2, 350, labelFontSize }, function() + local modLine = self.displayItem.rangeLineList[i] + if self.displayItem and modLine then + local colour = modLine.mutateActive and colorCodes.MUTATED or "^7" + local text = table.concat(main:WrapString(modLine.line, labelFontSize, 370), "\n") + local _, lineCount = text:gsub("\n", "") + self.controls["displayItemStackedRangeLine" .. i].lineCount = lineCount + 1 + return colour .. text + end + return "" + end) + self.controls["displayItemStackedRangeLine" .. i].shown = function() + return slider:IsShown() or box:IsShown() end end - -- Tooltip anchor self.controls.displayItemTooltipAnchor = new("Control", {"TOPLEFT",self.controls.displayItemSectionRange,"BOTTOMLEFT"}) @@ -2002,10 +2117,9 @@ end function ItemsTabClass:UpdateDisplayItemRangeLines() if self.displayItem and self.displayItem.rangeLineList[1] then wipeTable(self.controls.displayItemRangeLine.list) - for i, modLine in ipairs(self.displayItem.rangeLineList) do - t_insert(self.controls.displayItemRangeLine.list, modLine.line) - if self.controls["displayItemStackedRangeSlider"..i] then - self.controls["displayItemStackedRangeSlider"..i].val = modLine.range + for _, modLine in ipairs(self.displayItem.rangeLineList) do + if (modLine.mutatedLine ~= nil) or (modLine.originalLine ~= nil) or modLine.range then + t_insert(self.controls.displayItemRangeLine.list, { modLine = modLine, label = modLine.line }) end end self.controls.displayItemRangeLine.selIndex = 1 diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index f47a433e225..4414f11fe18 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -1888,6 +1888,7 @@ c["0% reduced Mana Reservation Efficiency of Skills"]={{[1]={flags=0,keywordFlag c["0% reduced Rarity of Items found"]={{[1]={flags=0,keywordFlags=0,name="LootRarity",type="INC",value=-0}},nil} c["0% reduced Skill Effect Duration"]={{[1]={flags=0,keywordFlags=0,name="Duration",type="INC",value=-0}},nil} c["0% reduced maximum Life"]={{[1]={flags=0,keywordFlags=0,name="Life",type="INC",value=-0}},nil} +c["0.2% of Attack Damage Leeched as Mana"]={{[1]={flags=1,keywordFlags=0,name="DamageManaLeech",type="BASE",value=0.2}},nil} c["0.2% of Attack Damage Leeched as Mana per Power Charge"]={{[1]={[1]={type="Multiplier",var="PowerCharge"},flags=1,keywordFlags=0,name="DamageManaLeech",type="BASE",value=0.2}},nil} c["0.2% of Chaos Damage Leeched as Life"]={{[1]={flags=0,keywordFlags=0,name="ChaosDamageLifeLeech",type="BASE",value=0.2}},nil} c["0.2% of Cold Damage Leeched as Life"]={{[1]={flags=0,keywordFlags=0,name="ColdDamageLifeLeech",type="BASE",value=0.2}},nil} diff --git a/src/Data/ModFoulbornMap.jsonc b/src/Data/ModFoulbornMap.jsonc new file mode 100644 index 00000000000..048dd4dd8f5 --- /dev/null +++ b/src/Data/ModFoulbornMap.jsonc @@ -0,0 +1,871 @@ +// spell-checker: disable +// https://www.poewiki.net/wiki/Foulborn_unique_item +// let out = {}; +// Array.from( +// document.querySelectorAll( +// "table.wikitable > tbody:nth-child(3) > tr", +// ), +// ).forEach((it) => { +// const unique = it.querySelector("a").innerText.replace("รถ", "o"); +// if (!(unique in out)) { +// out[unique] = {}; +// } +// const pattern = it.querySelector(".tc.-mod").innerText.replace(/\d+\.?\d*/g, "#").replace(/\u2013/g, "-") +// .replace(/\(-?#-#\)/g, "#").replaceAll("\n", " ").toLowerCase() +// out[unique][pattern] = it.querySelector(".tc.-foulborn").innerText; +// }); +// // replace nbsp and en dash +// JSON.stringify(out) +// .replace(/\u00A0/g, " ") +// .replace(/\u2013/g, "-"); +{ + "Voll's Devotion": { + "#% reduced power charge duration": "Gain a Power Charge every Second if you haven't lost Power Charges Recently", + "#% reduced endurance charge duration": "Lose all Power Charges on reaching Maximum Power Charges" + }, + "Warped Timepiece": { + "#% increased movement speed": "(20-30)% increased Temporal Chains Curse Effect" + }, + "The Aylardex": { + "+# to maximum life": "Eldritch Battery" + }, + "Xoph's Heart": { + "nearby enemies are covered in ash": "Nearby Enemies are Debilitated", + "+#% to fire resistance": "(5-15)% of Physical Damage from Hits taken as Fire Damage" + }, + "Xoph's Blood": { + "+#% to fire resistance": "(5-15)% of Physical Damage from Hits taken as Fire Damage", + "avatar of fire": "Elemental Overload" + }, + "The Halcyon": { + "#% increased damage if you've frozen an enemy recently": "Cannot be Frozen", + "+#% to cold resistance": "(5-15)% of Physical Damage from Hits taken as Cold Damage" + }, + "The Pandemonius": { + "+#% to cold resistance": "(5-15)% of Physical Damage from Hits taken as Cold Damage", + "#% increased cold damage": "Cannot be Chilled" + }, + "Voice of the Storm": { + "lightning damage with non-critical strikes is lucky": "Damage of Enemies Hitting you is Unlucky", + "#% increased maximum mana": "+3% to maximum Lightning Resistance" + }, + "Choir of the Storm": { + "#% increased maximum mana": "+3% to maximum Lightning Resistance", + "critical strike chance is increased by overcapped lightning resistance": "Mana is increased by 1% per 4% Overcapped Lightning Resistance" + }, + "Eye of Chayula": { + "#% reduced maximum life": "20% reduced maximum Mana", + "#% increased rarity of items found": "20% increased Quantity of Gold Dropped by Slain Enemies" + }, + "Presence of Chayula": { + "#% of maximum life converted to energy shield": "Recoup Energy Shield instead of Life", + "+#% to chaos resistance": "50% of Chaos Damage taken Recouped as Life" + }, + "Maligaro's Restraint": { + "adds # to # lightning damage to attacks": "40% of Lightning Damage Converted to Chaos Damage" + }, + "Belt of the Deceiver": { + "you take #% reduced extra damage from critical strikes": "20% reduced Effect of Curses on you", + "nearby enemies are intimidated": "Nearby Enemies are Unnerved" + }, + "Dyadian Dawn": { + "ignites you inflict with attacks deal damage #% faster": "All Damage can Ignite", + "#% of attack damage leeched as life against chilled enemies": "Recover 2% of Life when you Chill a non-Chilled Enemy" + }, + "Umbilicus Immortalis": { + "regenerate #% of life per second": "Flasks gain a Charge every 3 seconds", + "flasks do not apply to you": "100% increased Flask Charges used" + }, + "Soul Tether": { + "gain #% of maximum life as extra maximum energy shield": "(8-12)% of Leech is Instant", + "immortal ambition": "Everlasting Sacrifice" + }, + "Meginord's Girdle": { + "#% increased flask life recovery rate": "(5-15)% increased Strength" + }, + "Headhunter": { + "#% increased damage with hits against rare monsters": "Culling Strike", + "when you kill a rare monster, you gain its modifiers for # seconds": "Eat a Soul when you Hit a Rare or Unique Enemy, no more than once every 0.25 seconds", + "+# to strength": "Rare and Unique Enemies within 120 metres have Minimap Icons" + }, + "The Snowblind Grace": { + "#% increased evasion rating": "+6 to Evasion Rating per 10 Player Maximum Life", + "#% increased arctic armour buff effect": "Purity of Ice has no Reservation" + }, + "The Perfect Form": { + "#% increased evasion rating": "+8 to Evasion Rating per 10 Player Maximum Life", + "acrobatics": "Ghost Dance" + }, + "Ashrend": { + "#% increased physical damage with ranged weapons": "(75-150)% increased Melee Fire Damage" + }, + "Bronn's Lithe": { + "#% increased attack speed": "(20-40)% increased Cooldown Recovery Rate of Movement Skills" + }, + "Queen of the Forest": { + "#% reduced movement speed": "25% increased Damage taken", + "#% increased movement speed per # evasion rating, up to #%": "1% increased Projectile Speed per 600 Evasion Rating, up to 75%" + }, + "Kintsugi": { + "#% increased evasion rating if you have been hit recently": "+(20-30)% chance to Suppress Spell Damage if you've Suppressed Spell Damage Recently", + "#% less damage taken if you have not been hit recently": "Prevent +35% of Suppressed Spell Damage if you have not Suppressed Spell Damage Recently" + }, + "Carcass Jack": { + "#% increased area damage": "(15-30)% increased Effect of Non-Curse Auras from your Skills on Enemies", + "#% increased area of effect": "Socketed Gems are Supported by Level 20 Intensify" + }, + "Cloak of Defiance": { + "+# to maximum mana": "Eldritch Battery", + "#% increased evasion and energy shield": "Gain (15-20)% of Maximum Mana as Extra Maximum Energy Shield" + }, + "Victario's Influence": { + "#% increased area of effect of aura skills": "Non-Curse Aura Skills have (40-80)% increased Duration" + }, + "The Coming Calamity": { + "mana reservation of herald skills is always #%": "(80-100)% increased Effect of Herald Buffs on you", + "#% chance to avoid being stunned for each herald buff affecting you": "Lone Messenger" + }, + "Skin of the Loyal": { + "+# to level of socketed gems": "+20% to Quality of Socketed Gems", + "#% increased global defences": "50% increased Elemental and Chaos Resistances" + }, + "Skin of the Lords": { + "#% increased global defences": "50% increased Elemental and Chaos Resistances", + "+# to level of socketed gems": "+30% to Quality of Socketed Gems" + }, + "Dialla's Malefaction": { + "gems socketed in blue sockets gain #% increased experience": "Ignore Attribute Requirements of Gems Socketed in Blue Sockets", + "gems socketed always have the quality bonus from socket colour": "+1 to Level of Socketed Gems" + }, + "Ghostwrithe": { + "+#% to chaos resistance": "50% of Chaos Damage taken Recouped as Life", + "#% of maximum life converted to energy shield": "Your Maximum Energy Shield is Equal to 40% of Your Maximum Life" + }, + "Cloak of Flame": { + "#% increased ignite duration on enemies": "(50-100)% increased Damage while Ignited", + "+#% to fire resistance": "(5-7)% of Fire Damage Leeched as Life" + }, + "The Covenant": { + "skills gain a base life cost equal to #% of base mana cost": "Blood Magic" + }, + "Soul Mantle": { + "socketed gems are supported by level # spell totem": "Socketed Gems are Supported by Level 20 Flamewood" + }, + "Vis Mortis": { + "minions gain #% of elemental damage as extra chaos damage": "Minions have Unholy Might" + }, + "Bramblejack": { + "+# to maximum life": "+(500-800) to Armour" + }, + "Solaris Lorica": { + "-# chaos damage taken": "25% of Elemental Damage from Hits taken as Chaos Damage" + }, + "Greed's Embrace": { + "#% increased rarity of items found": "5% increased Experience gain" + }, + "Lioneye's Vision": { + "socketed gems are supported by level # pierce": "25% chance to avoid Projectiles" + }, + "The Brass Dome": { + "strength provides no bonus to maximum life": "Gain no inherent bonuses from Strength", + "+#% to all maximum elemental resistances": "Transcendence" + }, + "Cherrubim's Maleficence": { + "#% increased total recovery per second from life leech": "20% of Physical Damage from Hits taken as Chaos Damage" + }, + "Gruthkul's Pelt": { + "your spells are disabled": "Your Warcries are disabled", + "#% increased global physical damage": "100% increased Attack Damage" + }, + "Ambu's Charge": { + "+#% to all elemental resistances": "+1 to Maximum Endurance Charges" + }, + "Goldwyrm": { + "#% increased rarity of items found": "(20-30)% increased Quantity of Gold Dropped by Slain Enemies" + }, + "Victario's Flight": { + "#% increased movement speed": "(12-6)% increased Action Speed" + }, + "Skyforth": { + "#% chance to gain a power charge on critical strike": "+(3-5)% to Critical Strike Multiplier per Power Charge" + }, + "The Infinite Pursuit": { + "cannot be stunned while bleeding": "Immune to Elemental Ailments while Bleeding", + "moving while bleeding doesn't cause you to take extra damage": "(50-100)% increased Armour while Bleeding" + }, + "The Red Trail": { + "gain a frenzy charge on hit while bleeding": "Gain a Power Charge on Hit while Bleeding", + "#% additional physical damage reduction while stationary": "Gain an Endurance Charge each second while Stationary" + }, + "Darkray Vectors": { + "#% increased evasion rating per frenzy charge": "(4-8)% increased Accuracy Rating per Frenzy Charge" + }, + "Alberon's Warpath": { + "#% increased strength": "(15-18)% increased Intelligence" + }, + "Null's Inclination": { + "trigger socketed minion spells on kill with this weapon minion spells triggered by this item have a # second cooldown with # uses": "Socketed Gems are Supported by Level 20 Summon Phantasm", + "adds # to # chaos damage": "Minions deal (25-35) to (50-65) additional Chaos Damage", + "+#% to chaos resistance": "An Enemy Writhing Worm spawns every 2 seconds" + }, + "Xoph's Inception": { + "projectiles pierce all burning enemies": "Socketed Gems are Supported by Level 20 Returning Projectiles", + "gain #% of physical damage as extra fire damage": "Ignites you inflict deal Damage (20-40)% faster" + }, + "Xoph's Nurture": { + "#% of physical damage converted to fire damage": "All Damage can Ignite", + "socketed gems are supported by level # ignite proliferation": "Socketed Gems are Supported by Level 30 Immolate" + }, + "Quill Rain": { + "#% increased projectile speed": "(40-60)% increased Area of Effect", + "grants # mana per enemy hit": "Insufficient Mana doesn't prevent your Bow Attacks" + }, + "Chin Sol": { + "#% more damage with arrow hits at close range": "50% more Damage with Arrow Hits not at Close Range", + "bow knockback at close range": "Projectiles Pierce all nearby Targets" + }, + "Rive": { + "#% chance to cause bleeding on hit": "25% chance to Crush on Hit", + "#% increased bleeding duration per # intelligence": "Physical Skills have 1% increased Duration per 12 Intelligence" + }, + "Hand of Thought and Motion": { + "#% increased critical strike chance per # intelligence": "3% increased Accuracy Rating per 25 Intelligence", + "recover #% of life on kill": "(8-12)% increased Strength" + }, + "Hand of Wisdom and Action": { + "#% increased attack speed per # dexterity": "+3% to Critical Strike Multiplier per 25 Dexterity", + "#% of attack damage leeched as life": "(8-12)% increased Strength" + }, + "Al Dhih": { + "#% increased physical damage": "(100-120)% increased Chaos Damage" + }, + "The Consuming Dark": { + "your chaos damage has #% chance to poison enemies": "Your Chaos Damage can Ignite" + }, + "Song of the Sirens": { + "siren worm bait": "Wombgift Bait", + "you can catch exotic fish": "You can catch Foulborn Fish" + }, + "Reefbane": { + "#% increased cast speed": "(30-40)% chance to Ignore Stuns while Casting", + "thaumaturgical lure": "Otherworldly Lure" + }, + "Maligaro's Virtuosity": { + "your critical strike multiplier is #%": "Your Action Speed is at least 90% of base value", + "#% increased global critical strike chance": "50% increased Effect of non-Damaging Ailments you inflict with Critical Strikes" + }, + "Facebreaker": { + "+#% to global critical strike multiplier": "(20-30)% increased Area of Effect while Unarmed" + }, + "Snakebite": { + "+# to maximum life": "+(100-130) to maximum Energy Shield" + }, + "Shadows and Dust": { + "#% of attack damage leeched as mana": "(20-30)% increased Blind Effect" + }, + "The Embalmer": { + "socketed gems are supported by level # vile toxins": "Poisons you inflict deal Damage (15-20)% faster", + "#% increased poison duration": "On Killing a Poisoned Enemy, nearby Enemies are Poisoned" + }, + "Sadima's Touch": { + "#% increased rarity of items found": "(5-15)% increased Quantity of Gold Dropped by Slain Enemies" + }, + "Kalisa's Grace": { + "socketed gems are supported by level # faster casting": "Socketed Gems are Supported by Level 18 Focused Channelling", + "gain +#% to critical strike chance for # seconds after spending a total of # mana": "50% reduced Mana Cost of Skills for 2 seconds after Spending a total of 800 Mana" + }, + "Voidbringer": { + "#% increased spell critical strike chance": "(10-15)% increased Chaos Damage for each Corrupted Item Equipped" + }, + "Kaom's Spirit": { + "+#% to fire resistance": "Inherent Rage Loss starts 2 seconds later" + }, + "Flesh and Spirit": { + "recover #% of life on rampage": "Vaal Skills have (20-40)% increased Skill Effect Duration" + }, + "Wyrmsign": { + "socketed gems are supported by level # concentrated effect": "Socketed Gems are Supported by Level 5 Manaforged Arrows" + }, + "Shackles of the Wretched": { + "#% increased stun and block recovery": "(-30-30)% increased Duration of Curses on you" + }, + "Null and Void": { + "#% increased mana regeneration rate": "(15-25)% increased Life Regeneration rate" + }, + "Goldrim": { + "+#% to all elemental resistances": "+(50-75)% to Chaos Resistance" + }, + "Starkonja's Head": { + "+# to maximum life": "+(100-200) to maximum Mana" + }, + "Alpha's Howl": { + "#% increased mana reservation efficiency of skills": "32% increased Life Reservation Efficiency of Skills", + "+# to level of socketed aura gems": "+2 to Level of Socketed Minion Gems" + }, + "Malachai's Simula": { + "spells have a #% chance to deal double damage": "Minions have 20% chance to deal Double Damage" + }, + "The Three Dragons": { + "your lightning damage can freeze but not shock": "Your Chaos Damage can Freeze", + "your cold damage can ignite but not freeze or chill": "Your Chaos Damage can Ignite", + "your fire damage can shock but not ignite": "Your Chaos Damage can Shock" + }, + "Crown of the Pale King": { + "#% of physical attack damage leeched as life": "Retaliation Skills have (25-35)% increased Cooldown Recovery Rate" + }, + "Doedre's Scorn": { + "+# to level of socketed curse gems": "Socketed Gems are Supported by Level 30 Impending Doom" + }, + "The Formless Flame": { + "-#% to fire resistance": "30% of Fire Damage from Hits taken as Physical Damage", + "armour is increased by overcapped fire resistance": "Totem Life is increased by their Overcapped Fire Resistance" + }, + "The Formless Inferno": { + "-#% to fire resistance": "30% of Fire Damage from Hits taken as Physical Damage", + "socketed gems are supported by level # infernal legion": "Socketed Gems are Supported by Level 30 Minion Life" + }, + "Devoto's Devotion": { + "#% increased movement speed": "20% increased Attack Speed with Movement Skills", + "+# to dexterity": "+(10-20)% chance to Suppress Spell Damage" + }, + "Deidbell": { + "skills which exert an attack have #% chance to not count that attack": "(20-35)% increased Warcry Buff Effect", + "+# to dexterity": "(20-40)% increased Warcry Cooldown Recovery Rate" + }, + "Veil of the Night": { + "#% increased global defences": "50% increased maximum Life" + }, + "Kitava's Thirst": { + "#% chance to trigger socketed spells when you spend at least # mana on an upfront cost to use or trigger a skill, with a # second cooldown": "50% chance to Trigger Socketed Spells when you Spend at least 200 Life on an Upfront Cost to Use or Trigger a Skill, with a 0.1 second Cooldown" + }, + "Voll's Vision": { + "regenerate # life per second if no equipped items are corrupted": "(15-20)% of Life Regeneration also applies to Energy Shield if no Equipped Items are Corrupted", + "#% increased maximum life if no equipped items are corrupted": "Gain (8-12)% of Maximum Life as Extra Maximum Energy Shield if no Equipped Items are Corrupted" + }, + "Might of the Meek": { + "#% increased effect of non-keystone passive skills in radius notable passive skills in radius grant nothing": "100% increased Effect of non-Keystone Passive Skills in Radius\nNotable Passive Skills in Radius grant nothing\nRadius: Small" + }, + "Unnatural Instinct": { + "allocated small passive skills in radius grant nothing": "Allocated Notable Passive Skills in Radius grant nothing", + "grants all bonuses of unallocated small passive skills in radius": "Grants all bonuses of Unallocated Notable Passive Skills in Radius" + }, + "Stormshroud": { + "+#% to lightning resistance": "(10-15)% increased Shock Duration on Enemies", + "modifiers to chance to avoid being shocked apply to all elemental ailments": "(25-50)% increased Effect of Shock" + }, + "Firesong": { + "modifiers to ignite duration on you apply to all elemental ailments": "(25-50)% increased Ignite Duration on Enemies", + "+#% to fire resistance": "+(10-15)% to Fire Damage over Time Multiplier" + }, + "Witchbane": { + "+# to intelligence": "Curse Skills have (10-15)% increased Skill Effect Duration", + "when you kill an enemy cursed with a non-aura hex, become immune to curses for remaining hex duration": "Cursed Enemies you or your Minions Kill have a (10-15)% chance to Explode, dealing a quarter of their maximum Life as Chaos Damage" + }, + "Ancestral Vision": { + "+# to dexterity": "+(5-10)% chance to Suppress Spell Damage", + "modifiers to chance to suppress spell damage also apply to chance to avoid elemental ailments at #% of their value": "Prevent +(3-5)% of Suppressed Spell Damage" + }, + "Inspired Learning": { + "with # notables allocated in radius, when you kill a rare monster, you gain # of its modifiers for # seconds": "With (8-12) Small Passives Allocated in Radius, When you Kill a Rare monster, you gain 1 of its Modifiers for 20 seconds" + }, + "Lioneye's Fall": { + "melee and melee weapon type modifiers in radius are transformed to bow modifiers": "Increases and Reductions to Evasion Rating in Radius are Transformed to apply to Armour" + }, + "Intuitive Leap": { + "passive skills in radius can be allocated without being connected to your tree passage": "Keystone Passive Skills in Radius can be Allocated without being connected to your tree\nPassage\nRadius: Massive" + }, + "The Red Dream": { + "passives granting fire resistance or all elemental resistances in radius also grant an equal chance to gain an endurance charge on kill": "Passives granting Fire Resistance or all Elemental Resistances in Radius also grant increased Maximum Life at 50% of its value", + "gain #% of fire damage as extra chaos damage": "Fire Damage with Hits is Lucky if you've Blocked an Attack Recently" + }, + "The Red Nightmare": { + "gain #% of fire damage as extra chaos damage": "Fire Damage with Hits is Lucky if you've Blocked an Attack Recently", + "passives granting fire resistance or all elemental resistances in radius also grant chance to block attack damage at #% of its value": "Passives granting Fire Resistance or all Elemental Resistances in Radius also grant Fire Damage Converted to Chaos Damage at 100% of its value" + }, + "The Green Dream": { + "passives granting cold resistance or all elemental resistances in radius also grant an equal chance to gain a frenzy charge on kill": "Passives granting Cold Resistance or all Elemental Resistances in Radius also grant increased Maximum Mana at 75% of its value", + "gain #% of cold damage as extra chaos damage": "Cold Damage with Hits is Lucky if you've Suppressed Spell Damage Recently" + }, + "The Green Nightmare": { + "gain #% of cold damage as extra chaos damage": "Cold Damage with Hits is Lucky if you've Suppressed Spell Damage Recently", + "passives granting cold resistance or all elemental resistances in radius also grant chance to suppress spell damage at #% of its value": "Passives granting Cold Resistance or all Elemental Resistances in Radius also grant Cold Damage Converted to Chaos Damage at 100% of its value" + }, + "The Blue Dream": { + "passives granting lightning resistance or all elemental resistances in radius also grant an equal chance to gain a power charge on kill": "Passives granting Lightning Resistance or all Elemental Resistances in Radius also grant increased Maximum Energy Shield at 75% of its value", + "gain #% of lightning damage as extra chaos damage": "Lightning Damage with Hits is Lucky if you've Blocked Spell Damage Recently" + }, + "The Blue Nightmare": { + "gain #% of lightning damage as extra chaos damage": "Lightning Damage with Hits is Lucky if you've Blocked Spell Damage Recently", + "passives granting lightning resistance or all elemental resistances in radius also grant chance to block spell damage at #% of its value": "Passives granting Lightning Resistance or all Elemental Resistances in Radius also grant Lightning Damage Converted to Chaos Damage at 100% of its value" + }, + "Moonbender's Wing": { + "#% increased physical damage": "(30-50)% increased Attack Speed" + }, + "Rigwald's Savagery": { + "#% increased physical attack damage while dual wielding": "Your Melee Hits can't be Evaded while wielding a Sword" + }, + "Frostbreath": { + "#% increased attack speed": "(60-100)% increased Critical Strike Chance" + }, + "Mjolner": { + "skills chain +# times": "(20-30)% increased Area of Effect", + "trigger a socketed lightning spell on hit, with a # second cooldown socketed lightning spells have no cost if triggered": "Trigger Level 20 Lightning Bolt on Melee Hit with this Weapon, with a 0.25 second cooldown" + }, + "Rigwald's Command": { + "+# to accuracy rating": "+10% Chance to Block Spell Damage while Dual Wielding" + }, + "Severed in Sleep": { + "minions have #% chance to inflict withered on hit": "Minions have +5% to Critical Strike Chance", + "minions have +#% to chaos resistance": "Minions have 25% chance to gain Unholy Might for 4 seconds on Kill" + }, + "United in Dream": { + "minions recover #% of life on killing a poisoned enemy": "+(20-30)% to Quality of all Minion Skill Gems", + "minions have +#% to chaos resistance": "Minions have 25% chance to gain Unholy Might for 4 seconds on Kill" + }, + "Drillneck": { + "#% increased attack speed": "(20-30)% increased Impale Effect" + }, + "Rearguard": { + "#% increased projectile speed": "50% reduced Enemy Stun Threshold with Bows" + }, + "Soul Strike": { + "+# to maximum energy shield": "(10-15)% of Maximum Life Converted to Energy Shield", + "#% faster start of energy shield recharge": "(10-15)% chance for Energy Shield Recharge to start when you use a Skill" + }, + "Gifts from Above": { + "#% increased global critical strike chance": "(25-40)% increased Effect of Consecrated Ground you create" + }, + "Shavronne's Revelation": { + "left ring slot: +# to maximum energy shield": "Left ring slot: +1000 to Evasion Rating", + "right ring slot: +# to maximum mana": "Right ring slot: +1000 to Armour" + }, + "Ming's Heart": { + "#% reduced maximum life": "Can't use other Rings" + }, + "Romira's Banquet": { + "+# to accuracy rating": "+1 to Maximum Power Charges" + }, + "Berek's Pass": { + "#% increased damage while ignited": "Action Speed cannot be modified to below Base Value while Ignited", + "+# to armour while frozen": "25% of Damage taken while Frozen Recouped as Life" + }, + "Berek's Grip": { + "#% of damage leeched as life against shocked enemies": "Enemies Shocked by you are Debilitated", + "#% of damage leeched as energy shield against frozen enemies": "Enemies Frozen by you take 10% increased Damage" + }, + "Berek's Respite": { + "when you kill a shocked enemy, inflict an equivalent shock on each nearby enemy": "Shocked Enemies you Kill Explode, dealing 5% of their Life as Lightning Damage which cannot Shock", + "when you kill an ignited enemy, inflict an equivalent ignite on each nearby enemy": "Ignited Enemies you Kill Explode, dealing 5% of their Life as Fire Damage which cannot Ignite" + }, + "Mokou's Embrace": { + "+#% to cold resistance": "+3% to maximum Fire Resistance" + }, + "Kikazaru": { + "regenerate # life per second per level": "+2 Maximum Mana per Level" + }, + "Timeclasp": { + "#% increased skill effect duration": "Debuffs on you expire (-20-20)% faster" + }, + "Ventor's Gamble": { + "+# to maximum life": "+(0-60) to maximum Energy Shield\n+(0-60) to maximum Mana" + }, + "Heartbound Loop": { + "regenerate # life per second": "(20-10)% reduced Mana Cost of Minion Skills" + }, + "Le Heup of All": { + "#% increased damage": "(10-30)% increased Global Defences", + "#% increased rarity of items found": "+(10-30)% to Global Critical Strike Multiplier" + }, + "Thief's Torment": { + "#% reduced effect of curses on you": "50% reduced Effect of Non-Damaging Ailments on you" + }, + "Lori's Lantern": { + "damage of enemies hitting you is unlucky while you are on low life": "Your Damage with Hits is Lucky while on Low Life", + "#% increased movement speed when on low life": "(12-16)% increased Attack Speed when on Low Life" + }, + "Singularity": { + "adds # to # lightning damage to spells": "+(30-40)% to Cold Damage over Time Multiplier" + }, + "Chalice of Horrors": { + "+#% chance to block attack damage from cursed enemies": "+(23-37)% to Chaos Damage over Time Multiplier" + }, + "Great Old One's Ward": { + "#% chance to block spell damage": "(20-40)% chance to Impale Enemies on Hit with Attacks" + }, + "Kiloava's Bluster": { + "+#% chance to block": "(15-10)% reduced Damage taken from Damage Over Time", + "#% chance for elemental resistances to count as being #% against enemy hits": "Hits have (20-25)% chance to treat Enemy Monster Elemental Resistance values as inverted" + }, + "Rathpith Globe": { + "#% increased spell critical strike chance per # player maximum life": "Deal 5% increased Damage Over Time per 100 Player Maximum Life", + "#% increased spell damage per # player maximum life": "2% increased Effect of Non-Damaging Ailments you inflict with Critical Strikes per 100 Player Maximum Life" + }, + "Matua Tupuna": { + "+# to level of socketed minion gems": "+2 to Level of Socketed Aura Gems" + }, + "Kongming's Stratagem": { + "#% increased fire damage with hits and ailments against blinded enemies": "Enemies Blinded by you have 100% reduced Critical Strike Chance" + }, + "Malachai's Loop": { + "#% increased spell damage per power charge": "+4% chance to Suppress Spell Damage per Power Charge" + }, + "Esh's Mirror": { + "shocks you inflict spread to other enemies within # metres": "Hits always Shock Enemies that are on Low Life", + "+#% to lightning resistance": "Damage cannot be Reflected" + }, + "Esh's Visage": { + "chaos damage taken does not bypass energy shield while not on low life": "Chaos Damage taken does not bypass Energy Shield while not on Low Mana", + "+#% to lightning resistance": "Damage cannot be Reflected" + }, + "The Anticipation": { + "+# armour if you've blocked recently": "(8-12)% of Armour applies to Fire, Cold and Lightning Damage taken from Hits if you have Blocked Recently", + "+#% chance to block": "+3% to maximum Chance to Block Attack Damage" + }, + "The Surrender": { + "+#% chance to block": "+3% to maximum Chance to Block Attack Damage", + "recover # life when you block": "Gain (300-650) Energy Shield when you Block" + }, + "The Squire": { + "all sockets are white": "Ignore Attribute Requirements of Socketed Gems", + "+#% to quality of socketed support gems": "+1 to Level of Socketed Gems" + }, + "The Oppressor": { + "-#% to amount of suppressed spell damage prevented": "(10-15)% chance to Avoid All Damage from Hits", + "you take #% of damage from blocked hits": "(20-30)% Chance to Block Spell Damage" + }, + "Rise of the Phoenix": { + "#% increased movement speed when on low life": "(10-20)% increased Cast Speed when on Low Life", + "regenerate # life per second": "Regenerate (100-200) Energy Shield per second" + }, + "Sire of Shards": { + "#% increased projectile damage": "Socketed Gems Chain 2 additional times" + }, + "Uul-Netol's Kiss": { + "curse enemies with vulnerability on hit": "Warcries Exert 1 additional Attack", + "exerted attacks deal #% increased damage": "500% increased Warcry Cooldown Recovery Rate" + }, + "Uul-Netol's Embrace": { + "#% chance to cause bleeding on hit": "Bleeding you inflict deals Damage (20-40)% faster", + "#% reduced attack speed": "80% reduced Intelligence" + }, + "The Harvest": { + "#% increased physical damage": "(120-140)% increased Spell Damage" + }, + "Kongor's Undying Rage": { + "hits can't be evaded": "Battlemage", + "your critical strikes do not deal extra damage": "Lose 1% of Life when you deal a Critical Strike" + }, + "Jorrhast's Blacksteel": { + "weapons you animate create an additional copy": "Maximum number of Animated Weapons is Doubled", + "#% increased cast speed": "Minions deal (50-70)% increased Damage if you've Hit Recently" + }, + "Edge of Madness": { + "+# to level of socketed skill gems": "+(6-8) to Accuracy Rating per Level" + }, + "Doomsower": { + "you have vaal pact while all socketed gems are red": "You have Immortal Ambition while all Socketed Gems are Red", + "attack skills gain #% of physical damage as extra fire damage per socketed red gem": "10% of Damage taken Recouped as Life per Socketed Red Gem" + }, + "The Dancing Dervish": { + "#% increased physical damage": "Adds 1 to 777 Lightning Damage" + }, + "Ashcaller": { + "adds # to # fire damage to spells": "Minions gain (20-40)% of Physical Damage as Extra Fire Damage" + }, + "Tulborn": { + "adds # to # cold damage to spells": "+(2-3) to Level of all Cold Spell Skill Gems", + "gain a power charge on killing a frozen enemy": "Gain a Power Charge after Spending a total of 200 Mana" + }, + "Tulfall": { + "gain a power charge on killing a frozen enemy": "Gain a Power Charge after Spending a total of 200 Mana", + "adds # to # cold damage to spells per power charge": "+(15-20)% to Cold Damage over Time Multiplier per Power Charge" + }, + "Midnight Bargain": { + "reserves #% of life cannot be used with chaos inoculation": "Lose 0.5% Life and Energy Shield per Second per Minion", + "+# to maximum number of raised zombies +# to maximum number of skeletons +# to maximum number of spectres": "+2 to maximum number of Summoned Golems" + }, + "Piscator's Vigil": { + "#% increased critical strike chance": "(30-50)% increased effect of Wishes granted by Ancient Fish" + }, + "Reverberation Rod": { + "+# to level of socketed gems": "Socketed Gems are Supported by Level 1 Greater Spell Cascade" + }, + "Abyssus": { + "+#% to melee critical strike multiplier": "+(50-75)% to Damage over Time Multiplier for Bleeding" + }, + "Aegis Aurora": { + "#% increased elemental damage with attack skills": "Damage cannot be Reflected" + }, + "Anathema": { + "#% chance to gain a power charge when you cast a curse spell": "Curse Skills have (-30-30)% reduced Skill Effect Duration" + }, + "Apep's Rage": { + "adds # to # chaos damage to spells": "Skills gain Added Chaos Damage equal to (20-25)% of Mana Cost, if Mana Cost is not higher than the maximum you could spend" + }, + "Asenath's Gentle Touch": { + "curse enemies with temporal chains on hit": "Curse Enemies with Punishment on Hit" + }, + "Asenath's Mark": { + "#% increased attack speed": "(10-15)% increased Cooldown Recovery Rate" + }, + "Astral Projector": { + "nova spells have #% less area of effect": "(40-50)% reduced maximum Energy Shield" + }, + "Astramentis": { + "-# physical damage taken from attack hits": "+(-13-13)% to Chaos Resistance" + }, + "Aurseize": { + "+#% to all elemental resistances": "15% increased Quantity of Gold Dropped by Slain Enemies" + }, + "Auxium": { + "#% of attack damage leeched as mana per power charge": "(4-6)% increased Energy Shield per Power Charge" + }, + "Badge of the Brotherhood": { + "#% increased cooldown recovery rate of travel skills per frenzy charge": "2% increased Movement Speed per Frenzy Charge" + }, + "Belly of the Beast": { + "+#% to all elemental resistances": "+(8-12) to Maximum Rage" + }, + "Call of the Brotherhood": { + "your spells have #% chance to shock against frozen enemies": "Gain a Power Charge on Killing a Frozen Enemy" + }, + "Cospri's Malice": { + "adds # to # cold damage to spells": "(80-120)% increased Elemental Damage with Attack Skills" + }, + "Cospri's Will": { + "always poison on hit against cursed enemies": "(15-20)% chance to inflict Withered for 2 seconds on Hit against Cursed Enemies" + }, + "Cybil's Paw": { + "gain # life per enemy hit with spells": "Recover Energy Shield equal to 1% of Evasion Rating when you Block" + }, + "Darkscorn": { + "#% of physical damage converted to chaos damage": "25% chance to gain Unholy Might for 4 seconds on Critical Strike" + }, + "Death Rush": { + "recover #% of life on kill": "(20-40)% chance to gain an additional Vaal Soul on Kill" + }, + "Death's Hand": { + "#% chance to gain a power charge when you stun": "Gain Chaotic Might for 4 seconds on Critical Strike" + }, + "Death's Harp": { + "#% increased physical damage": "Gain (67-83)% of Physical Damage as Extra Chaos Damage" + }, + "Death's Oath": { + "#% increased attack speed": "(20-40)% increased Effect of Withered" + }, + "Defiance of Destiny": { + "gain #% of missing unreserved life before being hit by an enemy": "Gain (15-30)% of Missing Unreserved Mana before being Hit by an Enemy" + }, + "Doomfletch": { + "#% increased attack speed": "Socketed Gems are Supported by Level 25 Prismatic Burst" + }, + "Doon Cuebiyari": { + "#% increased armour per # strength when in off hand": "1% increased maximum Energy Shield per 25 Strength when in Off Hand", + "#% increased damage per # strength when in main hand": "1% increased maximum Mana per 18 Strength when in Main Hand" + }, + "Doryani's Fist": { + "#% chance to shock": "30% chance to Sap Enemies" + }, + "Dream Fragments": { + "#% increased mana regeneration rate": "(200-300)% increased Stun and Block Recovery" + }, + "Dreamfeather": { + "adds # to # physical damage": "1% increased Attack Speed per 150 Accuracy Rating" + }, + "Emperor's Vigilance": { + "#% chance to block spell damage": "+20% to Maximum Quality" + }, + "Fleshcrafter": { + "minions have #% faster start of energy shield recharge": "Minions Leech 5% of Elemental Damage as Energy Shield" + }, + "Gang's Momentum": { + "#% increased movement speed": "(25-50)% increased Movement Speed while Ignited" + }, + "Heatshiver": { + "#% increased mana regeneration rate": "50% of Cold Damage Converted to Fire Damage" + }, + "Hinekora's Sight": { + "+# to evasion rating": "(30-50)% increased Light Radius" + }, + "Hyrri's Ire": { + "+#% chance to suppress spell damage": "Prevent +(8-10)% of Suppressed Spell Damage" + }, + "Incandescent Heart": { + "+# to maximum life": "Increases and Reductions to Light Radius also apply to Accuracy" + }, + "Infernal Mantle": { + "#% increased global critical strike chance": "Gain (15-20)% of Maximum Mana as Extra Maximum Energy Shield" + }, + "Inpulsa's Broken Heart": { + "#% increased damage if you have shocked an enemy recently": "Purity of Lightning has no Reservation" + }, + "Kaom's Primacy": { + "culling strike": "+1 to Maximum Endurance Charges" + }, + "Kaom's Roots": { + "+# to maximum life": "+(150-200) to Strength" + }, + "Lightning Coil": { + "+# to maximum life": "50% of Chaos Damage taken as Lightning Damage" + }, + "Lioneye's Remorse": { + "+# to maximum life": "Gain (20-25)% of Maximum Life as Extra Armour" + }, + "Maata's Teaching": { + "#% increased critical strike chance": "Minions have +(20-40)% to Critical Strike Multiplier" + }, + "Mageblood": { + "leftmost # magic utility flasks constantly apply their flask effects to you": "Rightmost (2-4) Magic Utility Flasks constantly apply their Flask Effects to you" + }, + "Maloney's Mechanism": { + "#% increased attack speed": "Socketed Gems are Supported by Level 25 Arrow Nova" + }, + "Meginord's Vise": { + "+# to strength": "+700 Strength Requirement\n(10-15)% chance to deal Triple Damage" + }, + "Mind of the Council": { + "#% increased maximum mana": "30% of Physical Damage is taken from Mana before Life" + }, + "Ngamahu's Flame": { + "damage penetrates #% fire resistance": "Attacks fire 3 additional Projectiles" + }, + "Prism Guardian": { + "socketed gems have #% increased reservation efficiency": "+(20-30)% to Quality of Socketed Gems" + }, + "Pyre": { + "#% increased burning damage": "40% of Cold Damage from Hits taken as Fire Damage" + }, + "Razor of the Seventh Sun": { + "#% increased melee physical damage against ignited enemies": "Ignites inflicted with this Weapon deal 100% more Damage" + }, + "Rebuke of the Vaal": { + "#% increased attack speed": "(20-40)% increased Trap Throwing Speed" + }, + "Repentance": { + "#% increased strength": "(12-16)% increased Intelligence" + }, + "Rigwald's Quills": { + "#% increased projectile damage": "(30-50)% chance to Aggravate Bleeding on targets you Hit with Attacks" + }, + "Rime Gaze": { + "socketed gems are supported by level # concentrated effect": "Socketed Gems are Supported by Level 25 Frigid Bond" + }, + "Saffell's Frame": { + "#% chance to block spell damage": "Maximum Energy Shield is increased by Chaos Resistance" + }, + "Scold's Bridle": { + "#% reduced cast speed": "(20-30)% increased Mana Cost of Skills" + }, + "Seven-League Step": { + "#% increased movement speed": "15% increased Action Speed" + }, + "Shavronne's Wrappings": { + "#% faster start of energy shield recharge": "Socketed Gems are Supported by Level 20 Living Lightning" + }, + "Soulthirst": { + "#% reduced flask effect duration": "50% reduced Mana Recovery rate" + }, + "Sunblast": { + "#% reduced trap duration": "Skills used by Traps have (40-60)% increased Area of Effect" + }, + "The Baron": { + "+# to maximum number of raised zombies per # strength": "+1 to maximum number of Raised Zombies per 500 Intelligence", + "with at least # strength, #% of damage dealt by your raised zombies is leeched to you as life": "With at least 1000 Intelligence, (1.5-2)% of Damage dealt by your Raised Zombies is Leeched to you as Energy Shield" + }, + "The Bringer of Rain": { + "socketed gems are supported by level # faster attacks": "Socketed Gems are Supported by Level 30 Sadism" + }, + "The Fourth Vow": { + "physical damage taken bypasses energy shield": "40% of Non-Chaos Damage taken bypasses Energy Shield" + }, + "The Gull": { + "+# to maximum life": "(15-20)% of Maximum Life Converted to Energy Shield" + }, + "The Iron Fortress": { + "+#% chance to block attack damage per # strength": "+1% Chance to Block Spell Damage per 50 Strength", + "chance to block spell damage is unlucky": "Chance to Block Attack Damage is Unlucky" + }, + "The Magnate": { + "#% increased flask charges gained": "Flasks applied to you have (10-15)% increased Effect" + }, + "The Poet's Pen": { + "adds # to # physical damage to attacks with this weapon per # player levels": "Adds 3 to 5 Physical Damage to Spells per 3 Player Levels" + }, + "The Searing Touch": { + "#% increased cast speed": "+(3-5) to maximum number of Summoned Searing Bond Totems" + }, + "The Whispering Ice": { + "#% increased spell damage per # intelligence": "1% increased Area of Effect per 20 Intelligence" + }, + "Thunderfist": { + "#% increased effect of lightning ailments": "Herald of Thunder has 100% increased Buff Effect" + }, + "Tinkerskin": { + "#% chance to gain a frenzy charge when your trap is triggered by an enemy": "Skills which Throw Traps have +2 Cooldown Uses" + }, + "Tremor Rod": { + "socketed gems are supported by level # blastchain mine": "(60-100)% increased Effect of Auras from Mines" + }, + "Utula's Hunger": { + "+# to maximum life if there are no life modifiers on other equipped items": "+(1200-1800) to maximum Energy Shield if there are no Defence Modifiers on other Equipped Items" + }, + "Vaal Caress": { + "you gain onslaught for # seconds on using a vaal skill": "You gain Adrenaline for 3 seconds on using a Vaal Skill" + }, + "Veruso's Battering Rams": { + "you cannot be shocked while at maximum endurance charges": "Cannot be Ignited while at maximum Endurance Charges" + }, + "Void Battery": { + "#% increased global critical strike chance": "3% increased Area of Effect per Power Charge" + }, + "Voll's Protector": { + "#% reduced maximum mana": "-(17-13)% to Chaos Resistance" + }, + "Warrior's Legacy": { + "#% less attack speed": "(40-50)% reduced maximum Life" + }, + "Windscream": { + "#% increased area of effect of hex skills": "You are Immune to Curses" + }, + "Ylfeban's Trickery": { + "trigger level # shock ground when hit": "Increases and Reductions to Spell Damage also apply to Attack Damage with Retaliation Skills at 200% of their value" + }, + "Zahndethus' Cassock": { + "#% increased light radius": "Create Profane Ground instead of Consecrated Ground" + }, + "Khatal's Weeping": { + "while on low life, life flasks gain # charges every # seconds": "Life Flasks gain (1-3) Charge every 3 seconds", + "+# to maximum life": "(8-10)% increased maximum Life" + }, + "Khatal's Geyser": { + "mana flask effects are not removed when unreserved mana is filled mana flask effects do not queue": "Mana Flasks gain (1-3) Charge every 3 seconds", + "+# to maximum mana": "(12-14)% increased maximum Mana" + }, + "The Desecrated Chalice": { + "#% of chaos damage leeched as life": "+(25-30)% to Chaos Resistance", + "#% increased critical strike chance": "(20-22)% increased Attack Speed", + "gain #% of physical damage as extra chaos damage if you've used an amethyst flask recently": "Gain (35-45)% of Physical Damage as Extra Chaos Damage" + }, + "The Sacred Chalice": { + "#% increased critical strike chance": "(20-22)% increased Attack Speed", + "gain #% of physical damage as extra cold damage if you've used a sapphire flask recently": "Gain (20-25)% of Physical Damage as Extra Cold Damage", + "gain #% of physical damage as extra fire damage if you've used a ruby flask recently": "Gain (20-25)% of Physical Damage as Extra Fire Damage", + "gain #% of physical damage as extra lightning damage if you've used a topaz flask recently": "Gain (20-25)% of Physical Damage as Extra Lightning Damage" + }, + "Saresh's Darkness": { + "#% increased poison duration": "(40-60)% chance to Avoid being Poisoned", + "+#% to chaos resistance": "(30-50)% increased Chaos Damage" + }, + "Solerai's Radiance": { + "+#% to fire resistance": "(30-50)% increased Fire Damage", + "#% increased ignite duration on enemies": "(40-60)% reduced Ignite Duration on you" + }, + "The Bane of Hope": { + "+#% to physical damage over time multiplier": "(14-18)% increased Attack Speed", + "#% chance to impale enemies on hit with attacks": "(10-15)% chance on Hitting an Enemy for all Impales on that Enemy to last for an additional Hit" + }, + "The Flame of Hope": { + "#% increased fire damage": "+(28-35)% to Fire Damage over Time Multiplier", + "#% increased attack speed": "(30-34)% increased Critical Strike Chance" + } +} \ No newline at end of file diff --git a/src/Data/Uniques/gloves.lua b/src/Data/Uniques/gloves.lua index 613d47966ac..0e98f59fcba 100644 --- a/src/Data/Uniques/gloves.lua +++ b/src/Data/Uniques/gloves.lua @@ -1071,7 +1071,9 @@ Requires Level 31, 25 Dex, 25 Int {variant:2}+(25-45)% to Global Critical Strike Multiplier {variant:3}+(20-30)% to Global Critical Strike Multiplier (100-130)% increased Evasion and Energy Shield -0.2% of Physical Attack Damage Leeched as Mana +{variant:1}0.2% of Physical Attack Damage Leeched as Mana +{variant:2}0.2% of Physical Attack Damage Leeched as Mana +{variant:3}0.2% of Attack Damage Leeched as Mana Creates a Smoke Cloud on Rampage Gain Unholy Might for 3 seconds on Rampage Rampage diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index a880019c7f8..e0cccdf8e68 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -3,7 +3,7 @@ -- Module: Data -- Contains static data used by other modules. -- - +local dkjson = require("dkjson") LoadModule("Data/Global") local m_min = math.min @@ -1167,4 +1167,5 @@ end LoadModule("Data/Uniques/Special/Generated") LoadModule("Data/Uniques/Special/New") +data.foulbornMap = dkjson.decode(io.open("Data/ModFoulbornMap.jsonc", "r"):read("*a")) data.flavourText = LoadModule("Data/FlavourText") diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index beae31b7beb..c2b8873c3d0 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -154,7 +154,7 @@ function main:Init() local function loadItemDBs() for type, typeList in pairsYield(data.uniques) do for _, raw in pairs(typeList) do - newItem = new("Item", raw, "UNIQUE", true) + local newItem = new("Item", raw, "UNIQUE", true) if newItem.base then self.uniqueDB.list[newItem.name] = newItem elseif launch.devMode then @@ -167,7 +167,7 @@ function main:Init() ConPrintf("Uniques loaded") for _, raw in pairsYield(data.rares) do - newItem = new("Item", raw, "RARE", true) + local newItem = new("Item", raw, "RARE", true) if newItem.base then if newItem.crafted then if newItem.base.implicit and #newItem.implicitModLines == 0 then