Skip to content

Commit 1028e79

Browse files
Improvements and correct enchantmentWeights calculation
NOTE: there is still a discrepancy in the calculation we make, need to figure out why.
1 parent bc45105 commit 1028e79

1 file changed

Lines changed: 51 additions & 78 deletions

File tree

src/main/java/meteordevelopment/meteorclient/utils/misc/EnchantmentOptimizer.java

Lines changed: 51 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.jspecify.annotations.Nullable;
1717

1818
import java.util.*;
19+
import java.util.stream.Collectors;
1920

2021
@NullMarked
2122
public 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

Comments
 (0)