@@ -84,12 +84,16 @@ local tradeStatCategoryIndices = {
8484}
8585
8686local influenceSuffixes = { " _shaper" , " _elder" , " _adjudicator" , " _basilisk" , " _crusader" , " _eyrie" }
87- local influenceDropdownNames = { " None" }
87+ local INFLUENCE_IGNORE_INDEX = 1
88+ local INFLUENCE_NONE_INDEX = 2
89+ local INFLUENCE_ANY_INDEX = 3
90+ local influenceDropdownNames = { " Ignore" , " None" , " Any" }
8891local hasInfluenceModIds = { }
8992for i , curInfluenceInfo in ipairs (itemLib .influenceInfo .default ) do
90- influenceDropdownNames [i + 1 ] = curInfluenceInfo .display
93+ influenceDropdownNames [i + INFLUENCE_ANY_INDEX ] = curInfluenceInfo .display
9194 hasInfluenceModIds [i ] = " pseudo.pseudo_has_" .. string.lower (curInfluenceInfo .display ) .. " _influence"
9295end
96+ local hasAnyInfluenceModId = " pseudo.pseudo_has_influence_count"
9397
9498-- slots that allow eldritch mods (non-unique only)
9599local eldritchModSlots = {
@@ -105,6 +109,91 @@ local function logToFile(...)
105109 ConPrintf (... )
106110end
107111
112+ local function isIgnoredSelection (selectionIndex )
113+ return selectionIndex == nil or selectionIndex == INFLUENCE_IGNORE_INDEX
114+ end
115+
116+ local function isSpecificInfluenceSelection (selectionIndex )
117+ return selectionIndex and selectionIndex > INFLUENCE_ANY_INDEX
118+ end
119+
120+ local function isNoInfluenceSelection (selectionIndex )
121+ return selectionIndex == INFLUENCE_NONE_INDEX
122+ end
123+
124+ local function getInfluenceInfoForSelection (selectionIndex )
125+ if not isSpecificInfluenceSelection (selectionIndex ) then
126+ return nil
127+ end
128+ return itemLib .influenceInfo .default [selectionIndex - INFLUENCE_ANY_INDEX ]
129+ end
130+
131+ -- Influence dropdown semantics:
132+ -- Ignore = no constraint for that slot, None = missing influence slot,
133+ -- Any = present but unspecified influence slot, Specific = named influence slot.
134+ local function resolveInfluenceQueryState (selection1 , selection2 )
135+ local state = {
136+ exactCount = nil ,
137+ minCount = nil ,
138+ specificInfluenceModIds = { },
139+ hasNoneConstraint = false ,
140+ }
141+ local positiveSelectionCount = 0
142+ local ignoreSelectionCount = 0
143+ local noneSelectionCount = 0
144+ local seenSpecificInfluenceModIds = { }
145+
146+ for _ , selectionIndex in ipairs ({ selection1 or INFLUENCE_IGNORE_INDEX , selection2 or INFLUENCE_IGNORE_INDEX }) do
147+ if isIgnoredSelection (selectionIndex ) then
148+ ignoreSelectionCount = ignoreSelectionCount + 1
149+ elseif isNoInfluenceSelection (selectionIndex ) then
150+ noneSelectionCount = noneSelectionCount + 1
151+ else
152+ positiveSelectionCount = positiveSelectionCount + 1
153+ if isSpecificInfluenceSelection (selectionIndex ) then
154+ local influenceModId = hasInfluenceModIds [selectionIndex - INFLUENCE_ANY_INDEX ]
155+ if not seenSpecificInfluenceModIds [influenceModId ] then
156+ seenSpecificInfluenceModIds [influenceModId ] = true
157+ t_insert (state .specificInfluenceModIds , influenceModId )
158+ end
159+ end
160+ end
161+ end
162+
163+ if noneSelectionCount > 0 then
164+ state .hasNoneConstraint = true
165+ state .exactCount = positiveSelectionCount
166+ elseif ignoreSelectionCount == 2 then
167+ return state
168+ elseif ignoreSelectionCount > 0 then
169+ if positiveSelectionCount > # state .specificInfluenceModIds then
170+ state .minCount = positiveSelectionCount
171+ end
172+ else
173+ state .exactCount = positiveSelectionCount
174+ end
175+
176+ return state
177+ end
178+
179+ -- Returns true when pseudo_has_influence must be added to enforce a count constraint.
180+ local function needsHasInfluenceFilter (influenceState )
181+ if influenceState .exactCount ~= nil then
182+ return influenceState .exactCount == 0
183+ or influenceState .hasNoneConstraint
184+ or # influenceState .specificInfluenceModIds < influenceState .exactCount
185+ end
186+ return influenceState .minCount ~= nil
187+ end
188+
189+ local function getInfluenceFilterCost (influenceState )
190+ local cost = # influenceState .specificInfluenceModIds
191+ if needsHasInfluenceFilter (influenceState ) then
192+ cost = cost + 1
193+ end
194+ return cost
195+ end
196+
108197local TradeQueryGeneratorClass = newClass (" TradeQueryGenerator" , function (self , queryTab )
109198 self :InitMods ()
110199 self .queryTab = queryTab
@@ -866,11 +955,13 @@ function TradeQueryGeneratorClass:StartQuery(slot, options)
866955 local testItem = new (" Item" , itemRawStr )
867956
868957 -- Apply any requests influences
869- if options .influence1 > 1 then
870- testItem [itemLib .influenceInfo .default [options .influence1 - 1 ].key ] = true
958+ local influence1 = getInfluenceInfoForSelection (options .influence1 )
959+ if influence1 then
960+ testItem [influence1 .key ] = true
871961 end
872- if options .influence2 > 1 then
873- testItem [itemLib .influenceInfo .default [options .influence2 - 1 ].key ] = true
962+ local influence2 = getInfluenceInfoForSelection (options .influence2 )
963+ if influence2 then
964+ testItem [influence2 .key ] = true
874965 end
875966
876967 -- Calculate base output with a blank item
@@ -927,8 +1018,8 @@ function TradeQueryGeneratorClass:ExecuteQuery()
9271018 if self .calcContext .options .includeEldritch ~= " None" and
9281019 -- skip weights if we need an influenced item as they can produce really
9291020 -- bad results due to the filter limit
930- self .calcContext .options .influence1 == 1 and
931- self .calcContext .options .influence2 == 1 then
1021+ not isSpecificInfluenceSelection ( self .calcContext .options .influence1 ) and
1022+ not isSpecificInfluenceSelection ( self .calcContext .options .influence2 ) then
9321023 local omitConditional = self .calcContext .options .includeEldritch == " Omit While"
9331024 local eaterMods = self .modData [" Eater" ]
9341025 local exarchMods = self .modData [" Exarch" ]
@@ -1062,8 +1153,10 @@ function TradeQueryGeneratorClass:FinishQuery()
10621153 }
10631154
10641155 local options = self .calcContext .options
1156+ local influenceState = resolveInfluenceQueryState (options .influence1 , options .influence2 )
1157+ local influenceFilterCost = getInfluenceFilterCost (influenceState )
10651158
1066- local num_extra = 2
1159+ local num_extra = influenceFilterCost
10671160 if not options .includeMirrored then
10681161 num_extra = num_extra + 1
10691162 end
@@ -1096,12 +1189,20 @@ function TradeQueryGeneratorClass:FinishQuery()
10961189
10971190 local andFilters = { type = " and" , filters = { } }
10981191 local options = self .calcContext .options
1099- if options .influence1 > 1 then
1100- t_insert (andFilters .filters , { id = hasInfluenceModIds [options .influence1 - 1 ] })
1192+ if needsHasInfluenceFilter (influenceState ) then
1193+ if influenceState .exactCount == 0 then
1194+ -- "has 0 influences" cannot be queried with a value range; use NOT instead
1195+ t_insert (queryTable .query .stats , { type = " not" , filters = { { id = hasAnyInfluenceModId } } })
1196+ elseif influenceState .exactCount ~= nil then
1197+ t_insert (andFilters .filters , { id = hasAnyInfluenceModId , value = { min = influenceState .exactCount , max = influenceState .exactCount } })
1198+ else
1199+ t_insert (andFilters .filters , { id = hasAnyInfluenceModId , value = { min = influenceState .minCount } })
1200+ end
11011201 filters = filters + 1
11021202 end
1103- if options .influence2 > 1 then
1104- t_insert (andFilters .filters , { id = hasInfluenceModIds [options .influence2 - 1 ] })
1203+
1204+ for _ , modId in ipairs (influenceState .specificInfluenceModIds ) do
1205+ t_insert (andFilters .filters , { id = modId })
11051206 filters = filters + 1
11061207 end
11071208
@@ -1195,6 +1296,15 @@ function TradeQueryGeneratorClass:FinishQuery()
11951296 main :ClosePopup ()
11961297end
11971298
1299+ -- Test accessors for influence query state logic (not used in production paths)
1300+ TradeQueryGeneratorClass ._resolveInfluenceQueryState = resolveInfluenceQueryState
1301+ TradeQueryGeneratorClass ._getInfluenceFilterCost = getInfluenceFilterCost
1302+ TradeQueryGeneratorClass ._needsHasInfluenceFilter = needsHasInfluenceFilter
1303+ TradeQueryGeneratorClass ._hasAnyInfluenceModId = hasAnyInfluenceModId
1304+ TradeQueryGeneratorClass ._INFLUENCE_IGNORE_INDEX = INFLUENCE_IGNORE_INDEX
1305+ TradeQueryGeneratorClass ._INFLUENCE_NONE_INDEX = INFLUENCE_NONE_INDEX
1306+ TradeQueryGeneratorClass ._INFLUENCE_ANY_INDEX = INFLUENCE_ANY_INDEX
1307+
11981308function TradeQueryGeneratorClass :RequestQuery (slot , context , statWeights , callback )
11991309 self .requesterCallback = callback
12001310 self .requesterContext = context
@@ -1295,23 +1405,46 @@ Remove: %s will be removed from the search results.]], term, term, term)
12951405 controls .jewelTypeLabel = new (" LabelControl" , {" RIGHT" ,controls .jewelType ," LEFT" }, {- 5 , 0 , 0 , 16 }, " Jewel Type:" )
12961406 updateLastAnchor (controls .jewelType )
12971407 elseif slot and not isAbyssalJewelSlot and context .slotTbl .slotName ~= " Watcher's Eye" then
1298- local selFunc = function ()
1408+ local function normalizeInfluenceSelections (changedControl )
1409+ local changedDropdown = changedControl == 1 and controls .influence1 or changedControl == 2 and controls .influence2 or nil
1410+ local otherDropdown = changedControl == 1 and controls .influence2 or changedControl == 2 and controls .influence1 or nil
1411+
1412+ if changedDropdown and otherDropdown then
1413+ if isIgnoredSelection (changedDropdown .selIndex ) and isNoInfluenceSelection (otherDropdown .selIndex ) then
1414+ changedDropdown :SetSel (INFLUENCE_NONE_INDEX )
1415+ return
1416+ elseif isNoInfluenceSelection (changedDropdown .selIndex ) and isIgnoredSelection (otherDropdown .selIndex ) then
1417+ otherDropdown :SetSel (INFLUENCE_NONE_INDEX )
1418+ return
1419+ elseif isSpecificInfluenceSelection (changedDropdown .selIndex ) and changedDropdown .selIndex == otherDropdown .selIndex then
1420+ changedDropdown :SetSel (INFLUENCE_ANY_INDEX )
1421+ return
1422+ end
1423+ end
1424+
1425+ if isIgnoredSelection (controls .influence1 .selIndex ) and isNoInfluenceSelection (controls .influence2 .selIndex ) then
1426+ controls .influence1 :SetSel (INFLUENCE_NONE_INDEX )
1427+ elseif isNoInfluenceSelection (controls .influence1 .selIndex ) and isIgnoredSelection (controls .influence2 .selIndex ) then
1428+ controls .influence2 :SetSel (INFLUENCE_NONE_INDEX )
1429+ end
1430+
12991431 -- influenced items can't have eldritch implicits
13001432 if controls .copyEldritch and isEldritchModSlot then
1301- local hasInfluence1 = controls .influence1 and controls .influence1 : GetSelValue () ~= " None "
1302- local hasInfluence2 = controls .influence2 and controls .influence2 : GetSelValue () ~= " None "
1433+ local hasInfluence1 = controls .influence1 and not isIgnoredSelection ( controls .influence1 . selIndex ) and not isNoInfluenceSelection ( controls . influence1 . selIndex )
1434+ local hasInfluence2 = controls .influence2 and not isIgnoredSelection ( controls .influence2 . selIndex ) and not isNoInfluenceSelection ( controls . influence2 . selIndex )
13031435 controls .copyEldritch .enabled = not hasInfluence1 and not hasInfluence2
13041436 end
13051437 end
1438+
13061439 controls .influence1 = new (" DropDownControl" , { " TOPLEFT" , lastItemAnchor , " BOTTOMLEFT" }, { 0 , 5 , 100 , 18 },
1307- influenceDropdownNames , selFunc )
1308- controls .influence1 :SetSel (self .lastInfluence1 or 1 )
1440+ influenceDropdownNames , function () normalizeInfluenceSelections ( 1 ) end )
1441+ controls .influence1 :SetSel (self .lastInfluence1 or INFLUENCE_IGNORE_INDEX )
13091442 controls .influence1Label = new (" LabelControl" , {" RIGHT" ,controls .influence1 ," LEFT" }, {- 5 , 0 , 0 , 16 }, " ^7Influence 1:" )
13101443
13111444 controls .influence2 = new (" DropDownControl" , { " TOPLEFT" , controls .influence1 , " BOTTOMLEFT" }, { 0 , 5 , 100 , 18 },
1312- influenceDropdownNames , selFunc )
1313- controls .influence2 :SetSel (self .lastInfluence2 or 1 )
1314- selFunc ()
1445+ influenceDropdownNames , function () normalizeInfluenceSelections ( 2 ) end )
1446+ controls .influence2 :SetSel (self .lastInfluence2 or INFLUENCE_IGNORE_INDEX )
1447+ normalizeInfluenceSelections ()
13151448 controls .influence2Label = new (" LabelControl" , { " RIGHT" , controls .influence2 , " LEFT" }, { - 5 , 0 , 0 , 16 },
13161449 " ^7Influence 2:" )
13171450 updateLastAnchor (controls .influence2 , 46 )
@@ -1412,12 +1545,12 @@ Remove: %s will be removed from the search results.]], term, term, term)
14121545 if controls .influence1 then
14131546 self .lastInfluence1 , options .influence1 = controls .influence1 .selIndex , controls .influence1 .selIndex
14141547 else
1415- options .influence1 = 1
1548+ options .influence1 = INFLUENCE_IGNORE_INDEX
14161549 end
14171550 if controls .influence2 then
14181551 self .lastInfluence2 , options .influence2 = controls .influence2 .selIndex , controls .influence2 .selIndex
14191552 else
1420- options .influence2 = 1
1553+ options .influence2 = INFLUENCE_IGNORE_INDEX
14211554 end
14221555 if controls .jewelType then
14231556 self .lastJewelType = controls .jewelType .selIndex
0 commit comments