@@ -22,21 +22,46 @@ public SpellGroupingEngine(PlayerInfo activePlayer, EQToolSettings settings)
2222 this . settings = settings ;
2323 }
2424
25- public void Recategorize ( IEnumerable < SpellViewModel > changedSpells = null )
25+ public void Recategorize ( IEnumerable < SpellViewModel > recentSpells = null )
2626 {
27- if ( changedSpells != null && changedSpells . Any ( ) )
28- changedSpells = GetConnectedSpells ( changedSpells ) ;
27+ if ( recentSpells != null && recentSpells . Any ( ) )
28+ recentSpells = GetConnectedSpells ( recentSpells ) ;
2929
30- var visibleForEval = GetSpellsNeedingAutomaticGrouping ( changedSpells ?? allSpells ) ;
31- if ( ! visibleForEval . Any ( ) )
30+ var allRelatedSpells = recentSpells ?? allSpells ;
31+ if ( ! allRelatedSpells . Any ( ) )
3232 return ;
3333
34- PerformAutomaticGrouping ( visibleForEval ) ;
34+ var nonConciseGroupingSpells = new List < SpellViewModel > ( ) ;
35+ var adaptiveGroupingSpells = new List < SpellViewModel > ( ) ;
36+ if ( settings . PlayerSpellGroupingType == SpellGroupingType . Adaptive && settings . NpcSpellGroupingType == SpellGroupingType . Adaptive )
37+ {
38+ adaptiveGroupingSpells = allRelatedSpells . Where ( s => s . ColumnVisibility == Visibility . Visible ) . ToList ( ) ;
39+ }
40+ else if ( settings . PlayerSpellGroupingType == SpellGroupingType . Adaptive )
41+ {
42+ nonConciseGroupingSpells . AddRange ( allRelatedSpells . Where ( s => ! s . IsPlayerTarget ) ) ; // Handle em all, even the hidden ones
43+ adaptiveGroupingSpells = allRelatedSpells . Where ( s => s . ColumnVisibility == Visibility . Visible && s . IsPlayerTarget ) . ToList ( ) ;
44+ }
45+ else if ( settings . NpcSpellGroupingType == SpellGroupingType . Adaptive )
46+ {
47+ nonConciseGroupingSpells . AddRange ( allRelatedSpells . Where ( s => s . IsPlayerTarget ) ) ; // Handle em all, even the hidden ones
48+ adaptiveGroupingSpells = allRelatedSpells . Where ( s => s . ColumnVisibility == Visibility . Visible && ! s . IsPlayerTarget ) . ToList ( ) ;
49+ }
50+ else
51+ {
52+ nonConciseGroupingSpells . AddRange ( allRelatedSpells ) ;
53+ }
54+
55+ foreach ( var spell in nonConciseGroupingSpells )
56+ ApplyNonAdaptiveGroupingRules ( spell ) ;
57+
58+ if ( adaptiveGroupingSpells . Any ( ) )
59+ PerformAdaptiveGrouping ( adaptiveGroupingSpells ) ;
3560 }
3661
37- private IEnumerable < SpellViewModel > GetConnectedSpells ( IEnumerable < SpellViewModel > changedSpells )
62+ private IEnumerable < SpellViewModel > GetConnectedSpells ( IEnumerable < SpellViewModel > recentSpells )
3863 {
39- var seed = changedSpells as IList < SpellViewModel > ?? changedSpells . ToList ( ) ;
64+ var seed = recentSpells as IList < SpellViewModel > ?? recentSpells . ToList ( ) ;
4065 if ( ! seed . Any ( ) )
4166 return Enumerable . Empty < SpellViewModel > ( ) ;
4267
@@ -116,7 +141,7 @@ public void RemoveSpells(IEnumerable<SpellViewModel> spells)
116141 }
117142 }
118143
119- public void ApplyNonAutomaticGroupingRule ( SpellViewModel spell )
144+ private void ApplyNonAdaptiveGroupingRules ( SpellViewModel spell )
120145 {
121146 var mode = GetConfiguredGroupingMode ( spell ) ;
122147
@@ -129,17 +154,6 @@ public void ApplyNonAutomaticGroupingRule(SpellViewModel spell)
129154
130155 // Automatic grouping handled elsewhere due to it needed to evaluate the whole list at once.
131156 }
132-
133- private IEnumerable < SpellViewModel > GetSpellsNeedingAutomaticGrouping ( IEnumerable < SpellViewModel > spells )
134- {
135- var playerMode = settings . PlayerSpellGroupingType == SpellGroupingType . Automatic ;
136- var npcMode = settings . NpcSpellGroupingType == SpellGroupingType . Automatic ;
137-
138- if ( ! playerMode && ! npcMode )
139- return Enumerable . Empty < SpellViewModel > ( ) ;
140-
141- return spells . Where ( s => s . ColumnVisibility == Visibility . Visible && ( ( s . IsPlayerTarget && playerMode ) || ( ! s . IsPlayerTarget && npcMode ) ) ) ;
142- }
143157
144158 private SpellGroupingType GetConfiguredGroupingMode ( SpellViewModel spell )
145159 => spell . IsPlayerTarget
@@ -149,7 +163,7 @@ private SpellGroupingType GetConfiguredGroupingMode(SpellViewModel spell)
149163 // -----------------------
150164 // Branch-and-bound algorithm. Determine what should be grouped by target and what should be grouped by Id.
151165 // -----------------------
152- private void PerformAutomaticGrouping ( IEnumerable < SpellViewModel > visibleSpells )
166+ private void PerformAdaptiveGrouping ( IEnumerable < SpellViewModel > visibleSpells )
153167 {
154168 var context = new BranchAndBoundContext ( activePlayer , visibleSpells ) ;
155169
@@ -161,14 +175,9 @@ private void PerformAutomaticGrouping(IEnumerable<SpellViewModel> visibleSpells)
161175 spell . IsCategorizeById = context . SelectedIdGroups . Contains ( spell . Id ) ;
162176 }
163177
164- private static void ComputeOptimalGroups (
165- BranchAndBoundContext context ,
166- int groupIndex ,
167- int selectedGroupCount ,
168- int totalSpellsSelectedById ,
169- int remainingTargets )
178+ private static void ComputeOptimalGroups ( BranchAndBoundContext context , int groupIndex , int selectedGroupCount , int totalSpellsSelectedById , int remainingTargets )
170179 {
171- var stateKey = groupIndex + "|" + new string ( context . SelectionMask ) ;
180+ var stateKey = groupIndex + "|" + new string ( context . DecisionCache ) ;
172181 if ( ! context . Visited . Add ( stateKey ) )
173182 return ;
174183
@@ -187,16 +196,18 @@ private static void ComputeOptimalGroups(
187196 context . SelectedIdGroups . Clear ( ) ;
188197
189198 for ( var i = 0 ; i < context . Groups . Length ; i ++ )
190- if ( context . SelectionMask [ i ] == '1' )
199+ {
200+ if ( context . DecisionCache [ i ] == '1' )
191201 context . SelectedIdGroups . Add ( context . Groups [ i ] . GroupId ) ;
202+ }
192203 }
193204 return ;
194205 }
195206
196207 var group = context . Groups [ groupIndex ] ;
197208 if ( group . SpellCount > 1 )
198209 {
199- context . SelectionMask [ groupIndex ] = '1' ;
210+ context . DecisionCache [ groupIndex ] = '1' ;
200211 var touchedTargets = new List < int > ( ) ;
201212
202213 foreach ( var t in context . TargetIndicesPerGroup [ groupIndex ] )
@@ -208,21 +219,17 @@ private static void ComputeOptimalGroups(
208219 touchedTargets . Add ( t ) ;
209220 }
210221
211- ComputeOptimalGroups (
212- context ,
213- groupIndex + 1 ,
214- selectedGroupCount + 1 ,
215- totalSpellsSelectedById + group . SpellCount ,
216- remainingTargets ) ;
222+ ComputeOptimalGroups ( context , groupIndex + 1 , selectedGroupCount + 1 , totalSpellsSelectedById + group . SpellCount , remainingTargets ) ;
217223
218224 foreach ( var t in touchedTargets )
219225 {
220226 if ( context . RemainingSpellsPerTarget [ t ] == 0 )
221227 remainingTargets ++ ;
228+
222229 context . RemainingSpellsPerTarget [ t ] ++ ;
223230 }
224231
225- context . SelectionMask [ groupIndex ] = '0' ;
232+ context . DecisionCache [ groupIndex ] = '0' ;
226233 }
227234
228235 ComputeOptimalGroups ( context , groupIndex + 1 , selectedGroupCount , totalSpellsSelectedById , remainingTargets ) ;
@@ -233,8 +240,7 @@ private class BranchAndBoundContext
233240 public SpellIdGroupInfo [ ] Groups { get ; }
234241 public List < int > [ ] TargetIndicesPerGroup { get ; }
235242 public int [ ] RemainingSpellsPerTarget { get ; }
236- public string [ ] TargetList { get ; }
237- public char [ ] SelectionMask { get ; }
243+ public char [ ] DecisionCache { get ; }
238244 public HashSet < string > Visited { get ; } = new HashSet < string > ( StringComparer . Ordinal ) ;
239245 public HashSet < string > SelectedIdGroups { get ; } = new HashSet < string > ( ) ;
240246
@@ -247,32 +253,38 @@ public BranchAndBoundContext(PlayerInfo activePlayer, IEnumerable<SpellViewModel
247253 . GroupBy ( s => s . Id )
248254 . ToDictionary ( g => g . Key , g => g . ToList ( ) ) ;
249255
256+ var groupCount = groupsById . Count ;
257+ var classSpellWeight = ( int ) Math . Log ( groupCount , 2 ) ; // Log weight since it's weak on small datasets and strong on larger ones.
258+
250259 Groups = groupsById . Values
251- . Select ( list =>
260+ . Select ( instances =>
252261 {
253262 var info = new SpellIdGroupInfo
254263 {
255- GroupId = list [ 0 ] . Id ,
256- Spells = list ,
257- SpellCount = list . Count ,
258- DistinctTargetCount = list . Select ( s => s . Target ) . Distinct ( ) . Count ( ) ,
259- CastableByPlayerClass = list . Any ( s => s . CastByYourClass ( activePlayer ) )
264+ GroupId = instances [ 0 ] . Id ,
265+ Spells = instances ,
266+ SpellCount = instances . Count ,
267+ DistinctTargetCount = instances . Select ( s => s . Target ) . Distinct ( ) . Count ( ) ,
268+ CastableByPlayerClass = instances . Any ( s => s . CastByYourClass ( activePlayer ) )
260269 } ;
270+
261271 info . PriorityScore = info . SpellCount - info . DistinctTargetCount ;
272+ // Attribute a bit more weight towards grouping by spell Id when the spell is cast by your class.
273+ if ( info . CastableByPlayerClass )
274+ info . PriorityScore += classSpellWeight ;
275+
262276 return info ;
263277 } )
264278 . OrderByDescending ( g => g . CastableByPlayerClass )
265279 . ThenByDescending ( g => g . PriorityScore )
266280 . ThenByDescending ( g => g . SpellCount )
267281 . ToArray ( ) ;
268282
269- TargetList = visibleSpells . Select ( s => s . Target ) . Distinct ( ) . ToArray ( ) ;
270- var targetIndex = TargetList
271- . Select ( ( t , i ) => ( t , i ) )
272- . ToDictionary ( x => x . t , x => x . i , StringComparer . Ordinal ) ;
283+ var distinctTargets = visibleSpells . Select ( s => s . Target ) . Distinct ( ) . ToArray ( ) ;
284+ var targetIndex = distinctTargets . Select ( ( t , i ) => ( t , i ) ) . ToDictionary ( x => x . t , x => x . i , StringComparer . Ordinal ) ;
273285
274286 TargetIndicesPerGroup = new List < int > [ Groups . Length ] ;
275- RemainingSpellsPerTarget = new int [ TargetList . Length ] ;
287+ RemainingSpellsPerTarget = new int [ distinctTargets . Length ] ;
276288
277289 for ( var i = 0 ; i < Groups . Length ; i ++ )
278290 {
@@ -287,7 +299,7 @@ public BranchAndBoundContext(PlayerInfo activePlayer, IEnumerable<SpellViewModel
287299 RemainingSpellsPerTarget [ idx ] ++ ;
288300 }
289301
290- SelectionMask = Enumerable . Repeat ( '0' , Groups . Length ) . ToArray ( ) ;
302+ DecisionCache = Enumerable . Repeat ( '0' , Groups . Length ) . ToArray ( ) ;
291303 }
292304 }
293305
0 commit comments