diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index 95ab73e319..ba79486bfb 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -929,6 +929,8 @@ function ImportTabClass:ImportItem(itemData, slotName) item.requirements.level = req.values[1][1] elseif req.name == "Class:" then item.classRestriction = req.values[1][1] + elseif req.name == "Charm Slots:" then + item.charmLimit = req.values[1][1] end end end diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index f6a31eb09b..24b1412f22 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -363,6 +363,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self.baseLines = { } local importedLevelReq local flaskBuffLines + local charmBuffLines local tinctureBuffLines local deferJewelRadiusIndexAssignment local gameModeStage = "FINDIMPLICIT" @@ -372,6 +373,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) local line = self.rawLines[l] if flaskBuffLines and flaskBuffLines[line] then flaskBuffLines[line] = nil + elseif charmBuffLines and charmBuffLines[line] then + charmBuffLines[line] = nil elseif tinctureBuffLines and tinctureBuffLines[line] then tinctureBuffLines[line] = nil elseif line == "--------" then @@ -424,6 +427,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self.itemLevel = specToNumber(specVal) elseif specName == "Requires Class" then self.classRestriction = specVal + elseif specName == "Charm Slots" then + self.charmLimit = specToNumber(specVal) elseif specName == "Quality" then self.quality = specToNumber(specVal) elseif specName == "Sockets" then @@ -690,7 +695,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) else self.enchantments = data.enchantments[self.base.type] end - self.corruptible = self.base.type ~= "Flask" + self.corruptible = self.base.type ~= "Flask" and self.base.type ~= "Charm" self.canBeInfluenced = self.base.influenceTags ~= nil self.clusterJewel = data.clusterJewels and data.clusterJewels.jewels[self.baseName] self.requirements.str = self.base.req.str or 0 @@ -706,6 +711,14 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) t_insert(self.buffModLines, { line = line, extra = extra, modList = modList or { } }) end end + if self.base.charm and self.base.charm.buff and not charmBuffLines then + charmBuffLines = { } + for _, line in ipairs(self.base.charm.buff) do + charmBuffLines[line] = true + local modList, extra = modLib.parseMod(line) + t_insert(self.buffModLines, { line = line, extra = extra, modList = modList or { } }) + end + end if self.base.tincture and self.base.tincture.buff and not tinctureBuffLines then tinctureBuffLines = { } for _, line in ipairs(self.base.tincture.buff) do @@ -1508,6 +1521,17 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum) for _, value in ipairs(modList:List(nil, "FlaskData")) do flaskData[value.key] = value.value end + elseif self.base.charm then + local charmData = self.charmData + local durationInc = calcLocal(modList, "Duration", "INC", 0) + local durationMore = calcLocal(modList, "Duration", "MORE", 0) + charmData.duration = round(self.base.charm.duration * (1 + durationInc / 100) * durationMore, 1) + charmData.chargesMax = self.base.charm.chargesMax + calcLocal(modList, "CharmCharges", "BASE", 0) + charmData.chargesUsed = m_floor(self.base.charm.chargesUsed * (1 + calcLocal(modList, "CharmChargesUsed", "INC", 0) / 100)) + charmData.effectInc = calcLocal(modList, "CharmEffect", "INC", 0) + calcLocal(modList, "LocalEffect", "INC", 0) + for _, value in ipairs(modList:List(nil, "CharmData")) do + charmData[value.key] = value.value + end elseif self.base.tincture then local tinctureData = self.tinctureData tinctureData.manaBurn = (self.base.tincture.manaBurn + 0.01) / (1 + calcLocal(modList, "TinctureManaBurnRate", "INC", 0) / 100) / (1 + calcLocal(modList, "TinctureManaBurnRate", "MORE", 0) / 100) @@ -1581,6 +1605,9 @@ function ItemClass:BuildModList() elseif self.base.flask then self.flaskData = { } self.buffModList = { } + elseif self.base.charm then + self.charmData = { } + self.buffModList = { } elseif self.base.tincture then self.tinctureData = { } self.buffModList = { } diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index 53f049af12..d8a858afd0 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -230,7 +230,7 @@ function ItemDBClass:ListBuilder() item.measuredPower = 0 for slotName, slot in pairs(self.itemsTab.slots) do if self.itemsTab:IsItemValidForSlot(item, slotName) and not slot.inactive and (not slot.weaponSet or slot.weaponSet == (self.itemsTab.activeItemSet.useSecondWeaponSet and 2 or 1)) then - local output = calcFunc(item.base.flask and { toggleFlask = item } or item.base.tincture and { toggleTincture = item } or { repSlotName = slotName, repItem = item }, useFullDPS) + local output = calcFunc(item.base.flask and { toggleFlask = item } or item.base.tincture and { toggleTincture = item } or item.base.charm and { toggleCharm = item } or { repSlotName = slotName, repItem = item }, useFullDPS) local measuredPower = output.Minion and output.Minion[self.sortMode] or output[self.sortMode] or 0 if self.sortDetail.transform then measuredPower = self.sortDetail.transform(measuredPower) diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index 596f09d207..fdfc5e8fee 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -40,6 +40,18 @@ local ItemSlotClass = newClass("ItemSlotControl", "DropDownControl", function(se end self.controls.activate.tooltipText = "Activate this flask." self.labelOffset = -24 + elseif slotName:match("Charm") then + self.controls.activate = new("CheckBoxControl", {"RIGHT",self,"LEFT"}, {-2, 0, 20}, nil, function(state) + self.active = state + itemsTab.activeItemSet[self.slotName].active = state + itemsTab:AddUndoState() + itemsTab.build.buildFlag = true + end) + self.controls.activate.enabled = function() + return self.selItemId ~= 0 + end + self.controls.activate.tooltipText = "Activate this charm." + self.labelOffset = -24 else self.labelOffset = -2 end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 94458ec3f4..c05f50df75 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -493,7 +493,8 @@ holding Shift will put it in the second.]]) end) self.controls.displayItemAddImplicit.shown = function() return self.displayItem and - self.displayItem.type ~= "Tincture" and (self.displayItem.corruptible or ((self.displayItem.type ~= "Flask" and self.displayItem.type ~= "Jewel") and + self.displayItem.type ~= "Tincture" and self.displayItem.type ~= "Charm" and + (self.displayItem.corruptible or ((self.displayItem.type ~= "Flask" and self.displayItem.type ~= "Jewel") and (self.displayItem.rarity == "NORMAL" or self.displayItem.rarity == "MAGIC" or self.displayItem.rarity == "RARE"))) and not self.displayItem.implicitsCannotBeChanged end @@ -1971,8 +1972,8 @@ function ItemsTabClass:CraftItem() item.implicitModLines = { } item.explicitModLines = { } item.crucibleModLines = { } - if base.base.type == "Amulet" or base.base.type == "Belt" or base.base.type == "Jewel" or base.base.type == "Quiver" or base.base.type == "Ring" or - base.base.type == "SoulCore" then + if base.base.type == "Amulet" or base.base.type == "Belt" or base.base.type == "Charm" or base.base.type == "Jewel" + or base.base.type == "Quiver" or base.base.type == "Ring" or base.base.type == "SoulCore" then item.quality = nil else item.quality = 0 @@ -1980,7 +1981,7 @@ function ItemsTabClass:CraftItem() local raritySel = controls.rarity.selIndex if base.base.flask or (base.base.type == "Jewel" and base.base.subType == "Charm") - or base.base.type == "Tincture" + or base.base.type == "Tincture" or base.base.type == "Charm" then if raritySel == 3 then raritySel = 2 @@ -3185,7 +3186,7 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode) tooltip:AddLine(20, rarityCode..item.namePrefix..item.baseName:gsub(" %(.+%)","")..item.nameSuffix) end if item.charmLimit then - tooltip:AddLine(16, colorCodes.SOURCE.."Charm Slots: "..item.charmLimit) + tooltip:AddLine(16, s_format("^x7F7F7FCharm Slots: %d", item.charmLimit)) end for _, curInfluenceInfo in ipairs(influenceInfo) do if item[curInfluenceInfo.key] then @@ -3321,6 +3322,18 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode) for _, modLine in pairs(item.buffModLines) do tooltip:AddLine(16, (modLine.extra and colorCodes.UNSUPPORTED or colorCodes.MAGIC) .. modLine.line) end + elseif base.charm then + -- Charm-specific info + local charmData = item.charmData + + tooltip:AddLine(16, s_format("^x7F7F7FLasts %s%.2f ^x7F7F7FSeconds", main:StatColor(charmData.duration, base.charm.duration), charmData.duration)) + tooltip:AddLine(16, s_format("^x7F7F7FConsumes %s%d ^x7F7F7Fof %s%d ^x7F7F7FCharges on use", + main:StatColor(charmData.chargesUsed, base.charm.chargesUsed), charmData.chargesUsed, + main:StatColor(charmData.chargesMax, base.charm.chargesMax), charmData.chargesMax + )) + for _, modLine in pairs(item.buffModLines) do + tooltip:AddLine(16, (modLine.extra and colorCodes.UNSUPPORTED or colorCodes.MAGIC) .. modLine.line) + end elseif base.tincture then -- Tincture-specific info local tinctureData = item.tinctureData @@ -3656,6 +3669,41 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode) header = "^7Activating this flask will give you:" end self.build:AddStatComparesToTooltip(tooltip, calcBase, output, header) + elseif base.charm then + -- Special handling for charms + local stats = { } + local charmData = item.charmData + local modDB = self.build.calcsTab.mainEnv.modDB + local output = self.build.calcsTab.mainOutput + local durInc = modDB:Sum("INC", nil, "CharmDuration") + local effectInc = modDB:Sum("INC", { actor = "player" }, "CharmEffect") + + if item.rarity == "MAGIC" then + effectInc = effectInc + modDB:Sum("INC", { actor = "player" }, "MagicCharmEffect") + end + local effectMod = (1 + (charmData.effectInc + effectInc) / 100) * (1 + (item.quality or 0) / 100) + if effectMod ~= 1 then + t_insert(stats, s_format("^8Charm effect modifier: ^7%+d%%", effectMod * 100 - 100)) + end + + if durInc ~= 0 then + t_insert(stats, s_format("^8Charm effect duration: ^7%.1f0s", charmData.duration * (1 + durInc / 100))) + end + + if stats[1] then + tooltip:AddLine(14, "^7Effective charm stats:") + for _, stat in ipairs(stats) do + tooltip:AddLine(14, stat) + end + end + local output = calcFunc({ toggleCharm = item }) + local header + if self.build.calcsTab.mainEnv.charms[item] then + header = "^7Deactivating this charm will give you:" + else + header = "^7Activating this charm will give you:" + end + self.build:AddStatComparesToTooltip(tooltip, calcBase, output, header) elseif base.tincture then -- Special handling for tinctures local stats = { } diff --git a/src/Data/Bases/flask.lua b/src/Data/Bases/flask.lua index 043e957924..c281375152 100644 --- a/src/Data/Bases/flask.lua +++ b/src/Data/Bases/flask.lua @@ -2,6 +2,103 @@ -- Item data (c) Grinding Gear Games local itemBases = ... +itemBases["Thawing Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you become Frozen", + implicitModTypes = { { }, }, + charm = { duration = 3, chargesUsed = 80, chargesMax = 80, buff = { "Immune to Freeze" }, }, + req = { level = 12, }, +} +itemBases["Staunching Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you start Bleeding", + implicitModTypes = { { }, }, + charm = { duration = 3, chargesUsed = 60, chargesMax = 80, buff = { "You are Immune to Bleeding" }, }, + req = { level = 18, }, +} +itemBases["Antidote Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you become Poisoned", + implicitModTypes = { { }, }, + charm = { duration = 3, chargesUsed = 40, chargesMax = 80, buff = { "Immune to Poison" }, }, + req = { level = 24, }, +} +itemBases["Dousing Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you become Ignited", + implicitModTypes = { { }, }, + charm = { duration = 3, chargesUsed = 60, chargesMax = 80, buff = { "Immune to Ignite" }, }, + req = { level = 32, }, +} +itemBases["Grounding Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you become Shocked", + implicitModTypes = { { }, }, + charm = { duration = 3, chargesUsed = 50, chargesMax = 80, buff = { "Immune to Shock" }, }, + req = { level = 32, }, +} +itemBases["Stone Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you become Stunned", + implicitModTypes = { { }, }, + charm = { duration = 2.5, chargesUsed = 40, chargesMax = 80, buff = { "Cannot be Stunned" }, }, + req = { level = 8, }, +} +itemBases["Silver Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you are affected by a Slow", + implicitModTypes = { { }, }, + charm = { duration = 3, chargesUsed = 40, chargesMax = 80, buff = { "Your speed is unaffected by Slows" }, }, + req = { level = 10, }, +} +itemBases["Ruby Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you take Fire damage from a Hit", + implicitModTypes = { { }, }, + charm = { duration = 4, chargesUsed = 40, chargesMax = 80, buff = { "+25% to Fire Resistance" }, }, + req = { level = 5, }, +} +itemBases["Sapphire Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you take Cold damage from a Hit", + implicitModTypes = { { }, }, + charm = { duration = 4, chargesUsed = 40, chargesMax = 80, buff = { "+25% to Cold Resistance" }, }, + req = { level = 5, }, +} +itemBases["Topaz Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you take Lightning damage from a Hit", + implicitModTypes = { { }, }, + charm = { duration = 4, chargesUsed = 40, chargesMax = 80, buff = { "+25% to Lightning Resistance" }, }, + req = { level = 5, }, +} +itemBases["Amethyst Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you take Chaos damage from a Hit", + implicitModTypes = { { }, }, + charm = { duration = 4, chargesUsed = 60, chargesMax = 80, buff = { "+18% to Chaos Resistance" }, }, + req = { level = 40, }, +} +itemBases["Golden Charm"] = { + type = "Charm", + tags = { flask = true, utility_flask = true, default = true, }, + implicit = "Used when you Kill a Rare or Unique Enemy", + implicitModTypes = { { }, }, + charm = { duration = 1, chargesUsed = 80, chargesMax = 80, buff = { "20% increased Rarity of Items found" }, }, + req = { level = 50, }, +} + itemBases["Lesser Life Flask"] = { type = "Flask", subType = "Life", @@ -147,112 +244,3 @@ itemBases["Ultimate Mana Flask"] = { flask = { mana = 310, duration = 3, chargesUsed = 10, chargesMax = 75, }, req = { level = 60, }, } - -itemBases["Thawing Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you become Frozen", - implicitModTypes = { { }, }, - flask = { duration = 3, chargesUsed = 80, chargesMax = 80, }, - req = { level = 12, }, -} -itemBases["Staunching Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you start Bleeding", - implicitModTypes = { { }, }, - flask = { duration = 3, chargesUsed = 60, chargesMax = 80, }, - req = { level = 18, }, -} -itemBases["Antidote Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you become Poisoned", - implicitModTypes = { { }, }, - flask = { duration = 3, chargesUsed = 40, chargesMax = 80, }, - req = { level = 24, }, -} -itemBases["Dousing Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you become Ignited", - implicitModTypes = { { }, }, - flask = { duration = 3, chargesUsed = 60, chargesMax = 80, }, - req = { level = 32, }, -} -itemBases["Grounding Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you become Shocked", - implicitModTypes = { { }, }, - flask = { duration = 3, chargesUsed = 50, chargesMax = 80, }, - req = { level = 32, }, -} -itemBases["Stone Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you become Stunned", - implicitModTypes = { { }, }, - flask = { duration = 2.5, chargesUsed = 40, chargesMax = 80, }, - req = { level = 8, }, -} -itemBases["Silver Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you are affected by a Slow", - implicitModTypes = { { }, }, - flask = { duration = 3, chargesUsed = 40, chargesMax = 80, }, - req = { level = 10, }, -} -itemBases["Ruby Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you take Fire damage from a Hit", - implicitModTypes = { { }, }, - flask = { duration = 4, chargesUsed = 40, chargesMax = 80, }, - req = { level = 5, }, -} -itemBases["Sapphire Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you take Cold damage from a Hit", - implicitModTypes = { { }, }, - flask = { duration = 4, chargesUsed = 40, chargesMax = 80, }, - req = { level = 5, }, -} -itemBases["Topaz Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you take Lightning damage from a Hit", - implicitModTypes = { { }, }, - flask = { duration = 4, chargesUsed = 40, chargesMax = 80, }, - req = { level = 5, }, -} -itemBases["Amethyst Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you take Chaos damage from a Hit", - implicitModTypes = { { }, }, - flask = { duration = 4, chargesUsed = 60, chargesMax = 80, }, - req = { level = 40, }, -} -itemBases["Golden Charm"] = { - type = "Charm", - subType = "Mana", - tags = { flask = true, utility_flask = true, default = true, }, - implicit = "Used when you Kill a Rare or Unique Enemy", - implicitModTypes = { { }, }, - flask = { duration = 1, chargesUsed = 80, chargesMax = 80, }, - req = { level = 50, }, -} diff --git a/src/Export/Bases/flask.txt b/src/Export/Bases/flask.txt index 7b27a07521..573e649bd8 100644 --- a/src/Export/Bases/flask.txt +++ b/src/Export/Bases/flask.txt @@ -1,12 +1,12 @@ -- Item data (c) Grinding Gear Games local itemBases = ... +#type Charm +#baseMatch Metadata/Items/Flasks/FourCharm + #type Flask #subType Life #baseMatch Metadata/Items/Flasks/FourFlaskLife #subType Mana #baseMatch Metadata/Items/Flasks/FourFlaskMana - -#type Charm -#baseMatch Metadata/Items/Flasks/FourCharm diff --git a/src/Export/Scripts/bases.lua b/src/Export/Scripts/bases.lua index 787f5b6a08..9a27808e48 100644 --- a/src/Export/Scripts/bases.lua +++ b/src/Export/Scripts/bases.lua @@ -204,7 +204,11 @@ directiveTable.base = function(state, args, out) local flask = dat("Flasks"):GetRow("BaseItemType", baseItemType) if flask then local compCharges = dat("ComponentCharges"):GetRow("BaseItemType", baseItemType.Id) - out:write('\tflask = { ') + if state.type == "Charm" then + out:write('\tcharm = { ') + else + out:write('\tflask = { ') + end if flask.LifePerUse > 0 then out:write('life = ', flask.LifePerUse, ', ') end @@ -214,13 +218,15 @@ directiveTable.base = function(state, args, out) out:write('duration = ', flask.RecoveryTime / 10, ', ') out:write('chargesUsed = ', compCharges.PerUse, ', ') out:write('chargesMax = ', compCharges.Max, ', ') - if flask.Buff then + if next(flask.UtilityBuffs) then local stats = { } - for i, stat in ipairs(flask.Buff.Stats) do - stats[stat.Id] = { min = flask.BuffMagnitudes[i], max = flask.BuffMagnitudes[i] } - end - for i, stat in ipairs(flask.Buff.GrantedFlags) do - stats[stat.Id] = { min = 1, max = 1 } + for _, buff in ipairs(flask.UtilityBuffs) do + for i, stat in ipairs(buff.BuffDefinitionsKey.GrantedStats) do + stats[stat.Id] = { min = buff.StatValues[i], max = buff.StatValues[i] } + end + for i, stat in ipairs(buff.BuffDefinitionsKey.GrantedFlags) do + stats[stat.Id] = { min = 1, max = 1 } + end end out:write('buff = { "', table.concat(describeStats(stats), '", "'), '" }, ') end diff --git a/src/Export/Scripts/mods.lua b/src/Export/Scripts/mods.lua index 02194a736d..76bbd7f7c8 100644 --- a/src/Export/Scripts/mods.lua +++ b/src/Export/Scripts/mods.lua @@ -110,7 +110,7 @@ writeMods("../Data/ModItem.lua", function(mod) and #mod.AuraFlags == 0 end) writeMods("../Data/ModFlask.lua", function(mod) - return mod.Domain == 2 and (mod.GenerationType == 1 or mod.GenerationType == 2 or mod.GenerationType == 3) + return mod.Domain == 2 and (mod.GenerationType == 1 or mod.GenerationType == 2) end) writeMods("../Data/ModJewel.lua", function(mod) return (mod.Domain == 10 or mod.Domain == 16) and (mod.GenerationType == 1 or mod.GenerationType == 2 or mod.GenerationType == 5) diff --git a/src/Export/spec.lua b/src/Export/spec.lua index 86b39e686f..e1fc585b2c 100644 --- a/src/Export/spec.lua +++ b/src/Export/spec.lua @@ -2074,7 +2074,7 @@ return { name="Description", refTo="", type="String", - width=150 + width=440 }, [3]={ list=false, @@ -4800,11 +4800,11 @@ return { width=150 }, [10]={ - list=false, - name="UtilityBuff", + list=true, + name="UtilityBuffs", refTo="UtilityFlaskBuffs", type="Key", - width=150 + width=240 } }, flaskstashbasetypeordering={ @@ -13826,28 +13826,28 @@ return { name="BuffDefinitionsKey", refTo="BuffDefinitions", type="Key", - width=220 + width=250 }, [2]={ list=true, name="StatValues", refTo="", type="Int", - width=150 + width=240 }, [3]={ list=true, name="StatValues2", refTo="", type="Int", - width=150 + width=250 }, [4]={ list=false, name="", refTo="", type="Key", - width=150 + width=250 } }, villageuniquedisenchantvalues={ diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index b5bf3a9078..907b817dc3 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1430,7 +1430,71 @@ function calcs.perform(env, skipEHP) end end end - + + local effectInc = modDB:Sum("INC", {actor = "player"}, "CharmEffect") + local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicCharmEffect") + local charmLimit = modDB:Sum("BASE", nil, "CharmLimit") + + -- charm breakdown + if breakdown then + output.CharmEffect = effectInc + output.CharmLimit = charmLimit + end + + local function mergeCharms(charms) + local charmBuffs = { } + local charmConditions = {} + local charmBuffsPerBase = {} + + local function calcCharmMods(item, baseName, buffModList, modList) + local charmEffectInc = effectInc + item.charmData.effectInc + if item.rarity == "MAGIC" then + charmEffectInc = charmEffectInc + effectIncMagic + end + local effectMod = (1 + (charmEffectInc) / 100) * (1 + (item.quality or 0) / 100) + + -- same deal as flasks, go look at the comment there + if buffModList[1] then + local srcList = new("ModList") + srcList:ScaleAddList(buffModList, effectMod) + mergeBuff(srcList, charmBuffs, baseName) + mergeBuff(srcList, charmBuffsPerBase[item.baseName], baseName) + end + + if modList[1] then + local srcList = new("ModList") + srcList:ScaleAddList(modList, effectMod) + local key + if item.rarity == "UNIQUE" then + key = item.title + else + key = "" + for _, mod in ipairs(modList) do + key = key .. modLib.formatModParams(mod) .. "&" + end + end + mergeBuff(srcList, charmBuffs, key) + mergeBuff(srcList, charmBuffsPerBase[item.baseName], key) + end + end + for item in pairs(charms) do + if charmLimit <= 0 then + break + end + charmLimit = charmLimit - 1 + charmBuffsPerBase[item.baseName] = charmBuffsPerBase[item.baseName] or {} + charmConditions["UsingCharm"] = true + charmConditions["Using"..item.baseName:gsub("%s+", "")] = true + calcCharmMods(item, item.baseName, item.buffModList, item.modList) + end + for charmCond, status in pairs(charmConditions) do + modDB.conditions[charmCond] = status + end + for _, buffModList in pairs(charmBuffs) do + modDB:AddList(buffModList) + end + end + local effectInc = modDB:Sum("INC", {actor = "player"}, "TinctureEffect") local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicTinctureEffect") local tinctureLimit = modDB:Sum("BASE", nil, "TinctureLimit") @@ -1511,6 +1575,7 @@ function calcs.perform(env, skipEHP) -- This needs to be done in 2 steps to account for effects affecting life recovery from flasks -- For example Sorrow of the Divine and buffs (like flask recovery watchers eye) mergeFlasks(env.flasks, false, true) + mergeCharms(env.charms) mergeTinctures(env.tinctures) -- Merge keystones again to catch any that were added by flasks diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index f42c48a05f..6f4994aeda 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -1654,6 +1654,11 @@ return { { label = "Charges/s", { format = "{2:output:ManaFlaskChargeGen}", { label = "Mana Flask Charges/s", modName = "ManaFlaskChargesGenerated", modType = "BASE"}, { label = "Generic Flask Charges/s", modName = { "FlaskChargesGenerated", "FlaskChargesGeneratedPerEmptyFlask" }, modType = "BASE" }}, }, +} }, { defaultCollapsed = true, label = "Charms", data = { + extra = "+{0:output:CharmEffect}%, {0:output:CharmLimit}", + { label = "Inc. Effect", { format = "{0:mod:1}%", { modName = "CharmEffect", modType = "INC", actor = "player"}, }, }, + { label = "Inc. Duration", { format = "{0:mod:1}%", { modName = "CharmDuration", modType = "INC" }, }, }, + { label = "Charm Limit", { format = "{0:mod:1}", { modName = "CharmLimit", modType = "BASE"}, }, }, } }, { defaultCollapsed = true, label = "Tinctures", data = { extra = "+{0:output:TinctureEffect}%, {0:output:TinctureLimit}", { label = "Inc. Effect", { format = "{0:mod:1}%", { modName = "TinctureEffect", modType = "INC", actor = "player"}, }, }, diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 230af93623..c266beca75 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -235,6 +235,7 @@ function wipeEnv(env, accelerate) wipeTable(env.player.itemList) wipeTable(env.grantedSkillsItems) wipeTable(env.flasks) + wipeTable(env.charms) wipeTable(env.tinctures) -- Special / Unique Items that have their own ModDB() @@ -406,6 +407,7 @@ function calcs.initEnv(build, mode, override, specEnv) env.explodeSources = { } env.itemWarnings = { } env.flasks = { } + env.charms = { } env.tinctures = { } -- tree based @@ -513,6 +515,7 @@ function calcs.initEnv(build, mode, override, specEnv) modDB:NewMod("PerAfflictionNonDamageEffect", "BASE", 8, "Base") modDB:NewMod("PerAbsorptionElementalEnergyShieldRecoup", "BASE", 12, "Base") modDB:NewMod("TinctureLimit", "BASE", 1, "Base") + modDB:NewMod("CharmLimit", "BASE", 0, "Base") modDB:NewMod("ManaDegenPercent", "BASE", 1, "Base", { type = "Multiplier", var = "EffectiveManaBurnStacks" }) modDB:NewMod("LifeDegenPercent", "BASE", 1, "Base", { type = "Multiplier", var = "WeepingWoundsStacks" }) modDB:NewMod("WeaponSwapSpeed", "BASE", 250, "Base") -- 250ms @@ -830,6 +833,11 @@ function calcs.initEnv(build, mode, override, specEnv) end end item = nil + elseif item and item.type == "Charm" then + if slot.active then + env.charms[item] = true + end + item = nil elseif item and item.type == "Tincture" then if slot.active then env.tinctures[item] = true @@ -1041,7 +1049,10 @@ function calcs.initEnv(build, mode, override, specEnv) if item.classRestriction then env.itemModDB.conditions[item.title:gsub(" ", "")] = item.classRestriction end - if item.type ~= "Jewel" and item.type ~= "Flask" and item.type ~= "Tincture" then + if item.charmLimit then + env.modDB:NewMod("CharmLimit", "BASE", item.charmLimit, item.title) + end + if item.type ~= "Jewel" and item.type ~= "Flask" and item.type ~= "Charm" and item.type ~= "Tincture" then -- Update item counts local key if item.rarity == "UNIQUE" or item.rarity == "RELIC" then @@ -1115,6 +1126,13 @@ function calcs.initEnv(build, mode, override, specEnv) env.flasks[override.toggleFlask] = true end end + if override.toggleCharm then + if env.charms[override.toggleCharm] then + env.charms[override.toggleCharm] = nil + else + env.charms[override.toggleCharm] = true + end + end if override.toggleTincture then if env.tinctures[override.toggleTincture] then env.tinctures[override.toggleTincture] = nil diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index b862b65f60..f57eb9465f 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -766,10 +766,11 @@ local modNameList = { ["to apply lightning exposure on hit"] = "LightningExposureChance", ["to ignore enemy physical damage reduction"] = "ChanceToIgnoreEnemyPhysicalDamageReduction", ["weapon swap speed"] = "WeaponSwapSpeed", - -- Flask and Tincture modifiers + -- Flask and Charm modifiers ["effect"] = "LocalEffect", ["effect of flasks"] = "FlaskEffect", ["effect of tinctures"] = "TinctureEffect", + ["effect of charms"] = "CharmEffect", ["amount recovered"] = "FlaskRecovery", ["life recovered"] = "FlaskRecovery", ["life recovery from flasks used"] = "FlaskLifeRecovery", @@ -779,6 +780,7 @@ local modNameList = { ["mana recovery from flasks"] = "FlaskManaRecovery", ["life and mana recovery from flasks"] = { "FlaskLifeRecovery", "FlaskManaRecovery" }, ["flask effect duration"] = "FlaskDuration", + ["charm effect duration"] = "CharmDuration", ["recovery speed"] = "FlaskRecoveryRate", ["recovery rate"] = "FlaskRecoveryRate", ["flask recovery rate"] = "FlaskRecoveryRate", @@ -790,7 +792,9 @@ local modNameList = { ["charges used"] = "FlaskChargesUsed", ["charges per use"] = "FlaskChargesUsed", ["flask charges used"] = "FlaskChargesUsed", + ["charm charges used"] = "CharmChargesUsed", ["flask charges gained"] = "FlaskChargesGained", + ["charm charges gained"] = "CharmChargesGained", ["charge recovery"] = "FlaskChargeRecovery", ["for flasks you use to not consume charges"] = "FlaskChanceNotConsumeCharges", ["for tinctures to not inflict mana burn"] = "TincturesNotInflictManaBurn", @@ -4490,6 +4494,9 @@ local specialModList = { ["flasks gain (%d+) charges? every (%d+) seconds"] = function(num, _, div) return { mod("FlaskChargesGenerated", "BASE", num / div) } end, + ["charms gain (%d+) charges? every per seconds"] = function(num) return { + mod("CharmChargesGenerated", "BASE", num) + } end, ["flasks gain a charge every (%d+) seconds"] = function(_, div) return { mod("FlaskChargesGenerated", "BASE", 1 / div) } end, @@ -4520,6 +4527,11 @@ local specialModList = { ["life flask effects are not removed when unreserved life is filled"] = { flag("LifeFlaskEffectNotRemoved") }, + ["+(%d+) charm slots?"] = function(num) return { mod("CharmLimit", "BASE", num) } end, + ["charms use no charges"] = { flag("CharmsUseNoCharges") }, + ["(%d+)%% of charges used by charms granted to your life flasks"] = function(num) return { + mod("FlaskChargesGained", "MORE", num / 100, nil, nil, { type = "Multiplier", var = "AvgCharmChargesUsed"} ) + } end, -- Jewels ["passives in radius of ([%a%s']+) can be allocated without being connected to your tree"] = function(_, name) return { mod("JewelData", "LIST", { key = "impossibleEscapeKeystone", value = name }), @@ -4555,6 +4567,7 @@ local specialModList = { ["tincture effects also apply to ranged weapons"] = { flag("TinctureRangedWeapons"), }, ["you can have an additional tincture active"] = { mod("TinctureLimit", "BASE", 1), }, ["(%d+)%% increased tincture cooldown recovery rate"] = function(num) return { mod("TinctureCooldownRecovery", "INC", num) } end, + ["(%d+)%% increased charm cooldown recovery rate"] = function(num) return { mod("CharmCooldownRecovery", "INC", num) } end, ["adds (%d+) passive skills"] = function(num) return { mod("JewelData", "LIST", { key = "clusterJewelNodeCount", value = num }) } end, ["1 added passive skill is a jewel socket"] = { mod("JewelData", "LIST", { key = "clusterJewelSocketCount", value = 1 }) }, ["(%d+) added passive skills are jewel sockets"] = function(num) return { mod("JewelData", "LIST", { key = "clusterJewelSocketCount", value = num }) } end,