Skip to content

Commit b5ff326

Browse files
Blitz54LocalIdentity
andauthored
Support Spectres and Companions, and overhaul Spectre Library (#936)
* initial spectres * library shows up, and some spectres+skills added * Tarnished Scarab * Two more spectres and some skill changes * Blood Priest and Priestess * Drudge Osseodon * Diretusk Boar * Terracotta Soldier * Fettered Monstrosity * Filthy First-born * experienceMultiplier for Spectre Reservation * Faridun Spearwoman * Better Reservation calcs for spectre * Crabs * Export Monster Type * Show Spirit Cost in the spectre library window * Add monster type to library * Serpent Shaman and enable spectre curses * Initial support for Companion and small calc change for reservedFlat * Add separate beast library * tooltip shows companion reservation now * Initial import of Companion/Spectre gems * Put reservation calcs into exporter * Imports spectres to spectre list * import changes and sets current minion to imported one * Update Spectre/Companion tooltip reservations, and dynamically update gem names * Update gem names according to selected spectre - mostly * Moved spectre skills to Spectre.lua * Sync minion dropdown between calc tab and left bar * Gem name updates, and dropdowns for minion are in sync * Companion uses beast list now * Prowling Shade and potential crash fix * Replace sub prefix check with match * Clean up gem tooltip code + fix display issue Cleans up the code for displaying the reservation Fixes an issue where the gem name didn't update on opening a saved build * Tooltip fix for companion * Cultist Archer, Witch, Daggerdancer * Spectre life fix and "Show minion stats" refixed * Fix minion ES and life calcs * Add Tons of Spectres for import, no skills yet * Export list of spectres with unique names * Remove a few spectres and update exporter * Initial Spectre Library UI Update * Export monster category images * Show Monster Images on checkboxes in Spectre Library * removed test mob * Spec stuff for Spawn Locations * Most Spectre Spawn Locations shown in tooltip * Prettied up library and actually committed tooltip this time * Preparation for popular spectre list, and some small fixes * Fix bug on importing build with broken greyed gem * Recommended Spectres/Beasts button, and removed some spectres * fix UI more * sorting name change, forgot * Spectre build list also shows minion stats if selected * Fix space indentation * Some skills and fixes/revert * Add skillName partialMatch to fix config for spectre curses * More skills, basic imports as spectre skills are BUUUSTED on db * More skills, and export minion movement speed for library * Many skills, spectreList changes, code cleanup, and spectre removals * Every monster has at least 1 skill now, lots of broken skills to still import though * More Spec stuff, which lets us grab which Maps some monsters spawn in * Spawn Locations shown in library, and world Areas imported * Spec stuff and fixes/cleanup * Tooltip stuff and sort spawn locations * Spawn location changes and small fixes * more worldarea stuff, and random spec.lua * Tiny fix and spectre change from 0.2.1 * fix comment on top of exporter file * Fix rounding error for a few monsters * Update Spec * Fix act location when act is > 5 With there only being 3 unique acts that are duplicated atm, GGG is using different values internally for the second set of duplicated acts which messes with the tooltips for the spawn location in PoB * Add comment and remove line --------- Co-authored-by: LocalIdentity <[email protected]>
1 parent a207f10 commit b5ff326

37 files changed

+35231
-16494
lines changed

src/Classes/CalcsTab.lua

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,36 @@ local CalcsTabClass = newClass("CalcsTab", "UndoHandler", "ControlHost", "Contro
104104
control = new("DropDownControl", nil, {0, 0, 160, 16}, nil, function(index, value)
105105
local mainSocketGroup = self.build.skillsTab.socketGroupList[self.input.skill_number]
106106
local srcInstance = mainSocketGroup.displaySkillListCalcs[mainSocketGroup.mainActiveSkillCalcs].activeEffect.srcInstance
107+
-- Synchronize DropDownControl between CalcActiveSkill and skillMinionCalcs
107108
if value.itemSetId then
108109
srcInstance.skillMinionItemSetCalcs = value.itemSetId
110+
srcInstance.skillMinionItemSet = value.itemSetId
111+
if srcInstance.nameSpec:match("^Spectre:") then
112+
srcInstance.nameSpec = "Spectre: ".. value.label
113+
elseif srcInstance.nameSpec:match("^Companion:") then
114+
srcInstance.nameSpec = "Companion: ".. value.label
115+
end
109116
else
110117
srcInstance.skillMinionCalcs = value.minionId
118+
srcInstance.skillMinion = value.minionId
119+
if srcInstance.nameSpec:match("^Spectre:") then
120+
srcInstance.nameSpec = "Spectre: ".. value.label
121+
elseif srcInstance.nameSpec:match("^Companion:") then
122+
srcInstance.nameSpec = "Companion: ".. value.label
123+
end
111124
end
112125
self:AddUndoState()
113126
self.build.buildFlag = true
114127
end)
115128
} },
116129
{ label = "Spectre Library", flag = "spectre", { controlName = "mainSkillMinionLibrary",
117130
control = new("ButtonControl", nil, {0, 0, 100, 16}, "Manage Spectres...", function()
118-
self.build:OpenSpectreLibrary()
131+
self.build:OpenSpectreLibrary("spectre")
132+
end)
133+
} },
134+
{ label = "Beast Library", flag = "summonBeast", { controlName = "mainSkillBeastLibrary",
135+
control = new("ButtonControl", nil, {0, 0, 100, 16}, "Manage Beasts...", function()
136+
self.build:OpenSpectreLibrary("beast")
119137
end)
120138
} },
121139
{ label = "Minion Skill", flag = "haveMinion", { controlName = "mainSkillMinionSkill",

src/Classes/GemSelectControl.lua

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,11 @@ function GemSelectClass:AddGemTooltip(gemInstance)
529529
local grantedEffect = gemInstance.gemData.grantedEffect
530530
local additionalEffects = gemInstance.gemData.additionalGrantedEffects
531531

532-
self.tooltip:AddLine(20, colorCodes.GEM .. grantedEffect.name)
532+
if grantedEffect.name:match("^Spectre:") or grantedEffect.name:match("^Companion:") then
533+
self.tooltip:AddLine(20, colorCodes.GEM .. (gemInstance.displayEffect and gemInstance.displayEffect.nameSpec or gemInstance.gemData.name))
534+
else
535+
self.tooltip:AddLine(20, colorCodes.GEM .. grantedEffect.name)
536+
end
533537
self.tooltip:AddSeparator(10)
534538
self.tooltip:AddLine(18, colorCodes.NORMAL .. gemInstance.gemData.gemType)
535539
if gemInstance.gemData.tagString ~= "" then
@@ -592,9 +596,19 @@ function GemSelectClass:AddGrantedEffectInfo(gemInstance, grantedEffect, addReq)
592596
self.tooltip:AddLine(16, string)
593597
end
594598
else
599+
if gemInstance.skillMinion then
600+
if gemInstance.nameSpec:match("^Spectre:") then
601+
grantedEffectLevel.spiritReservationFlat = data.spectres[gemInstance.skillMinion].spectreReservation
602+
elseif gemInstance.nameSpec:match("^Companion:") then
603+
grantedEffectLevel.spiritReservationPercent = data.spectres[gemInstance.skillMinion].companionReservation
604+
end
605+
end
595606
if grantedEffectLevel.spiritReservationFlat then
596607
self.tooltip:AddLine(16, string.format("^x7F7F7FReservation: ^7%d Spirit", grantedEffectLevel.spiritReservationFlat))
597608
end
609+
if grantedEffectLevel.spiritReservationPercent then
610+
self.tooltip:AddLine(16, string.format("^x7F7F7FReservation: ^7%.1f%% Spirit", grantedEffectLevel.spiritReservationPercent))
611+
end
598612
local cost
599613
for _, res in ipairs(self.costs) do
600614
if grantedEffectLevel.cost and grantedEffectLevel.cost[res.Resource] then

src/Classes/ImportTab.lua

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,12 +691,47 @@ function ImportTabClass:ImportItemsAndSkills(charData)
691691
local funcGetGemInstance = function(skillData)
692692
local typeLine = sanitiseText(skillData.typeLine) .. (skillData.support and " Support" or "")
693693
local gemId = self.build.data.gemForBaseName[typeLine:lower()]
694+
695+
if typeLine:match("^Spectre:") then
696+
gemId = "Metadata/Items/Gems/SkillGemSummonSpectre"
697+
end
698+
if typeLine:match("^Companion:") then
699+
gemId = "Metadata/Items/Gems/SkillGemSummonBeast"
700+
end
694701

695702
if gemId then
696703
local gemInstance = { level = 20, quality = 0, enabled = true, enableGlobal1 = true, enableGlobal2 = true, count = 1, gemId = gemId }
697-
gemInstance.nameSpec = self.build.data.gems[gemId].name
698704
gemInstance.support = skillData.support
699705

706+
local spectreList = data.spectres
707+
if typeLine:sub(1, 8) == "Spectre:" then
708+
local spectreName = typeLine:sub(10) -- gets monster name after "Spectre: "
709+
for id, spectre in pairs(spectreList) do
710+
if spectre.name == spectreName then
711+
if not isValueInArray(self.build.spectreList, id) then
712+
t_insert(self.build.spectreList, id)
713+
end
714+
gemInstance.skillMinion = id -- Sets imported minion in dropdown on left
715+
gemInstance.skillMinionCalcs = id-- Sets imported minion in dropdown in calcs tab
716+
break
717+
end
718+
end
719+
end
720+
if typeLine:sub(1, 10) == "Companion:" then
721+
local companionName = typeLine:sub(12)
722+
for id, spectre in pairs(spectreList) do
723+
if spectre.name == companionName then
724+
if not isValueInArray(self.build.beastList, id) then
725+
t_insert(self.build.beastList, id)
726+
end
727+
gemInstance.skillMinion = id
728+
gemInstance.skillMinionCalcs = id
729+
break
730+
end
731+
end
732+
end
733+
734+
gemInstance.nameSpec = self.build.data.gems[gemId].name
700735
for _, property in pairs(skillData.properties) do
701736
if property.name == "Level" then
702737
gemInstance.level = tonumber(property.values[1][1]:match("%d+"))

src/Classes/MinionListControl.lua

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ local ipairs = ipairs
77
local t_insert = table.insert
88
local t_remove = table.remove
99
local s_format = string.format
10+
local m_max = math.max
1011

11-
local MinionListClass = newClass("MinionListControl", "ListControl", function(self, anchor, rect, data, list, dest)
12+
local MinionListClass = newClass("MinionListControl", "ListControl", function(self, anchor, rect, data, list, dest, label)
1213
self.ListControl(anchor, rect, 16, "VERTICAL", not dest, list)
1314
self.data = data
1415
self.dest = dest
1516
if dest then
1617
self.dragTargetList = { dest }
17-
self.label = "^7Available Spectres:"
18+
self.label = label or "^7Available Spectres:"
1819
self.controls.add = new("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, {0, -2, 60, 18}, "Add", function()
1920
self:AddSel()
2021
end)
2122
self.controls.add.enabled = function()
2223
return self.selValue ~= nil and not isValueInArray(dest.list, self.selValue)
2324
end
2425
else
25-
self.label = "^7Spectres in Build:"
26+
self.label = label or "^7Spectres in Build:"
2627
self.controls.delete = new("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, {0, -2, 60, 18}, "Remove", function()
2728
self:OnSelDelete(self.selIndex, self.selValue)
2829
end)
@@ -49,6 +50,10 @@ function MinionListClass:AddValueTooltip(tooltip, index, minionId)
4950
if tooltip:CheckForUpdate(minionId) then
5051
local minion = self.data.minions[minionId]
5152
tooltip:AddLine(18, "^7"..minion.name)
53+
tooltip:AddSeparator(10)
54+
tooltip:AddLine(14, s_format("^7Spectre Reservation: %s%d", colorCodes.SPIRIT, tostring(minion.spectreReservation)))
55+
tooltip:AddLine(14, s_format("^7Companion Reservation: %s%s%%", colorCodes.SPIRIT, tostring(minion.companionReservation)))
56+
tooltip:AddLine(14, "^7Category: "..minion.monsterCategory)
5257
tooltip:AddLine(14, s_format("^7Life Multiplier: x%.2f", minion.life))
5358
if minion.energyShield then
5459
tooltip:AddLine(14, s_format("^7Energy Shield: %d%% of base Life", minion.energyShield * 100))
@@ -59,20 +64,35 @@ function MinionListClass:AddValueTooltip(tooltip, index, minionId)
5964
if minion.evasion then
6065
tooltip:AddLine(14, s_format("^7Evasion Multiplier: x%.2f", 1 + minion.evasion))
6166
end
62-
tooltip:AddLine(14, s_format("^7Resistances: %s%d^7/%s%d^7/%s%d^7/%s%d",
67+
tooltip:AddLine(14, s_format("^7Resistances: %s%d ^7/ %s%d ^7/ %s%d ^7/ %s%d",
6368
colorCodes.FIRE, minion.fireResist,
6469
colorCodes.COLD, minion.coldResist,
6570
colorCodes.LIGHTNING, minion.lightningResist,
6671
colorCodes.CHAOS, minion.chaosResist
6772
))
6873
tooltip:AddLine(14, s_format("^7Base Damage: x%.2f", minion.damage))
6974
tooltip:AddLine(14, s_format("^7Base Attack Speed: %.2f", 1 / minion.attackTime))
70-
75+
tooltip:AddLine(14, s_format("^7Base Movement Speed: %.2f", minion.baseMovementSpeed / 10))
7176
for _, skillId in ipairs(minion.skillList) do
7277
if self.data.skills[skillId] then
7378
tooltip:AddLine(14, "^7Skill: "..self.data.skills[skillId].name)
7479
end
7580
end
81+
tooltip:AddSeparator(10)
82+
if #minion.spawnLocation > 0 then
83+
local coloredLocations = {}
84+
for _, location in ipairs(minion.spawnLocation) do -- Print (Map) or (Act 7) in white, and map name in green.
85+
local mainText, bracket = location:match("^(.-)%s*(%b())%s*$")
86+
table.insert(coloredLocations, bracket and (colorCodes.RELIC .. mainText .. " " .. "^7" .. bracket) or (colorCodes.RELIC .. location))
87+
end
88+
for i, spawn in ipairs(coloredLocations) do
89+
if i == 1 then
90+
tooltip:AddLine(14, s_format("^7Spawn: %s", spawn))
91+
else
92+
tooltip:AddLine(14, s_format("^7%s%s", " ", spawn)) -- Indented so all locations line up vertically in tooltip
93+
end
94+
end
95+
end
7696
end
7797
end
7898

@@ -101,3 +121,66 @@ function MinionListClass:OnSelDelete(index, minionId)
101121
self.selValue = nil
102122
end
103123
end
124+
125+
local SpawnListClass = newClass("SpawnListControl", "ListControl", function(self, anchor, rect, data, list, label)
126+
self.ListControl(anchor, rect, 16, "VERTICAL", false)
127+
self.data = data
128+
self.label = label or "^7Available Items:"
129+
end)
130+
131+
function SpawnListClass:GetRowValue(column, index, spawnLocation)
132+
return spawnLocation
133+
end
134+
function SpawnListClass:AddValueTooltip(tooltip, index, value)
135+
if tooltip:CheckForUpdate(value) then
136+
local foundArea = nil
137+
for _, area in pairs(data.worldAreas) do
138+
if area.name == value and #area .monsterVarieties > 0 then
139+
foundArea = area
140+
break
141+
end
142+
end
143+
if foundArea then
144+
tooltip:AddLine(18, foundArea.name)
145+
if foundArea.description and foundArea.description ~= "" then
146+
tooltip:AddLine(14, colorCodes.CURRENCY .. '"' .. foundArea.description .. '"')
147+
end
148+
if foundArea.bossVarieties and #foundArea.bossVarieties > 0 then
149+
tooltip:AddLine(14, colorCodes.UNIQUE.. "Bosses: ^7" .. table.concat(foundArea.bossVarieties, ", "))
150+
end
151+
tooltip:AddLine(14, "^7Area Level: "..foundArea.level)
152+
local biomeNameMap = {
153+
water_biome = "Water",
154+
mountain_biome = "Mountain",
155+
grass_biome = "Grass",
156+
forest_biome = "Forest",
157+
swamp_biome = "Swamp",
158+
desert_biome = "Desert",
159+
faridun_city = "Faridun City",
160+
ezomyte_city = "Ezomyte City",
161+
vaal_city = "Vaal City",
162+
}
163+
if #foundArea.tags > 0 then
164+
local biomeNameList = {}
165+
for _, tag in ipairs(foundArea.tags) do
166+
local biomeName = biomeNameMap[tag]
167+
if biomeName then
168+
table.insert(biomeNameList, biomeName)
169+
end
170+
end
171+
if #biomeNameList > 0 then
172+
tooltip:AddLine(14, "^7Biome: " .. table.concat(biomeNameList, ", "))
173+
end
174+
end
175+
tooltip:AddSeparator(10)
176+
tooltip:AddLine(14, "^7Spectres:")
177+
for _, monsterName in ipairs(foundArea.monsterVarieties) do
178+
tooltip:AddLine(14, " - " .. monsterName)
179+
end
180+
elseif value == "Found in Maps" then
181+
-- no tooltip
182+
else
183+
tooltip:AddLine(18, "^7World area not found: " .. tostring(value))
184+
end
185+
end
186+
end

src/Classes/MinionSearchListControl.lua

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,44 @@ local t_insert = table.insert
88
local t_remove = table.remove
99
local s_format = string.format
1010

11-
local MinionSearchListClass = newClass("MinionSearchListControl", "MinionListControl", function(self, anchor, rect, data, list, dest)
12-
self.MinionListControl(anchor, rect, data, list, dest)
11+
local MinionSearchListClass = newClass("MinionSearchListControl", "MinionListControl", function(self, anchor, rect, data, list, dest, label)
12+
self.MinionListControl(anchor, rect, data, list, dest, label)
13+
self:sortSourceList()
1314
self.unfilteredList = copyTable(list)
1415
self.isMutable = false
1516

16-
self.controls.searchText = new("EditControl", {"BOTTOMLEFT",self,"TOPLEFT"}, {0, -2, 128, 18}, "", "Search", "%c", 100, function(buf)
17+
self.controls.searchText = new("EditControl", {"BOTTOMLEFT",self,"TOPLEFT"}, {0, -2, 148, 18}, "", "Search", "%c", 100, function(buf)
1718
self:ListFilterChanged(buf, self.controls.searchModeDropDown.selIndex)
19+
self:sortSourceList()
1820
end, nil, nil, true)
1921

2022
self.controls.searchModeDropDown = new("DropDownControl", {"LEFT",self.controls.searchText,"RIGHT"}, {2, 0, 60, 18}, { "Names", "Skills", "Both"}, function(index, value)
2123
self:ListFilterChanged(self.controls.searchText.buf, index)
24+
self:sortSourceList()
25+
end)
26+
self.controls.sortModeDropDown = new("DropDownControl", {"BOTTOMRIGHT", self.controls.searchModeDropDown, "TOPRIGHT"}, {0, -2, self.width, 18}, {
27+
"Sort by Names",
28+
"Sort by Life + ES",
29+
"Sort by Life",
30+
"Sort by Energy Shield",
31+
"Sort by Attack Speed",
32+
"Sort by Companion Reservation",
33+
"Sort by Spectre Reservation",
34+
"Sort by Fire Resistance",
35+
"Sort by Cold Resistance",
36+
"Sort by Lightning Resistance",
37+
"Sort by Chaos Resistance",
38+
"Sort by Total Resistance",
39+
"Sort by Movement Speed",
40+
}, function(index, value)
41+
self:sortSourceList()
2242
end)
2343

24-
self.labelPositionOffset = {0, -20}
44+
self.labelPositionOffset = {0, -40}
2545
if dest then
26-
self.controls.add.y = self.controls.add.y - 20
46+
self.controls.add.y = self.controls.add.y - 40
2747
else
28-
self.controls.delete.y = self.controls.add.y - 20
48+
self.controls.delete.y = self.controls.add.y - 40
2949
end
3050

3151
end)
@@ -65,3 +85,53 @@ function MinionSearchListClass:ListFilterChanged(buf, filterMode)
6585
self.list = self.unfilteredList
6686
end
6787
end
88+
89+
function MinionSearchListClass:sortSourceList()
90+
local sortFields = {
91+
[1] = { field = "name", asc = true },
92+
[2] = { field = "totalHitPoints", asc = false },
93+
[3] = { field = "life", asc = false },
94+
[4] = { field = "energyShield", asc = false },
95+
[5] = { field = "attackTime", asc = true },
96+
[6] = { field = "companionReservation", asc = true },
97+
[7] = { field = "spectreReservation", asc = true },
98+
[8] = { field = "fireResist", asc = false },
99+
[9] = { field = "coldResist", asc = false },
100+
[10] = { field = "lightningResist", asc = false },
101+
[11] = { field = "chaosResist", asc = false },
102+
[12] = { field = "totalResist", asc = false },
103+
[13] = { field = "baseMovementSpeed", asc = false },
104+
}
105+
local sortModeIndex = self.controls.sortModeDropDown and self.controls.sortModeDropDown.selIndex or 1
106+
local sortOption = sortFields[sortModeIndex]
107+
if sortOption then
108+
table.sort(self.list, function(a, b)
109+
local minionA = self.data.minions[a]
110+
local minionB = self.data.minions[b]
111+
local valueA = minionA[sortOption.field]
112+
local valueB = minionB[sortOption.field]
113+
if sortOption.field == "life" then
114+
valueA = minionA.life * (1 - (minionA.energyShield or 0))
115+
valueB = minionB.life * (1 - (minionB.energyShield or 0))
116+
elseif sortOption.field == "totalHitPoints" then
117+
valueA = minionA.life
118+
valueB = minionB.life
119+
elseif sortOption.field == "energyShield" then
120+
valueA = (minionA.energyShield or 0) * minionA.life
121+
valueB = (minionB.energyShield or 0) * minionB.life
122+
elseif sortOption.field == "totalResist" then
123+
valueA = (minionA.fireResist or 0) + (minionA.coldResist or 0) + (minionA.lightningResist or 0) + (minionA.chaosResist or 0)
124+
valueB = (minionB.fireResist or 0) + (minionB.coldResist or 0) + (minionB.lightningResist or 0) + (minionB.chaosResist or 0)
125+
end
126+
if valueA == valueB then
127+
return minionA.name < minionB.name
128+
else
129+
if sortOption.asc then
130+
return valueA < valueB
131+
else
132+
return valueA > valueB
133+
end
134+
end
135+
end)
136+
end
137+
end

src/Classes/ModStore.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -698,13 +698,14 @@ function ModStoreClass:EvalMod(mod, cfg)
698698
matchName = matchName:lower()
699699
if tag.skillNameList then
700700
for _, name in pairs(tag.skillNameList) do
701-
if name:lower() == matchName then
701+
local nameLower = name:lower()
702+
if (tag.partialMatch and matchName:find(nameLower, 1, true)) or (not tag.partialMatch and nameLower == matchName) then
702703
match = true
703704
break
704705
end
705706
end
706707
else
707-
match = (tag.skillName and tag.skillName:lower() == matchName)
708+
match = (tag.partialMatch and matchName:find(tag.skillName:lower(), 1, true) ~= nil) or (tag.skillName:lower() == matchName)
708709
end
709710
end
710711
if tag.neg then

0 commit comments

Comments
 (0)