Skip to content

Commit c0a8a5c

Browse files
EnchantmentOptimizer
I think it works, but can be improved for sure :)
1 parent 5308665 commit c0a8a5c

File tree

4 files changed

+760
-0
lines changed

4 files changed

+760
-0
lines changed

src/main/java/meteordevelopment/meteorclient/commands/Commands.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public static void init() {
3636
add(new DamageCommand());
3737
add(new DropCommand());
3838
add(new EnchantCommand());
39+
add(new EnchantOptimizeCommand());
3940
add(new FakePlayerCommand());
4041
add(new FriendsCommand());
4142
add(new CommandsCommand());
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client).
3+
* Copyright (c) Meteor Development.
4+
*/
5+
6+
package meteordevelopment.meteorclient.commands.arguments;
7+
8+
import com.mojang.brigadier.StringReader;
9+
import com.mojang.brigadier.arguments.ArgumentType;
10+
import com.mojang.brigadier.context.CommandContext;
11+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
12+
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
13+
import com.mojang.brigadier.suggestion.Suggestions;
14+
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
15+
import net.minecraft.enchantment.Enchantment;
16+
import net.minecraft.registry.entry.RegistryEntry;
17+
import net.minecraft.text.Text;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.concurrent.CompletableFuture;
22+
23+
public class EnchantmentLevelArgumentType implements ArgumentType<Integer> {
24+
private final String enchantmentArgName;
25+
26+
27+
public EnchantmentLevelArgumentType(String enchantmentArgName) {
28+
this.enchantmentArgName = enchantmentArgName;
29+
}
30+
31+
public static EnchantmentLevelArgumentType enchantmentLevel(String enchantmentArgName) {
32+
return new EnchantmentLevelArgumentType(enchantmentArgName);
33+
}
34+
35+
36+
@Override
37+
public Integer parse(StringReader reader) throws CommandSyntaxException {
38+
int start = reader.getCursor();
39+
int level = reader.readInt();
40+
41+
if (level < 1) {
42+
reader.setCursor(start);
43+
throw new CommandSyntaxException(
44+
new DynamicCommandExceptionType(obj -> Text.literal("Level must be at least 1")),
45+
Text.literal("Level must be at least 1")
46+
);
47+
}
48+
49+
return level;
50+
}
51+
52+
@Override
53+
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
54+
try {
55+
// Try to get the enchantment from the previous argument
56+
RegistryEntry.Reference<Enchantment> enchantment =
57+
RegistryEntryReferenceArgumentType.getEnchantment(context, enchantmentArgName);
58+
59+
int maxLevel = enchantment.value().getMaxLevel();
60+
String enchantName = enchantment.value().description().getString();
61+
62+
// Build suggestions based on max level
63+
// Suggest 1 through maxLevel
64+
for (int i = 1; i <= Math.min(maxLevel, 10); i++) {
65+
builder.suggest(i);
66+
}
67+
68+
// TODO: this isn't working, only the above suggestions show up; overengineering?
69+
// Add a tooltip showing the valid range
70+
String remaining = builder.getRemaining();
71+
if (!remaining.isEmpty()) {
72+
try {
73+
int typedLevel = Integer.parseInt(remaining);
74+
if (typedLevel > maxLevel) {
75+
// Show error in suggestions
76+
builder.suggest(maxLevel, Text.literal("§c" + enchantName + " max: " + maxLevel));
77+
}
78+
} catch (NumberFormatException ignored) {
79+
// Command handler highlights invalid input
80+
}
81+
}
82+
83+
return builder.buildFuture();
84+
} catch (Exception e) {
85+
return Suggestions.empty();
86+
}
87+
}
88+
89+
@Override
90+
public Collection<String> getExamples() {
91+
return List.of("1", "2", "3", "4", "5");
92+
}
93+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client).
3+
* Copyright (c) Meteor Development.
4+
*/
5+
6+
package meteordevelopment.meteorclient.commands.commands;
7+
8+
import com.mojang.brigadier.arguments.IntegerArgumentType;
9+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
10+
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
11+
import com.mojang.brigadier.context.CommandContext;
12+
import meteordevelopment.meteorclient.commands.Command;
13+
import meteordevelopment.meteorclient.commands.arguments.EnchantmentLevelArgumentType;
14+
import meteordevelopment.meteorclient.commands.arguments.RegistryEntryReferenceArgumentType;
15+
import meteordevelopment.meteorclient.utils.misc.EnchantmentOptimizer;
16+
import meteordevelopment.meteorclient.utils.player.ChatUtils;
17+
import net.minecraft.command.CommandSource;
18+
import net.minecraft.command.argument.ItemStackArgumentType;
19+
import net.minecraft.enchantment.Enchantment;
20+
import net.minecraft.item.Item;
21+
import net.minecraft.registry.RegistryKeys;
22+
import net.minecraft.registry.entry.RegistryEntry;
23+
import net.minecraft.text.MutableText;
24+
import net.minecraft.text.Text;
25+
import net.minecraft.util.Formatting;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
public class EnchantOptimizeCommand extends Command {
31+
32+
public EnchantOptimizeCommand() {
33+
super("enchant-optimize", "Calculates the optimal order to apply enchantments for minimum XP cost.", "eopt");
34+
}
35+
36+
@Override
37+
public void build(LiteralArgumentBuilder<CommandSource> builder) {
38+
// TODO: The optimizer supports book-only mode (item=null) for combining enchanted books,
39+
// but this command currently requires an item argument, so item will never be null.
40+
// Should we add an item-less version of the command for book-only optimization?
41+
42+
// TODO: should we restrict the available items to only those that can be enchanted?
43+
// e.g. armors, weapons, tools, books, etc.
44+
builder.then(argument("item", ItemStackArgumentType.itemStack(REGISTRY_ACCESS))
45+
.then(buildEnchantmentChain(1, 20))
46+
// TODO: what should the max depth be? Idk how many enchantments on a single item MC supports.
47+
);
48+
}
49+
50+
/**
51+
* Recursively builds a chain of enchantment arguments.
52+
* Each enchantment requires a name and level, and can optionally chain to the next.
53+
*/
54+
private RequiredArgumentBuilder<CommandSource, ?> buildEnchantmentChain(int index, int maxDepth) {
55+
String enchantArg = "enchantment" + index;
56+
String levelArg = "level" + index;
57+
58+
var enchantmentArg = argument(enchantArg, RegistryEntryReferenceArgumentType.enchantment());
59+
var levelArgBuilder = argument(levelArg, EnchantmentLevelArgumentType.enchantmentLevel(enchantArg))
60+
.executes(context -> {
61+
executeOptimization(context, index);
62+
return SINGLE_SUCCESS;
63+
});
64+
65+
if (index < maxDepth) {
66+
levelArgBuilder.then(buildEnchantmentChain(index + 1, maxDepth));
67+
}
68+
69+
return enchantmentArg.then(levelArgBuilder);
70+
}
71+
72+
/**
73+
* Extracts all enchantments from context and runs optimization.
74+
*/
75+
private void executeOptimization(CommandContext<CommandSource> context, int enchantmentCount) {
76+
try {
77+
Item item = getItem(context);
78+
List<EnchantmentOptimizer.EnchantmentEntry> enchants = new ArrayList<>();
79+
80+
for (int i = 1; i <= enchantmentCount; i++) {
81+
String enchantArg = "enchantment" + i;
82+
String levelArg = "level" + i;
83+
84+
try {
85+
RegistryEntry.Reference<Enchantment> enchantment =
86+
RegistryEntryReferenceArgumentType.getEnchantment(context, enchantArg);
87+
int level = IntegerArgumentType.getInteger(context, levelArg);
88+
89+
// TODO: keep validation or allow arbitary levels? MC supports up to 255.
90+
// Validate level against enchantment's max level
91+
int maxLevel = enchantment.value().getMaxLevel();
92+
if (level > maxLevel) {
93+
String enchantName = enchantment.value().description().getString();
94+
error("Enchantment (highlight)%s(default) has max level (highlight)%d(default), but you specified (highlight)%d(default).",
95+
enchantName, maxLevel, level);
96+
return;
97+
}
98+
99+
enchants.add(new EnchantmentOptimizer.EnchantmentEntry(enchantment, level));
100+
} catch (IllegalArgumentException e) {
101+
// Argument doesn't exist, we've reached the end
102+
break;
103+
}
104+
}
105+
106+
if (enchants.isEmpty()) {
107+
error("No enchantments specified.");
108+
return;
109+
}
110+
111+
optimize(item, enchants);
112+
} catch (Exception e) {
113+
error("Failed to parse enchantments: %s", e.getMessage());
114+
}
115+
}
116+
117+
private Item getItem(CommandContext<CommandSource> context) {
118+
try {
119+
var itemArg = ItemStackArgumentType.getItemStackArgument(context, "item");
120+
return itemArg.getItem();
121+
} catch (Exception e) {
122+
return null;
123+
}
124+
}
125+
126+
private void optimize(Item item, List<EnchantmentOptimizer.EnchantmentEntry> enchants) {
127+
try {
128+
// Create optimizer from current registry
129+
var registry = mc.getNetworkHandler().getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT);
130+
EnchantmentOptimizer.OptimizationResult result = EnchantmentOptimizer.create(registry).optimize(item, enchants);
131+
132+
// Display header
133+
String itemName = item != null ? item.getName().getString() : "Book";
134+
ChatUtils.info("=== Enchantment Optimization for %s ===", itemName);
135+
info("Total Cost: (highlight)%d levels(default) (%d XP)", result.totalLevels(), result.totalXp());
136+
137+
if (result.instructions().isEmpty()) {
138+
info("No combinations needed - single enchantment only.");
139+
return;
140+
}
141+
142+
info("Steps:");
143+
144+
// Display steps
145+
for (int i = 0; i < result.instructions().size(); i++) {
146+
EnchantmentOptimizer.Instruction instr = result.instructions().get(i);
147+
148+
MutableText stepText = Text.literal(String.format(" %d. ", i + 1)).formatted(Formatting.GRAY);
149+
stepText.append(Text.literal("Combine ").formatted(Formatting.GRAY));
150+
stepText.append(formatItem(instr.left()).copy().formatted(Formatting.YELLOW));
151+
stepText.append(Text.literal(" with ").formatted(Formatting.GRAY));
152+
stepText.append(formatItem(instr.right()).copy().formatted(Formatting.AQUA));
153+
154+
ChatUtils.sendMsg(stepText);
155+
156+
info(" Cost: (highlight)%d levels(default) (%d XP), Prior Work Penalty: %d",
157+
instr.levels(),
158+
instr.xp(),
159+
instr.priorWorkPenalty()
160+
);
161+
}
162+
163+
} catch (Exception e) {
164+
error("Failed to optimize enchantments: %s", e.getMessage());
165+
}
166+
}
167+
168+
private Text formatItem(EnchantmentOptimizer.Combination comb) {
169+
if (comb.item != null) {
170+
return comb.item.getName();
171+
}
172+
173+
if (comb.enchantment != null) {
174+
String enchName = comb.enchantment.value().description().getString();
175+
String level = romanNumeral(comb.level);
176+
return Text.literal(enchName + " " + level + " Book");
177+
}
178+
179+
return Text.literal("Combined Item");
180+
}
181+
182+
private String romanNumeral(int num) {
183+
return switch (num) {
184+
case 1 -> "I";
185+
case 2 -> "II";
186+
case 3 -> "III";
187+
case 4 -> "IV";
188+
case 5 -> "V";
189+
case 6 -> "VI";
190+
case 7 -> "VII";
191+
case 8 -> "VIII";
192+
case 9 -> "IX";
193+
case 10 -> "X";
194+
default -> String.valueOf(num);
195+
};
196+
}
197+
}

0 commit comments

Comments
 (0)