diff options
9 files changed, 399 insertions, 8 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 41fd013..263ecf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [1.8.9-0.14.0] - unreleased ### Added +- New command: `/commandslist` to list all client-side commands added by all installed mods - Chest Tracker & Analyzer: - added support for 'lowest BIN' prices - double clicking an analysis row now highlights chests that contain the clicked item @@ -14,6 +14,7 @@ It is a collection of different features mainly focused on Hypixel SkyBlock. ðŸ | Search through your Minecraft log files | `/moo search` (click the `?` for more info) | | Stalk a player (check online status, current game, ...) | `/moo stalk` | | Toggle join/leave notifications for friends, guild members or best friends separately | `/moo config` → Notifications | +| Show all client-side commands added by all installed mods | `/commandslist` | | Copy chat component | <kbd>ALT</kbd> + <kbd>right click</kbd><br>Hold <kbd>shift</kbd> to copy full component | | Copy inventories to clipboard as JSON | <kbd>CTRL</kbd> + <kbd>C</kbd> (whole inventory)<br><kbd>CTRL</kbd> + <kbd>SHIFT</kbd> + <kbd>C</kbd> (single item) | | Copy info of "the thing" you're looking at (NPC or mob + nearby "text-only" armor stands; armor stand, placed skull, banner, sign, dropped item, item in item frame, map on wall) | `/moo whatAmILookingAt` | diff --git a/src/main/java/de/cowtipper/cowlection/Cowlection.java b/src/main/java/de/cowtipper/cowlection/Cowlection.java index fad3338..e91f0b6 100644 --- a/src/main/java/de/cowtipper/cowlection/Cowlection.java +++ b/src/main/java/de/cowtipper/cowlection/Cowlection.java @@ -1,10 +1,7 @@ package de.cowtipper.cowlection; import de.cowtipper.cowlection.chesttracker.ChestTracker; -import de.cowtipper.cowlection.command.MooCommand; -import de.cowtipper.cowlection.command.ReplyCommand; -import de.cowtipper.cowlection.command.ShrugCommand; -import de.cowtipper.cowlection.command.TabCompletableCommand; +import de.cowtipper.cowlection.command.*; import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.handler.DungeonCache; @@ -75,6 +72,7 @@ public class Cowlection { MinecraftForge.EVENT_BUS.register(new ChatListener(this)); MinecraftForge.EVENT_BUS.register(new PlayerListener(this)); ClientCommandHandler.instance.registerCommand(new MooCommand(this)); + ClientCommandHandler.instance.registerCommand(new NumerousCommandsCommand()); ClientCommandHandler.instance.registerCommand(new ReplyCommand()); ClientCommandHandler.instance.registerCommand(new ShrugCommand(this)); for (String tabCompletableNamesCommand : MooConfig.tabCompletableNamesCommands) { diff --git a/src/main/java/de/cowtipper/cowlection/command/NumerousCommandsCommand.java b/src/main/java/de/cowtipper/cowlection/command/NumerousCommandsCommand.java new file mode 100644 index 0000000..08486e3 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/command/NumerousCommandsCommand.java @@ -0,0 +1,79 @@ +package de.cowtipper.cowlection.command; + +import de.cowtipper.cowlection.numerouscommands.ModInfo; +import de.cowtipper.cowlection.numerouscommands.NumerousCommandsGui; +import de.cowtipper.cowlection.util.TickDelay; +import net.minecraft.client.Minecraft; +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraftforge.client.ClientCommandHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; + +import java.util.*; + +public class NumerousCommandsCommand extends CommandBase { + @Override + public String getCommandName() { + return "commandslist"; + } + + @Override + public List<String> getCommandAliases() { + return Arrays.asList("clientcommands", "listcommands"); + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return "/" + getCommandName(); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + HashSet<String> ignoredMods = new HashSet<>(Arrays.asList("Minecraft Coder Pack", "Forge Mod Loader", "Minecraft Forge")); + + Map<String, ModInfo> modsInfo = new TreeMap<>(); + + for (ModContainer mod : Loader.instance().getActiveModList()) { + String modName = mod.getName(); + if (ignoredMods.contains(modName)) { + // ignored mod + continue; + } + modsInfo.put(modName, new ModInfo(mod)); + } + + ModInfo unknownMod = new ModInfo(); + for (Map.Entry<String, ICommand> cmdEntry : ClientCommandHandler.instance.getCommands().entrySet()) { + String cmdName = cmdEntry.getKey(); + ICommand cmd = cmdEntry.getValue(); + if (!cmdName.equalsIgnoreCase(cmd.getCommandName())) { + // skip command alias + continue; + } + String cmdPackageName = cmd.getClass().getPackage().getName(); + boolean foundOwningMod = false; + for (ModInfo modInfo : modsInfo.values()) { + if (modInfo.isOwnedPackage(cmdPackageName)) { + modInfo.addCommand(cmd, sender); + foundOwningMod = true; + break; + } + } + if (!foundOwningMod) { + unknownMod.addCommand(cmd, sender); + } + } + if (unknownMod.getCommandsCount() > 0) { + modsInfo.put("zzzzzzzz_unknown", unknownMod); + } + new TickDelay(() -> Minecraft.getMinecraft().displayGuiScreen(new NumerousCommandsGui(modsInfo.values())), 1); + } + + @Override + public int getRequiredPermissionLevel() { + return 0; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java index e72d27b..bd29d4a 100644 --- a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigMenuList.java @@ -5,7 +5,7 @@ import de.cowtipper.cowlection.config.MooConfigCategory; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.Tessellator; -import net.minecraft.util.StringUtils; +import net.minecraft.util.EnumChatFormatting; import net.minecraftforge.fml.client.GuiScrollingList; /** @@ -54,7 +54,7 @@ public class MooConfigMenuList extends GuiScrollingList { @Override protected void drawSlot(int idx, int right, int top, int height, Tessellator tess) { MooConfigCategory configCategory = MooConfig.getConfigCategories().get(idx); - String name = StringUtils.stripControlCodes(configCategory.getMenuDisplayName()); + String name = EnumChatFormatting.getTextWithoutFormattingCodes(configCategory.getMenuDisplayName()); FontRenderer font = Minecraft.getMinecraft().fontRendererObj; font.drawString(font.trimStringToWidth(name, listWidth - 10), this.left + 3, top + 2, 0xFFFFFF); diff --git a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java index e0ce09b..00f9649 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java @@ -16,7 +16,6 @@ import net.minecraft.client.gui.GuiNewChat; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; -import net.minecraft.util.StringUtils; import net.minecraftforge.client.ClientCommandHandler; import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.client.event.GuiOpenEvent; @@ -119,7 +118,7 @@ public class ChatListener { if (copyWithFormatting) { chatData = main.getChatHelper().cleanChatComponent(chatComponent); } else { - chatData = StringUtils.stripControlCodes(chatComponent.getUnformattedText()); + chatData = EnumChatFormatting.getTextWithoutFormattingCodes(chatComponent.getUnformattedText()); if (chatData.startsWith(": ")) { chatData = chatData.substring(2); } diff --git a/src/main/java/de/cowtipper/cowlection/numerouscommands/CommandInfo.java b/src/main/java/de/cowtipper/cowlection/numerouscommands/CommandInfo.java new file mode 100644 index 0000000..2807068 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/numerouscommands/CommandInfo.java @@ -0,0 +1,40 @@ +package de.cowtipper.cowlection.numerouscommands; + +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; + +import java.util.List; + +public class CommandInfo { + private final String name; + private final List<String> aliases; + private final String usage; + private boolean isListCommandsCommand; + + public CommandInfo(ICommand cmd, ICommandSender sender) { + name = cmd.getCommandName(); + aliases = cmd.getCommandAliases(); + usage = cmd.getCommandUsage(sender); + isListCommandsCommand = false; + } + + public String getName() { + return name; + } + + public List<String> getAliases() { + return aliases; + } + + public String getUsage() { + return (usage != null && !usage.replace("/", "").equalsIgnoreCase(name)) ? usage : null; + } + + public boolean isListCommandsCommand() { + return isListCommandsCommand; + } + + public void setIsListCommandsCommand() { + isListCommandsCommand = true; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/numerouscommands/ModInfo.java b/src/main/java/de/cowtipper/cowlection/numerouscommands/ModInfo.java new file mode 100644 index 0000000..15f4522 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/numerouscommands/ModInfo.java @@ -0,0 +1,70 @@ +package de.cowtipper.cowlection.numerouscommands; + +import de.cowtipper.cowlection.command.NumerousCommandsCommand; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.ModMetadata; + +import java.util.*; + +public class ModInfo { + private final String name; + private final ModMetadata metadata; + private final Set<String> ownedPackages; + private final Map<String, CommandInfo> commands; + private final boolean hasUpdate; + + public ModInfo(ModContainer mod) { + name = mod.getName(); + metadata = mod.getMetadata(); + ownedPackages = new HashSet<>(mod.getOwnedPackages()); + commands = new TreeMap<>(); + hasUpdate = ForgeVersion.getResult(mod).status == ForgeVersion.Status.OUTDATED; + } + + public ModInfo() { + name = "Unknown mod"; + metadata = new ModMetadata(); + metadata.modId = "unknownmodwithoutanid"; + metadata.name = name; + metadata.authorList = Collections.singletonList(EnumChatFormatting.ITALIC + "Unknown"); + ownedPackages = new HashSet<>(); + commands = new TreeMap<>(); + hasUpdate = false; + } + + public void addCommand(ICommand cmd, ICommandSender sender) { + CommandInfo commandInfo = new CommandInfo(cmd, sender); + if (cmd instanceof NumerousCommandsCommand) { + commandInfo.setIsListCommandsCommand(); + } + commands.put(cmd.getClass().getSimpleName() + cmd.getCommandName(), commandInfo); + } + + public String getName() { + return name; + } + + public ModMetadata getModMetadata() { + return metadata; + } + + public Collection<CommandInfo> getCommands() { + return commands.values(); + } + + public int getCommandsCount() { + return commands.size(); + } + + public boolean hasUpdate() { + return hasUpdate; + } + + public boolean isOwnedPackage(String packAge) { + return ownedPackages.contains(packAge); + } +} diff --git a/src/main/java/de/cowtipper/cowlection/numerouscommands/NumerousCommandsGui.java b/src/main/java/de/cowtipper/cowlection/numerouscommands/NumerousCommandsGui.java new file mode 100644 index 0000000..cc06055 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/numerouscommands/NumerousCommandsGui.java @@ -0,0 +1,203 @@ +package de.cowtipper.cowlection.numerouscommands; + + +import com.google.common.base.Joiner; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiUtilRenderComponents; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.command.CommandBase; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.fml.client.GuiScrollingList; +import net.minecraftforge.fml.client.config.GuiButtonExt; +import net.minecraftforge.fml.common.ModMetadata; +import net.minecraftforge.fml.relauncher.ReflectionHelper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Based on GuiModList + */ +public class NumerousCommandsGui extends GuiScreen { + private final List<String> lines; + private CommandsListGui commandsList; + private GuiButtonExt btnClose; + private static float lastScrollDistance; + + public NumerousCommandsGui(Collection<ModInfo> modsInfo) { + this.lines = new ArrayList<>(); + String unknown = EnumChatFormatting.ITALIC + "unknown"; + for (ModInfo modInfo : modsInfo) { + if (!lines.isEmpty()) { + lines.add(null); + lines.add("" + EnumChatFormatting.GRAY + EnumChatFormatting.BOLD + EnumChatFormatting.STRIKETHROUGH + "--------------------------------------------------"); + lines.add(null); + } + lines.add("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + modInfo.getName()); + ModMetadata modMetadata = modInfo.getModMetadata(); + if (modMetadata != null) { + int authorsCount = modMetadata.authorList.size(); + addKeyValue("Author" + (authorsCount > 1 ? "s" : ""), (authorsCount == 0 ? unknown : CommandBase.joinNiceStringFromCollection(modMetadata.authorList)), true); + addKeyValue("Version", (modMetadata.version.isEmpty() ? unknown : modMetadata.version) + (modInfo.hasUpdate() ? EnumChatFormatting.GREEN + " (update available!)" : ""), true); + if (!modMetadata.url.isEmpty()) { + addKeyValue("URL", modMetadata.url, true); + } + } + lines.add("" + EnumChatFormatting.UNDERLINE + EnumChatFormatting.ITALIC + "Commands" + EnumChatFormatting.RESET + EnumChatFormatting.GRAY + " (" + modInfo.getCommandsCount() + ")"); + Collection<CommandInfo> commands = modInfo.getCommands(); + for (CommandInfo cmd : commands) { + String cmdNameAndAliases = EnumChatFormatting.YELLOW + " ‣ /" + cmd.getName(); + if (cmd.getAliases().size() > 0) { + cmdNameAndAliases += EnumChatFormatting.DARK_GRAY + " (" + (cmd.getAliases().size() == 1 ? "alias" : "aliases") + ": " + EnumChatFormatting.GRAY + Joiner.on(", ").join(cmd.getAliases()) + EnumChatFormatting.DARK_GRAY + ")"; + } + lines.add(cmdNameAndAliases); + String cmdUsage = cmd.getUsage(); + if (cmdUsage != null) { + if (cmdUsage.contains("\n")) { + addKeyValue("Usage", "", false); + for (String usageLine : cmdUsage.split("\n")) { + lines.add(" " + usageLine); + } + } else { + addKeyValue("Usage", cmdUsage, false); + } + } else if (cmd.isListCommandsCommand()) { + addKeyValue("Usage", EnumChatFormatting.GREEN + "You have just used this command to open this GUI", false); + } + } + } + } + + private void addKeyValue(String key, String value, boolean light) { + EnumChatFormatting colorCodeKey = light ? EnumChatFormatting.GRAY : EnumChatFormatting.DARK_GRAY; + EnumChatFormatting colorCodeValue = light ? EnumChatFormatting.RESET : EnumChatFormatting.GRAY; + lines.add(" " + colorCodeKey + key + ":" + colorCodeValue + " " + value); + } + + @Override + public void initGui() { + // close button + this.buttonList.add(this.btnClose = new GuiButtonExt(1, this.width - 25, 4, 22, 20, EnumChatFormatting.RED + "X")); + updateLastScrollDistance(); + // scrollable commands list + commandsList = new CommandsListGui(this.width - 30, this.lines, lastScrollDistance); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawDefaultBackground(); + super.drawScreen(mouseX, mouseY, partialTicks); + GlStateManager.pushMatrix(); + double scaleFactor = 1.5; + GlStateManager.scale(scaleFactor, scaleFactor, 0); + this.drawString(this.fontRendererObj, "All client-side commands", 30, 6, 0xFFCC00); + GlStateManager.popMatrix(); + this.commandsList.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + protected void actionPerformed(GuiButton button) { + if (button == btnClose) { + this.mc.displayGuiScreen(null); + } + } + + @Override + public void onGuiClosed() { + updateLastScrollDistance(); + } + + private void updateLastScrollDistance() { + if (this.commandsList != null) { + try { + lastScrollDistance = ReflectionHelper.getPrivateValue(GuiScrollingList.class, this.commandsList, "scrollDistance"); + } catch (ReflectionHelper.UnableToAccessFieldException ignored) { + lastScrollDistance = 0; + } + } + } + + /** + * Based on GuiModList.Info + */ + private class CommandsListGui extends GuiScrollingList { + private final List<IChatComponent> lines; + + public CommandsListGui(int width, List<String> lines, float lastScrollDistance) { + super(NumerousCommandsGui.this.mc, + width, + NumerousCommandsGui.this.height, + 30, NumerousCommandsGui.this.height - 5, 12, 11, + NumerousCommandsGui.this.width, + NumerousCommandsGui.this.height); + this.lines = resizeContent(lines); + try { + // scroll to previous location + ReflectionHelper.setPrivateValue(GuiScrollingList.class, this, lastScrollDistance, "scrollDistance"); + } catch (ReflectionHelper.UnableToAccessFieldException ignored) { + } + } + + private List<IChatComponent> resizeContent(List<String> lines) { + List<IChatComponent> ret = new ArrayList<>(); + for (String line : lines) { + if (line == null) { + ret.add(null); + continue; + } + IChatComponent chat = ForgeHooks.newChatWithLinks(line, false); + ret.addAll(GuiUtilRenderComponents.splitText(chat, this.listWidth - 8, NumerousCommandsGui.this.fontRendererObj, false, true)); + } + return ret; + } + + @Override + protected int getSize() { + return lines != null ? lines.size() : 0; + } + + @Override + protected void elementClicked(int index, boolean doubleClick) { + IChatComponent line = lines.get(index); + if (line != null) { + int xOffset = this.left; + for (IChatComponent part : line) { + if (!(part instanceof ChatComponentText)) { + continue; + } + xOffset += NumerousCommandsGui.this.fontRendererObj.getStringWidth(((ChatComponentText) part).getChatComponentText_TextValue()); + if (xOffset >= this.mouseX) { + NumerousCommandsGui.this.handleComponentClick(part); + break; + } + } + } + } + + @Override + protected boolean isSelected(int index) { + return false; + } + + @Override + protected void drawBackground() { + } + + @Override + protected void drawSlot(int slotIdx, int entryRight, int slotTop, int slotBuffer, Tessellator tess) { + IChatComponent line = lines.get(slotIdx); + if (line != null) { + GlStateManager.enableBlend(); + NumerousCommandsGui.this.fontRendererObj.drawStringWithShadow(line.getFormattedText(), this.left + 4, slotTop, 0xFFFFFF); + GlStateManager.disableAlpha(); + GlStateManager.disableBlend(); + } + } + } +} |