Skip to content

Commit dd08c61

Browse files
author
LocalIdentity
committed
Merge branch 'dev' into awakened-gems-legacy
2 parents 3c0f6f4 + 865e265 commit dd08c61

204 files changed

Lines changed: 502348 additions & 52983 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
### Fixed Crashes
1414
- Fix crash on adding support gems and importing items to many builds [\#9340](https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/9340) ([LocalIdentity](https://github.com/LocalIdentity))
1515
- Fix Radius Jewels in Shared Items Crashing on Load [\#9349](https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/9349) ([Peechey](https://github.com/Peechey))
16-
- Fix Crash when sorting gems while using Foulborn Gruthkel's Pelt [\#9376](https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/9376) ([LocalIdentity](https://github.com/LocalIdentity))
16+
- Fix Crash when sorting gems while using Foulborn Gruthkul's Pelt [\#9376](https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/9376) ([LocalIdentity](https://github.com/LocalIdentity))
1717
### User Interface
1818
- Fix Foulborn Icons showing on tree nodes, and foil items not importing type [\#9363](https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/9363) ([Blitz54](https://github.com/Blitz54))
1919
### Fixed Calculations

changelog.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ VERSION[2.60.0][2026/01/28]
88
--- Fixed Crashes ---
99
* Fix crash on adding support gems and importing items to many builds (LocalIdentity)
1010
* Fix Radius Jewels in Shared Items Crashing on Load (Peechey)
11-
* Fix Crash when sorting gems while using Foulborn Gruthkel's Pelt (LocalIdentity)
11+
* Fix Crash when sorting gems while using Foulborn Gruthkul's Pelt (LocalIdentity)
1212

1313
--- User Interface ---
1414
* Fix Foulborn Icons showing on tree nodes, and foil items not importing type (Blitz54)

docs/modSyntax.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Used as a key, so you can reference this mod elsewhere in PoB. Can really be an
1313
- "OVERRIDE": used when you want to ignore any calculations done on this mod and just use the value (e.g. "your resistances are 78%" from Loreweave)
1414
- "FLAG": used for conditions. Value will be true/false when this type is used.
1515
- When you need the "FLAG" ModType, consider using the function `flag(name, source, modFlags, keywordFlags, extraTags)` instead. This method shortens the code and clarifies the intent. For example, `flag("ZealotsOath", { type = "Condition", var = "UsingFlask" })` is the same as `mod("ZealotsOath", "FLAG", true, { type = "Condition", var = "UsingFlask" })`
16+
- "MAX" and "MIN": used for values where only the highest or lowest value should take effect respectively. Examples are `"ImprovedMinionDamageAppliesToPlayer"` for "Increases and Reductions to Minion Damage apply ... at X% of their value" or `"PoisonStackLimit"` for "Cannot Poison Enemies with at least X Poisons on them"
1617
### Value
1718
This represents the raw value of the mod. When it's used in the skills to map from the skill data, this will be `nil`, as it pulls the number from the gem based on the level.
1819
### Source

fix_ascendancy_positions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __sub__(self, other: Point2D) -> Point2D:
3939
"Trickster": Point2D(10200, -3700),
4040
"Saboteur": Point2D(10200, -2200),
4141
"Ascendant": Point2D(-7800, 7200),
42+
"Reliquarian": Point2D(-7800, 8900),
4243
"Warden": Point2D(8250, 8350),
4344
"Primalist": Point2D(7200, 9400),
4445
"Warlock": Point2D(9300, 7300),
@@ -52,6 +53,7 @@ def __sub__(self, other: Point2D) -> Point2D:
5253
"KingInTheMists": Point2D(3750, 12000),
5354
"Olroth": Point2D(5250, 12000),
5455
"Oshabi": Point2D(6750, 12000),
56+
"Necromantic": Point2D(8250, 12000),
5557
}
5658
EXTRA_NODES = {
5759
"Necromancer": [{"Node": {"name": "Nine Lives", "icon": "Art/2DArt/SkillIcons/passives/Ascendants/Int.png", "isNotable": True, "skill" : 27602},

manifest.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ exclude-directories =
1111
[program]
1212
path = src
1313
exclude-files = HeadlessWrapper.lua,LaunchInstall.lua,Settings.xml
14-
exclude-directories = src/Export,src/TreeData,src/Builds,src/luacov.stats.out,src/Data/TimelessJewelData/BrutalRestraint.bin,src/Data/TimelessJewelData/ElegantHubris.bin,src/Data/TimelessJewelData/GloriousVanity.bin,src/Data/TimelessJewelData/LethalPride.bin,src/Data/TimelessJewelData/MilitantFaith.bin
14+
exclude-directories = src/Export,src/TreeData,src/Builds,src/luacov.stats.out,src/Data/TimelessJewelData/BrutalRestraint.bin,src/Data/TimelessJewelData/ElegantHubris.bin,src/Data/TimelessJewelData/GloriousVanity.bin,src/Data/TimelessJewelData/LethalPride.bin,src/Data/TimelessJewelData/MilitantFaith.bin,src/Data/TimelessJewelData/HeroicTragedy.bin
1515

1616
[tree]
1717
path = src

src/Classes/ImportTab.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(
7070
end
7171
self.controls.accountNameGo.tooltipFunc = function(tooltip)
7272
tooltip:Clear()
73-
if not self.controls.accountName.buf:match("[#%-]%d%d%d%d$") then
73+
if not self.controls.accountName.buf:match("[#%-]%d%d%d%d$") and self.controls.accountName.buf ~= "" then
7474
tooltip:AddLine(16, "^7Missing discriminator e.g. " .. self.controls.accountName.buf .. "#1234")
7575
end
7676
end
@@ -97,7 +97,7 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(
9797

9898
self.controls.accountNameMissingDiscriminator = new("LabelControl", {"TOPLEFT",self.controls.accountName,"BOTTOMLEFT"}, {0, 8, 0, 16}, "^1Missing discriminator e.g. #1234")
9999
self.controls.accountNameMissingDiscriminator.shown = function()
100-
return not self.controls.accountName.buf:match("[#%-]%d%d%d%d$")
100+
return not self.controls.accountName.buf:match("[#%-]%d%d%d%d$") and self.controls.accountName.buf ~= ""
101101
end
102102

103103
self.controls.accountNameUnicode = new("LabelControl", {"TOPLEFT",self.controls.accountRealm,"BOTTOMLEFT"}, {0, 34, 0, 14}, "^7Note: if the account name contains non-ASCII characters it must be pasted into the textbox,\nnot typed manually.")
@@ -542,7 +542,7 @@ function ImportTabClass:BuildCharacterList(league)
542542
elseif (charClass == "Juggernaut" or charClass == "Berserker" or charClass == "Chieftain" or
543543
charClass == "Antiquarian" or charClass == "Behemoth" or charClass == "Ancestral Commander") then
544544
classColor = colorCodes["MARAUDER"]
545-
elseif (charClass == "Ascendant" or charClass == "Scavenger") then
545+
elseif (charClass == "Ascendant" or charClass == "Reliquarian" or charClass == "Scavenger") then
546546
classColor = colorCodes["SCION"]
547547
end
548548
end

src/Classes/Item.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
756756
self.prefixes.limit = (self.prefixes.limit or 0) + (tonumber(lineLower:match("%+(%d+) prefix modifiers? allowed")) or 0) - (tonumber(lineLower:match("%-(%d+) prefix modifiers? allowed")) or 0)
757757
elseif lineLower:match(" suffix modifiers? allowed") then
758758
self.suffixes.limit = (self.suffixes.limit or 0) + (tonumber(lineLower:match("%+(%d+) suffix modifiers? allowed")) or 0) - (tonumber(lineLower:match("%-(%d+) suffix modifiers? allowed")) or 0)
759-
elseif lineLower == "this item can be anointed by cassia" then
759+
elseif lineLower:find("can be anointed") then -- blight uniques and Cord Belt
760760
self.canBeAnointed = true
761761
elseif lineLower == "can have a second enchantment modifier" then
762762
self.canHaveTwoEnchants = true

src/Classes/ItemsTab.lua

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ local socketDropList = {
3030
{ label = colorCodes.SCION.."W", color = "W" }
3131
}
3232

33-
local baseSlots = { "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2", "Ring 3", "Belt", "Flask 1", "Flask 2", "Flask 3", "Flask 4", "Flask 5", "Graft 1", "Graft 2" }
33+
local baseSlots = { "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2", "Ring 3", "Belt", "Graft 1", "Graft 2", "Flask 1", "Flask 2", "Flask 3", "Flask 4", "Flask 5" }
3434

3535
local influenceInfo = itemLib.influenceInfo.all
3636

@@ -56,6 +56,9 @@ for _, entry in pairs(data.flavourText) do
5656
end
5757
end
5858

59+
local function isAnointable(item)
60+
return (item.canBeAnointed or item.base.type == "Amulet")
61+
end
5962

6063
local ItemsTabClass = newClass("ItemsTab", "UndoHandler", "ControlHost", "Control", function(self, build)
6164
self.UndoHandler()
@@ -142,6 +145,10 @@ local ItemsTabClass = newClass("ItemsTab", "UndoHandler", "ControlHost", "Contro
142145
end
143146
swapSlot.abyssalSocketList[i] = abyssal
144147
end
148+
elseif slotName == "Graft 1" or slotName == "Graft 2" then
149+
slot.shown = function()
150+
return self.build.spec.treeVersion:find("3_27")
151+
end
145152
elseif slotName == "Ring 3" then
146153
slot.shown = function()
147154
return self.build.calcsTab.mainEnv.modDB:Flag(nil, "AdditionalRingSlot")
@@ -438,7 +445,7 @@ holding Shift will put it in the second.]])
438445
self.controls.displayItemAddSocket.shown = function()
439446
return #self.displayItem.sockets < self.displayItem.selectableSocketCount + self.displayItem.abyssalSocketCount
440447
end
441-
448+
442449
-- Section: Enchant / Anoint / Corrupt
443450
self.controls.displayItemSectionEnchant = new("Control", {"TOPLEFT",self.controls.displayItemSectionSockets,"BOTTOMLEFT"}, {0, 0, 0, function()
444451
return (self.controls.displayItemEnchant:IsShown() or self.controls.displayItemEnchant2:IsShown() or self.controls.displayItemAnoint:IsShown() or self.controls.displayItemAnoint2:IsShown() or self.controls.displayItemCorrupt:IsShown() ) and 28 or 0
@@ -459,14 +466,14 @@ holding Shift will put it in the second.]])
459466
self:AnointDisplayItem(1)
460467
end)
461468
self.controls.displayItemAnoint.shown = function()
462-
return self.displayItem and (self.displayItem.base.type == "Amulet" or self.displayItem.canBeAnointed)
469+
return self.displayItem and isAnointable(self.displayItem)
463470
end
464471
self.controls.displayItemAnoint2 = new("ButtonControl", {"TOPLEFT",self.controls.displayItemAnoint,"TOPRIGHT",true}, {8, 0, 100, 20}, "Anoint 2...", function()
465472
self:AnointDisplayItem(2)
466473
end)
467474
self.controls.displayItemAnoint2.shown = function()
468475
return self.displayItem and
469-
(self.displayItem.base.type == "Amulet" or self.displayItem.canBeAnointed) and
476+
isAnointable(self.displayItem) and
470477
self.displayItem.canHaveTwoEnchants and
471478
#self.displayItem.enchantModLines > 0
472479
end
@@ -475,7 +482,7 @@ holding Shift will put it in the second.]])
475482
end)
476483
self.controls.displayItemAnoint3.shown = function()
477484
return self.displayItem and
478-
(self.displayItem.base.type == "Amulet" or self.displayItem.canBeAnointed) and
485+
isAnointable(self.displayItem) and
479486
self.displayItem.canHaveThreeEnchants and
480487
#self.displayItem.enchantModLines > 1
481488
end
@@ -484,7 +491,7 @@ holding Shift will put it in the second.]])
484491
end)
485492
self.controls.displayItemAnoint4.shown = function()
486493
return self.displayItem and
487-
(self.displayItem.base.type == "Amulet" or self.displayItem.canBeAnointed) and
494+
isAnointable(self.displayItem) and
488495
self.displayItem.canHaveFourEnchants and
489496
#self.displayItem.enchantModLines > 2
490497
end
@@ -1578,18 +1585,44 @@ function ItemsTabClass:DeleteItem(item, deferUndoState)
15781585
end
15791586
end
15801587

