diff --git a/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java b/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java index b001b1d..eb196c4 100644 --- a/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java +++ b/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java @@ -1,73 +1,33 @@ package org.geysermc.packgenerator; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.logging.LogUtils; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ServerboundChatCommandPacket; -import net.minecraft.world.item.ItemStack; +import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; +import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraft.resources.ResourceLocation; +import org.geysermc.packgenerator.command.CommandSuggestionsArgumentType; +import org.geysermc.packgenerator.command.ItemSuggestionMapper; +import org.geysermc.packgenerator.command.PackGeneratorCommand; import org.slf4j.Logger; -import java.util.ArrayList; -import java.util.List; - public class GeyserMappingsGenerator implements ClientModInitializer { public static final String MOD_ID = "geyser-mappings-generator"; public static final String MOD_NAME = "Geyser Mappings Generator"; public static final Logger LOGGER = LogUtils.getLogger(); - private final List pendingPackCommands = new ArrayList<>(); - private boolean waitingOnItem = false; - private boolean waitingOnClear = false; - @Override public void onInitializeClient() { ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher)); + ClientTickEvents.START_CLIENT_TICK.register(minecraft -> ItemSuggestionMapper.getInstance().tick(minecraft)); - ClientTickEvents.START_CLIENT_TICK.register(client -> { - if (!pendingPackCommands.isEmpty() || waitingOnItem || waitingOnClear) { - if (waitingOnClear && client.player.getInventory().isEmpty()) { - waitingOnClear = false; - } else if (!waitingOnItem) { - String command = pendingPackCommands.removeFirst(); - client.getConnection().send(new ServerboundChatCommandPacket("loot give @s loot " + command)); - waitingOnItem = true; - } else { - if (!client.player.getInventory().isEmpty()) { - int mapped = 0; - for (ItemStack stack : client.player.getInventory()) { - try { - if (PackManager.getInstance().map(stack)) { - mapped++; - } - } catch (Exception exception) { - client.player.displayClientMessage(Component.literal("Failed to map item " + exception.getMessage()), false); - } - } - client.player.displayClientMessage(Component.literal("Mapped " + mapped + " items from your inventory"), false); + ArgumentTypeRegistry.registerArgumentType(getModdedLocation("command_suggestions"), + CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); + } - client.getConnection().send(new ServerboundChatCommandPacket("clear")); - - waitingOnItem = false; - if (pendingPackCommands.isEmpty()) { - try { - if (!PackManager.getInstance().finish()) { - client.player.displayClientMessage(Component.literal("Errors occurred whilst trying to write the pack to disk!"), false); - return; - } - } catch (CommandSyntaxException e) { - throw new RuntimeException(e); - } - client.player.displayClientMessage(Component.literal("Wrote pack to disk"), false); - } else { - waitingOnClear = true; - } - } - } - } - }); + public ResourceLocation getModdedLocation(String path) { + return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); } } diff --git a/src/main/java/org/geysermc/packgenerator/PackManager.java b/src/main/java/org/geysermc/packgenerator/PackManager.java index a5c3aeb..12a2d35 100644 --- a/src/main/java/org/geysermc/packgenerator/PackManager.java +++ b/src/main/java/org/geysermc/packgenerator/PackManager.java @@ -1,8 +1,5 @@ package org.geysermc.packgenerator; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import org.geysermc.packgenerator.pack.BedrockPack; @@ -25,22 +22,22 @@ public final class PackManager { currentPack = new BedrockPack(name); } - public Optional map(ItemStack stack) throws CommandSyntaxException { + public Optional map(ItemStack stack) { ensurePackIsCreated(); return currentPack.map(stack); } - public boolean finish() throws CommandSyntaxException { + public boolean finish() { ensurePackIsCreated(); boolean success = currentPack.save(); currentPack = null; return success; } - private void ensurePackIsCreated() throws CommandSyntaxException { + public void ensurePackIsCreated() { if (currentPack == null) { - throw new SimpleCommandExceptionType(Component.literal("Create a new pack first!")).create(); + throw new IllegalStateException("Create a new pack first!"); } } diff --git a/src/main/java/org/geysermc/packgenerator/command/CommandSuggestionsArgumentType.java b/src/main/java/org/geysermc/packgenerator/command/CommandSuggestionsArgumentType.java new file mode 100644 index 0000000..eca82bc --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/command/CommandSuggestionsArgumentType.java @@ -0,0 +1,54 @@ +package org.geysermc.packgenerator.command; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.Minecraft; + +import java.util.concurrent.CompletableFuture; + +public class CommandSuggestionsArgumentType implements ArgumentType>> { + + public static final CommandSuggestionsArgumentType TYPE = new CommandSuggestionsArgumentType(); + + @Override + public Pair> parse(StringReader reader) { + String command = reader.getRemaining(); + reader.setCursor(reader.getTotalLength()); + return Pair.of(command, Minecraft.getInstance().getConnection().getSuggestionsProvider().customSuggestion(createCommandSuggestionsContext(command))); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + int offset = builder.getStart(); + return Minecraft.getInstance().getConnection().getSuggestionsProvider().customSuggestion(createCommandSuggestionsContext(builder.getRemaining())) + .thenApply(suggestions -> addOffset(suggestions, offset)); + } + + public static Pair> getSuggestions(CommandContext context, String argument) { + return context.getArgument(argument, Pair.class); + } + + private Suggestions addOffset(Suggestions suggestions, int offset) { + StringRange offsetRange = addOffset(suggestions.getRange(), offset); + return new Suggestions(offsetRange, suggestions.getList().stream() + .map(suggestion -> new Suggestion(addOffset(suggestion.getRange(), offset), suggestion.getText(), suggestion.getTooltip())) + .toList()); + } + + private StringRange addOffset(StringRange range, int offset) { + return new StringRange(range.getStart() + offset, range.getEnd() + offset); + } + + private static CommandContext createCommandSuggestionsContext(String string) { + // hack + return new CommandContext<>(null, + string, + null, null, null, null, null, null, null, false); + } +} diff --git a/src/main/java/org/geysermc/packgenerator/command/ItemSuggestionMapper.java b/src/main/java/org/geysermc/packgenerator/command/ItemSuggestionMapper.java new file mode 100644 index 0000000..da031e8 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/command/ItemSuggestionMapper.java @@ -0,0 +1,73 @@ +package org.geysermc.packgenerator.command; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ServerboundChatCommandPacket; + +import java.util.ArrayList; +import java.util.List; + +// TODO safety +public final class ItemSuggestionMapper { + private static final ItemSuggestionMapper INSTANCE = new ItemSuggestionMapper(); + + private List remainingCommands = new ArrayList<>(); + private boolean waitingOnItem = false; + private boolean waitingOnClear = false; + private int mapped = 0; + + private ItemSuggestionMapper() {} + + public boolean start(List commands) { + if (remainingCommands.isEmpty()) { + remainingCommands.addAll(commands); + return true; + } + return false; + } + + public void tick(Minecraft minecraft) { + if (minecraft.player == null || minecraft.getConnection() == null) { + stop(); + return; + } + + if (!remainingCommands.isEmpty() || waitingOnItem) { + if (waitingOnClear && minecraft.player.getInventory().isEmpty()) { + waitingOnClear = false; + } else if (!waitingOnItem) { + minecraft.getConnection().send(new ServerboundChatCommandPacket(remainingCommands.removeFirst())); + waitingOnItem = true; + } else { + if (!minecraft.player.getInventory().isEmpty()) { + mapped += PackGeneratorCommand.mapInventory(minecraft.player.getInventory(), + component -> minecraft.player.displayClientMessage(component, false), false); + + minecraft.getConnection().send(new ServerboundChatCommandPacket("clear")); + + waitingOnItem = false; + if (remainingCommands.isEmpty()) { + minecraft.player.displayClientMessage(Component.literal("Done, " + mapped + " items have been mapped"), false); + } else { + waitingOnClear = true; + } + } + } + } + } + + public int queueSize() { + return remainingCommands.size(); + } + + public void stop() { + remainingCommands.clear(); + waitingOnItem = false; + waitingOnClear = false; + mapped = 0; + } + + public static ItemSuggestionMapper getInstance() { + return INSTANCE; + } +} diff --git a/src/main/java/org/geysermc/packgenerator/PackGeneratorCommand.java b/src/main/java/org/geysermc/packgenerator/command/PackGeneratorCommand.java similarity index 50% rename from src/main/java/org/geysermc/packgenerator/PackGeneratorCommand.java rename to src/main/java/org/geysermc/packgenerator/command/PackGeneratorCommand.java index 8e2356d..d4db82d 100644 --- a/src/main/java/org/geysermc/packgenerator/PackGeneratorCommand.java +++ b/src/main/java/org/geysermc/packgenerator/command/PackGeneratorCommand.java @@ -1,17 +1,20 @@ -package org.geysermc.packgenerator; +package org.geysermc.packgenerator.command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.datafixers.util.Pair; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.item.ItemStack; +import org.geysermc.packgenerator.PackManager; -import java.io.IOException; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; public class PackGeneratorCommand { @@ -46,22 +49,7 @@ public class PackGeneratorCommand { }) ) .then(ClientCommandManager.literal("mapinventory") - .executes(context -> { - int mapped = 0; - boolean errors = false; - for (ItemStack stack : context.getSource().getPlayer().getInventory()) { - Optional problems = PackManager.getInstance().map(stack); - if (problems.isPresent()) { - mapped++; - errors |= problems.get(); - } - } - if (errors) { - context.getSource().sendError(Component.literal("Problems occurred whilst mapping items!")); - } - context.getSource().sendFeedback(Component.literal("Mapped " + mapped + " items from your inventory")); - return 0; - }) + .executes(context -> mapInventory(context.getSource().getPlayer().getInventory(), context.getSource()::sendFeedback, true)) ) .then(ClientCommandManager.literal("finish") .executes(context -> { @@ -74,32 +62,42 @@ public class PackGeneratorCommand { }) ) .then(ClientCommandManager.literal("auto") - .then(ClientCommandManager.argument("namespace", StringArgumentType.word()) - .then(ClientCommandManager.argument("path", StringArgumentType.string()) - .executes(context -> { - String namespace = StringArgumentType.getString(context, "namespace"); - String path = StringArgumentType.getString(context, "path"); - - // Duplicated code, this is just to try this out - try { - PackManager.getInstance().startPack(namespace); - } catch (IOException exception) { - throw new SimpleCommandExceptionType(Component.literal(exception.getMessage())).create(); - } - context.getSource().sendFeedback(Component.literal("Created pack with name " + namespace)); - - // hack - CommandContext suggestionsContext = new CommandContext<>(null, - "loot give @s loot " + namespace + ":" + path, - null, null, null, null, null, null, null, false); - context.getSource().getClient().getConnection().getSuggestionsProvider() - .customSuggestion(suggestionsContext) - .whenComplete((suggestions, throwable) -> pendingPackCommands.addAll(suggestions.getList().stream().map(Suggestion::getText).toList())); - return 0; - }) - ) + .then(ClientCommandManager.argument("suggestions", CommandSuggestionsArgumentType.TYPE) + .executes(context -> { + PackManager.getInstance().ensurePackIsCreated(); + Pair> suggestions = CommandSuggestionsArgumentType.getSuggestions(context, "suggestions"); + String baseCommand = suggestions.getFirst(); + suggestions.getSecond().thenAccept(completed -> { + ItemSuggestionMapper.getInstance().start(completed.getList().stream() + .map(suggestion -> baseCommand.substring(0, suggestion.getRange().getStart()) + suggestion.getText()) + .toList()); + context.getSource().sendFeedback(Component.literal("Running " + ItemSuggestionMapper.getInstance().queueSize() + " commands to obtain custom items to map")); + }); + return 0; + }) ) ) ); } + + public static int mapInventory(Inventory inventory, Consumer feedback, boolean feedbackOnEmpty) { + int mapped = 0; + boolean errors = false; + for (ItemStack stack : inventory) { + Optional problems = PackManager.getInstance().map(stack); + if (problems.isPresent()) { + mapped++; + errors |= problems.get(); + } + } + if (mapped > 0) { + if (errors) { + feedback.accept(Component.literal("Problems occurred whilst mapping items!").withStyle(ChatFormatting.RED)); + } + feedback.accept(Component.literal("Mapped " + mapped + " items from your inventory")); + } else if (feedbackOnEmpty) { + feedback.accept(Component.literal("No items were mapped")); + } + return mapped; + } }