diff --git a/docs/wiki/Commands.md b/docs/wiki/Commands.md index 1fc424883..a8f1e7f7c 100644 --- a/docs/wiki/Commands.md +++ b/docs/wiki/Commands.md @@ -1468,6 +1468,18 @@ Dropping can be configured based on modes: Automatically eats food when health or hunger is below a set threshold. + + +Which foods to eat can be configured based on modes: + + + + * `all`: any food + + * `whitelist`: only added foods + + * `blacklist`: any food not added + **Usage** ```autoEat on/off``` @@ -1480,6 +1492,16 @@ Automatically eats food when health or hunger is below a set threshold. ```autoEat allowUnsafeFood on/off``` + ```autoEat mode ``` + + ```autoEat add/del ``` + + ```autoEat addAll ,,...``` + + ```autoEat list``` + + ```autoEat clear``` + ### autoFish diff --git a/src/main/java/com/zenith/command/brigadier/FoodArgument.java b/src/main/java/com/zenith/command/brigadier/FoodArgument.java new file mode 100644 index 000000000..70995a2bc --- /dev/null +++ b/src/main/java/com/zenith/command/brigadier/FoodArgument.java @@ -0,0 +1,80 @@ +package com.zenith.command.brigadier; + +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.zenith.mc.food.FoodData; +import com.zenith.mc.food.FoodRegistry; +import lombok.Data; +import org.geysermc.mcprotocollib.protocol.data.game.command.CommandParser; +import org.jspecify.annotations.NonNull; + +import java.util.concurrent.CompletableFuture; + +@Data +public class FoodArgument implements SerializableArgumentType { + public static final SimpleCommandExceptionType INVALID_ITEM_EXCEPTION = new SimpleCommandExceptionType( + new LiteralMessage("Invalid item") + ); + + public static FoodArgument food() { + return new FoodArgument(); + } + + @Override + public FoodData parse(final StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + var itemName = readItemString(reader); + if (itemName.contains(":")) { + // may look like minecraft:item_name + var itemNameNamespaceSplit = itemName.split(":"); + if (itemNameNamespaceSplit.length != 2) { + reader.setCursor(i); + throw INVALID_ITEM_EXCEPTION.create(); + } + itemName = itemNameNamespaceSplit[1]; + } + var itemData = FoodRegistry.REGISTRY.get(itemName); + if (itemData == null) { + reader.setCursor(i); + throw INVALID_ITEM_EXCEPTION.create(); + } + return itemData; + } + + public static FoodData getFood(final CommandContext context, String name) { + return context.getArgument(name, FoodData.class); + } + + private String readItemString(StringReader reader) throws CommandSyntaxException { + final int start = reader.getCursor(); + while (reader.canRead() && isAllowedInItemString(reader.peek())) { + reader.skip(); + } + if (reader.getCursor() == start) { + reader.setCursor(start); + throw INVALID_ITEM_EXCEPTION.createWithContext(reader); + } + return reader.getString().substring(start, reader.getCursor()); + } + + private static boolean isAllowedInItemString(final char c) { + return c >= 'A' && c <= 'Z' + || c >= 'a' && c <= 'z' + || c == '_' || c == ':'; + } + + @Override + public @NonNull CommandParser commandParser() { + return CommandParser.ITEM_STACK; + } + + @Override + public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { + return RegistryDataArgument.listRegistrySuggestions(context, builder, FoodRegistry.REGISTRY); + } +} diff --git a/src/main/java/com/zenith/command/impl/AutoEatCommand.java b/src/main/java/com/zenith/command/impl/AutoEatCommand.java index b3b859bd3..0ebfd455a 100644 --- a/src/main/java/com/zenith/command/impl/AutoEatCommand.java +++ b/src/main/java/com/zenith/command/impl/AutoEatCommand.java @@ -1,17 +1,27 @@ package com.zenith.command.impl; import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.zenith.command.api.Command; import com.zenith.command.api.CommandCategory; import com.zenith.command.api.CommandContext; import com.zenith.command.api.CommandUsage; -import com.zenith.discord.Embed; +import com.zenith.mc.food.FoodRegistry; import com.zenith.module.impl.AutoEat; +import com.zenith.util.config.Config; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.zenith.Globals.CONFIG; import static com.zenith.Globals.MODULE; +import static com.zenith.command.brigadier.CustomStringArgumentType.getString; +import static com.zenith.command.brigadier.FoodArgument.food; +import static com.zenith.command.brigadier.FoodArgument.getFood; +import static com.zenith.command.brigadier.ItemArgument.item; import static com.zenith.command.brigadier.ToggleArgumentType.getToggle; import static com.zenith.command.brigadier.ToggleArgumentType.toggle; @@ -23,13 +33,24 @@ public CommandUsage commandUsage() { .category(CommandCategory.MODULE) .description(""" Automatically eats food when health or hunger is below a set threshold. + + Which foods to eat can be configured based on modes: + + * `all`: any food + * `whitelist`: only added foods + * `blacklist`: any food not added """) .usageLines( "on/off", "health ", "hunger ", "warning on/off", - "allowUnsafeFood on/off" + "allowUnsafeFood on/off", + "mode ", + "add/del ", + "addAll ,,...", + "list", + "clear" ) .build(); } @@ -66,17 +87,83 @@ public LiteralArgumentBuilder register() { CONFIG.client.extra.autoEat.allowUnsafeFood = getToggle(c, "toggle"); c.getSource().getEmbed() .title("AutoEat Allow Unsafe Food " + toggleStrCaps(CONFIG.client.extra.autoEat.allowUnsafeFood)); - }))); + }))) + .then(literal("mode").then(argument("mode", enumStrings("all", "whitelist", "blacklist")).executes(c -> { + var modeString = getString(c, "mode").toUpperCase(); + CONFIG.client.extra.autoEat.mode = Config.Client.Extra.AutoEat.Mode.valueOf(modeString); + c.getSource().getEmbed() + .title("Mode Set"); + }))) + .then(literal("add").then(argument("food", food()).executes(c -> { + var food = getFood(c, "food"); + CONFIG.client.extra.autoEat.foods.add(food.name()); + CONFIG.client.extra.autoEat.foods.removeIf(i -> FoodRegistry.REGISTRY.get(i) == null); + c.getSource().getEmbed() + .title("Food Added") + .description(foodList()); + }))) + .then(literal("addAll").then(argument("foods", StringArgumentType.greedyString()).executes(c -> { + var foodList = getString(c, "foods").split(","); + List invalidFoods = new ArrayList<>(); + for (var food : foodList) { + var foodData = FoodRegistry.REGISTRY.get(food); + if (foodData == null) { + invalidFoods.add(food); + continue; + } + CONFIG.client.extra.autoEat.foods.add(foodData.name()); + } + c.getSource().getEmbed() + .title("Foods Added") + .addField("Added Foods Count", foodList.length - invalidFoods.size()) + .description(foodList()); + if (!invalidFoods.isEmpty()) { + c.getSource().getEmbed() + .addField("Invalid Foods", invalidFoods.stream().map(s -> "`" + s + "`").reduce((a, b) -> a + ", " + b).orElse("")); + } + }))) + .then(literal("del").then(argument("food", item()).executes(c -> { + var food = getFood(c, "food"); + CONFIG.client.extra.autoEat.foods.remove(food.name()); + CONFIG.client.extra.autoEat.foods.removeIf(i -> FoodRegistry.REGISTRY.get(i) == null); + c.getSource().getEmbed() + .title("Food Deleted") + .description(foodList()); + }))) + .then(literal("list").executes(c -> { + CONFIG.client.extra.autoEat.foods.removeIf(i -> FoodRegistry.REGISTRY.get(i) == null); + c.getSource().getEmbed() + .title("Food Blacklist") + .description(foodList()); + })) + .then(literal("clear").executes(c -> { + CONFIG.client.extra.autoEat.foods.clear(); + c.getSource().getEmbed() + .title("Food Blacklist Cleared") + .description(foodList()); + })); } @Override - public void defaultEmbed(final Embed builder) { - builder + public void defaultHandler(final CommandContext context) { + if (context.getData().containsKey("noDefaultEmbed")) return; + context.getEmbed() .addField("AutoEat", toggleStr(CONFIG.client.extra.autoEat.enabled)) + .addField("Mode", CONFIG.client.extra.autoEat.mode.name().toLowerCase()) .addField("Health Threshold", CONFIG.client.extra.autoEat.healthThreshold) .addField("Hunger Threshold", CONFIG.client.extra.autoEat.hungerThreshold) .addField("Warning", toggleStr(CONFIG.client.extra.autoEat.warning)) .addField("Allow Unsafe Food", toggleStr(CONFIG.client.extra.autoEat.allowUnsafeFood)) .primaryColor(); } + + String foodList() { + var items = new ArrayList<>(CONFIG.client.extra.autoEat.foods); + Collections.sort(items); + String list = "**Food List**:\n" + String.join("\n", items); + if (list.length() > 4000) { + list = list.substring(0, 4000) + "\n... (and more)"; + } + return list; + } } diff --git a/src/main/java/com/zenith/module/impl/AutoEat.java b/src/main/java/com/zenith/module/impl/AutoEat.java index bd886df67..ade3bc30c 100644 --- a/src/main/java/com/zenith/module/impl/AutoEat.java +++ b/src/main/java/com/zenith/module/impl/AutoEat.java @@ -160,9 +160,22 @@ public boolean itemPredicate(final ItemStack itemStack) { boolean hasFood(boolean ignoreHunger, ItemStack itemStack) { FoodData foodData = FoodRegistry.REGISTRY.get(itemStack.getId()); + if (foodData == null) return false; + switch (CONFIG.client.extra.autoEat.mode) { + case BLACKLIST -> { + if (CONFIG.client.extra.autoEat.foods.contains(foodData.name())) { + return false; + } + } + case WHITELIST -> { + if (!CONFIG.client.extra.autoEat.foods.contains(foodData.name())) { + return false; + } + } + case ALL -> {} + } boolean canEat = ignoreHunger || CACHE.getPlayerCache().getThePlayer().getFood() < 20; - return foodData != null - && (CONFIG.client.extra.autoEat.allowUnsafeFood || foodData.isSafeFood()) + return (CONFIG.client.extra.autoEat.allowUnsafeFood || foodData.isSafeFood()) && (canEat || foodData.canAlwaysEat()); } diff --git a/src/main/java/com/zenith/util/config/Config.java b/src/main/java/com/zenith/util/config/Config.java index 92c0f3f93..4634ab18a 100644 --- a/src/main/java/com/zenith/util/config/Config.java +++ b/src/main/java/com/zenith/util/config/Config.java @@ -433,6 +433,13 @@ public static final class AutoEat { public boolean warning = true; public boolean warningMention = false; public boolean allowUnsafeFood = false; + public Mode mode = Mode.ALL; + public enum Mode { + ALL, + BLACKLIST, + WHITELIST + } + public HashSet foods = new HashSet<>(); } public static final class AutoOmen { diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json index 7cbb14e74..256716885 100644 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -2678,6 +2678,20 @@ } ] }, + { + "type": "com.zenith.util.config.Config$Client$Extra$AutoEat$Mode", + "fields": [ + { + "name": "ALL" + }, + { + "name": "BLACKLIST" + }, + { + "name": "WHITELIST" + } + ] + }, { "type": "com.zenith.util.config.Config$Client$Extra$AutoFish", "allDeclaredFields": true, @@ -8776,4 +8790,4 @@ } ] } -} +} \ No newline at end of file