diff --git a/src/main/java/kaptainwutax/seedcracker/ClientCommands.java b/src/main/java/kaptainwutax/seedcracker/ClientCommands.java new file mode 100644 index 0000000..7a52f40 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/ClientCommands.java @@ -0,0 +1,81 @@ +package kaptainwutax.seedcracker; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import kaptainwutax.seedcracker.command.ClientCommand; +import kaptainwutax.seedcracker.command.RenderCommand; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.command.CommandException; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class ClientCommands { + + public static final String PREFIX = "seed"; + public static final List COMMANDS = new ArrayList<>(); + + public static RenderCommand RENDER; + + static { + COMMANDS.add(RENDER = new RenderCommand()); + } + + public static void registerCommands(CommandDispatcher dispatcher) { + COMMANDS.forEach(clientCommand -> clientCommand.register(dispatcher)); + } + + public static boolean isClientSideCommand(String[] params) { + if(params.length < 2)return false; + if(!PREFIX.equals(params[0]))return false; + + for(ClientCommand command: COMMANDS) { + if(command.getName().equals(params[1])) { + return true; + } + } + + return false; + } + + public static int executeCommand(StringReader reader) { + ClientPlayerEntity player = MinecraftClient.getInstance().player; + + try { + return player.networkHandler.getCommandDispatcher().execute(reader, new FakeCommandSource(player)); + } catch(CommandException e) { + //lazy + e.printStackTrace(); + } catch(CommandSyntaxException e) { + //lazy + e.printStackTrace(); + } catch(Exception e) { + //lazy + e.printStackTrace(); + } + + return 1; + } + + /** + * Shoutout to Earthcomputer for this awesome class. + * https://github.com/Earthcomputer/clientcommands/blob/fabric/src/main/java/net/earthcomputer/clientcommands/command/FakeCommandSource.java + * */ + public static class FakeCommandSource extends ServerCommandSource { + public FakeCommandSource(ClientPlayerEntity player) { + super(player, player.getPosVector(), player.getRotationClient(), null, 0, player.getEntityName(), player.getName(), null, player); + } + + @Override + public Collection getPlayerNames() { + return MinecraftClient.getInstance().getNetworkHandler().getPlayerList() + .stream().map(e -> e.getProfile().getName()).collect(Collectors.toList()); + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java b/src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java new file mode 100644 index 0000000..3de448d --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java @@ -0,0 +1,28 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.ClientCommands; +import net.minecraft.client.MinecraftClient; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralText; + +import static net.minecraft.server.command.CommandManager.literal; + +public abstract class ClientCommand { + + public abstract String getName(); + + public abstract void build(LiteralArgumentBuilder builder); + + protected final void sendFeedback(String message, boolean overlay) { + MinecraftClient.getInstance().player.addChatMessage(new LiteralText(message), overlay); + } + + public final void register(CommandDispatcher dispatcher) { + LiteralArgumentBuilder builder = literal(this.getName()); + this.build(builder); + dispatcher.register(literal(ClientCommands.PREFIX).then(builder)); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java b/src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java new file mode 100644 index 0000000..058c026 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java @@ -0,0 +1,37 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.FinderQueue; +import net.minecraft.server.command.ServerCommandSource; + +import static net.minecraft.server.command.CommandManager.literal; + +public class RenderCommand extends ClientCommand { + + @Override + public String getName() { + return "render"; + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("outlines") + .executes(context -> this.printRenderMode()) + .then(literal("XRAY").executes(context -> this.setRenderMode(FinderQueue.RenderType.XRAY))) + .then(literal("ON").executes(context -> this.setRenderMode(FinderQueue.RenderType.ON))) + .then(literal("OFF").executes(context -> this.setRenderMode(FinderQueue.RenderType.OFF))) + ); + } + + private int printRenderMode() { + this.sendFeedback("Current render mode is set to [" + FinderQueue.get().renderType + "].", false); + return 0; + } + + private int setRenderMode(FinderQueue.RenderType renderType) { + FinderQueue.get().renderType = renderType; + this.sendFeedback("Set render mode to [" + FinderQueue.get().renderType + "].", false); + return 0; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java index 0d8adcf..b774fb7 100644 --- a/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java @@ -1,9 +1,18 @@ package kaptainwutax.seedcracker.mixin; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import kaptainwutax.seedcracker.ClientCommands; import kaptainwutax.seedcracker.FinderQueue; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.packet.ChunkDataS2CPacket; +import net.minecraft.client.network.packet.CommandTreeS2CPacket; import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.command.CommandSource; +import net.minecraft.server.command.ServerCommandSource; import net.minecraft.util.math.ChunkPos; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -15,6 +24,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class ClientPlayNetworkHandlerMixin { @Shadow private ClientWorld world; + @Shadow private CommandDispatcher commandDispatcher; @Inject(method = "onChunkData", at = @At("RETURN")) private void onChunkData(ChunkDataS2CPacket packet, CallbackInfo ci) { @@ -23,4 +33,15 @@ public abstract class ClientPlayNetworkHandlerMixin { FinderQueue.get().onChunkData(this.world, new ChunkPos(chunkX, chunkZ)); } + @Inject(method = "", at = @At("RETURN")) + public void onInit(MinecraftClient mc, Screen screen, ClientConnection connection, GameProfile profile, CallbackInfo ci) { + ClientCommands.registerCommands((CommandDispatcher)(Object)this.commandDispatcher); + } + + @SuppressWarnings("unchecked") + @Inject(method = "onCommandTree", at = @At("TAIL")) + public void onOnCommandTree(CommandTreeS2CPacket packet, CallbackInfo ci) { + ClientCommands.registerCommands((CommandDispatcher)(Object)this.commandDispatcher); + } + } diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java new file mode 100644 index 0000000..ca2edd9 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java @@ -0,0 +1,29 @@ +package kaptainwutax.seedcracker.mixin; + +import com.mojang.brigadier.StringReader; +import kaptainwutax.seedcracker.ClientCommands; +import net.minecraft.client.network.ClientPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayerEntity.class) +public class ClientPlayerEntityMixin { + + @Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true) + private void onSendChatMessage(String message, CallbackInfo ci) { + if(!message.startsWith("/"))return; + + StringReader reader = new StringReader(message); + reader.skip(); + int cursor = reader.getCursor(); + reader.setCursor(cursor); + + if(ClientCommands.isClientSideCommand(message.substring(1).split(" "))) { + ClientCommands.executeCommand(reader); + ci.cancel(); + } + } + +} diff --git a/src/main/resources/seedcracker.mixins.json b/src/main/resources/seedcracker.mixins.json index c085f97..a86a770 100644 --- a/src/main/resources/seedcracker.mixins.json +++ b/src/main/resources/seedcracker.mixins.json @@ -8,7 +8,8 @@ "DisableableProfilerMixin", "GameRendererMixin", "ClientPlayNetworkHandlerMixin", - "ClientWorldMixin" + "ClientWorldMixin", + "ClientPlayerEntityMixin" ], "injectors": { "defaultRequire": 1