From b3c715c31255db602dfb46434685a3149f352d46 Mon Sep 17 00:00:00 2001 From: Lex Manos Date: Mon, 30 Sep 2013 13:08:57 -0700 Subject: [PATCH] Add the ability to register chat commands that only execute on the client. Works with autocomplete. Client commands are gray when shown in the autocomplete list (when you press tab) Closes #640 --- .../client/ClientCommandHandler.java | 140 ++++++++++++++++++ .../net/minecraft/client/Minecraft.java.patch | 38 +++-- .../minecraft/client/gui/GuiChat.java.patch | 44 ++++++ 3 files changed, 208 insertions(+), 14 deletions(-) create mode 100644 client/net/minecraftforge/client/ClientCommandHandler.java create mode 100644 patches/minecraft/net/minecraft/client/gui/GuiChat.java.patch diff --git a/client/net/minecraftforge/client/ClientCommandHandler.java b/client/net/minecraftforge/client/ClientCommandHandler.java new file mode 100644 index 000000000..b929e723e --- /dev/null +++ b/client/net/minecraftforge/client/ClientCommandHandler.java @@ -0,0 +1,140 @@ +package net.minecraftforge.client; + +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiChat; +import net.minecraft.command.CommandException; +import net.minecraft.command.CommandHandler; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraft.command.WrongUsageException; +import net.minecraft.util.ChatMessageComponent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.CommandEvent; +import cpw.mods.fml.client.FMLClientHandler; +import static net.minecraft.util.EnumChatFormatting.*; + +/** + * The class that handles client-side chat commands. You should register any + * commands that you want handled on the client with this command handler. + * + * If there is a command with the same name registered both on the server and + * client, the client takes precedence! + * + */ +public class ClientCommandHandler extends CommandHandler +{ + public static final ClientCommandHandler instance = new ClientCommandHandler(); + + public String[] latestAutoComplete = null; + + /** + * @return 1 if successfully executed, 0 if wrong usage, it doesn't exist or + * it was canceled. + */ + @Override + public int executeCommand(ICommandSender sender, String message) + { + message = message.trim(); + + if (message.startsWith("/")) + { + message = message.substring(1); + } + + String[] args = message.split(" "); + String commandName = args[0]; + System.arraycopy(args, 1, args, 0, args.length - 1); + ICommand icommand = (ICommand) getCommands().get(commandName); + + try + { + if (icommand == null) + { + return 0; + } + + if (icommand.canCommandSenderUseCommand(sender)) + { + CommandEvent event = new CommandEvent(icommand, sender, args); + if (MinecraftForge.EVENT_BUS.post(event)) + { + if (event.exception != null) + { + throw event.exception; + } + return 0; + } + + icommand.processCommand(sender, args); + return 1; + } + else + { + sender.sendChatToPlayer(format("commands.generic.permission").setColor(RED)); + } + } + catch (WrongUsageException wue) + { + sender.sendChatToPlayer(format("commands.generic.usage", format(wue.getMessage(), wue.getErrorOjbects())).setColor(RED)); + } + catch (CommandException ce) + { + sender.sendChatToPlayer(format(ce.getMessage(), ce.getErrorOjbects()).setColor(RED)); + } + catch (Throwable t) + { + sender.sendChatToPlayer(format("commands.generic.exception").setColor(RED)); + t.printStackTrace(); + } + + return 0; + } + + //Couple of helpers because the mcp names are stupid and long... + private ChatMessageComponent format(String str, Object... args) + { + return ChatMessageComponent.createFromTranslationWithSubstitutions(str, args); + } + + private ChatMessageComponent format(String str) + { + return ChatMessageComponent.createFromTranslationKey(str); + } + + public void autoComplete(String leftOfCursor, String full) + { + latestAutoComplete = null; + + if (leftOfCursor.charAt(0) == '/') + { + leftOfCursor = leftOfCursor.substring(1); + + Minecraft mc = FMLClientHandler.instance().getClient(); + if (mc.currentScreen instanceof GuiChat) + { + List commands = getPossibleCommands(mc.thePlayer, leftOfCursor); + if (commands != null && !commands.isEmpty()) + { + if (leftOfCursor.indexOf(' ') == -1) + { + for (int i = 0; i < commands.size(); i++) + { + commands.set(i, GRAY + "/" + commands.get(i) + RESET); + } + } + else + { + for (int i = 0; i < commands.size(); i++) + { + commands.set(i, GRAY + commands.get(i) + RESET); + } + } + + latestAutoComplete = commands.toArray(new String[commands.size()]); + } + } + } + } +} \ No newline at end of file diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 021d32ef9..5e3cec260 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -1,9 +1,10 @@ --- ../src_base/minecraft/net/minecraft/client/Minecraft.java +++ ../src_work/minecraft/net/minecraft/client/Minecraft.java -@@ -137,6 +137,15 @@ +@@ -137,6 +137,16 @@ import com.google.common.collect.MapDifference; ++import net.minecraftforge.client.ClientCommandHandler; +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.GuiIngameForge; +import net.minecraftforge.client.event.GuiOpenEvent; @@ -16,7 +17,7 @@ @SideOnly(Side.CLIENT) public class Minecraft implements IPlayerUsage { -@@ -416,7 +425,7 @@ +@@ -416,7 +426,7 @@ try { @@ -25,7 +26,7 @@ } catch (LWJGLException lwjglexception) { -@@ -497,7 +506,7 @@ +@@ -497,7 +507,7 @@ this.effectRenderer = new EffectRenderer(this.theWorld, this.renderEngine); FMLClientHandler.instance().finishMinecraftLoading(); this.checkGLError("Post startup"); @@ -34,7 +35,7 @@ if (this.serverName != null) { -@@ -679,11 +688,6 @@ +@@ -679,11 +689,6 @@ */ public void displayGuiScreen(GuiScreen par1GuiScreen) { @@ -46,7 +47,7 @@ this.statFileWriter.syncStats(); if (par1GuiScreen == null && this.theWorld == null) -@@ -693,6 +697,20 @@ +@@ -693,6 +698,20 @@ else if (par1GuiScreen == null && this.thePlayer.getHealth() <= 0.0F) { par1GuiScreen = new GuiGameOver(); @@ -67,7 +68,7 @@ } if (par1GuiScreen instanceof GuiMainMenu) -@@ -1300,7 +1318,7 @@ +@@ -1300,7 +1319,7 @@ if (this.thePlayer.isCurrentToolAdventureModeExempt(j, k, l)) { @@ -76,7 +77,7 @@ this.thePlayer.swingItem(); } } -@@ -1366,7 +1384,8 @@ +@@ -1366,7 +1385,8 @@ { int j1 = itemstack != null ? itemstack.stackSize : 0; @@ -86,7 +87,7 @@ { flag = false; this.thePlayer.swingItem(); -@@ -1392,7 +1411,8 @@ +@@ -1392,7 +1412,8 @@ { ItemStack itemstack1 = this.thePlayer.inventory.getCurrentItem(); @@ -96,7 +97,7 @@ { this.entityRenderer.itemRenderer.resetEquippedProgress2(); } -@@ -1574,6 +1594,8 @@ +@@ -1574,6 +1595,8 @@ while (Mouse.next()) { @@ -105,7 +106,7 @@ i = Mouse.getEventButton(); if (isRunningOnMac && i == 0 && (Keyboard.isKeyDown(29) || Keyboard.isKeyDown(157))) -@@ -2046,6 +2068,11 @@ +@@ -2046,6 +2069,11 @@ { this.statFileWriter.syncStats(); @@ -117,7 +118,7 @@ if (par1WorldClient == null) { NetClientHandler netclienthandler = this.getNetHandler(); -@@ -2063,6 +2090,18 @@ +@@ -2063,6 +2091,18 @@ if (this.theIntegratedServer != null) { this.theIntegratedServer.initiateShutdown(); @@ -136,7 +137,16 @@ } this.theIntegratedServer = null; -@@ -2236,107 +2275,12 @@ +@@ -2225,7 +2265,7 @@ + */ + public boolean handleClientCommand(String par1Str) + { +- return false; ++ return ClientCommandHandler.instance.executeCommand(thePlayer, par1Str) == 1; + } + + /** +@@ -2236,107 +2276,12 @@ if (this.objectMouseOver != null) { boolean flag = this.thePlayer.capabilities.isCreativeMode; @@ -248,7 +258,7 @@ if (flag) { -@@ -2419,11 +2363,18 @@ +@@ -2419,11 +2364,18 @@ par1PlayerUsageSnooper.addData("gl_max_texture_size", Integer.valueOf(getGLMaximumTextureSize())); } @@ -267,7 +277,7 @@ for (int i = 16384; i > 0; i >>= 1) { GL11.glTexImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, i, i, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null); -@@ -2431,6 +2382,7 @@ +@@ -2431,6 +2383,7 @@ if (j != 0) { diff --git a/patches/minecraft/net/minecraft/client/gui/GuiChat.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiChat.java.patch new file mode 100644 index 000000000..755d5e311 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/gui/GuiChat.java.patch @@ -0,0 +1,44 @@ +--- ../src_base/minecraft/net/minecraft/client/gui/GuiChat.java ++++ ../src_work/minecraft/net/minecraft/client/gui/GuiChat.java +@@ -7,8 +7,11 @@ + import java.util.Iterator; + import java.util.List; + import net.minecraft.network.packet.Packet203AutoComplete; ++import net.minecraft.util.EnumChatFormatting; ++import net.minecraftforge.client.ClientCommandHandler; + import org.lwjgl.input.Keyboard; + import org.lwjgl.input.Mouse; ++import com.google.common.collect.ObjectArrays; + + @SideOnly(Side.CLIENT) + public class GuiChat extends GuiScreen +@@ -278,13 +281,14 @@ + this.mc.ingameGUI.getChatGUI().printChatMessageWithOptionalDeletion(stringbuilder.toString(), 1); + } + +- this.inputField.writeText((String)this.field_73904_o.get(this.field_73903_n++)); ++ this.inputField.writeText(EnumChatFormatting.func_110646_a((String)this.field_73904_o.get(this.field_73903_n++))); + } + + private void func_73893_a(String par1Str, String par2Str) + { + if (par1Str.length() >= 1) + { ++ ClientCommandHandler.instance.autoComplete(par1Str, par2Str); + this.mc.thePlayer.sendQueue.addToSendQueue(new Packet203AutoComplete(par1Str)); + this.field_73905_m = true; + } +@@ -347,6 +351,13 @@ + String[] astring1 = par1ArrayOfStr; + int i = par1ArrayOfStr.length; + ++ String[] complete = ClientCommandHandler.instance.latestAutoComplete; ++ if (complete != null) ++ { ++ astring1 = ObjectArrays.concat(complete, astring1, String.class); ++ i = astring1.length; ++ } ++ + for (int j = 0; j < i; ++j) + { + String s = astring1[j];