1616import org .jspecify .annotations .Nullable ;
1717
1818import java .util .*;
19+ import java .util .stream .Collectors ;
1920
2021@ NullMarked
2122public class EnchantmentOptimizer {
@@ -41,7 +42,7 @@ public EnchantmentOptimizer(List<RegistryEntry<Enchantment>> enchantments) {
4142 where bl = itemStack3.contains(DataComponentTypes.STORED_ENCHANTMENTS)
4243 Since we're optimizing book-based enchanting, we divide by 2 to match in-game behavior
4344 */
44- enchantmentWeights [id ] = anvilCost / 2 ;
45+ enchantmentWeights [id ] = Math . max ( 1 , anvilCost / 2 ) ;
4546
4647 id ++;
4748 }
@@ -54,19 +55,19 @@ public OptimizationResult optimize(@Nullable Item item, List<EnchantmentEntry> e
5455 memoCache .clear ();
5556
5657 // Create enchantment objects
57- List <ItemObject > enchantObjs = new ArrayList <>();
58- for ( EnchantmentEntry e : enchants ) {
59- int id = enchantmentIds .getOrDefault (e .enchantment (), -1 );
60- if (id == -1 ) {
61- throw new IllegalArgumentException ("Unknown enchantment: " + e .enchantment ().getKey ().orElseThrow ().getValue ());
62- }
63- int value = e .level () * enchantmentWeights [id ];
64- IntList ids = new IntArrayList ( );
65- ids . add ( id );
66- ItemObject obj = new ItemObject ( ItemType . BOOK , value , ids );
67- obj . combination = new Combination ( e . enchantment (), e . level ()) ;
68- enchantObjs . add ( obj );
69- }
58+ List <ItemObject > enchantObjs = enchants . stream ()
59+ . map ( e -> {
60+ int id = enchantmentIds .getOrDefault (e .enchantment (), -1 );
61+ if (id == -1 ) {
62+ throw new IllegalArgumentException ("Unknown enchantment: " + e .enchantment ().getKey ().orElseThrow ().getValue ());
63+ }
64+ int value = e .level () * enchantmentWeights [id ];
65+ IntList ids = IntLists . singleton ( id );
66+ ItemObject obj = new ItemObject ( ItemType . BOOK , value , ids );
67+ obj . combination = new Combination ( e . enchantment (), e . level () );
68+ return obj ;
69+ })
70+ . collect ( Collectors . toCollection ( ArrayList :: new ));
7071
7172 // Find most expensive enchant
7273 int mostExpensiveIdx = findMostExpensive (enchantObjs );
@@ -75,8 +76,7 @@ public OptimizationResult optimize(@Nullable Item item, List<EnchantmentEntry> e
7576 ItemObject baseItem ;
7677 if (item == null ) { // Book-only mode
7778 ItemObject expensive = enchantObjs .get (mostExpensiveIdx );
78- IntList ids = new IntArrayList ();
79- ids .add (expensive .enchantIds .getInt (0 ));
79+ IntList ids = IntLists .singleton (expensive .enchantIds .getInt (0 ));
8080 baseItem = new ItemObject (ItemType .ENCHANTED_BOOK , expensive .value , ids );
8181 baseItem .combination = expensive .combination ;
8282 enchantObjs .remove (mostExpensiveIdx );
@@ -132,24 +132,14 @@ private int findMostExpensive(List<ItemObject> items) {
132132
133133 private Int2ObjectMap <ItemObject > cheapestItemsFromList (List <ItemObject > items ) {
134134 ResultKey key = ResultKey .fromItems (items );
135- if (memoCache .containsKey (key )) {
136- return memoCache .get (key );
137- }
135+ Int2ObjectMap <ItemObject > cached = memoCache .get (key );
136+ if (cached != null ) return cached ;
138137
139138 Int2ObjectMap <ItemObject > result = switch (items .size ()) {
140- case 1 -> {
141- Int2ObjectMap <ItemObject > map = new Int2ObjectOpenHashMap <>();
142- map .put (items .getFirst ().priorWork , items .getFirst ());
143- yield map ;
144- }
139+ case 1 -> Int2ObjectMaps .singleton (items .getFirst ().priorWork , items .getFirst ());
145140 case 2 -> {
146141 ItemObject cheapest = cheapestItemFromItems (items .getFirst (), items .get (1 ));
147- if (cheapest == null ) {
148- throw new IllegalStateException ("Both merge attempts were too expensive" );
149- }
150- Int2ObjectMap <ItemObject > map = new Int2ObjectOpenHashMap <>();
151- map .put (cheapest .priorWork , cheapest );
152- yield map ;
142+ yield Int2ObjectMaps .singleton (cheapest .priorWork , cheapest );
153143 }
154144 default -> cheapestItemsFromListN (items , items .size () / 2 );
155145 };
@@ -158,7 +148,7 @@ private Int2ObjectMap<ItemObject> cheapestItemsFromList(List<ItemObject> items)
158148 return result ;
159149 }
160150
161- private @ Nullable ItemObject cheapestItemFromItems (ItemObject left , ItemObject right ) {
151+ private ItemObject cheapestItemFromItems (ItemObject left , ItemObject right ) {
162152 if (left .type == ItemType .ITEM ) return new MergeEnchants (left , right );
163153 if (right .type == ItemType .ITEM ) return new MergeEnchants (right , left );
164154
@@ -177,53 +167,43 @@ private Int2ObjectMap<ItemObject> cheapestItemsFromList(List<ItemObject> items)
177167 // Ignore too expensive merges
178168 }
179169
170+ if (normal == null && reversed == null ) {
171+ throw new IllegalStateException ("Both merge attempts were too expensive" );
172+ }
173+
180174 if (normal == null ) return reversed ;
181175 if (reversed == null ) return normal ;
182176
183- ItemObject result = compareCheapest (normal , reversed );
184- if (result == null ) {
185- // This should never happen - both items are merges of the same two items, so same priorWork
186- throw new IllegalStateException ("Merge comparison returned null unexpectedly" );
187- }
188- return result ;
177+ // Both merges succeeded - they have same priorWork, so compareCheapest cannot return null
178+ return compareCheapest (normal , reversed );
189179 }
190180
191181 private Int2ObjectMap <ItemObject > cheapestItemsFromListN (List <ItemObject > items , int maxSubcount ) {
192182 Int2ObjectMap <ItemObject > cheapestWork2Item = new Int2ObjectOpenHashMap <>();
193183
194184 for (int subcount = 1 ; subcount <= maxSubcount ; subcount ++) {
195185 for (List <ItemObject > leftItems : combinations (items , subcount )) {
196- List <ItemObject > rightItems = items .stream ()
197- .filter (item -> !leftItems .contains (item ))
198- .toList ();
186+ List <ItemObject > rightItems = new ArrayList <>(items );
187+ rightItems .removeAll (leftItems );
199188
200189 Int2ObjectMap <ItemObject > leftWork2Item = cheapestItemsFromList (leftItems );
201190 Int2ObjectMap <ItemObject > rightWork2Item = cheapestItemsFromList (rightItems );
202191 Int2ObjectMap <ItemObject > newWork2Item = cheapestItemsFromDictionaries (leftWork2Item , rightWork2Item );
203192
204- newWork2Item .int2ObjectEntrySet ().forEach (entry -> {
205- int work = entry .getIntKey ();
206- ItemObject newItem = entry .getValue ();
207-
208- if (cheapestWork2Item .containsKey (work )) {
209- ItemObject result = compareCheapest (cheapestWork2Item .get (work ), newItem );
210- if (result == null ) {
211- throw new IllegalStateException ("Compared items have different priorWork values" );
212- }
213- cheapestWork2Item .put (work , result );
214- } else {
215- cheapestWork2Item .put (work , newItem );
216- }
217- });
193+ for (Int2ObjectMap .Entry <ItemObject > entry : newWork2Item .int2ObjectEntrySet ()) {
194+ cheapestWork2Item .merge (entry .getIntKey (), entry .getValue (), this ::compareCheapest );
195+ }
218196 }
219197 }
220198 return cheapestWork2Item ;
221199 }
222200
223- private @ Nullable ItemObject compareCheapest (ItemObject item1 , ItemObject item2 ) {
201+ private ItemObject compareCheapest (ItemObject item1 , ItemObject item2 ) {
224202 // This method assumes both items have the same priorWork (enforced by callers using work-indexed maps)
225- // If they somehow differ, we can't meaningfully compare them, so return null
226- if (item1 .priorWork != item2 .priorWork ) return null ;
203+ // If they somehow differ, we can't meaningfully compare them
204+ if (item1 .priorWork != item2 .priorWork ) {
205+ throw new IllegalStateException ("Items must have same priorWork: " + item1 .priorWork + " vs " + item2 .priorWork );
206+ }
227207
228208 // Prefer lower value (fewer enchantment levels)
229209 if (item1 .value != item2 .value ) return item1 .value < item2 .value ? item1 : item2 ;
@@ -241,20 +221,9 @@ private Int2ObjectMap<ItemObject> cheapestItemsFromDictionaries(
241221 try {
242222 Int2ObjectMap <ItemObject > newWork2Item = cheapestItemsFromList (List .of (leftItem , rightItem ));
243223
244- newWork2Item .int2ObjectEntrySet ().forEach (entry -> {
245- int work = entry .getIntKey ();
246- ItemObject newItem = entry .getValue ();
247-
248- if (cheapest .containsKey (work )) {
249- ItemObject result = compareCheapest (cheapest .get (work ), newItem );
250- if (result == null ) {
251- throw new IllegalStateException ("Compared items have different priorWork values" );
252- }
253- cheapest .put (work , result );
254- } else {
255- cheapest .put (work , newItem );
256- }
257- });
224+ for (Int2ObjectMap .Entry <ItemObject > entry : newWork2Item .int2ObjectEntrySet ()) {
225+ cheapest .merge (entry .getIntKey (), entry .getValue (), this ::compareCheapest );
226+ }
258227 } catch (MergeLevelsTooExpensiveException ignored ) {
259228 // Ignore too expensive merges
260229 }
@@ -309,7 +278,7 @@ private static <T> List<List<T>> combinations(List<T> set, int k) {
309278 T head = set .get (i );
310279 List <List <T >> tailCombs = combinations (set .subList (i + 1 , set .size ()), k - 1 );
311280 for (List <T > tail : tailCombs ) {
312- List <T > combination = new ArrayList <>();
281+ List <T > combination = new ArrayList <>(tail . size () + 1 );
313282 combination .add (head );
314283 combination .addAll (tail );
315284 combs .add (combination );
@@ -447,13 +416,17 @@ public int compareTo(ItemHash o) {
447416 if (c != 0 ) return c ;
448417 c = Integer .compare (priorWork , o .priorWork );
449418 if (c != 0 ) return c ;
450- // Compare int lists element by element
451- int minSize = Math .min (sortedEnchants .size (), o .sortedEnchants .size ());
452- for (int i = 0 ; i < minSize ; i ++) {
453- int cmp = Integer .compare (sortedEnchants .getInt (i ), o .sortedEnchants .getInt (i ));
454- if (cmp != 0 ) return cmp ;
419+
420+ // Compare sizes first
421+ c = Integer .compare (sortedEnchants .size (), o .sortedEnchants .size ());
422+ if (c != 0 ) return c ;
423+
424+ // Then element by element
425+ for (int i = 0 ; i < sortedEnchants .size (); i ++) {
426+ c = Integer .compare (sortedEnchants .getInt (i ), o .sortedEnchants .getInt (i ));
427+ if (c != 0 ) return c ;
455428 }
456- return Integer . compare ( sortedEnchants . size (), o . sortedEnchants . size ()) ;
429+ return 0 ;
457430 }
458431 }
459432
0 commit comments