@@ -85,12 +85,16 @@ local tradeStatCategoryIndices = {
8585}
8686
8787local influenceSuffixes = { " _shaper" , " _elder" , " _adjudicator" , " _basilisk" , " _crusader" , " _eyrie" }
88- local influenceDropdownNames = { " None" }
88+ local INFLUENCE_IGNORE_INDEX = 1
89+ local INFLUENCE_NONE_INDEX = 2
90+ local INFLUENCE_ANY_INDEX = 3
91+ local influenceDropdownNames = { " Ignore" , " None" , " Any" }
8992local hasInfluenceModIds = { }
9093for i , curInfluenceInfo in ipairs (itemLib .influenceInfo .default ) do
91- influenceDropdownNames [i + 1 ] = curInfluenceInfo .display
94+ influenceDropdownNames [i + INFLUENCE_ANY_INDEX ] = curInfluenceInfo .display
9295 hasInfluenceModIds [i ] = " pseudo.pseudo_has_" .. string.lower (curInfluenceInfo .display ) .. " _influence"
9396end
97+ local hasAnyInfluenceModId = " pseudo.pseudo_has_influence_count"
9498
9599-- slots that allow eldritch mods (non-unique only)
96100local eldritchModSlots = {
@@ -106,6 +110,91 @@ local function logToFile(...)
106110 ConPrintf (... )
107111end
108112
113+ local function isIgnoredSelection (selectionIndex )
114+ return selectionIndex == nil or selectionIndex == INFLUENCE_IGNORE_INDEX
115+ end
116+
117+ local function isSpecificInfluenceSelection (selectionIndex )
118+ return selectionIndex and selectionIndex > INFLUENCE_ANY_INDEX
119+ end
120+
121+ local function isNoInfluenceSelection (selectionIndex )
122+ return selectionIndex == INFLUENCE_NONE_INDEX
123+ end
124+
125+ local function getInfluenceInfoForSelection (selectionIndex )
126+ if not isSpecificInfluenceSelection (selectionIndex ) then
127+ return nil
128+ end
129+ return itemLib .influenceInfo .default [selectionIndex - INFLUENCE_ANY_INDEX ]
130+ end
131+
132+ -- Influence dropdown semantics:
133+ -- Ignore = no constraint for that slot, None = missing influence slot,
134+ -- Any = present but unspecified influence slot, Specific = named influence slot.
135+ local function resolveInfluenceQueryState (selection1 , selection2 )
136+ local state = {
137+ exactCount = nil ,
138+ minCount = nil ,
139+ specificInfluenceModIds = { },
140+ hasNoneConstraint = false ,
141+ }
142+ local positiveSelectionCount = 0
143+ local ignoreSelectionCount = 0
144+ local noneSelectionCount = 0
145+ local seenSpecificInfluenceModIds = { }
146+
147+ for _ , selectionIndex in ipairs ({ selection1 or INFLUENCE_IGNORE_INDEX , selection2 or INFLUENCE_IGNORE_INDEX }) do
148+ if isIgnoredSelection (selectionIndex ) then
149+ ignoreSelectionCount = ignoreSelectionCount + 1
150+ elseif isNoInfluenceSelection (selectionIndex ) then
151+ noneSelectionCount = noneSelectionCount + 1
152+ else
153+ positiveSelectionCount = positiveSelectionCount + 1
154+ if isSpecificInfluenceSelection (selectionIndex ) then
155+ local influenceModId = hasInfluenceModIds [selectionIndex - INFLUENCE_ANY_INDEX ]
156+ if not seenSpecificInfluenceModIds [influenceModId ] then
157+ seenSpecificInfluenceModIds [influenceModId ] = true
158+ t_insert (state .specificInfluenceModIds , influenceModId )
159+ end
160+ end
161+ end
162+ end
163+
164+ if noneSelectionCount > 0 then
165+ state .hasNoneConstraint = true
166+ state .exactCount = positiveSelectionCount
167+ elseif ignoreSelectionCount == 2 then
168+ return state
169+ elseif ignoreSelectionCount > 0 then
170+ if positiveSelectionCount > # state .specificInfluenceModIds then
171+ state .minCount = positiveSelectionCount
172+ end
173+ else
174+ state .exactCount = positiveSelectionCount
175+ end
176+
177+ return state
178+ end
179+
180+ -- Returns true when pseudo_has_influence must be added to enforce a count constraint.
181+ local function needsHasInfluenceFilter (influenceState )
182+ if influenceState .exactCount ~= nil then
183+ return influenceState .exactCount == 0
184+ or influenceState .hasNoneConstraint
185+ or # influenceState .specificInfluenceModIds < influenceState .exactCount
186+ end
187+ return influenceState .minCount ~= nil
188+ end
189+
190+ local function getInfluenceFilterCost (influenceState )
191+ local cost = # influenceState .specificInfluenceModIds
192+ if needsHasInfluenceFilter (influenceState ) then
193+ cost = cost + 1
194+ end
195+ return cost
196+ end
197+
109198local TradeQueryGeneratorClass = newClass (" TradeQueryGenerator" , function (self , queryTab )
110199 self :InitMods ()
111200 self .queryTab = queryTab
@@ -789,11 +878,13 @@ function TradeQueryGeneratorClass:StartQuery(slot, options)
789878 local testItem = new (" Item" , itemRawStr )
790879
791880 -- Apply any requests influences
792- if options .influence1 > 1 then
793- testItem [itemLib .influenceInfo .default [options .influence1 - 1 ].key ] = true
881+ local influence1 = getInfluenceInfoForSelection (options .influence1 )
882+ if influence1 then
883+ testItem [influence1 .key ] = true
794884 end
795- if options .influence2 > 1 then
796- testItem [itemLib .influenceInfo .default [options .influence2 - 1 ].key ] = true
885+ local influence2 = getInfluenceInfoForSelection (options .influence2 )
886+ if influence2 then
887+ testItem [influence2 .key ] = true
797888 end
798889
799890 -- Calculate base output with a blank item
@@ -850,8 +941,8 @@ function TradeQueryGeneratorClass:ExecuteQuery()
850941 if self .calcContext .options .includeEldritch ~= " None" and
851942 -- skip weights if we need an influenced item as they can produce really
852943 -- bad results due to the filter limit
853- self .calcContext .options .influence1 == 1 and
854- self .calcContext .options .influence2 == 1 then
944+ not isSpecificInfluenceSelection ( self .calcContext .options .influence1 ) and
945+ not isSpecificInfluenceSelection ( self .calcContext .options .influence2 ) then
855946 local omitConditional = self .calcContext .options .includeEldritch == " Omit While"
856947 local eaterMods = self .modData [" Eater" ]
857948 local exarchMods = self .modData [" Exarch" ]
@@ -985,8 +1076,10 @@ function TradeQueryGeneratorClass:FinishQuery()
9851076 }
9861077
9871078 local options = self .calcContext .options
1079+ local influenceState = resolveInfluenceQueryState (options .influence1 , options .influence2 )
1080+ local influenceFilterCost = getInfluenceFilterCost (influenceState )
9881081
989- local num_extra = 2
1082+ local num_extra = influenceFilterCost
9901083 if not options .includeMirrored then
9911084 num_extra = num_extra + 1
9921085 end
@@ -1019,12 +1112,20 @@ function TradeQueryGeneratorClass:FinishQuery()
10191112
10201113 local andFilters = { type = " and" , filters = { } }
10211114 local options = self .calcContext .options
1022- if options .influence1 > 1 then
1023- t_insert (andFilters .filters , { id = hasInfluenceModIds [options .influence1 - 1 ] })
1115+ if needsHasInfluenceFilter (influenceState ) then
1116+ if influenceState .exactCount == 0 then
1117+ -- "has 0 influences" cannot be queried with a value range; use NOT instead
1118+ t_insert (queryTable .query .stats , { type = " not" , filters = { { id = hasAnyInfluenceModId } } })
1119+ elseif influenceState .exactCount ~= nil then
1120+ t_insert (andFilters .filters , { id = hasAnyInfluenceModId , value = { min = influenceState .exactCount , max = influenceState .exactCount } })
1121+ else
1122+ t_insert (andFilters .filters , { id = hasAnyInfluenceModId , value = { min = influenceState .minCount } })
1123+ end
10241124 filters = filters + 1
10251125 end
1026- if options .influence2 > 1 then
1027- t_insert (andFilters .filters , { id = hasInfluenceModIds [options .influence2 - 1 ] })
1126+
1127+ for _ , modId in ipairs (influenceState .specificInfluenceModIds ) do
1128+ t_insert (andFilters .filters , { id = modId })
10281129 filters = filters + 1
10291130 end
10301131
@@ -1118,6 +1219,15 @@ function TradeQueryGeneratorClass:FinishQuery()
11181219 main :ClosePopup ()
11191220end
11201221
1222+ -- Test accessors for influence query state logic (not used in production paths)
1223+ TradeQueryGeneratorClass ._resolveInfluenceQueryState = resolveInfluenceQueryState
1224+ TradeQueryGeneratorClass ._getInfluenceFilterCost = getInfluenceFilterCost
1225+ TradeQueryGeneratorClass ._needsHasInfluenceFilter = needsHasInfluenceFilter
1226+ TradeQueryGeneratorClass ._hasAnyInfluenceModId = hasAnyInfluenceModId
1227+ TradeQueryGeneratorClass ._INFLUENCE_IGNORE_INDEX = INFLUENCE_IGNORE_INDEX
1228+ TradeQueryGeneratorClass ._INFLUENCE_NONE_INDEX = INFLUENCE_NONE_INDEX
1229+ TradeQueryGeneratorClass ._INFLUENCE_ANY_INDEX = INFLUENCE_ANY_INDEX
1230+
11211231function TradeQueryGeneratorClass :RequestQuery (slot , context , statWeights , callback )
11221232 self .requesterCallback = callback
11231233 self .requesterContext = context
@@ -1218,23 +1328,46 @@ Remove: %s will be removed from the search results.]], term, term, term)
12181328 controls .jewelTypeLabel = new (" LabelControl" , {" RIGHT" ,controls .jewelType ," LEFT" }, {- 5 , 0 , 0 , 16 }, " Jewel Type:" )
12191329 updateLastAnchor (controls .jewelType )
12201330 elseif slot and not isAbyssalJewelSlot and context .slotTbl .slotName ~= " Watcher's Eye" then
1221- local selFunc = function ()
1331+ local function normalizeInfluenceSelections (changedControl )
1332+ local changedDropdown = changedControl == 1 and controls .influence1 or changedControl == 2 and controls .influence2 or nil
1333+ local otherDropdown = changedControl == 1 and controls .influence2 or changedControl == 2 and controls .influence1 or nil
1334+
1335+ if changedDropdown and otherDropdown then
1336+ if isIgnoredSelection (changedDropdown .selIndex ) and isNoInfluenceSelection (otherDropdown .selIndex ) then
1337+ changedDropdown :SetSel (INFLUENCE_NONE_INDEX )
1338+ return
1339+ elseif isNoInfluenceSelection (changedDropdown .selIndex ) and isIgnoredSelection (otherDropdown .selIndex ) then
1340+ otherDropdown :SetSel (INFLUENCE_NONE_INDEX )
1341+ return
1342+ elseif isSpecificInfluenceSelection (changedDropdown .selIndex ) and changedDropdown .selIndex == otherDropdown .selIndex then
1343+ changedDropdown :SetSel (INFLUENCE_ANY_INDEX )
1344+ return
1345+ end
1346+ end
1347+
1348+ if isIgnoredSelection (controls .influence1 .selIndex ) and isNoInfluenceSelection (controls .influence2 .selIndex ) then
1349+ controls .influence1 :SetSel (INFLUENCE_NONE_INDEX )
1350+ elseif isNoInfluenceSelection (controls .influence1 .selIndex ) and isIgnoredSelection (controls .influence2 .selIndex ) then
1351+ controls .influence2 :SetSel (INFLUENCE_NONE_INDEX )
1352+ end
1353+
12221354 -- influenced items can't have eldritch implicits
12231355 if controls .copyEldritch and isEldritchModSlot then
1224- local hasInfluence1 = controls .influence1 and controls .influence1 : GetSelValue () ~= " None "
1225- local hasInfluence2 = controls .influence2 and controls .influence2 : GetSelValue () ~= " None "
1356+ local hasInfluence1 = controls .influence1 and not isIgnoredSelection ( controls .influence1 . selIndex ) and not isNoInfluenceSelection ( controls . influence1 . selIndex )
1357+ local hasInfluence2 = controls .influence2 and not isIgnoredSelection ( controls .influence2 . selIndex ) and not isNoInfluenceSelection ( controls . influence2 . selIndex )
12261358 controls .copyEldritch .enabled = not hasInfluence1 and not hasInfluence2
12271359 end
12281360 end
1361+
12291362 controls .influence1 = new (" DropDownControl" , { " TOPLEFT" , lastItemAnchor , " BOTTOMLEFT" }, { 0 , 5 , 100 , 18 },
1230- influenceDropdownNames , selFunc )
1231- controls .influence1 :SetSel (self .lastInfluence1 or 1 )
1363+ influenceDropdownNames , function () normalizeInfluenceSelections ( 1 ) end )
1364+ controls .influence1 :SetSel (self .lastInfluence1 or INFLUENCE_IGNORE_INDEX )
12321365 controls .influence1Label = new (" LabelControl" , {" RIGHT" ,controls .influence1 ," LEFT" }, {- 5 , 0 , 0 , 16 }, " ^7Influence 1:" )
12331366
12341367 controls .influence2 = new (" DropDownControl" , { " TOPLEFT" , controls .influence1 , " BOTTOMLEFT" }, { 0 , 5 , 100 , 18 },
1235- influenceDropdownNames , selFunc )
1236- controls .influence2 :SetSel (self .lastInfluence2 or 1 )
1237- selFunc ()
1368+ influenceDropdownNames , function () normalizeInfluenceSelections ( 2 ) end )
1369+ controls .influence2 :SetSel (self .lastInfluence2 or INFLUENCE_IGNORE_INDEX )
1370+ normalizeInfluenceSelections ()
12381371 controls .influence2Label = new (" LabelControl" , { " RIGHT" , controls .influence2 , " LEFT" }, { - 5 , 0 , 0 , 16 },
12391372 " ^7Influence 2:" )
12401373 updateLastAnchor (controls .influence2 , 46 )
@@ -1335,12 +1468,12 @@ Remove: %s will be removed from the search results.]], term, term, term)
13351468 if controls .influence1 then
13361469 self .lastInfluence1 , options .influence1 = controls .influence1 .selIndex , controls .influence1 .selIndex
13371470 else
1338- options .influence1 = 1
1471+ options .influence1 = INFLUENCE_IGNORE_INDEX
13391472 end
13401473 if controls .influence2 then
13411474 self .lastInfluence2 , options .influence2 = controls .influence2 .selIndex , controls .influence2 .selIndex
13421475 else
1343- options .influence2 = 1
1476+ options .influence2 = INFLUENCE_IGNORE_INDEX
13441477 end
13451478 if controls .jewelType then
13461479 self .lastJewelType = controls .jewelType .selIndex
0 commit comments