Skip to content

Commit 6409dd3

Browse files
author
LocalIdentity
committed
Merge branch 'dev' into item-origin
2 parents d828e65 + eabe806 commit 6409dd3

81 files changed

Lines changed: 1889 additions & 465 deletions

Some content is hidden

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

spec/System/TestAttacks_spec.lua

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,113 @@ describe("TestAttacks", function()
167167
assert.are.equals(1.1, build.calcsTab.mainOutput.MainHand.AverageHit)
168168
end)
169169

170+
it("matches in-game tooltip DPS for low-level spear skills", function()
171+
build.spec:SelectClass(build.spec.tree.classNameMap.Huntress)
172+
build.characterLevel = 11
173+
build.characterLevelAutoMode = false
174+
build.controls.characterLevel:SetText(11)
175+
build.configTab.input.customMods = [[
176+
10% increased Attack Damage
177+
+10000 to Accuracy Rating
178+
nearby enemies have 100% less armour
179+
nearby enemies have 100% less evasion
180+
]]
181+
build.configTab:BuildModList()
182+
build.itemsTab:CreateDisplayItemFromRaw([[
183+
Apocalypse Edge
184+
Ironhead Spear
185+
Item Level: 7
186+
Quality: 0
187+
LevelReq: 5
188+
Implicits: 1
189+
Grants Skill: Spear Throw
190+
Adds 2 to 4 Physical Damage
191+
]])
192+
build.itemsTab:AddDisplayItem()
193+
194+
local skills = {
195+
{ gemId = "Metadata/Items/Gems/SkillGemPlayerDefaultSpear", level = 4, dps = 32.8 },
196+
{ gemId = "Metadata/Items/Gems/SkillGemWhirlingSlash", level = 1, dps = 11.8 },
197+
{ gemId = "Metadata/Items/Gems/SkillGemPlayerDefaultSpearThrow", level = 4, dps = 28.8 },
198+
{ gemId = "Metadata/Items/Gems/SkillGemTwister", level = 2, dps = 17.5 },
199+
}
200+
for _, skill in ipairs(skills) do
201+
local group = {
202+
enabled = true,
203+
gemList = { {
204+
gemId = skill.gemId,
205+
level = skill.level,
206+
quality = 0,
207+
enabled = true,
208+
count = 1,
209+
enableGlobal1 = true,
210+
enableGlobal2 = true,
211+
} },
212+
}
213+
table.insert(build.skillsTab.socketGroupList, group)
214+
build.skillsTab:ProcessSocketGroup(group)
215+
skill.groupIndex = #build.skillsTab.socketGroupList
216+
end
217+
218+
for _, skill in ipairs(skills) do
219+
local group = build.skillsTab.socketGroupList[skill.groupIndex]
220+
build.mainSocketGroup = skill.groupIndex
221+
build.calcsTab.input.skill_number = skill.groupIndex
222+
group.mainActiveSkill = 1
223+
group.mainActiveSkillCalcs = 1
224+
build.buildFlag = true
225+
build.modFlag = true
226+
runCallback("OnFrame")
227+
build.calcsTab:BuildOutput()
228+
runCallback("OnFrame")
229+
230+
assert.are.equals(skill.dps, round(build.calcsTab.mainOutput.TotalDPS, 1))
231+
end
232+
end)
233+
234+
it("correctly calculates Garukhan's Resolve bifurcated critical hit damage", function()
235+
local function setup(socketGroup)
236+
newBuild()
237+
build.itemsTab:CreateDisplayItemFromRaw([[
238+
New Item
239+
Razor Quarterstaff
240+
Quality: 0
241+
This Weapon's Critical Hit Chance is 0%
242+
-100% increased physical damage
243+
adds 1 to 1 physical damage to attacks
244+
nearby enemies have 100% less armour
245+
nearby enemies have 100% less evasion
246+
]])
247+
build.itemsTab:AddDisplayItem()
248+
runCallback("OnFrame")
249+
build.skillsTab:PasteSocketGroup(socketGroup)
250+
runCallback("OnFrame")
251+
252+
build.configTab.input.customMods = [[
253+
+50% to critical hit chance
254+
your critical damage bonus is 1000000%
255+
+4000 to accuracy
256+
]]
257+
build.configTab:BuildModList()
258+
runCallback("OnFrame")
259+
build.calcsTab:BuildOutput()
260+
runCallback("OnFrame")
261+
262+
return build.calcsTab.mainOutput.MainHand
263+
end
264+
265+
local normalOutput = setup("Quarterstaff Strike 1/0 1")
266+
assert.are.equals(50, normalOutput.CritChance)
267+
assert.are.equals(10001, normalOutput.CritMultiplier)
268+
assert.are.equals(5001, normalOutput.AverageHit)
269+
270+
local garukhanOutput = setup("Quarterstaff Strike 1/0 1\nGarukhan's Resolve 1/0 1")
271+
assert.are.equals(50, garukhanOutput.PreBifurcateCritChance)
272+
assert.are.equals(75, garukhanOutput.CritChance)
273+
assert.is_true(math.abs(1 / 3 - (garukhanOutput.CritBifurcates - 1)) < 0.000001)
274+
assert.is_true(math.abs(10001 - garukhanOutput.AverageHit) < 0.01)
275+
end)
276+
170277
it("correctly adds damage with oracle forced outcome", function()
171278
-- Setup: Add weapon with no crit chance, and strip enemy defenses
172279
build.itemsTab:CreateDisplayItemFromRaw([[

spec/System/TestDebuffs_spec.lua

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
describe("TestAilments", function()
2+
before_each(function()
3+
newBuild()
4+
end)
5+
6+
teardown(function()
7+
-- newBuild() takes care of resetting everything in setup()
8+
end)
9+
10+
it("correctly applies effects dependent on 'Condition:Slowed'", function()
11+
build.skillsTab:PasteSocketGroup("Chaos Bolt 1/0 1\n")
12+
runCallback("OnFrame")
13+
14+
local defaultDmg = build.calcsTab.mainOutput.TotalDPS
15+
assert.True(defaultDmg > 0, "build should deal damage")
16+
17+
build.configTab.input.customMods = "100% increased damage against slowed enemies"
18+
build.configTab:BuildModList()
19+
runCallback("OnFrame")
20+
21+
-- no effect yet
22+
local nonSlowedDmg = build.calcsTab.mainOutput.TotalDPS
23+
assert.are.equals(nonSlowedDmg, defaultDmg, "damage should be unchanged until enemy is slowed")
24+
25+
-- action speed
26+
build.configTab.input.customMods = [[
27+
100% increased damage against slowed enemies
28+
Nearby enemies have 10% reduced action speed
29+
]]
30+
31+
build.configTab:BuildModList()
32+
runCallback("OnFrame")
33+
local actionSlowedDmg = build.calcsTab.mainOutput.TotalDPS
34+
assert.True(actionSlowedDmg > nonSlowedDmg, "damage should be higher vs. reduced action speed")
35+
36+
-- movement speed
37+
build.configTab.input.customMods = [[
38+
100% increased damage against slowed enemies
39+
Nearby enemies have 10% reduced movement speed
40+
]]
41+
42+
build.configTab:BuildModList()
43+
runCallback("OnFrame")
44+
local movementSlowedDmg = build.calcsTab.mainOutput.TotalDPS
45+
assert.True(movementSlowedDmg > nonSlowedDmg, "damage should be higher vs. reduced movement speed")
46+
47+
-- specific slowing debuffs checks
48+
-- NOTE: there might be more conditions that should be checked here, feel free to add more
49+
for _, debuff in ipairs({"chilled", "maimed", "hindered"}) do
50+
build.configTab.input.customMods = [[
51+
100% increased damage against slowed enemies
52+
nearby enemies are ]] .. debuff .. [[
53+
]]
54+
55+
build.configTab:BuildModList()
56+
runCallback("OnFrame")
57+
local debuffSlowedDmg = build.calcsTab.mainOutput.TotalDPS
58+
assert.True(debuffSlowedDmg > nonSlowedDmg, "damage should be higher vs. " .. debuff .. " enemies")
59+
end
60+
61+
-- temporal chains curse
62+
build.configTab.input.customMods = [[
63+
100% increased damage against slowed enemies
64+
]]
65+
build.skillsTab:PasteSocketGroup("Temporal Chains 20/0 1\n")
66+
build.configTab:BuildModList()
67+
runCallback("OnFrame")
68+
local temporalChainsSlowedDmg = build.calcsTab.mainOutput.TotalDPS
69+
assert.True(temporalChainsSlowedDmg > nonSlowedDmg, "damage should be higher with Temporal Chains curse")
70+
end)
71+
end)

spec/System/TestImportTab_spec.lua

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,59 @@ describe("ImportTab", function()
4343
assert.are.equals(1, #importTab.controls.charSelect.list)
4444
assert.True(importTab.controls.charSelect.list[1].detail:match("Future Ascendancy") ~= nil)
4545
end)
46+
47+
it("imports Split Personality alternate class start from character JSON", function()
48+
local spec = build.spec
49+
local socketNode = spec.nodes[60735]
50+
local rangerStartPassive = spec.nodes[56651]
51+
assert.is_not_nil(socketNode)
52+
assert.is_not_nil(rangerStartPassive)
53+
54+
local hashes = { socketNode.id, rangerStartPassive.id }
55+
for _, pathNode in ipairs(socketNode.path or { }) do
56+
table.insert(hashes, pathNode.id)
57+
end
58+
59+
local rangerStart = spec.nodes[spec.tree.classes[spec.tree.classNameMap.Ranger].startNodeId]
60+
local importPayload = {
61+
name = "Split Import Test",
62+
class = "Witch2",
63+
league = "Test",
64+
level = 90,
65+
jewels = {
66+
{
67+
id = "split-personality-test",
68+
frameType = 3,
69+
name = "Split Personality",
70+
typeLine = "Ruby",
71+
inventoryId = "PassiveJewels",
72+
x = 4,
73+
ilvl = 84,
74+
properties = { },
75+
explicitMods = {
76+
"Can Allocate Passive Skills from the Ranger's starting point",
77+
},
78+
},
79+
},
80+
passives = {
81+
hashes = hashes,
82+
specialisations = { },
83+
skill_overrides = { },
84+
jewel_data = { },
85+
quest_stats = { },
86+
},
87+
}
88+
89+
build.importTab:ImportPassiveTreeAndJewels(importPayload)
90+
91+
local importedSpec = build.spec
92+
local importedJewel = build.itemsTab.items[importedSpec.jewels[socketNode.id]]
93+
assert.are.equals("Ranger", importedJewel.jewelData.alternateClassStart)
94+
95+
assert.are.equals(0, importedSpec.nodes[rangerStart.id].pathDist)
96+
assert.True(importedSpec.nodes[rangerStartPassive.id].alloc)
97+
assert.True(importedSpec.nodes[rangerStartPassive.id].connectedToStart)
98+
end)
4699
end)
47100

48101
describe("ImportTab quest reward import", function()

spec/System/TestSkills_spec.lua

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ describe("TestSkills", function()
118118

119119
local normalArmour = env.player.output.Armour
120120
local normalDPS = env.player.output.TotalDPS
121-
assert.are.equals(1050, normalArmour)
121+
assert.are.equals(1200, normalArmour)
122122
assert.is_true(normalDPS > 0)
123123

124124
env = calcs.initEnv(build, "CALCULATOR", {}, {
@@ -354,15 +354,27 @@ describe("TestSkills", function()
354354
assert.True(baseLeapSlamHit < build.calcsTab.mainOutput.AverageDamage)
355355
end)
356356

357-
it("applies minion offensive multiplier to all attack damage", function()
357+
it("applies generated minion offensive multiplier to attack damage", function()
358358
build.skillsTab:PasteSocketGroup("Wolf Pack 20/0 1")
359359
runCallback("OnFrame")
360360

361361
local minion = build.calcsTab.mainEnv.minion
362-
local expectedPhysicalMax = round(build.calcsTab.mainEnv.data.monsterAllyDamageTable[minion.level] * (1 + minion.minionData.damageSpread))
362+
local expectedPhysicalMax = floor(floor(build.calcsTab.mainEnv.data.monsterAllyDamageTable[minion.level]) * minion.minionData.damage * (1 + minion.minionData.damageSpread))
363363

364364
assert.are.equals(expectedPhysicalMax, minion.weaponData1.PhysicalMax)
365-
assert.are.near(-30, minion.mainSkill.skillModList:Sum("MORE", minion.mainSkill.skillCfg, "Damage"), 0.0001)
365+
assert.are.near(-30, minion.mainSkill.skillModList:Sum("MORE", minion.mainSkill.skillCfg, "AddedDamage"), 0.0001)
366+
assert.are.equals(0, minion.mainSkill.skillModList:Sum("MORE", minion.mainSkill.skillCfg, "Damage"))
367+
end)
368+
369+
it("does not apply minion offensive multiplier to spectre or companion added damage", function()
370+
for _, skill in ipairs({ "Spectre: Lightless Abomination 20/0 1", "Companion: Lightless Abomination 20/0 1" }) do
371+
newBuild()
372+
build.skillsTab:PasteSocketGroup(skill)
373+
runCallback("OnFrame")
374+
375+
local minion = build.calcsTab.mainEnv.minion
376+
assert.are.equals(0, minion.mainSkill.skillModList:Sum("MORE", minion.mainSkill.skillCfg, "AddedDamage"))
377+
end
366378
end)
367379

368380
it("Inspiring Ally only mirrors companion damage, not generic minion damage", function()
@@ -1106,4 +1118,5 @@ describe("TestSkills", function()
11061118
local noParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
11071119
assert.equals(withParrySpellDmg, noParrySpellDmg, "Parry should not affect spell damage")
11081120
end)
1121+
11091122
end)

src/Classes/CalcBreakdownControl.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ function CalcBreakdownClass:AddModSection(sectionData, modList)
275275
-- Build list of modifiers to display
276276
local cfg = (sectionData.cfg and actor.mainSkill[sectionData.cfg.."Cfg"] and copyTable(actor.mainSkill[sectionData.cfg.."Cfg"], true)) or { }
277277
cfg.source = sectionData.modSource
278-
cfg.ignoreSourceinCheckConditions = true
278+
cfg.ignoreSourceInCheckConditions = true
279279
cfg.actor = sectionData.actor
280280
local rowList
281281
local modStore = (sectionData.enemy and actor.enemy.modDB) or (sectionData.cfg and actor.mainSkill.skillModList) or actor.modDB

src/Classes/CalcSectionControl.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ function CalcSectionClass:FormatStr(str, actor, colData)
190190
local modCfg = (sectionData.cfg and actor.mainSkill[sectionData.cfg.."Cfg"]) or { }
191191
if sectionData.modSource then
192192
modCfg.source = sectionData.modSource
193-
modCfg.ignoreSourceinCheckConditions = true
193+
modCfg.ignoreSourceInCheckConditions = true
194194
end
195195
if sectionData.actor then
196196
modCfg.actor = sectionData.actor

src/Classes/Item.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,9 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
902902
-- Replace non-number ranges as unsupported
903903
line = line:gsub("(%a+)%([%a%s]+%-[%a%s]+%)", "%1")
904904

905+
-- Strip single values like 25(50) -> 25
906+
line = line:gsub("(%d+)%((%d+)%)", "%1")
907+
905908
for value, range in line:gmatch("(%-?%d+%.?%d*)%((%-?%d+%.?%d*%-%-?%d+%.?%d*)%)") do
906909
local min, max = range:match("(%-?%d+%.?%d*)%-(%-?%d+%.?%d*)")
907910
local delta = tonumber(max) - min
@@ -1895,6 +1898,9 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum)
18951898
for _, value in ipairs(modList:List(nil, "JewelData")) do
18961899
jewelData[value.key] = value.value
18971900
end
1901+
for _, className in ipairs(modList:List(nil, "AlternateClassStart")) do
1902+
jewelData.alternateClassStart = className
1903+
end
18981904
if modList:List(nil, "FromNothingKeystones") then
18991905
jewelData.fromNothingKeystones = { }
19001906
for _, value in ipairs(modList:List(nil, "FromNothingKeystones")) do

src/Classes/ModDB.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ function ModDBClass:FlagInternal(context, cfg, flags, keywordFlags, source, ...)
198198
if modList then
199199
for i = 1, #modList do
200200
local mod = modList[i]
201-
local checkSource = not cfg or not cfg.ignoreSourceinCheckConditions
201+
local checkSource = not cfg or not cfg.ignoreSourceInCheckConditions
202202
if mod.type == "FLAG" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not checkSource or not source or mod.source:match("[^:]+") == source) then
203203
if mod[1] then
204204
if context:EvalMod(mod, cfg) then

src/Classes/ModStore.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,23 @@ function ModStoreClass:SumPositiveValues(modType, cfg, modName, ...)
176176
return total
177177
end
178178

179+
--- Returns the value of all negative modifiers to a mod added together, ignoring any negative modifiers.
180+
--- Works by creating a table using Tabulate and then filtering for negative values.
181+
---
182+
--- @param modType string # the mod type for which we want to create the table, e.g. "INC" or "MORE"
183+
--- @param cfg table | nil # passed configuration, may be nil
184+
--- @param modName string # the name of the mod for which we want to create the table, e.g. "FlaskRecoveryRate", "ActionSpeed", ...
185+
function ModStoreClass:SumNegativeValues(modType, cfg, modName, ...)
186+
local total = 0
187+
local modTable = self:Tabulate(modType, cfg, modName)
188+
for i = 1, #modTable do
189+
if modTable[i].value < 0 then
190+
total = total + modTable[i].value
191+
end
192+
end
193+
return total
194+
end
195+
179196
function ModStoreClass:More(cfg, ...)
180197
local flags, keywordFlags = 0, 0
181198
local source

src/Classes/PartyTab.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ local PartyTabClass = newClass("PartyTab", "ControlHost", "Control", function(se
371371
self:ParseBuffs(self.enemyModList, self.controls.enemyMods.buf, "EnemyMods", self.controls.simpleEnemyMods)
372372
self.build.buildFlag = true
373373
end)
374-
self.controls.rebuild.tooltipText = "^7Reparse all the inputs incase they have been disabled or they have changed since loading the build or importing"
374+
self.controls.rebuild.tooltipText = "^7Reparse all the inputs in case they have been disabled or they have changed since loading the build or importing"
375375
self.controls.rebuild.x = function()
376376
return (self.width > theme.widthThreshold1) and 8 or (-328)
377377
end

0 commit comments

Comments
 (0)