1588+
local function copyAnointsAndEldritchImplicits(newItem, activeItemSet, items)
1589+
local newItemType = newItem.base.type
1590+
if activeItemSet[newItemType] then
1591+
local currentItem = activeItemSet[newItemType].selItemId and items[activeItemSet[newItemType].selItemId]
1592+
-- if you don't have an equipped item that matches the type of the newItem, no need to do anything
1593+
if currentItem then
1594+
-- if the new item is anointable and does not have an anoint and your current respective item does, apply that anoint to the new item
1595+
if isAnointable(newItem) and #newItem.enchantModLines == 0 and activeItemSet[newItemType].selItemId > 0 then
1596+
local currentAnoint = currentItem.enchantModLines
1597+
if currentAnoint and #currentAnoint == 1 then -- skip if amulet has more than one anoint e.g. Stranglegasp
1598+
newItem.enchantModLines = currentAnoint
1599+
end
1600+
end
1601+
-- if the new item is a non-corrupted Normal, Magic, or Rare Helmet, Body Armour, Gloves, or Boots and does not have any influence
1602+
-- and your current respective item is Eater and/or Exarch, apply those implicits and influence to the new item
1603+
local eldritchBaseTypes = { "Helmet", "Body Armour", "Gloves", "Boots" }
1604+
local eldritchRarities = { "NORMAL", "MAGIC", "RARE" }
1605+
for _, influence in ipairs(itemLib.influenceInfo.default) do
1606+
if newItem[influence.key] then
1607+
return
1608+
end
1609+
end
1610+
if main.migrateEldritchImplicits and isValueInTable(eldritchBaseTypes, newItem.base.type) and isValueInTable(eldritchRarities, newItem.rarity)
1611+
and #newItem.implicitModLines == 0 and not newItem.corrupted and (currentItem.cleansing or currentItem.tangle) and currentItem.implicitModLines then
1612+
newItem.implicitModLines = currentItem.implicitModLines
1613+
newItem.tangle = currentItem.tangle
1614+
newItem.cleansing = currentItem.cleansing
1615+
end
1616+
newItem:BuildAndParseRaw()
1617+
end
1618+
end
1619+
end
1620+
15811621
-- Attempt to create a new item from the given item raw text and sets it as the new display item
15821622
function ItemsTabClass:CreateDisplayItemFromRaw(itemRaw, normalise)
15831623
local newItem = new("Item", itemRaw)
15841624
if newItem.base then
1585-
-- if the new item is an amulet and does not have an anoint and your current amulet does, apply that anoint to the new item
1586-
if newItem.base.type == "Amulet" and #newItem.enchantModLines == 0 and self.activeItemSet["Amulet"].selItemId > 0 then
1587-
local currentAnoint = self.items[self.activeItemSet["Amulet"].selItemId].enchantModLines
1588-
if currentAnoint and #currentAnoint == 1 then -- skip if amulet has more than one anoint e.g. Stranglegasp
1589-
newItem.enchantModLines = currentAnoint
1590-
newItem:BuildAndParseRaw()
1591-
end
1592-
end
1625+
copyAnointsAndEldritchImplicits(newItem, self.activeItemSet, self.items)
15931626
if normalise then
15941627
newItem:NormaliseQuality()
15951628
newItem:BuildModList()
@@ -1783,10 +1816,17 @@ function ItemsTabClass:UpdateAffixControl(control, item, type, outputTable, outp
17831816
extraTags[skill.tag] = true
17841817
end
17851818
end
1819+
local selAffix = item[outputTable][outputIndex] and item[outputTable][outputIndex].modId
17861820
local affixList = { }
1821+
local retainedAffixes = { }
17871822
for modId, mod in pairs(item.affixes) do
1788-
if mod.type == type and not excludeGroups[mod.group] and item:GetModSpawnWeight(mod, extraTags) > 0 and not item:CheckIfModIsDelve(mod) then
1789-
t_insert(affixList, modId)
1823+
if mod.type == type and not excludeGroups[mod.group] and not item:CheckIfModIsDelve(mod) then
1824+
if item:GetModSpawnWeight(mod, extraTags) > 0 then
1825+
t_insert(affixList, modId)
1826+
elseif modId == selAffix then
1827+
t_insert(affixList, modId)
1828+
retainedAffixes[modId] = true
1829+
end
17901830
end
17911831
end
17921832
table.sort(affixList, function(a, b)
@@ -1821,7 +1861,9 @@ function ItemsTabClass:UpdateAffixControl(control, item, type, outputTable, outp
18211861
end
18221862
local modString = table.concat(mod, "/")
18231863
local label = modString
1824-
if item.type == "Flask" then
1864+
if retainedAffixes[modId] then
1865+
label = "^8[Retained] " .. modString
1866+
elseif item.type == "Flask" then
18251867
label = mod.affix .. " ^8[" .. modString .. "]"
18261868
end
18271869
control.list[i + 1] = {

src/Classes/ModDB.lua

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local ipairs = ipairs
77
local pairs = pairs
88
local select = select
99
local t_insert = table.insert
10+
local t_remove = table.remove
1011
local m_floor = math.floor
1112
local m_min = math.min
1213
local m_max = math.max
@@ -65,6 +66,45 @@ function ModDBClass:ReplaceModInternal(mod)
6566
return false
6667
end
6768

69+
---ConvertModInternal
70+
--- Converts an existing mod with oldName to a new mod with a different name.
71+
--- Moves the mod from the old name's bucket to the new name's bucket.
72+
--- If no matching mod exists, then the function returns false
73+
---@param oldName string @The name of the existing mod to find
74+
---@param mod table @The new mod to replace it with
75+
---@return boolean @Whether any mod was converted
76+
function ModDBClass:ConvertModInternal(oldName, mod)
77+
if not self.mods[oldName] then
78+
if self.parent then
79+
return self.parent:ConvertModInternal(oldName, mod)
80+
end
81+
return false
82+
end
83+
84+
local oldList = self.mods[oldName]
85+
for i = 1, #oldList do
86+
local curMod = oldList[i]
87+
if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source and not curMod.converted then
88+
-- Remove from old name's bucket
89+
t_remove(oldList, i)
90+
-- Add to new name's bucket
91+
local newName = mod.name
92+
if not self.mods[newName] then
93+
self.mods[newName] = { }
94+
end
95+
mod.converted = true
96+
t_insert(self.mods[newName], mod)
97+
return true
98+
end
99+
end
100+
101+
if self.parent then
102+
return self.parent:ConvertModInternal(oldName, mod)
103+
end
104+
105+
return false
106+
end
107+
68108
function ModDBClass:AddList(modList)
69109
local mods = self.mods
70110
for i, mod in ipairs(modList) do

src/Classes/ModList.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@ function ModListClass:ReplaceModInternal(mod)
4545
return false
4646
end
4747

48+
---ConvertModInternal
49+
--- Converts an existing mod with oldName to a new mod with a different name.
50+
--- If no matching mod exists, then the function returns false
51+
---@param oldName string @The name of the existing mod to find
52+
---@param mod table @The new mod to replace it with
53+
---@return boolean @Whether any mod was converted
54+
function ModListClass:ConvertModInternal(oldName, mod)
55+
for i, curMod in ipairs(self) do
56+
if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source then
57+
self[i] = mod
58+
return true
59+
end
60+
end
61+
62+
if self.parent then
63+
return self.parent:ConvertModInternal(oldName, mod)
64+
end
65+
66+
return false
67+
end
68+
4869
function ModListClass:MergeMod(mod, skipNonAdditive)
4970
if mod.type == "BASE" or mod.type == "INC" or mod.type == "MORE" then
5071
for i = 1, #self do

0 commit comments

Comments
 (0)