Skip to content

Commit

Permalink
Add Ruthless tree (#6367)
Browse files Browse the repository at this point in the history
* Add support for Ruthless trees

* Fix export URL for Ruthless trees

* Removed data.json

* Fix PR comments

* Fix edge cases around converting and importing

* Updating support for 3rd party ruthless tree links

* Update tree and add Tattoos

* Add support for new Ruthless tree mods

* Fix comment

---------

Co-authored-by: LocalIdentity <localidentity2@gmail.com>
  • Loading branch information
Wires77 and LocalIdentity authored Aug 23, 2023
1 parent 0d84d1c commit ddec29a
Show file tree
Hide file tree
Showing 22 changed files with 136,262 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/Classes/ImportTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ function ImportTabClass:ImportPassiveTreeAndJewels(json, charData)
end
end
end
self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, charPassiveData.hashes, charPassiveData.mastery_effects or {}, latestTreeVersion)
self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, charPassiveData.hashes, charPassiveData.mastery_effects or {}, latestTreeVersion .. (charData.league:match("Ruthless") and "_ruthless" or ""))
self.build.spec:AddUndoState()
self.build.characterLevel = charData.level
self.build.characterLevelAutoMode = false
Expand Down
10 changes: 8 additions & 2 deletions src/Classes/ModStore.lua
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,19 @@ function ModStoreClass:EvalMod(mod, cfg)
end
elseif tag.type == "PercentStat" then
local base
local target = self
-- This functions similar to the above tagTypes in regard to which actor to use, but for PerStat
-- if the actor is 'parent', we don't want to return if we're already using 'parent', just keep using 'self'
if tag.actor and self.actor[tag.actor] then
target = self.actor[tag.actor].modDB
end
if tag.statList then
base = 0
for _, stat in ipairs(tag.statList) do
base = base + self:GetStat(stat, cfg)
base = base + target:GetStat(stat, cfg)
end
else
base = self:GetStat(tag.stat, cfg)
base = target:GetStat(tag.stat, cfg)
end
local mult = base * (tag.percent and tag.percent / 100 or 1)
local limitTotal
Expand Down
40 changes: 22 additions & 18 deletions src/Classes/TreeTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,12 @@ local TreeTabClass = newClass("TreeTab", "ControlHost", function(self, build)
end
self.treeVersions = { }
for _, num in ipairs(treeVersionList) do
if not num:find("^2") then
local vers = num:gsub("%_", ".")
t_insert(self.treeVersions, vers)
end
t_insert(self.treeVersions, treeVersions[num].display)
end
self.controls.versionText = new("LabelControl", { "LEFT", self.controls.export, "RIGHT" }, 8, 0, 0, 16, "Version:")
self.controls.versionSelect = new("DropDownControl", { "LEFT", self.controls.versionText, "RIGHT" }, 8, 0, 55, 20, self.treeVersions, function(index, value)
self.controls.versionSelect = new("DropDownControl", { "LEFT", self.controls.versionText, "RIGHT" }, 8, 0, 100, 20, self.treeVersions, function(index, value)
if value ~= self.build.spec.treeVersion then
convertToVersion(value:gsub("%.", "_"))
convertToVersion(value:gsub("[%(%)]", ""):gsub("[%.%s]", "_"))
end
end)
self.controls.versionSelect.maxDroppedWidth = 1000
Expand Down Expand Up @@ -199,8 +196,14 @@ local TreeTabClass = newClass("TreeTab", "ControlHost", function(self, build)
self.controls.specConvertText.shown = function()
return self.showConvert
end
self.controls.specConvert = new("ButtonControl", { "LEFT", self.controls.specConvertText, "RIGHT" }, 8, 0, 120, 20, "^2Convert to "..treeVersions[latestTreeVersion].display, function()
convertToVersion(latestTreeVersion)
local function getLatestTreeVersion()
return latestTreeVersion .. (self.specList[self.activeSpec].treeVersion:match("^" .. latestTreeVersion .. "(.*)") or "")
end
local function buildConvertButtonLabel()
return "^2Convert to "..treeVersions[getLatestTreeVersion()].display
end
self.controls.specConvert = new("ButtonControl", { "LEFT", self.controls.specConvertText, "RIGHT" }, 8, 0, function() return DrawStringWidth(16, "VAR", buildConvertButtonLabel()) + 20 end, 20, buildConvertButtonLabel, function()
convertToVersion(getLatestTreeVersion())
end)
self.jumpToNode = false
self.jumpToX = 0
Expand Down Expand Up @@ -232,15 +235,15 @@ function TreeTabClass:Draw(viewPort, inputEvents)

-- Determine positions if one line of controls doesn't fit in the screen width
local twoLineHeight = 24
if viewPort.width >= 1168 + (self.isComparing and 198 or 0) + (self.viewer.showHeatMap and 316 or 0) then
if viewPort.width >= 1336 + (self.isComparing and 198 or 0) + (self.viewer.showHeatMap and 316 or 0) then
twoLineHeight = 0
self.controls.findTimelessJewel:SetAnchor("LEFT", self.controls.treeSearch, "RIGHT", 8, 0)
self.controls.treeSearch:SetAnchor("LEFT", self.controls.versionSelect, "RIGHT", 8, 0)
if self.controls.powerReportList then
self.controls.powerReportList:SetAnchor("TOPLEFT", self.controls.specSelect, "BOTTOMLEFT", 0, self.controls.specSelect.height + 4)
self.controls.allocatedNodeToggle:SetAnchor("TOPLEFT", self.controls.powerReportList, "TOPRIGHT", 8, 0)
end
else
self.controls.findTimelessJewel:SetAnchor("TOPLEFT", self.controls.specSelect, "BOTTOMLEFT", 0, 4)
self.controls.treeSearch:SetAnchor("TOPLEFT", self.controls.specSelect, "BOTTOMLEFT", 0, 4)
if self.controls.powerReportList then
self.controls.powerReportList:SetAnchor("TOPLEFT", self.controls.findTimelessJewel, "BOTTOMLEFT", 0, self.controls.treeHeatMap.y + self.controls.treeHeatMap.height + 4)
self.controls.allocatedNodeToggle:SetAnchor("TOPLEFT", self.controls.powerReportList, "TOPRIGHT", -76, -44)
Expand Down Expand Up @@ -382,7 +385,7 @@ function TreeTabClass:SetActiveSpec(specId)
end
end
end
self.showConvert = curSpec.treeVersion ~= latestTreeVersion
self.showConvert = not curSpec.treeVersion:match("^" .. latestTreeVersion)
if self.build.itemsTab.itemOrderList[1] then
-- Update item slots if items have been loaded already
self.build.itemsTab:PopulateSlots()
Expand Down Expand Up @@ -434,20 +437,20 @@ function TreeTabClass:OpenImportPopup()
main:ClosePopup()
end
end
local function validateTreeVersion(major, minor)
local function validateTreeVersion(isRuthless, major, minor)
-- Take the Major and Minor version numbers and confirm it is a valid tree version. The point release is also passed in but it is not used
-- Return: the passed in tree version as text or latestTreeVersion
if major and minor then
--need leading 0 here
local newTreeVersionNum = tonumber(string.format("%d.%02d", major, minor))
if newTreeVersionNum >= treeVersions[defaultTreeVersion].num and newTreeVersionNum <= treeVersions[latestTreeVersion].num then
-- no leading 0 here
return string.format("%s_%s", major, minor)
return string.format("%s_%s", major, minor) .. isRuthless and "_ruthless" or ""
else
print(string.format("Version '%d_%02d' is out of bounds", major, minor))
end
end
return latestTreeVersion
return latestTreeVersion .. (isRuthless and "_ruthless" or "")
end

controls.editLabel = new("LabelControl", nil, 0, 20, 0, 16, "Enter passive tree link:")
Expand Down Expand Up @@ -487,18 +490,19 @@ function TreeTabClass:OpenImportPopup()
controls.import.enabled = true
return
else
decodeTreeLink(treeLink, validateTreeVersion(treeLink:match(versionLookup)))
decodeTreeLink(treeLink, validateTreeVersion(treeLink:match("tree/ruthless"), treeLink:match(versionLookup)))
end
end)
end
elseif treeLink:match("poeskilltree.com/") then
local oldStyleVersionLookup = "/%?v=([0-9]+)%.([0-9]+)%.([0-9]+)#"
-- Strip the version from the tree : https://poeskilltree.com/?v=3.6.0#AAAABAMAABEtfIOFMo6-ksHfsOvu -> https://poeskilltree.com/AAAABAMAABEtfIOFMo6-ksHfsOvu
decodeTreeLink(treeLink:gsub("/%?v=.+#","/"), validateTreeVersion(treeLink:match(oldStyleVersionLookup)))
decodeTreeLink(treeLink:gsub("/%?v=.+#","/"), validateTreeVersion(treeLink:match("tree/ruthless"), treeLink:match(oldStyleVersionLookup)))
else
-- EG: https://www.pathofexile.com/passive-skill-tree/3.15.0/AAAABgMADI6-HwKSwQQHLJwtH9-wTLNfKoP3ES3r5AAA
-- EG: https://www.pathofexile.com/fullscreen-passive-skill-tree/3.15.0/AAAABgMADAQHES0fAiycLR9Ms18qg_eOvpLB37Dr5AAA
decodeTreeLink(treeLink, validateTreeVersion(treeLink:match(versionLookup)))
-- EG: https://www.pathofexile.com/passive-skill-tree/ruthless/AAAABgAAAAAA (Ruthless doesn't have versions)
decodeTreeLink(treeLink, validateTreeVersion(treeLink:match("tree/ruthless"), treeLink:match(versionLookup)))
end
end)
controls.cancel = new("ButtonControl", nil, 45, 80, 80, 20, "Cancel", function()
Expand Down
7 changes: 6 additions & 1 deletion src/GameVersions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ liveTargetVersion = "3_0"
-- Skill tree versions
---Added for convenient indexing of skill tree versions.
---@type string[]
treeVersionList = { "2_6", "3_6", "3_7", "3_8", "3_9", "3_10", "3_11", "3_12", "3_13", "3_14", "3_15", "3_16", "3_17", "3_18", "3_19", "3_20", "3_21", "3_22", }
treeVersionList = { "2_6", "3_6", "3_7", "3_8", "3_9", "3_10", "3_11", "3_12", "3_13", "3_14", "3_15", "3_16", "3_17", "3_18", "3_19", "3_20", "3_21", "3_22_ruthless", "3_22", }
--- Always points to the latest skill tree version.
latestTreeVersion = treeVersionList[#treeVersionList]
---Tree version where multiple skill trees per build were introduced to PoBC.
Expand Down Expand Up @@ -100,6 +100,11 @@ treeVersions = {
num = 3.21,
url = "https://www.pathofexile.com/passive-skill-tree/3.21.0/",
},
["3_22_ruthless"] = {
display = "3.22 (ruthless)",
num = 3.22,
url = "https://www.pathofexile.com/passive-skill-tree/ruthless/",
},
["3_22"] = {
display = "3.22",
num = 3.22,
Expand Down
22 changes: 22 additions & 0 deletions src/Modules/ModParser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,9 @@ local specialModList = {
["totems explode on death, dealing (%d+)%% of their life as (.+) damage"] = function(amount, _, type) -- Crucible weapon mod
return explodeFunc(100, amount, type)
end,
["nearby corpses explode when you warcry, dealing (%d+)%% of their life as (.+) damage"] = function(amount, _, type) -- Ruthless Berserker node
return explodeFunc(100, amount, type)
end,
-- Keystones
["(%d+) rage regenerated for every (%d+) mana regeneration per second"] = function(num, _, div) return {
mod("RageRegen", "BASE", num, {type = "PerStat", stat = "ManaRegen", div = tonumber(div) }) ,
Expand Down Expand Up @@ -2160,6 +2163,7 @@ local specialModList = {
flag("Condition:CanGainRage", { type = "ActorCondition", actor = "enemy", var = "PinnacleBoss" }),
},
["inherent effects from having rage are tripled"] = { mod("Multiplier:RageEffect", "BASE", 2) },
["inherent effects from having rage are doubled"] = { mod("Multiplier:RageEffect", "BASE", 1) },
["cannot be stunned while you have at least (%d+) rage"] = function(num) return { flag("StunImmune", { type = "MultiplierThreshold", var = "Rage", threshold = num }) } end,
["lose ([%d%.]+)%% of life per second per rage while you are not losing rage"] = function(num) return { mod("LifeDegen", "BASE", 1, { type = "PercentStat", stat = "Life", percent = num }, { type = "Multiplier", var = "Rage" }) } end,
["if you've warcried recently, you and nearby allies have (%d+)%% increased attack speed"] = function(num) return { mod("ExtraAura", "LIST", { mod = mod("Speed", "INC", num, nil, ModFlag.Attack) }, { type = "Condition", var = "UsedWarcryRecently" }) } end,
Expand Down Expand Up @@ -2375,6 +2379,9 @@ local specialModList = {
["attack damage is lucky if you[' ]h?a?ve blocked in the past (%d+) seconds"] = {
flag("LuckyHits", nil, ModFlag.Attack, { type = "Condition", var = "BlockedRecently" })
},
["attack damage while dual wielding is lucky if you[' ]h?a?ve blocked in the past (%d+) seconds"] = {
flag("LuckyHits", nil, ModFlag.Attack, { type = "Condition", var = "BlockedRecently" }, { type = "Condition", var = "DualWielding" })
},
["hits ignore enemy monster physical damage reduction if you[' ]h?a?ve blocked in the past (%d+) seconds"] = {
flag("IgnoreEnemyPhysicalDamageReduction", { type = "Condition", var = "BlockedRecently" })
},
Expand All @@ -2384,6 +2391,7 @@ local specialModList = {
} end,
-- Guardian
["grants armour equal to (%d+)%% of your reserved life to you and nearby allies"] = function(num) return { mod("GrantReservedLifeAsAura", "LIST", { mod = mod("Armour", "BASE", num / 100) }) } end,
["grants armour equal to (%d+)%% of your reserved mana to you and nearby allies"] = function(num) return { mod("GrantReservedManaAsAura", "LIST", { mod = mod("Armour", "BASE", num / 100) }) } end,
["grants maximum energy shield equal to (%d+)%% of your reserved mana to you and nearby allies"] = function(num) return { mod("GrantReservedManaAsAura", "LIST", { mod = mod("EnergyShield", "BASE", num / 100) }) } end,
["grants armour equal to (%d+)%% of your reserved mana to you and nearby allies"] = function(num) return { mod("GrantReservedManaAsAura", "LIST", { mod = mod("Armour", "BASE", num / 100) }) } end,
["warcries cost no mana"] = { mod("ManaCost", "MORE", -100, nil, 0, KeywordFlag.Warcry) },
Expand Down Expand Up @@ -2477,6 +2485,11 @@ local specialModList = {
["regenerate (%d+)%% of mana over 2 seconds when you consume a corpse"] = function(num) return { mod("ManaRegen", "BASE", 1, { type = "PercentStat", stat = "Mana", percent = num / 2 }, { type = "Condition", var = "ConsumedCorpseInPast2Sec" }) } end,
["corpses you spawn have (%d+)%% increased maximum life"] = function(num) return { mod("CorpseLife", "INC", num) } end,
["corpses you spawn have (%d+)%% reduced maximum life"] = function(num) return { mod("CorpseLife", "INC", -num) } end,
["minions gain added physical damage equal to (%d+)%% of maximum energy shield on your equipped helmet"] = function(num) return {
mod("MinionModifier", "LIST", { mod = mod("PhysicalMin", "BASE", 1, { type = "PercentStat", stat = "EnergyShieldOnHelmet", actor = "parent", percent = num }) }),
mod("MinionModifier", "LIST", { mod = mod("PhysicalMax", "BASE", 1, { type = "PercentStat", stat = "EnergyShieldOnHelmet", actor = "parent", percent = num }) }),

} end,
-- Occultist
["when you kill an enemy, for each curse on that enemy, gain (%d+)%% of non%-chaos damage as extra chaos damage for 4 seconds"] = function(num) return {
mod("NonChaosDamageGainAsChaos", "BASE", num, { type = "Condition", var = "KilledRecently" }, { type = "Multiplier", var = "CurseOnEnemy" }),
Expand Down Expand Up @@ -2519,6 +2532,11 @@ local specialModList = {
mod("EnemyModifier", "LIST", { mod = mod("ColdExposure", "BASE", -num) }, { type = "Condition", var = "Phasing" }),
mod("EnemyModifier", "LIST", { mod = mod("LightningExposure", "BASE", -num) }, { type = "Condition", var = "Phasing" }),
} end,
["nearby enemies have fire, cold and lightning exposure while you have phasing"] = {
mod("EnemyModifier", "LIST", { mod = mod("FireExposure", "BASE", -10) }, { type = "Condition", var = "Phasing" }),
mod("EnemyModifier", "LIST", { mod = mod("ColdExposure", "BASE", -10) }, { type = "Condition", var = "Phasing" }),
mod("EnemyModifier", "LIST", { mod = mod("LightningExposure", "BASE", -10) }, { type = "Condition", var = "Phasing" }),
},
-- Saboteur
["hits have (%d+)%% chance to deal (%d+)%% more area damage"] = function (num, _, more) return {
mod("Damage", "MORE", (num*more/100), nil, bor(ModFlag.Area, ModFlag.Hit))
Expand All @@ -2540,6 +2558,7 @@ local specialModList = {
["gain (%d+)%% increased movement speed for 20 seconds when you kill an enemy"] = function(num) return { mod("MovementSpeed", "INC", num, { type = "Condition", var = "KilledRecently" }) } end,
["gain (%d+)%% increased attack speed for 20 seconds when you kill a rare or unique enemy"] = function(num) return { mod("Speed", "INC", num, nil, ModFlag.Attack, 0, { type = "Condition", var = "KilledUniqueEnemy" }) } end,
["kill enemies that have (%d+)%% or lower life when hit by your skills"] = function(num) return { mod("CullPercent", "MAX", num) } end,
["you are unaffected by bleeding while leeching"] = { mod("SelfBleedEffect", "MORE", -100, { type = "Condition", var = "Leeching" }) },
-- Trickster
["(%d+)%% chance to gain (%d+)%% of non%-chaos damage with hits as extra chaos damage"] = function(num, _, perc) return { mod("NonChaosDamageGainAsChaos", "BASE", num / 100 * tonumber(perc)) } end,
["movement skills cost no mana"] = { mod("ManaCost", "MORE", -100, nil, 0, KeywordFlag.Movement) },
Expand Down Expand Up @@ -3391,6 +3410,9 @@ local specialModList = {
mod("ExtraAura", "LIST", { mod = flag("Condition:ArcaneSurge")}, { type = "Condition", var = "UsedWarcryRecently" }),
mod("ArcaneSurgeEffect", "INC", num, { type = "PerStat", stat = "WarcryPower", div = tonumber(div), globalLimit = tonumber(limit), globalLimitKey = "Brinerot Flag"}, { type = "Condition", var = "UsedWarcryRecently" }),
} end,
["gain arcane surge after spending a total of (%d+) mana"] = function(num) return {
mod("ExtraAura", "LIST", { mod = flag("Condition:ArcaneSurge")}, { type = "MultiplierThreshold", var = "ManaSpentRecently", threshold = num }),
} end,
["gain onslaught for (%d+) seconds on hit while at maximum frenzy charges"] = { flag("Onslaught", { type = "StatThreshold", stat = "FrenzyCharges", thresholdStat = "FrenzyChargesMax" }, { type = "Condition", var = "HitRecently" }) },
["enemies in your chilling areas take (%d+)%% increased lightning damage"] = function(num) return { mod("EnemyModifier", "LIST", { mod = mod("LightningDamageTaken", "INC", num) }, { type = "ActorCondition", actor = "enemy", var = "InChillingArea" }) } end,
["warcries count as having (%d+) additional nearby enemies"] = function(num) return {
Expand Down
Binary file added src/TreeData/3_22_ruthless/ascendancy-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/background-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/frame-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/jewel-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/jewel-radius.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/line-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/mastery-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/skills-3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/TreeData/3_22_ruthless/skills-disabled-3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ddec29a

Please sign in to comment.